大文件分片上传功能实现

3 篇文章 0 订阅
2 篇文章 0 订阅

前言

本文章只是对开发过程的记录,点击查看完成代码
该使用组件说明: 点击查看组件使用说明
内部使用组件信息: Plupload中文文档

起因

公司业务需要上传较大文件,但是接口不支持超过50M的文件上传。
于是需要前端在上传时进行切片处理。
前端采用的技术是react + antDesign,查找了antd的官法文档,发现Upload组件没有文件分片的功能。
在npm上根据react 和upload为关键字进行查找,找到了react-plUpload包。
下载安装后,发现无论是样式效果还是功能实现都与antd的Upload组件相差较远react-plUpload效果展示
于是自己决定做一个满足需求的组件

开发

初步实现

  • 使用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>
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>

最终效果

立即上传

组件效果查看

分步上传效果

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值