背景
在ant-design提供的Upload组件中,有时候需要使用特定的上传方式,比如调用公司内部或者第三方提供的上传文件的sdk。对于这种需求场景,ant-design的Upload组件提供了prop:customRequest
,但是具体的使用方法并没有细说。
本文详细介绍prop:customRequest
的功能与使用。
功能介绍
通过覆盖默认的上传行为,可以自定义自己的上传实现。
用代码来说就是
const request = customRequest || defaultRequest;
所以接下来通过介绍默认的上传函数defaultRequest
,来把握prop:customRequest
的环境与功能。
默认上传函数
默认上传函数主要做了以下几件事:
- 使用XMLHttpRequest技术创建ajax请求
- 使用FormData构造表单数据
- 对请求的进度
xhr.upload.onprogress
、成功xhr.onload
、失败xhr.onerror
分别回调处理函数option.onProgress
、option.onSuccess
、option.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部分做了高斯模糊处理,放心不是眼睛的问题哈~😝