效果:
![8d788b794cc77eb63c43321302ee35f0.png](https://i-blog.csdnimg.cn/blog_migrate/0ae32a1cd8a00fe435ef95cb13c3fe86.png)
功能:antd 的单图片上传。
代码:
主文件:
/** * 图片上传控件 * 控件 * @authors 缥缈潇潇 * @date 2020-01-03 */import React, {useState, useRef, useEffect} from 'react';import {Upload, message, Row, Col, Modal, Button} from 'antd';import ImageCropper from './ImageCropper';import {RcFile} from "antd/lib/upload";import {CommIcon} from '..';import {useSelector} from 'dva';import {FileUpload, FileDownLoad} from '@/services/fileUploadService';interface IProps {/**文件上传地址,默认通用文件上传接口*/requestUrl?: string;/**完成返回事件*/onChange?: (value: any) => void;/** 当前值,父组件为form时通过initialValue传入 */value?: any;/** 固定写死的初始值 */defaultValue?: any;/**业务名称,用于文件上传的时候区分业务类型,接口处可能用于区分文件归属文件夹*/businessName?: string;/**是否禁用*/disabled?: boolean;/**图片宽度,默认一寸照片宽度120像素*/width?: number;/**图片高度,默认一寸照片高度168像素*/height?: number;/**待上传时的显示文字*/uploadText?: string;/**文件最大大小(M)*/maxSize?: number;/**裁切选择*/cropper?: ICropper;}interface ICropper {/**是否裁切*/isCrop: boolean;/**裁切模式有效,是否可用原图*/canUserMaster?: boolean;/**裁切模式有效,裁切固定比例,比如1:2为1/2*/aspectRatio?: string;}const PictureUpload = (props: IProps) => {const {
value, onChange, defaultValue, businessName, disabled, width, height, uploadText, maxSize = 2,
cropper = {isCrop: false,},
requestUrl = "/api/file/v1/doUploadFile"} = propsconst {languageList} = useSelector((global: any) => global.indexModel)const [avatar, setAvatar] = useState(value || defaultValue);const [loading, setLoading] = useState(false);const [cropVisible, setCropVisible] = useState(false);const [cropSrc, setCropSrc] = useState();const [uploadFile, setuploadFile] = useState<any>(null);const childRef = useRef();/** * 上传前事件 * @param file 文件 * * */const beforeUpload = (file: RcFile, fileList: RcFile[]) => {let maxPicSize = maxSize ? maxSize : 2;const isJPG = file.type === 'image/jpg';const isPNG = file.type === 'image/png';const isJPEG = file.type === 'image/jpeg';if (!(isJPG || isPNG || isJPEG)) {
message.error('只能上传jpg、png或者jpeg格式!');return false;}const isLt = file.size / 1024 / 1024 < maxPicSize;if (!isLt) {
message.error('最大只能上传' + maxPicSize + 'M大小的图片!');return false;}let isOk = (isJPG || isPNG || isJPEG) && isLt;if (isOk) {
setuploadFile(file);//需要开启裁剪 //if (false) {if (cropper && cropper.isCrop) {let reader = new FileReader();
reader.addEventListener("load", () => {
setCropSrc(reader.result);
setCropVisible(true);} );
reader.readAsDataURL(file);//开启裁切需要等裁切时间手动调起上传事件return false;} else {//无需裁剪return true;} }return false;}//dataUrl转换成fileconst dataURLtoFile = (dataurl: string, filename: string) => {if (!dataurl) {return null;}var arr: any = dataurl.split(','), mime = (arr || [])[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);while (n--) {
u8arr[n] = bstr.charCodeAt(n);}return new File([u8arr], filename, {type: mime});}const handleUpload = (type: number, key: any) => {let newFile: any = null;if (1 === type) {//原图
newFile = uploadFile;}if (2 === type) { // 使用的是裁切的图
newFile = dataURLtoFile(key, uploadFile.name);if (newFile) {
newFile.uid = uploadFile.uid;} }const formData = new FormData();
formData.append('localFlag', "false");
formData.append('privateFlag', "false");
formData.append('uploadFile', newFile);
formData.append('sysSign', process.env.sys_sign_no);
formData.append('catalogNo', businessName || 'pic');
setLoading(true);FileUpload('', formData).then((res: any) => {
setLoading(false);if (res.status === "200") {
getUrl(res.entity.file_id);
setCropVisible(false);if (onChange) {
onChange(res.entity.file_id);} } }) }/** * 上传事件 * @param info 返回参数 * * */const handleChange = (info: any) => {if (info.file.status === 'uploading') {
setLoading(true);return;}if (info.file.status === 'done') {
setLoading(false);if (info.file.response.status === "200") {
getUrl(info.file.response.entity.file_id);if (onChange) {
onChange(info.file.response.entity.file_id);} } } };/**图片裁剪方法 */ //onCancelconst handleCropCancel = () => {
setCropVisible(false);};// onOk方法,确定裁剪const handleCropperOk = () => {const child: any = childRef.current;const dataUrl = child.handleOk();
handleUpload(2, dataUrl);}//确定原图const handleFormer = () => {
setCropVisible(false);
handleUpload(1, true);};const getUrl = (fileIds: string) => {FileDownLoad({fileId: fileIds}).then(({entity}) => {if (entity) {
setAvatar(entity.accessPath);} }) }useEffect(() => { //加载图片if (value || defaultValue) {
getUrl(value || defaultValue);} else {
setAvatar(undefined) } }, [value, defaultValue]);/** * 没有图片时候的显示图标 * */const uploadButton = ( <div style={{width: width ? width : 120, height: height ? height : 168, verticalAlign: "middle"}}> <Row style={{width: '100%', height: '100%'}} justify="space-between" align="middle"> <Col span={24}> <CommIcon style={{fontSize: '30px'}} spin={loading} type={loading ? 'iconsync' : 'iconplus'}/> <div className="ant-upload-text">{uploadText ? uploadText : languageList('上传图片')}</div> </Col> </Row> </div> );return ( <div> <Upload name="uploadFile" accept='.jpg,.png,.jpeg' listType="picture-card" className="avatar-uploader" showUploadList={false}customRequest={async (options: any) => {
//自定义文件上传const formData = new FormData();
formData.append('localFlag', "false");
formData.append('privateFlag', "false");
formData.append('uploadFile', options.file);
formData.append('sysSign', process.env.sys_sign_no);
formData.append('catalogNo', businessName || 'pic');FileUpload('', formData).then((res: any) => {if (res.status === "200") {
options.onSuccess(res, options.file) } }) }}beforeUpload={beforeUpload}onChange={handleChange}disabled={disabled} > {avatar ?<img style={{width: width ? width : 120, height: height ? height : 168}}src={avatar}alt="avatar"/> : uploadButton} </Upload> {cropper.isCrop ? <Modal destroyOnClose centered={true}visible={cropVisible}maskClosable={false}onCancel={handleCropCancel}footer={[ <Button key="cancel" onClick={handleCropCancel}>取消</Button>,
cropper.canUserMaster ? (<Button key="former" onClick={handleFormer}>使用原图</Button>) : null,<Button key="cropper" type="primary" onClick={handleCropperOk}>确定裁剪</Button> ]}> <ImageCropper ref={childRef}src={cropSrc}aspectRatio={cropper.aspectRatio} /> </Modal> : null} </div> );}export default React.memo(PictureUpload)
裁切图片的方法文件,基于react-cropper插件:
import React, { useImperativeHandle,forwardRef} from 'react';import Cropper from 'react-cropper';import 'cropperjs/dist/cropper.css';interface IProps {src: string;aspectRatio?: string;
}var cropper = React.createRef();/** * 裁剪 */const ImageCropper = ({src, aspectRatio}: IProps, ref:any) => {//暴露出一个想要让父组件知道的对象,里面可以是属性也可以是函数 useImperativeHandle(ref, () => ({//第一个参数,要暴露给哪个(ref)?第二个参数要暴露出什么?
handleOk: () => {const dataUrl = cropper.current.cropper.getCroppedCanvas().toDataURL();return dataUrl;
}
}));return (
<div className="cropper-wrap">
<Cropper ref={cropper}src={src}style={{height: '400px', width: '100%'}}aspectRatio={aspectRatio}viewMode={1} //定义cropper的视图模式guides={false}zoomable={false} //是否允许放大图像background={false} //是否显示背景的马赛克rotatable={false} //是否旋转/
/>
</div>
);
}export default React.memo(forwardRef(ImageCropper));