Commit dacc0956 authored by 武广's avatar 武广

feat: 添加图片裁剪组件

parent 95b4583c
......@@ -15,6 +15,7 @@
"@ant-design/pro-table": "^1.0.31",
"@antv/data-set": "^0.10.2",
"antd": "^4.19.3",
"antd-img-crop": "^4.11.0",
"antd-virtual-select": "^1.1.2",
"array-move": "^4.0.0",
"babel-eslint": "^10.1.0",
......@@ -9555,6 +9556,48 @@
"react-dom": ">=16.9.0"
}
},
"node_modules/antd-img-crop": {
"version": "4.11.0",
"resolved": "http://npmprivate.quantgroups.com/antd-img-crop/-/antd-img-crop-4.11.0.tgz",
"integrity": "sha512-DWf72AsFc8r2BKRfNMhUrMDh3xg2PvYB6b0gCYPrBEkVbQE1VP7Qt3HnthdeWDapSGdnmUPKFTcMgzML/FyuTA==",
"license": "MIT",
"dependencies": {
"compare-versions": "6.0.0-rc.1",
"react-easy-crop": "^4.7.4",
"tslib": "^2.5.0"
},
"peerDependencies": {
"antd": ">=4.0.0",
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/antd-img-crop/node_modules/react-easy-crop": {
"version": "4.7.4",
"resolved": "http://npmprivate.quantgroups.com/react-easy-crop/-/react-easy-crop-4.7.4.tgz",
"integrity": "sha512-oDi1375Jo/zuPUvo3oauxnNbfy8L4wsbmHD1KB2vT55fdgu+q8/K0w/rDWzy9jz4jfQ94Q9+3Yu366sDDFVmiA==",
"license": "MIT",
"dependencies": {
"normalize-wheel": "^1.0.1",
"tslib": "2.0.1"
},
"peerDependencies": {
"react": ">=16.4.0",
"react-dom": ">=16.4.0"
}
},
"node_modules/antd-img-crop/node_modules/react-easy-crop/node_modules/tslib": {
"version": "2.0.1",
"resolved": "http://npmprivate.quantgroups.com/tslib/-/tslib-2.0.1.tgz",
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==",
"license": "0BSD"
},
"node_modules/antd-img-crop/node_modules/tslib": {
"version": "2.5.0",
"resolved": "http://npmprivate.quantgroups.com/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
"license": "0BSD"
},
"node_modules/antd-mobile": {
"version": "2.3.4",
"resolved": "http://npmprivate.quantgroups.com/antd-mobile/-/antd-mobile-2.3.4.tgz",
......@@ -12019,6 +12062,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/compare-versions": {
"version": "6.0.0-rc.1",
"resolved": "http://npmprivate.quantgroups.com/compare-versions/-/compare-versions-6.0.0-rc.1.tgz",
"integrity": "sha512-cFhkjbGY1jLFWIV7KegECbfuyYPxSGvgGkdkfM+ibboQDoPwg2FRHm5BSNTOApiauRBzJIQH7qvOJs2sW5ueKQ==",
"license": "MIT"
},
"node_modules/component-classes": {
"version": "1.2.6",
"resolved": "http://npmprivate.quantgroups.com/component-classes/-/component-classes-1.2.6.tgz",
......@@ -26458,6 +26507,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/normalize-wheel": {
"version": "1.0.1",
"resolved": "http://npmprivate.quantgroups.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
"integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU=",
"license": "BSD-3-Clause"
},
"node_modules/normalize.css": {
"version": "7.0.0",
"resolved": "http://npmprivate.quantgroups.com/normalize.css/-/normalize.css-7.0.0.tgz",
......@@ -45379,6 +45434,39 @@
}
}
},
"antd-img-crop": {
"version": "4.11.0",
"resolved": "http://npmprivate.quantgroups.com/antd-img-crop/-/antd-img-crop-4.11.0.tgz",
"integrity": "sha512-DWf72AsFc8r2BKRfNMhUrMDh3xg2PvYB6b0gCYPrBEkVbQE1VP7Qt3HnthdeWDapSGdnmUPKFTcMgzML/FyuTA==",
"requires": {
"compare-versions": "6.0.0-rc.1",
"react-easy-crop": "^4.7.4",
"tslib": "^2.5.0"
},
"dependencies": {
"react-easy-crop": {
"version": "4.7.4",
"resolved": "http://npmprivate.quantgroups.com/react-easy-crop/-/react-easy-crop-4.7.4.tgz",
"integrity": "sha512-oDi1375Jo/zuPUvo3oauxnNbfy8L4wsbmHD1KB2vT55fdgu+q8/K0w/rDWzy9jz4jfQ94Q9+3Yu366sDDFVmiA==",
"requires": {
"normalize-wheel": "^1.0.1",
"tslib": "2.0.1"
},
"dependencies": {
"tslib": {
"version": "2.0.1",
"resolved": "http://npmprivate.quantgroups.com/tslib/-/tslib-2.0.1.tgz",
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
}
}
},
"tslib": {
"version": "2.5.0",
"resolved": "http://npmprivate.quantgroups.com/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
}
}
},
"antd-mobile": {
"version": "2.3.4",
"resolved": "http://npmprivate.quantgroups.com/antd-mobile/-/antd-mobile-2.3.4.tgz",
......@@ -47135,6 +47223,11 @@
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true
},
"compare-versions": {
"version": "6.0.0-rc.1",
"resolved": "http://npmprivate.quantgroups.com/compare-versions/-/compare-versions-6.0.0-rc.1.tgz",
"integrity": "sha512-cFhkjbGY1jLFWIV7KegECbfuyYPxSGvgGkdkfM+ibboQDoPwg2FRHm5BSNTOApiauRBzJIQH7qvOJs2sW5ueKQ=="
},
"component-classes": {
"version": "1.2.6",
"resolved": "http://npmprivate.quantgroups.com/component-classes/-/component-classes-1.2.6.tgz",
......@@ -57633,6 +57726,11 @@
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
"dev": true
},
"normalize-wheel": {
"version": "1.0.1",
"resolved": "http://npmprivate.quantgroups.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
"integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU="
},
"normalize.css": {
"version": "7.0.0",
"resolved": "http://npmprivate.quantgroups.com/normalize.css/-/normalize.css-7.0.0.tgz",
......@@ -214,27 +214,31 @@ class goodsManage extends Component {
重置
</Button>
{this.state.productType !== 5 && (
<Button
loading={this.state.loading}
onClick={() => this.onExportGoodsInfo()}
className={styles.button}
>
导出
</Button>
<>
<Button
loading={this.state.loading}
onClick={() => this.onExportGoodsInfo()}
className={styles.button}
>
导出
</Button>
{canEditable ? (
<FormItem style={{ float: 'right' }}>
<Popover content={content} onVisibleChange={this.handleVisibleChange}>
<Button type="primary" className={styles.button}>
批量设置
</Button>
</Popover>
{this.props.selectNum > 0 && (
<Tag color="green">已选商品 {this.props.selectNum}</Tag>
)}
</FormItem>
) : (
''
)}
</>
)}
</FormItem>
{canEditable ? (
<FormItem style={{ float: 'right' }}>
<Popover content={content} onVisibleChange={this.handleVisibleChange}>
<Button type="primary" className={styles.button}>
批量设置
</Button>
</Popover>
{this.props.selectNum > 0 && <Tag color="green">已选商品 {this.props.selectNum}</Tag>}
</FormItem>
) : (
''
)}
</Form>
);
}
......
......@@ -19,7 +19,7 @@ const StockModal = options => {
}
};
const onChangeMaxStock = ({ target: { value } }) => {
const onChangeMaxStock = value => {
setMaxStock(value);
};
......@@ -75,7 +75,7 @@ const StockModal = options => {
setMaxStock(0);
setIsChecked(false);
form.resetFields();
if (options.skuIds && options.skuIds.length) {
if (options.skuIds && options.skuIds.length === 1) {
getStockInfo();
}
}
......
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { Modal, Radio, Form, TimePicker, Checkbox } from 'antd';
import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons';
import moment from 'moment';
......@@ -61,6 +61,10 @@ const WeekTime = options => {
options.initialValues,
);
useEffect(() => {
options.visible && setType(0);
}, [options.visible]);
return (
<Modal
visible={options.visible}
......
......@@ -186,3 +186,9 @@
color: #0e75fd;
cursor: pointer;
}
:global {
.reactEasyCrop_Container {
height: 750px !important;
}
}
import { PlusOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
import { Modal, Upload, notification, Spin } from 'antd';
import React, { useState, useEffect, useRef, forwardRef } from 'react';
import lodash from 'lodash';
import { ReactSortable } from 'react-sortablejs';
import ImgCrop from 'antd-img-crop';
import 'antd/es/modal/style';
import 'antd/es/slider/style';
import { merchantUpload } from '../service';
import styles from '../common.less';
const UploadButton = (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>上传图片</div>
</div>
);
const UploadCropImage = forwardRef((props, ref) => {
const {
name = `${Date.now()}`,
limit = null,
multiple = true,
disabled,
uploadParams,
pictures = [],
setPictureList = () => {},
...imgOptions
} = props;
const [uploadLoading, setUploadLoading] = useState(false);
const [previewVisible, setPreviewVisible] = useState(false);
const [previewImage, setPreviewImage] = useState('');
const [previewTitle, setPreviewTitle] = useState('');
const [fileList, setFileList] = useState([]);
const [activeImgIndex, setActiveImgIndex] = useState(null);
const fileListRef = useRef([]);
useEffect(() => {
const newPictures = pictures.map((url, ind) => ({
url,
name: url,
uid: `${ind}`,
}));
fileListRef.current = [...newPictures];
setFileList([...newPictures]);
}, [pictures]);
const handleCancel = () => setPreviewVisible(false);
const handlePreview = async file => {
setPreviewImage(file.url);
setPreviewVisible(true);
setPreviewTitle(file.name || file.url.substring(file.url.lastIndexOf('/') + 1));
};
const bundleChange = imgFile => {
// setFileList(imgFile);
const imgList = imgFile.map(item => item.url);
setPictureList(imgList);
props.onChange(imgList);
};
const handleRemove = file => {
const freshFiles = fileList?.filter(ele => ele.uid !== file.uid);
bundleChange(freshFiles);
};
const cleanArray = (actual = []) =>
actual.reduce((prev, cur) => {
cur && prev.push(cur);
return prev;
}, []);
const warningTip = description => {
notification.warning({
message: '图片上传失败',
description,
});
};
const getBase64 = (img, callback) => {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
};
const ImageInfo = file =>
new Promise((resolve, reject) => {
const LtMB = file.size / 1024 / 1024;
if (LtMB > 2) {
warningTip(`[${file.name}] 图片不可以大于2MB`);
resolve(null);
}
getBase64(file, url => {
const image = new Image();
image.addEventListener('load', () => {
const { width } = image;
const { height } = image;
file.width = width;
file.height = height;
file.LtMB = LtMB;
resolve(file);
});
image.addEventListener('error', () => {
warningTip(`${file.name}图片上传失败!`);
resolve(null);
});
image.src = url;
});
});
const CheckImageInfoList = async files => {
const promiseImage = files.map(file => ImageInfo(file));
const clearImage = await Promise.all(promiseImage);
return cleanArray(clearImage);
};
const isUploadNext = async imgFileList => {
const filterImage = imgFileList.filter(img => {
if (
(imgOptions.maxWidth && img.width > imgOptions.maxWidth) ||
(imgOptions.maxHeight && img.height > imgOptions.maxHeight)
) {
warningTip(`[${img.name}] ${imgOptions.superTips}`);
return false;
}
return true;
});
return filterImage;
};
const checkFile = files => {
const fileType = ['jpg', 'jpeg', 'png'];
const filterImage = files.filter(file => {
const curType = file.name.substr(file.name.lastIndexOf('.') + 1).toLowerCase();
if (!fileType.includes(curType)) {
warningTip('图片格式须为jpg、jpeg、png!');
return false;
}
return true;
});
return filterImage;
};
const imageLoading = (file, ret) =>
new Promise(resolve => {
const reader = new FileReader();
// 监听图片转换完成
reader.addEventListener(
'load',
() => {
const temFile = { uid: file.uid, status: 'done', name: file.name, url: ret };
resolve(temFile);
},
false,
);
reader.readAsDataURL(file);
});
const defaultBeforeUpload = lodash.debounce(
(file, fileArray) =>
// 文件显示
new Promise(async () => {
console.log('defaultBeforeUpload :>> ', 11111);
if (limit && fileListRef.current.length + fileArray.length > limit) {
Modal.warning({
maskClosable: true,
title: '超出上传个数',
});
return Upload.LIST_IGNORE;
}
const flies = checkFile(fileArray);
const optionsArray = await CheckImageInfoList(flies);
const checkFiles = await isUploadNext(optionsArray);
try {
if (checkFiles.length) {
setUploadLoading(true);
const res = await merchantUpload(checkFiles);
if (res.data) {
const proFiles = (res.data || []).map((urlItem, urlIndex) =>
imageLoading(checkFiles[urlIndex], urlItem),
);
const imagList = await Promise.all(proFiles);
const newFiles = [...fileListRef.current, ...imagList];
bundleChange(newFiles);
} else {
notification.warning({
message: '警告',
description: res.msg,
});
}
setUploadLoading(false);
}
} catch (error) {
console.log(error);
setUploadLoading(false);
Modal.warning({
maskClosable: true,
title: '上传失败,请重新尝试!',
});
}
return null;
}),
);
return (
<Spin tip="正在上传..." spinning={uploadLoading} delay={100}>
<div className={styles.imgContent}>
{fileList.length > 0 && (
<ReactSortable animation={300} list={fileList} setList={list => bundleChange(list)}>
{fileList.map((item, index) => (
<div
// eslint-disable-next-line react/no-array-index-key
key={index}
className={styles.sortImg}
onMouseEnter={() => setActiveImgIndex(index)}
onMouseLeave={() => setActiveImgIndex(null)}
>
<div style={{ width: '100%', height: '100%', overflow: 'hidden' }}>
<img width="100%" key={item.uid} src={item.url} alt="" />
</div>
{activeImgIndex === index && (
<div className={styles.mask}>
<EyeOutlined className={styles.maskIcon} onClick={() => handlePreview(item)} />
{!disabled && (
<DeleteOutlined
className={styles.maskIcon}
onClick={() => handleRemove(item)}
/>
)}
</div>
)}
</div>
))}
</ReactSortable>
)}
</div>
{limit !== null && fileList.length >= limit ? (
''
) : (
<ImgCrop rotationSlider modalWidth={1200} quality={0.5} showReset>
<Upload
{...uploadParams}
disabled={Boolean(disabled)}
multiple={multiple}
name={name}
customRequest={() => {}}
listType="picture-card"
beforeUpload={defaultBeforeUpload}
fileList={fileList}
onPreview={handlePreview}
onRemove={handleRemove}
showUploadList={false}
>
{UploadButton}
</Upload>
</ImgCrop>
)}
<Modal visible={previewVisible} title={previewTitle} footer={null} onCancel={handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
</Spin>
);
});
export default UploadCropImage;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment