前言
本文章只是对开发过程的记录,点击查看完成代码
该使用组件说明: 点击查看组件使用说明
内部使用组件信息: Plupload中文文档
起因
公司业务需要上传较大文件,但是接口不支持超过50M的文件上传。
于是需要前端在上传时进行切片处理。
前端采用的技术是react + antDesign,查找了antd的官法文档,发现Upload组件没有文件分片的功能。
在npm上根据react 和upload为关键字进行查找,找到了react-plUpload包。
下载安装后,发现无论是样式效果还是功能实现都与antd的Upload组件相差较远
于是自己决定做一个满足需求的组件
开发
初步实现
- 使用antd组件构建出 上传按钮 以及 选择的文件列表
<div id={`plupload_${this.props.id}`} >
<Space>
<Button
id={this.getComponentId()}
icon={<UploadOutlined/>}
>
{this.props.buttonSelect || 'select'}
</Button>
</Space>
{list.length > 0 &&
<List
size={'small'}
dataSource={list}
renderItem={item => item}
/>}
</div>
- 组挂载完成后件实例化plUpload组件
具体参数查看:plupload中文文档
this.uploader = new plUploadJs.Uploader(_.extend({
container: `plupload_${this.props.id}`,
runtimes: 'html5',
multipart: true,
chunk_size: '1mb',
browse_button: this.getComponentId(),
url: '/upload'
}, this.props));
- 文件添加到上传队列后
- 将添加失败的文件进行清除
- 将队列中是文件添加到文件列表状态中
- 根据传值autoUpload发起上传
this.uploader.bind('FilesAdded', (up, files) => {
if (_.get(this.props, 'multi_selection') === false) {
this.clearAllFiles();
} else {
this.clearFailedFiles();
}
const f = this.state.files;
_.map(files, (file) => {
f.push(file);
});
this.setState({files: f}, () => {
if (this.props.autoUpload === true) {
this.uploader.start();
}
});
});
- 文件上传成功后
- 将接口的返回值添加到文件列表状态中
- 将每个成功的文件添加标识uploaded 为 true
this.uploader.bind('FileUploaded', (up, file, responseObject) => {
const stateFiles = this.state.files
const response = JSON.parse(responseObject.response)
_.map(stateFiles, (val, key) => {
if (val.id === file.id) {
val.uploaded = true;
val.response = response
val.url = response.realUrl
stateFiles[key] = val;
}
});
this.setState({files: stateFiles});
});
- 上传文件列表显示
- 遍历文件列表状态数组
- 实现上传文件时的loading效果、进度条效果以及上传失败的提示
- 单个文件删除按钮实现
return (
<React.Fragment key={val.id}>
<Row gutter={10} wrap={false} className='listItem'>
<Col flex='15px'>{this.state.uploadState && val.uploaded !== true ? <LoadingOutlined/> : <PaperClipOutlined/>}</Col>
<Col flex='auto' className="fileLink">
<a target='_blank' title={val.url} href={val.url}>{val.name}</a>
</Col>
<Col flex="15px" onClick={removeFile}><DeleteOutlined className="deleteBtn"/></Col>
</Row>
{
(() => {
if (this.state.uploadState === true && val.uploaded !== true && _.isUndefined(val.error)) {
const percent = this.state.progress[val.id] || 0;
return (
<Row style={{width: '100%'}}>
<Progress size='small' percent={percent} showInfo={false}/>
</Row>
)
}
})()
}
{
(() => {
if (!_.isUndefined(val.error)) {
return (
<div className={'alert alert-danger'}>
{'Error: ' + val.error.code + ', Message: ' + val.error.message}
</div>
)
}
})()
}
</React.Fragment>
)
- 文件删除功能实现
removeFile = (id) => {
this.uploader.removeFile(id);
const state = _.filter(this.state.files, (file) => {
return file.id !== id;
});
this.setState({files: state});
}
功能扩展
1. 文件大小和文件类型限制功能实现
- 查找Plupload文档发现filters属性, 在实例化时添加该属性
- 接收父级 maxSize / accept 两个属性分别实现
- 当条件不满足时,会触发Error事件,根据错误吗进行不同的提示
- 正常情况下,类型不满足的文件会是不可选状态。在开发过程中,出现过仍可选,所以这里对**-601**状态码也进行错误提示
this.uploader = new plUploadJs.Uploader(_.extend({
container: `plupload_${this.props.id}`,
runtimes: 'html5',
multipart: true,
chunk_size: '1mb',
browse_button: this.getComponentId(),
url: '/upload',
filters: {
mime_types: this.props.accept ? [
{ title : "files filters", extensions : this.props.accept}
]: [],
max_file_size: this.props.maxSize
}
}, this.props));
this.uploader.bind('Error', (up, err) => {
switch (err.code) {
case -600:
return message.error(`上传文件最大为${this.props.maxSize}`)
case -601:
return message.error(`上传文件类型为${this.props.accept}`);
}
if (_.isUndefined(err.file) !== true) {
const stateFiles = this.state.files;
_.map(stateFiles, (val, key) => {
if (val.id === err.file.id) {
val.error = err;
stateFiles[key] = val;
}
});
this.setState({files: stateFiles});
}
});
2. 文件上传数量限制功能实现
- 添加到上传队列后判断符合以下两种情况,进行提示
- plupload实例中的文件对象长度是否大于最大限制长度(当前上传数量达到限制值)
- 或文件列表状态的长度是否等于最大限制(之前回显的文件数量已达到最大限制)
this.uploader.bind('FilesAdded', (up, files) => {
const f = this.state.files;
_.map(files, (file) => {
if (up.files.length > this.props.maxLength || f.length == this.props.maxLength) {
message.error(`最多上传${this.props.maxLength}个文件`);
this.uploader.stop()
this.removeFile(file.id)
} else {
f.push(file);
}
});
this.setState({files: f}, () => {
if (this.props.autoUpload === true) {
this.uploader.start();
}
});
});
3. 成功文件列表返回
- 在componentDidUpdate中监听文件列表状态,当其发生变化时,没有文件或文件均已上传成功时,将当前状态返回给父组件,进行后续操作
componentDidUpdate(prevProps, prevState) {
if(this.state.files != prevState.files){
if(this.state?.files?.length && this.state.files.every(item => item.uploaded == true) ||
this.state?.files?.length == 0
){
this.props.getFileList(this.state.files);
}
}
}
4. 查看详情时文件回显功能添加
- 接收父组件defaultFileList属性,在plUpload组件初始化前,赋值给文件列表的状态
- 点击回显的文件会在新窗口中访问url,查看文件内容
if(this.props.defaultFileList){
this.setState({files: this.props.defaultFileList})
}
// 回显列表举例
defaultFileList = [
{
id: '1',
uploaded: true,
name: '回显文件名',
url: 'https://www.****.cn/data/uploads/20210326/preview/file/2021/10/19/haha.png',
}
]
5. 选择文件与上传文件分布进行
- 添加上传文件按钮,根据autoUpload属性控制显隐
- 选择文件的icon根据autoUpload属性显示相应内容
- 根据文件列表内容的状态,设置上传按钮的禁用效果
- 显示列表中,根据文件状态修改已选择未上传的文件颜色
<Space>
{/*选择文件*/}
<Button
id={this.getComponentId()}
icon={this.props.autoUpload ? <UploadOutlined/> : <FileAddOutlined /> }
>
{this.props.buttonSelect || 'select'}
</Button>
{/*上传文件*/}
{
!this.props.autoUpload &&
<Button
type='primary'
icon={<UploadOutlined/>}
onClick={this.doUpload}
disabled={files.length === 0 || files.every((listItem) => listItem.uploaded) ? 'disabled' : false}
>
{this.props.buttonUpload || 'upload'}
</Button>
}
</Space>
<Row gutter={10} wrap={false} className='listItem' style={{color: '#8c8c8c'}}>
<Col flex='15px'>{this.state.uploadState && val.uploaded !== true ? <LoadingOutlined/> : <PaperClipOutlined/>}</Col>
<Col flex='auto' className={["fileLink", !val.uploaded && "fileAdd"]}>
<a target='_blank' title={val.url} href={val.url}>{val.name}</a>
</Col>
<Col flex="15px" onClick={removeFile}><DeleteOutlined className="deleteBtn"/></Col>
</Row>