Upload组件customRequest的使用(antd)

14,569 阅读3分钟

背景

在ant-design提供的Upload组件中,有时候需要使用特定的上传方式,比如调用公司内部或者第三方提供的上传文件的sdk。对于这种需求场景,ant-design的Upload组件提供了prop:customRequest,但是具体的使用方法并没有细说。 本文详细介绍prop:customRequest的功能与使用。

功能介绍

通过覆盖默认的上传行为,可以自定义自己的上传实现。

用代码来说就是

const request = customRequest || defaultRequest;

所以接下来通过介绍默认的上传函数defaultRequest,来把握prop:customRequest的环境与功能。

默认上传函数

默认上传函数主要做了以下几件事:

  1. 使用XMLHttpRequest技术创建ajax请求
  2. 使用FormData构造表单数据
  3. 对请求的进度xhr.upload.onprogress、成功xhr.onload、失败xhr.onerror分别回调处理函数option.onProgressoption.onSuccessoption.onError

默认上传函数的声明如下:

// 默认上传方法
function defaultRequest(option: UploadRequestOption): {
    abort(): void;
}

上传函数的参数

上传函数的参数(即上文代码中的option: UploadRequestOption)列举如下:

// 上传函数的参数
export interface UploadRequestOption<T = any> {
  /**
   * @description 
   * @example option.onProgress({ percent: 50 }) 已上传50%
   */
  onProgress?: (event: UploadProgressEvent) => void;

  /**
   * @description 上传失败回调函数
   * @example option.onError({ status: 500, method: 'POST', url: 'xxx' })
   */
  onError?: (event: UploadRequestError | ProgressEvent, body?: T) => void;

  /**
   * @description 上传成功回调函数
   * @example option.onSuccess(res.data)
   */
  onSuccess?: (body: T, xhr: XMLHttpRequest) => void;

  /**
   * @description 由Upload组件的props:data 指定的额外参数
   */
  data?: object;

  /**
   * @description 文件名
   * @example xxx.png
   */
  filename?: string;

  /**
   * @description 文件数据
   */
  file: Exclude<BeforeUploadFileType, File | boolean> | RcFile;

  /**
   * @description 若上传的目标站点与当前站点不一样,是否携带cookie
   */
  withCredentials?: boolean;

  /**
   * @description 由Upload组件的props:action 指定的上传地址
   */
  action: string;

  /**
   * @description 由Upload组件的props:headers 指定的上传的请求头部
   */
  headers?: UploadRequestHeader;

  /**
   * @description 由Upload组件的props:post 指定的上传的请求方法
   */
  method: UploadRequestMethod;
}

使用介绍

下面结合上文的介绍,给出一个笔者使用第三方上传sdk编写的DEMO,以供参考。

DEMO支持的功能有:

  • 批量上传图片文件
  • 显示前后的文件名与链接
  • 显示预览
  • 点击复制
import React, { useState } from 'react'
import {
  Upload,
  message,
  List,
  Button,
  Image,
  Tag,
  Spin,
  Typography,
  Modal,
} from 'antd'
import { InboxOutlined } from '@ant-design/icons'
// 第三方上传sdk
import uploadService from '~/services/uploadService'
import {
  RcFile,
  UploadChangeParam,
  UploadFile,
} from 'antd/lib/upload/interface'
import copy from 'copy-to-clipboard'

import './index.scss'

interface FileItem {
  uid: string
  filename: string
  status?: string
  thumbUrl?: string
  url?: string
  lastModifiedTime?: string
}

const handleCopy = (text?: string) => {
  if (text && copy(text)) return message.success('复制成功')

  message.error('复制失败, 请重试或者用自己动手吧')
}

function upload2Item(uploadFile: UploadFile): FileItem {
  return {
    uid: uploadFile.uid,
    filename: uploadFile.name,
    status: uploadFile.status,

    // 这里对应到 onSuccess 的回调参数
    url: uploadFile.response,
    thumbUrl: uploadFile.response,
    lastModifiedTime: uploadFile.lastModifiedDate?.toUTCString(),
  }
}

const DoraemonPage: React.FC<{}> = () => {
  // 自定义文件列表数据,收集文件上传状态,用于List列表展示
  const [fileList, setFileList] = useState<FileItem[]>([])

  const handleUpload = async (option: any) => {
    const file = option.file as File

    try {
      // 使用第三方服务进行文件上传
      const result = await uploadService.upload(file)

      // onSuccess的回调参数可以在 UploadFile.response 中获取
      option.onSuccess(result.url)
    } catch (error) {
      option.onError(error)
    }
  }

  const handleBeforeUpload = (file: RcFile, fileList: RcFile[]) => {
    const SAFE_FILE_SUM = 20

    if (fileList.length <= SAFE_FILE_SUM) return Promise.resolve()

    return new Promise<void>((resolve, reject) =>
      Modal.confirm({
        title: 'Are U Sure ???',
        content: `文件个数大于${SAFE_FILE_SUM}(${fileList.length}个)`,
        onOk() {
          resolve()
        },
        onCancel() {
          reject()
        },
      })
    )
  }

  const handleUploadChange = (info: UploadChangeParam) =>
    setFileList(info.fileList.map(upload2Item))

  const renderStatus = (status?: string) => {
    if (status === 'done') return <Tag color="green">done</Tag>
    if (status === 'error') return <Tag color="red">error</Tag>
    if (status === 'uploading') return <Tag color="geekblue">uploading</Tag>
    return <Tag color="blue">prepare</Tag>
  }

  const renderPreviewImage = (src?: string) => (
    <div className="preview-image-wrapper">
      {src ? (
        <Image
          style={{ height: 80, width: 'inherit', maxWidth: 120 }}
          alt="图片加载失败"
          src={src}
        />
      ) : (
        <Spin />
      )}
    </div>
  )

  const renderFileItem = (item: FileItem) => (
    <List.Item
      actions={[
        <Button key="copy" type="link" onClick={() => handleCopy(item.url)}>
          复制链接
        </Button>,
      ]}
      extra={renderPreviewImage(item.thumbUrl)}
    >
      <List.Item.Meta title={item.filename} description={item.url} />
      {renderStatus(item.status)}
      <Typography.Text type="secondary">
        {'At ' + item.lastModifiedTime}
      </Typography.Text>
    </List.Item>
  )

  return (
    <div className="doraemon-page">
      <div className="doraemon-upload-box">
        <Upload.Dragger
          accept="image/png, image/jpeg"
          multiple
          showUploadList={false}
          beforeUpload={handleBeforeUpload}
          customRequest={handleUpload}
          onChange={handleUploadChange}
        >
          <p className="ant-upload-drag-icon">
            <InboxOutlined />
          </p>
          <p className="ant-upload-text">文件上传</p>
          <p className="ant-upload-hint">
            Support for a single or bulk upload.
          </p>
        </Upload.Dragger>
      </div>

      <div className="doraemon-upload-list">
        <List
          bordered
          itemLayout="horizontal"
          dataSource={fileList}
          renderItem={renderFileItem}
        />
      </div>
    </div>
  )
}

export default DoraemonPage

页面的效果如下所示:

上传中👇 上传中

上传完成👇 上传完成

  • 列表右上角分功能按钮不在本次的讨论范围内,故代码中略去。
  • 图片url部分做了高斯模糊处理,放心不是眼睛的问题哈~😝