需求实现
1可以支持上传最多九张图片
2图片支持预览 替换 删除
3支持自定义上传文件大小 格式 未上传提示
实现效果
代码封装
UploadImage组件
* @Description: 公共上传图片 * @param {Array} type 图片格式。默认类型为png,jpeg * @param {Number} size 图片限制大小 默认大小为8M * @param {String} errorTypeMsg 图片格式错误文字提示 * @param {String} errorTypeMsg 图片大小错误文字提示 * @param {Function} handleFetchUrl 选中图片后回调函数 * @param {String} uploadUrl 上传图片请求的url,默认为admin/fileUpload * @param {String} iconText 按钮文字 * @param {Object} style 样式 * @param {String} value 当前图片地址 */ import React, { Component } from 'react'; import { Tooltip, Upload, Icon, message, Modal, Divider, Spin } from 'antd'; import styles from './index.less'; import { fileUpload } from '@/services/common/upload'; export default class UploadImage extends Component { constructor(props) { super(props); this.state = { visible: false, shadow: false, loading: false, }; } componentWillMount() { this.props.onRef && this.props.onRef(this); } /** * @description: 上传前格式大小等校验 */ beforeUpload = (file) => { /** * @description: 父组件传入的参数 * @param {Array} type 图片格式。默认类型为png,jpeg * @param {Number} size 图片限制大小 默认大小为8M * @param {String} errorTypeMsg 图片格式错误文字提示 * @param {String} errorSizeMsg 图片大小错误文字提示 * @param {Function} checkSize 文件大小校验方式 */ let { type = ['image/jpeg', 'image/png', 'image/gif'], size, errorTypeMsg, errorSizeMsg, checkSize, } = this.props; size ? (size = size) : (size = 8); if (checkSize) { size = checkSize(file.type); } const isJpgOrPng = type.includes(file.type); if (!isJpgOrPng) { message.error( errorTypeMsg || '图片/视频格式错误!请上传jpeg/png/gif格式图片或avi/mp4格式视频' ); } const isLt8M = file.size < size * 1024 * 1024; if (!isLt8M) { message.error(errorSizeMsg || '图片/视频大小限制' + size + 'M!'); } return isJpgOrPng && isLt8M; }; handleUpload = ({ file }) => { /** * @description: * @param {Function} handleFetchUrl 选中图片后回调函数 * @param {String} uploadUrl 上传图片请求的url,默认为admin/fileUpload */ const { dispatch, uploadUrl, handleFetchUrl } = this.props; const formData = new FormData(); formData.append('file', file); formData.append('fileCode', 'PIC'); this.setState({ loading: true }); const upload = async (record) => { let upUrl = uploadUrl || fileUpload; try { const response = await upUrl(formData); if (response.returnCode === 0) { if (handleFetchUrl && typeof handleFetchUrl === 'function') { handleFetchUrl(response.data, file.type); } message.success('上传成功'); //上传成功的回调 this.props.handleUploadImage && this.props.handleUploadImage(); } this.setState({ loading: false }); } catch (error) { console.log(error, 'error'); this.setState({ loading: false }); } }; upload(); // dispatch({ // type: uploadUrl || 'admin/fileUpload', // payload: formData, // callback: (response) => { // if (response.returnCode === 0) { // if (handleFetchUrl && typeof handleFetchUrl === 'function') { // handleFetchUrl(response.data, file.type); // } // message.success('上传成功'); // } // }, // }); }; /** * @description: 改变visible * @param {*} */ handleSetVisible = (val, event) => { this.setState({ visible: val, }); if (event) { event.stopPropagation(); } }; /** * @description: 改变image上的阴影显示 */ setShadow = (val) => { this.setState({ shadow: val, }); }; /** * @description: 删除图片 */ handleDeleteImg = (event) => { const { dispatch, uploadUrl, handleFetchUrl } = this.props; handleFetchUrl(''); if (event) { //函数删除的回调 this.props.onHandleDelete && this.props.onHandleDelete(); event.stopPropagation(); } }; render() { /** * @description: * @param {String} iconText 按钮文字 * @param {Object} style 样式 * @param {String} value 内容 * @param {String} fileType 文件类型 img/video * @param {String} backgroundImg 背景图,如果是video,缩略图中显示背景图,弹窗大图中显示video * @param {String} showDelete 是否显示删除按钮,showDelete=delete 不显示 * @param {Number} size 图片大小单位M,默认8 */ const { iconText, style, value, fileType, backgroundImg, disabled, infoText, showDelete, size, errorSizeMsg, errorTypeMsg, } = this.props; console.log(iconText) const { visible, shadow, loading } = this.state; return ( <div className={styles.ContentBox}> <Spin spinning={loading}> <Upload listType="picture-card" showUploadList={false} customRequest={this.handleUpload} beforeUpload={this.beforeUpload} disabled={disabled} > {value ? ( <div className={styles.imgContent} style={{ width: 150, height: 150, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', ...style, }} onMouseOver={() => this.setShadow(true)} onMouseLeave={() => this.setShadow(false)} > {(fileType === 'img' || !fileType) && ( <img alt={iconText} src={value} style={{ maxWidth: (style && style.width && style.width) || 150, maxHeight: (style && style.height && style.height) || 150, }} /> )} {fileType === 'video' && ( <img alt={iconText} src={backgroundImg} style={{ maxWidth: (style && style.width && style.width) || 150, maxHeight: (style && style.height && style.height) || 150, }} /> )} {shadow && ( <div className={styles.imgShadow}> <div className={styles.shadowDiv} onClick={(event) => { event.stopPropagation(); }} /> <div className={styles.shadowDiv}> <Tooltip title="放大"> <Icon type="zoom-in" onClick={(event) => { this.handleSetVisible(true, event); }} /> </Tooltip> <Divider type="vertical" style={{ width: 2 }} /> <Tooltip title="替换"> <Icon type="upload" /> </Tooltip> {!disabled && showDelete != 'delete' && ( <> <Divider type="vertical" style={{ width: 2 }} /> <Tooltip title="删除"> <Icon type="delete" onClick={(event) => { this.handleDeleteImg(event); }} /> </Tooltip> </> )} </div> <div className={styles.shadowDiv} onClick={(event) => { event.stopPropagation(); }} /> </div> )} </div> ) : ( <div style={{ width: 150, height: 150, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', ...style, }} > <Icon type="plus" /> <div className="ant-upload-text" style={{ marginTop: 14, whiteSpace: 'normal' }}> {iconText || '上传图片'} </div> </div> )} </Upload> <div style={{ color: '#666', fontSize: '12px', lineHeight: '12px', textAlign: 'center' }}> {infoText ? infoText : ''} </div> <Modal maskClosable={false} maskClosable={false} visible={visible} footer={null} onCancel={() => this.handleSetVisible(false)} > {(fileType === 'img' || !fileType) && ( <img alt={iconText} style={{ width: 476, }} src={value} /> )} {fileType === 'video' && ( <video alt={iconText} style={{ width: 476, }} src={value} controls /> )} </Modal> </Spin> </div> ); } }
备注
fileUpload为接口调用
export function fileUpload(params) {
return request({ url: `xxxxxxxx`, method: 'form', params });
}
BaseUploadImage组件
* @Description: 公共上传图片 * @param {Array} type 图片格式。默认类型为png,jpeg * @param {Number} size 图片限制大小 默认大小为8M * @param {String} errorTypeMsg 图片格式错误文字提示 * @param {String} errorTypeMsg 图片大小错误文字提示 * @param {Function} handleFetchUrl 选中图片后回调函数,目前只在单选中有,适配旧版本 * @param {String} uploadUrl 上传图片请求的url,默认为admin/fileUpload * @param {String} iconText 按钮文字 * @param {Object} style 样式 * @param {String} value 当前图片地址 * @param {Boolean} multiple 是否多选 * * *@param {String} showDelete 是否显示删除按钮,showDelete=0 不显示 * * @param {Number} size 图片大小单位M */ import React, { Component } from 'react'; import { Tooltip, Upload, Icon, message, Modal, Divider } from 'antd'; import styles from './index.less'; import UploadImage from './upload.js'; export default class BaseUploadImage extends Component { constructor(props) { super(props); this.state = { imgList: (props.multiple ? props.value : [props.value]) || [''], }; } componentWillMount() { this.props.onRef && this.props.onRef(this); window.up = this; } /** * @description: 父组件传入的默认值发生改变时,更改图片的默认显示 * @param {*} nextProps 最新的props */ componentWillReceiveProps(nextProps) { const { props: { multiple, maxCount = 9 }, } = this; const { value } = nextProps; let imgList; if (nextProps.value !== this.props.value) { if (multiple) { imgList = value; if (value && value.length && value.length < maxCount) { imgList.push(''); } } else { imgList = [value]; } this.setState({ imgList: imgList || [''], }); } } /** * @description: 上传图片回调 * @param {*} val 上传图片后服务器返回的值 * @param {*} index 上传的图片index,多选时用 */ handleSetImg = (val, index) => { const { multiple, maxCount = 9 } = this.props; const { imgList } = this.state; if (multiple) { if (!val && imgList.length > 0) { if (imgList.length === maxCount) { imgList.push(''); } imgList.splice(index, 1); } else { imgList[index] = val; if (maxCount > imgList.length) { imgList.push(''); } } this.setState({ imgList, }); } else { this.setState({ imgList: [val] }); } }; handleChange = () => { this.props.handleDeleteImg && this.props.handleDeleteImg(); }; handleUploadImg = () => { console.log(22222); this.props.handleUpload && this.props.handleUpload(); }; /** * @description: 获取当前上传成功的图片 * @return {*} 当前上传成功的图片 */ getActiveImg = () => { const { multiple, maxCount = 9 } = this.props; const { imgList } = this.state; if (multiple) { //因为多选的时候,如果没有上传到最大可上传限度,会多出一条数据,所以如果上传数量小于maxCount,需要删掉最尾的一条数据 if (maxCount === imgList.length && imgList[imgList.length - 1]) { return imgList; } else { return imgList.slice(0, imgList.length - 1); } } else { return imgList && imgList[0]; } }; render() { /** * @description: * @param {Boolean} multiple 是否多选 * @param {Number} maxCount 多选时,最多可以上传几个 */ const { multiple, maxCount = 9, iconText, style, fileType, backgroundImg, disabled, infoText, dispatch, showDelete, size, errorSizeMsg, errorTypeMsg, } = this.props; const { imgList } = this.state; return ( <div className={styles.ContentBox}> {multiple && (imgList && typeof imgList === 'object' && imgList.length > 0) ? ( imgList.map((item, index) => { console.log(item); return ( maxCount >= index && ( <UploadImage onHandleDelete={() => { this.handleChange(); }} handleUploadImage={() => { this.handleUploadImg(); }} iconText={iconText} style={style} fileType={fileType} backgroundImg={backgroundImg} disabled={disabled} infoText={infoText} key={index} dispatch={dispatch} value={item} showDelete={showDelete} size={size} errorTypeMsg={errorTypeMsg} errorSizeMsg={errorSizeMsg} handleFetchUrl={ this.props.handleFetchUrl || ((val) => this.handleSetImg(val, index)) } /> ) ); }) ) : ( <UploadImage onHandleDelete={() => { this.handleChange(); }} handleUploadImage={() => { this.handleUploadImg(); }} iconText={iconText} style={style} fileType={fileType} backgroundImg={backgroundImg} disabled={disabled} infoText={infoText} dispatch={dispatch} value={imgList[0]} showDelete={showDelete} size={size} errorTypeMsg={errorTypeMsg} errorSizeMsg={errorSizeMsg} handleFetchUrl={this.props.handleFetchUrl || ((val) => this.handleSetImg(val))} /> )} </div> ); } }
index.less
样式文件
.ContentBox { display: inline-block; .imgContent { padding: 8px; position: relative; display: flex; justify-content: center; align-items: center; .imgShadow { width: 100%; height: 100%; position: absolute; background: rgba(0,0,0,0.5); .shadowDiv { height: 33.3333333%; display:flex; justify-content: space-around; color: #fff; align-items: center; font-size: 18px; padding: 0 10px; } } } }
父组件引用
<Form.Item label="上传图片"> <BaseUploadImage onRef={(ref) => { this.upload = ref; }} value={this.state.themeImgPath} multiple /> </Form.Item>