前端学习案例-ant design带你封装一个上传图片组件

简介: 前端学习案例-ant design带你封装一个上传图片组件

需求实现

1可以支持上传最多九张图片

2图片支持预览 替换 删除

3支持自定义上传文件大小 格式 未上传提示

实现效果

image.png

image.png

image.png

 


 


 

代码封装

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>


相关文章
|
3月前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
68 0
|
2月前
|
监控 前端开发 数据可视化
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
@icraft/player-react 是 iCraft Editor 推出的 React 组件库,旨在简化3D数字孪生场景的前端集成。它支持零配置快速接入、自定义插件、丰富的事件和方法、动画控制及实时数据接入,帮助开发者轻松实现3D场景与React项目的无缝融合。
213 8
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
|
2月前
|
前端开发 开发者 C++
独家揭秘:前端大牛们如何高效学习新技术,保持竞争力!
【10月更文挑战第31天】前端技术飞速发展,如何高效学习新技术成为关键。本文通过对比普通开发者与大牛们的策略,揭示了高效学习的秘诀:明确目标、主动探索、系统资源、实践应用和持续学习。通过这些方法,大牛们能更好地掌握新技术,保持竞争力。示例代码展示了如何通过实践加深理解。
56 4
|
2月前
|
前端开发 JavaScript 开发者
揭秘前端高手的秘密武器:深度解析递归组件与动态组件的奥妙,让你代码效率翻倍!
【10月更文挑战第23天】在Web开发中,组件化已成为主流。本文深入探讨了递归组件与动态组件的概念、应用及实现方式。递归组件通过在组件内部调用自身,适用于处理层级结构数据,如菜单和树形控件。动态组件则根据数据变化动态切换组件显示,适用于不同业务逻辑下的组件展示。通过示例,展示了这两种组件的实现方法及其在实际开发中的应用价值。
49 1
|
3月前
|
缓存 前端开发 JavaScript
前端serverless探索之组件单独部署时,利用rxjs实现业务状态与vue-react-angular等框架的响应式状态映射
本文深入探讨了如何将RxJS与Vue、React、Angular三大前端框架进行集成,通过抽象出辅助方法`useRx`和`pushPipe`,实现跨框架的状态管理。具体介绍了各框架的响应式机制,展示了如何将RxJS的Observable对象转化为框架的响应式数据,并通过示例代码演示了使用方法。此外,还讨论了全局状态源与WebComponent的部署优化,以及一些实践中的改进点。这些方法不仅简化了异步编程,还提升了代码的可读性和可维护性。
|
3月前
|
前端开发 JavaScript
CSS样式穿透技巧:利用scoped与deep实现前端组件样式隔离与穿透
CSS样式穿透技巧:利用scoped与deep实现前端组件样式隔离与穿透
361 1
|
3月前
|
前端开发 JavaScript 开发者
Web组件:一种新的前端开发范式
【10月更文挑战第9天】Web组件:一种新的前端开发范式
96 2
|
3月前
|
JavaScript 前端开发 Java
VUE学习四:前端模块化,ES6和ES5如何实现模块化
这篇文章介绍了前端模块化的概念,以及如何在ES6和ES5中实现模块化,包括ES6模块化的基本用法、默认导出与混合导出、重命名export和import,以及ES6之前如何通过函数闭包和CommonJS规范实现模块化。
152 0
VUE学习四:前端模块化,ES6和ES5如何实现模块化
|
3月前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
239 14
|
3月前
|
人工智能 自然语言处理 运维
前端大模型应用笔记(一):两个指令反过来说大模型就理解不了啦?或许该让第三者插足啦 -通过引入中间LLM预处理用户输入以提高多任务处理能力
本文探讨了在多任务处理场景下,自然语言指令解析的困境及解决方案。通过增加一个LLM解析层,将复杂的指令拆解为多个明确的步骤,明确操作类型与对象识别,处理任务依赖关系,并将自然语言转化为具体的工具命令,从而提高指令解析的准确性和执行效率。