如何将antd的upload组件完全改造为定制化

302 阅读3分钟

无可厚非antd已经是react项目必不可少的组件库之一了,查阅文档

这篇文章主要写一下开发中遇到的对upload组件在formItem中的二次定制封装

样式基本完全与原antd一致,支持多选上传,自定义定制尾部按钮等 image.png

代码如下

index.tsx文件内容

import React, { useState } from 'react';
import { Button, Form, Progress, Tooltip, Upload, message } from 'antd';
import { FormItemProps } from 'antd/lib/form';
import { NamePath } from 'antd/lib/form/interface';
import axios from 'axios';
import { DeleteOutlined, DownloadOutlined, EyeOutlined, PaperClipOutlined, UploadOutlined } from '@ant-design/icons';
import './index.scss';

interface FormUploadPicFieldInterface {
  formItemProps?: FormItemProps //formItem其他的配置
  label: string  // formlabel
  name: NamePath  //form字段名
  minNum?: number //设置最少上传数量 -1表示不必传 默认必传1个
  maxNum?: number // 设置最大上传数量 -1表示随便传 默认6个
  disabled?: boolean //是否只读只读模式
  accept?: string[], //限制可上传的文件类型
  fileSize?: number, //可以上传的文件的大小 单位Mb默认50Mb
  fetchParams?: object //默认请求携带参数
  axiosUrl: string, //请求接口
  tip?: string //提示语
  disabledShowBtn?: boolean //禁用状态下是否显示上传按钮 默认显示
}

const FormUploadField: React.FC<FormUploadPicFieldInterface> = ({
                                                                  formItemProps,
                                                                  name,
                                                                  label,
                                                                  disabled,
                                                                  minNum,
                                                                  maxNum,
                                                                  accept,
                                                                  fileSize,
                                                                  fetchParams,
                                                                  axiosUrl,
                                                                  tip,
                                                                  disabledShowBtn,
                                                                  noLabel
                                                                }) => {
  const validator = (rule, value) => {
    if (disabled) return Promise.resolve();
    if (value) {
      if (minNum !== -1 && value.length < minNum) {
        return Promise.reject('请至少上传' + minNum + '个文件');
      }
      if (maxNum !== -1 && value.length > maxNum) {
        return Promise.reject('请最多上传' + maxNum + '个文件');
      }
      if (value.filter((item) => item.status === 'error').length > 0) {
        return Promise.reject('文件上传失败,请删除重试');
      }
      if (value.filter((item) => item.status === 'uploading').length > 0) {
        return Promise.reject('文件正在上传中');
      }
    } else {
      if (minNum !== -1) {
        return Promise.reject('请至少上传' + minNum + '个文件');
      }
    }
    return Promise.resolve();
  };
  
  return (
    <Form.Item
      labelCol={{ span: 24 }}
      wrapperCol={{ span: 24 }}
      {...formItemProps}
      label={noLabel?'':label}
      name={name}
      required={minNum !== -1}

      rules={[{ validator: validator }]}
    >
      <FormUpload disabled={disabled} maxNum={maxNum} accept={accept} fileSize={fileSize} fetchParams={fetchParams}
                  axiosUrl={axiosUrl} tip={tip} disabledShowBtn={disabledShowBtn} />
    </Form.Item>
  );
};

FormUploadField.defaultProps = {
  disabled: false,
  minNum: 1,
  maxNum: 6,
  accept: [],
  fileSize: 50,
  fetchParams: {},
  disabledShowBtn: true,
};
FormUploadField.displayName = 'FormUploadField';

export default FormUploadField;


interface FormPicProps {
  onChange?: (a) => void
  value?: any
  disabled: boolean
  maxNum: number
  accept: string[]
  fileSize: number
  fetchParams?: object,
  axiosUrl: string
  tip?: string
  disabledShowBtn?: boolean
}

const FormUpload: React.FC<FormPIcProps> = ({
                                              onChange,
                                              value,
                                              disabled,
                                              maxNum,
                                              accept,
                                              fileSize,
                                              fetchParams,
                                              axiosUrl,
                                              tip,
                                              disabledShowBtn,
                                            }) => {
  const [loadingBtn, setLoadingBtn] = useState(false);

  //内容变化后触发
  const uploadOnChange = (v) => {
    const fileList = v.fileList.map((item) => {
      if (item?.response) {
        return item?.response;
      }
      return item;
    });
    onChange(fileList);
  };

  //上传前文件校验
  const beforeUpload = (file) => {
    const mime = file.name.slice(file.name.lastIndexOf('.')).toLowerCase();
    if (accept.length > 0 && !accept.includes(mime)) {
      message.warning(`请上传${accept}文件`);
      return Upload.LIST_IGNORE;
    }
    if (file.size > fileSize * 1024 * 1024) {
      message.warning(`请上传小于${fileSize}Mb的文件`);
      return Upload.LIST_IGNORE;
    }
    return true;
  };

  //自定义上传
  const customRequest = (options) => {
    const { onProgress, onError, onSuccess, file } = options;
    let data = new FormData();
    data.append('file', file);
    Object.keys(fetchParams).forEach((item) => {
        data.append(item, fetchParams[item]);
    });
    axios({
      url: axiosUrl,
      method: 'post',
      data: data,
      onUploadProgress: (e) => {
        onProgress({ percent: e.loaded / e.total * 100 | 0 });
      },
    }).then((r) => {
      const { data } = r.data;
      if(data&&data?.file_url){
        onSuccess({
          uid: file.uid,
          name: file.name,
          status: 'done',
          url: data.file_url,
          key: data.key,
          thumbUrl: data?.thumb_url, //文件浏览地址
          sys_file_url: data?.sys_file_url, //文件下载地址
        });
      }else{
        onError({ message:'上传失败' });
      }
    }).catch((e) => {
      onError(e);
    });
  };
  //回显或上传成功文件下载
  const onDownload = (file) => {
    if (loadingBtn) {
      message.warning('有文件下载中,请稍后');
      return;
    }
    setLoadingBtn(true);
    //文件下载方法
    getDownload(file, {}).then(() => {
      message.success('下载成功');
      setLoadingBtn(false);
    }).catch(() => {
      message.error('下载失败');
      setLoadingBtn(false);
    });
  };
  //在线浏览
  const onPreview = (file) => {
    window.open(file.thumbUrl);
  };

  //自定义文件列表
  const itemRender = (originNode, file, fileList, action) => {
    return <div className={'upload-render-item'}>
      {file.status === 'uploading' ?
        <Progress percent={file?.percent} size={18} type={'circle'} />
        : <PaperClipOutlined className={'icon-color'} />
      }
      <div className={'render-item-label'} onClick={action?.download}>
        <Tooltip title={file?.error?.message}>
          <a title={file.name} style={{
            color: file?.status === 'error' ? '#ff4d4f' : file.status === 'uploading' ? '#333333' : '#4882f3',
          }}>{file.name}</a >
        </Tooltip>
      </div>
      <div>
        {file?.thumbUrl &&
        <Button size={'small'} type={'text'} block icon={<EyeOutlined className={'icon-color'} />}
                onClick={action?.preview} />
        }
        {file?.url &&
        <Button size={'small'} type={'text'} block icon={<DownloadOutlined className={'icon-color'} />}
                onClick={action?.download} />
        }
        {!disabled &&
        <Button size={'small'} type={'text'} danger block icon={<DeleteOutlined />} onClick={action?.remove} />
        }
      </div>
    </div>;
  };
  return (
    <>
      <Upload name='file'
              className={'form-upload'}
              fileList={value}
              multiple={maxNum!==1}
              onChange={uploadOnChange}
              disabled={disabled}
              accept={accept.join(',')}
              beforeUpload={beforeUpload}
              customRequest={customRequest}
              itemRender={itemRender}
              onDownload={onDownload}
              onPreview={onPreview}
      >
        {
          (!disabled || (disabled && disabledShowBtn)) && <>
            <Button disabled={disabled} icon={<UploadOutlined />}>上传</Button>
            {tip && <div className='ant-upload-hint'>{tip}</div>}
          </>
        }
      </Upload>
    </>
  );
};
FormUpload.defaultProps = {
  value: [],
};
  

index.scss代码

.form-upload {
  .upload-render-item {
    display: flex;
    align-items: center;
    margin: 2.5px 0;

    &:hover {
      background-color: #f5f5f5;
    }

    .ant-progress-line{
        margin-bottom: 0 !important;
    }
    
    .render-item-label {
      flex: 1;
      width: 0;
      padding: 0 5px;
      cursor: pointer;
      white-space: nowrap;
      text-overflow: ellipsis;
      overflow: hidden;
    }

    .ant-btn-sm {
      width: 22px !important;
      height: 22px !important;
      padding: 2px !important;
      margin: 0 3px;
    }

    .icon-color {
      color: rgba(0, 0, 0, 0.45)
    }
  }
}

使用案例

<FormUploadField label={'文件影像'} minNum={-1} name={'reply_files_list'} maxNum={-1} span={24}
                         formItemProps={{ labelCol: { span: 6 } }}
                         upload_type={'2'} axiosUrl={`/file/upload`}
                         accept={['.doc', '.docx', '.xls', '.xlsx', '.jpg', '.png', '.pdf', '.ofd']}
                         tip={'支持扩展名:doc, docx, xls, xlsx, jpg, png, pdf, ofd'} />