# antd 封装上传组件 FormUpload

import { UploadOutlined } from "@ant-design/icons";
import { Button, notification, Upload } from "antd";
import React, { useEffect, useRef } from "react";
// import { request } from 'umi';
import type { UploadProps } from "antd";

export const getExtension = (fileName: string) => {
  return fileName.substring(fileName.lastIndexOf("."));
};

export const getSizeMb = (fileSize: number) => {
  return fileSize / 1024 / 1024;
};

export const commonUpload = async (
  serviceName: string,
  file: File,
  options = {}
) => {
  const formData = new FormData();
  formData.append("file", file);
  try {
    const res = await new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({
          success: true,
          code: 200,
          data: {
            fileName: "xx.doc",
            ossKey: "common/upload/12345/xx.doc",
          },
        });
      }, 5000);
    });
    // request(serviceName, {
    //   method: 'POST',
    //   data: formData,
    //   params: options,
    // });
    return Promise.resolve(res);
  } catch (e) {
    return Promise.reject(e);
  }
};

export interface FileItem {
  name: string;
  fileName: string;
  ossKey: string;
}

export interface FormUploadProps
  extends Omit<
    UploadProps,
    "accept" | "beforeUpload" | "onChange" | "customRequest"
  > {
  serviceName: string;
  responseKey: string;
  accepts?: string[];
  limitSize?: number;
  options?: any;
  showBtn?: boolean;
  children?: React.ReactDOM | React.ReactDOM[];
  onChange?: (file: FileItem[]) => void;
  onLoadUpload?: (file: File) => void;
  onFinallyUpload?: () => void;
}

const ACCEPTS = [".doc", ".docx", ".pdf", ".xls", ".xlsx"];
const LIMIT_SIZE = 100; // 100M
const SHOW_BTN = true;
const LOADING = "loading";

/**
 * @param serviceName 接口 - '/common/upload'
 * @param responseKey 接口响应唯一标识字段 - ossFilePath
 * @param accepts 接受文件类型 - ['.doc','.docx']
 * @param limitSize 限制文件大小(单位: M) - 100
 * @param options 自定义其他额外参数
 * @param showBtn 是否显示上传按钮,用于查看时只显示列表
 * @param children 子组件
 * @param others 兼容Upload组件其他属性
 * @callback onLoadUpload 每个附件上传前的回调
 * @callback onFinallyUpload 全部附件上传成功或失败时的回调
 * @use Form.Item 需要加 valuePropName = 'fileList' -
 */
const FormUpload: React.FC<FormUploadProps> = (props: FormUploadProps) => {
  const {
    onChange: propsOnchange,
    disabled,
    serviceName,
    responseKey,
    accepts = ACCEPTS,
    limitSize = LIMIT_SIZE,
    options = {},
    showBtn = SHOW_BTN,
    onLoadUpload,
    onFinallyUpload,
    ...others
  } = props;

  const onLive = useRef(false); //保存当前组件是否已挂载
  const uploadMap = useRef({}); //保存正在上传的附件uid

  const onChange = (file: any) => {
    const { fileList } = file;
    const newFileList: FileItem[] = [];
    fileList.forEach((item: any) => {
      if (item.response && item.status === "done") {
        newFileList.push({
          name: item.name,
          fileName: item.name,
          ossKey: item.response[responseKey],
        });
      } else if (item.status !== "error" && item.status !== "removed") {
        newFileList.push(item);
      }
    });
    //调用Form传递下来的 onChange事件 把数据给Form实例保管
    propsOnchange?.(newFileList);
  };

  const customRequest = async (params: any) => {
    const currentFileId = params.file.uid;
    try {
      onLoadUpload?.(params.file);
      uploadMap.current[currentFileId] = LOADING;
      const res = await commonUpload(serviceName, params.file, options);
      console.log(res, "res");

      if (onLive.current) params.onSuccess(res?.data);
    } catch (e) {
      if (onLive.current) params.onError(e);
    } finally {
      const isLoading = uploadMap.current[currentFileId] === LOADING;
      delete uploadMap.current[currentFileId];
      if (
        isLoading &&
        onLive.current &&
        Object.keys(uploadMap.current).length === 0
      ) {
        onFinallyUpload?.();
      }
    }
  };

  const beforeUpload = (file: File) => {
    if (!accepts.includes(getExtension(file.name))) {
      notification.error({
        message: `文件格式不支持!请上传格式为 ${accepts.join(",")} 的文件`,
      });
      return Upload.LIST_IGNORE;
    }
    if (getSizeMb(file.size) > limitSize) {
      notification.error({
        message: `文件过大!请上传大小不超过 ${limitSize}M 的文件`,
      });
      return Upload.LIST_IGNORE;
    }

    return file;
  };

  const onRemove = (file: any) => {
    delete uploadMap.current[file.uid];
    if (
      file.originFileObj &&
      onLive.current &&
      Object.keys(uploadMap.current).length === 0
    ) {
      //移除的是 正在上传的文件 并且 组件已挂载 并且没有正在上传的文件时
      onFinallyUpload?.();
    }
  };

  useEffect(() => {
    onLive.current = true;
    return () => {
      onLive.current = false;
    };
  }, []);

  return (
    <Upload
      disabled={disabled}
      accept={accepts.join(",")}
      onChange={onChange}
      beforeUpload={beforeUpload}
      customRequest={customRequest}
      onRemove={onRemove}
      {...others}
    >
      {props.children
        ? props.children
        : showBtn && (
            <Button disabled={disabled} icon={<UploadOutlined />}>
              附件上传
            </Button>
          )}
    </Upload>
  );
};

export default FormUpload;

测试一下

import React from "react";
import { Form, Button } from "antd";
//使用
const App = () => {
  const [form] = useForm();

  return (
    <div>
      <Button
        onClick={() => {
          console.log(form.getFieldsValue());
        }}
      >
        获取结果
      </Button>
      <Form form={form}>
        <Form.Item name={"fileList"} valuePropName={"fileList"} label={"附件"}>
          <FormUpload serviceName="/upload" responseKey="ossKeys" />
        </Form.Item>
      </Form>
    </div>
  );
};
Last Updated: 5/4/2022, 5:39:41 PM