342 lines
12 KiB
JavaScript
342 lines
12 KiB
JavaScript
![]() |
import React from "react";
|
||
|
import { Table, Tag, Space, Progress, Button, Row, Col, Modal } from 'antd';
|
||
|
import { PauseOutlined, DeleteOutlined, CaretRightOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
|
||
|
import Resumablejs from "./resumable";
|
||
|
import './style.css';
|
||
|
|
||
|
/**
|
||
|
* 大文件上传组件
|
||
|
*/
|
||
|
export default class ReactResumableJs extends React.Component {
|
||
|
constructor(props) {
|
||
|
super(props);
|
||
|
this.state = {
|
||
|
fileList: [],
|
||
|
isPaused: false,
|
||
|
isUploading: false,
|
||
|
isSupported: false,
|
||
|
};
|
||
|
this.resumable = null;
|
||
|
}
|
||
|
|
||
|
componentDidMount = () => {
|
||
|
// 实例化上传组件( 用的是开源组件 resumablejs )
|
||
|
let _resumableField = this.__createResumbleObj();
|
||
|
|
||
|
// 当前浏览器是否支持 H5 Api
|
||
|
this.setState({ isSupported: _resumableField.support });
|
||
|
|
||
|
// 绑定上传组件, 拖拽组件
|
||
|
_resumableField.assignBrowse(this.uploader);
|
||
|
_resumableField.assignDrop(this.dropZone);
|
||
|
|
||
|
/* 注册新增文件通知 */
|
||
|
_resumableField.on('fileAdded', this.__fileAddedListener.bind(this));
|
||
|
/* 注册上传处理中文件通知 */
|
||
|
_resumableField.on('fileProgress', this.__fileProgressListener.bind(this));
|
||
|
/** 文件上传完成 */
|
||
|
_resumableField.on('fileSuccess', this.__fileSuccessListener.bind(this));
|
||
|
/** 文件上传失败 */
|
||
|
_resumableField.on('fileError', this.__fileErrorListener.bind(this));
|
||
|
|
||
|
this.resumable = _resumableField;
|
||
|
};
|
||
|
|
||
|
render() {
|
||
|
const columns = [
|
||
|
{
|
||
|
title: '文件名称',
|
||
|
dataIndex: 'filename',
|
||
|
key: 'filename',
|
||
|
render: text => <b>{text}</b>,
|
||
|
},
|
||
|
{
|
||
|
title: '上传进度',
|
||
|
dataIndex: 'prograss',
|
||
|
key: 'prograss',
|
||
|
responsive: ['lg'],
|
||
|
render: (val, record) => <Progress percent={val} status={ record.error ? "exception" : "active"} />,
|
||
|
},
|
||
|
{
|
||
|
title: '动作',
|
||
|
dataIndex: 'action',
|
||
|
key: 'action',
|
||
|
width: '30%',
|
||
|
responsive: ['md'],
|
||
|
render: (text, record) => (
|
||
|
<Space size="middle">
|
||
|
<Button type="dashed" style={{display: !record.pause && !record.complete && !record.error ? '' : 'none'}} icon={<PauseOutlined/>} onClick={() => this.__uploadPause(record.filename)}>暂停上传</Button>
|
||
|
<Button type="dashed" style={{display: record.pause && !record.complete && !record.error ? '' : 'none'}} icon={<CaretRightOutlined/>} onClick={() => this.__uploadResum(record.filename)}>恢复上传</Button>
|
||
|
<Button type="danger" style={{display: record.pause || record.complete || record.error ? '' : 'none'}} icon={<DeleteOutlined/>} onClick={() => this.__removeFile(record.filename)}>移除文件</Button>
|
||
|
</Space>
|
||
|
),
|
||
|
},
|
||
|
];
|
||
|
|
||
|
return (
|
||
|
<>
|
||
|
<Row>
|
||
|
<Col span={24}>
|
||
|
<div id={this.props.dropTargetID} ref={node => this.dropZone = node}>
|
||
|
<div className="resumable-error" style={{display: this.state.isSupported ? 'none' : ''}}>
|
||
|
Your browser, unfortunately, is not supported by Resumable.js. The library requires support for <a href="http://www.w3.org/TR/FileAPI/">the HTML5 File API</a> along with <a href="http://www.w3.org/TR/FileAPI/#normalization-of-params">file slicing</a>.
|
||
|
</div>
|
||
|
|
||
|
<div className="resumable-drop" style={{display: this.state.isSupported ? '' : 'none'}}>
|
||
|
文件拖拽到该区域或者
|
||
|
<a className="resumable-browse"
|
||
|
ref={node=> {this.uploader = node; this.dropZone = node;}}
|
||
|
type="file"
|
||
|
id={this.props.uploaderID}
|
||
|
className='btn'
|
||
|
name={this.props.uploaderID + '-upload'}
|
||
|
accept={this.props.fileAccept || '*'}
|
||
|
disabled={this.props.disableInput || false}><u>点击连接添加文件</u></a>
|
||
|
</div>
|
||
|
</div>
|
||
|
</Col>
|
||
|
</Row>
|
||
|
|
||
|
<Row>
|
||
|
<Col span={24}>
|
||
|
<Table columns={columns} dataSource={[...this.state.fileList]} pagination={false} bordered/>
|
||
|
</Col>
|
||
|
</Row>
|
||
|
</>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 实例化上传组件
|
||
|
*/
|
||
|
__createResumbleObj() {
|
||
|
return new Resumablejs({
|
||
|
target: this.props.service,
|
||
|
query: this.props.query,// 自定义参数
|
||
|
forceChunkSize: true,// 是否严格执行分片
|
||
|
fileType: this.props.filetypes,
|
||
|
maxFiles: this.props.maxFiles,
|
||
|
maxFileSize: this.props.maxFileSize,
|
||
|
headers: this.props.headerObject || {},
|
||
|
chunkSize: this.props.chunkSize,
|
||
|
testChunks: false,// 不启用测试块
|
||
|
simultaneousUploads: this.props.simultaneousUploads,
|
||
|
fileParameterName: this.props.fileParameterName,
|
||
|
forceChunkSize: true,// 是否严格执行分片
|
||
|
maxChunkRetries: 10,// 最大重试次数
|
||
|
chunkRetryInterval: 1000,// 重试间隔时间 10 秒
|
||
|
throttleProgressCallbacks: 1,// 上传进度播报周期 (1秒)
|
||
|
forceChunkSize: this.props.forceChunkSize
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 添加文件时的监听
|
||
|
*
|
||
|
* @param {*} file
|
||
|
*/
|
||
|
__fileAddedListener(file) {
|
||
|
// 更新文件队列表单
|
||
|
let _cache = this.state.fileList;
|
||
|
_cache.push({
|
||
|
key: _cache.length + 1,
|
||
|
filename: file.fileName,
|
||
|
uploading: true,
|
||
|
prograss: Math.floor(file.progress()*100),
|
||
|
});
|
||
|
|
||
|
// 更新组件状态,进行组件重绘
|
||
|
this.setState({ fileList: _cache });
|
||
|
|
||
|
if (typeof this.props.onFileAdded === "function") {
|
||
|
this.props.onFileAdded(file, this.resumable);
|
||
|
} else {
|
||
|
this.resumable.upload();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 文件上传进度播报
|
||
|
* @param {*} file
|
||
|
*/
|
||
|
__fileProgressListener(file) {
|
||
|
// 更新文件队列表单
|
||
|
let _cache = this.state.fileList;
|
||
|
|
||
|
let matchedObj = _cache.find((obj) => obj.filename === file.fileName);
|
||
|
|
||
|
if(matchedObj) {
|
||
|
matchedObj.prograss = Math.floor(file.progress()*100);
|
||
|
// 更新组件状态,进行组件重绘
|
||
|
this.setState({ fileList: _cache });
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 文件上传完成
|
||
|
* @param {*} file
|
||
|
*/
|
||
|
__fileSuccessListener(file) {
|
||
|
// 更新文件队列表单
|
||
|
let _cache = this.state.fileList;
|
||
|
|
||
|
let matchedObj = _cache.find((obj) => obj.filename === file.fileName);
|
||
|
|
||
|
if(matchedObj) {
|
||
|
matchedObj.complete = true;
|
||
|
// 更新组件状态,进行组件重绘
|
||
|
this.setState({ fileList: _cache });
|
||
|
}
|
||
|
|
||
|
if (typeof this.props.onUploadSuccess === "function") {
|
||
|
this.props.onUploadSuccess(file, this.resumable);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 文件上传失败
|
||
|
* @param {*} file
|
||
|
*/
|
||
|
__fileErrorListener(file) {
|
||
|
// 更新文件队列表单
|
||
|
let _cache = this.state.fileList;
|
||
|
|
||
|
let matchedObj = _cache.find((obj) => obj.filename === file.fileName);
|
||
|
|
||
|
if(matchedObj) {
|
||
|
matchedObj.error = true;
|
||
|
// 更新组件状态,进行组件重绘
|
||
|
this.setState({ fileList: _cache });
|
||
|
}
|
||
|
|
||
|
if (typeof this.props.onUploadError === "function") {
|
||
|
this.props.onUploadError(file, this.resumable);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 移除上传列表中的文件
|
||
|
*/
|
||
|
__removeFile(filename) {
|
||
|
/** 查询当前文件 */
|
||
|
const file = this.resumable.files.find(obj => obj.file.name === filename);
|
||
|
const selfRef = this;
|
||
|
|
||
|
if(!file.isComplete()) {
|
||
|
const { confirm } = Modal;
|
||
|
confirm({
|
||
|
title: '确定要移除当前任务吗?',
|
||
|
icon: <ExclamationCircleOutlined />,
|
||
|
content: '当前上传任务尚未完成,移除后将无法恢复,确定要执行该操作吗?',
|
||
|
okText: '确定',
|
||
|
okType: 'danger',
|
||
|
cancelText: '取消',
|
||
|
onOk() {// 取消选中文件的上传操作
|
||
|
file.cancel();
|
||
|
selfRef.__handleRemoveFile(filename);
|
||
|
},
|
||
|
});
|
||
|
} else {
|
||
|
selfRef.__handleRemoveFile(filename);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
__handleRemoveFile(filename) {
|
||
|
// 更新文件队列表单
|
||
|
let _cache= this.state.fileList;
|
||
|
// 移除指定的元素
|
||
|
_cache.splice(_cache.findIndex((obj) => obj.filename === filename), 1)
|
||
|
|
||
|
// 修改上传列表状态
|
||
|
this.setState({ fileList: _cache });
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 暂停上传
|
||
|
*/
|
||
|
__uploadPause(filename) {
|
||
|
// 取消选中文件的上传操作
|
||
|
this.resumable.files.find(obj => obj.file.name === filename)._pause = true;
|
||
|
// 更新文件队列表单
|
||
|
let _cache = this.state.fileList;
|
||
|
|
||
|
let matchedObj = _cache.find((obj) => obj.filename === filename);
|
||
|
|
||
|
if(matchedObj) {
|
||
|
matchedObj.pause = true;
|
||
|
// 更新组件状态,进行组件重绘
|
||
|
this.setState({ fileList: _cache });
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 恢复上传
|
||
|
*/
|
||
|
__uploadResum(filename) {
|
||
|
let file = this.resumable.files.find(obj => obj.file.name === filename);
|
||
|
// 取消暂停状态
|
||
|
file._pause = false;
|
||
|
// 取消选中文件的上传操作
|
||
|
file.upload();
|
||
|
// 更新文件队列表单
|
||
|
let _cache = this.state.fileList;
|
||
|
|
||
|
let matchedObj = _cache.find((obj) => obj.filename === filename);
|
||
|
|
||
|
if(matchedObj) {
|
||
|
delete matchedObj.pause;
|
||
|
// 更新组件状态,进行组件重绘
|
||
|
this.setState({ fileList: _cache });
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 对外暴露的属性值
|
||
|
*/
|
||
|
ReactResumableJs.defaultProps = {
|
||
|
maxFiles: 10,// 添加最大文件数
|
||
|
uploaderID: 'default-resumable-uploader',
|
||
|
dropTargetID: 'dropTarget',
|
||
|
query: {},
|
||
|
filetypes: [],// 文件扩展名
|
||
|
fileAccept: '*',// 文件头类型
|
||
|
maxFileSize: 1204 * 1024 * 1024 * 1024,// max size 1T
|
||
|
onUploadErrorCallback: (file, errorCount) => {
|
||
|
console.log('error', file, errorCount);
|
||
|
},
|
||
|
onFileRemoved: function (file) {
|
||
|
return file;
|
||
|
},
|
||
|
onCancelUpload: function () {
|
||
|
return true;
|
||
|
},
|
||
|
onPauseUpload: function () {
|
||
|
return true;
|
||
|
},
|
||
|
onResumeUpload: function () {
|
||
|
return true;
|
||
|
},
|
||
|
onStartUpload: function () {
|
||
|
return true;
|
||
|
},
|
||
|
onUploadSuccess: function() {
|
||
|
return true;
|
||
|
},
|
||
|
onUploadError: function() {
|
||
|
return true;
|
||
|
},
|
||
|
disableDragAndDrop: false,// 允许拖拽操作
|
||
|
chunkSize: 5 * 1024 * 1024,// 每个分批那的尺寸 5M
|
||
|
simultaneousUploads: 5,// 同步上传的分片数
|
||
|
fileParameterName: 'file',
|
||
|
generateUniqueIdentifier: null,
|
||
|
maxFilesErrorCallback: null,
|
||
|
cancelButton: false,
|
||
|
pause: false,
|
||
|
startButton: null,
|
||
|
pauseButton: null,
|
||
|
previousText: "",
|
||
|
headerObject : {},
|
||
|
forceChunkSize: false
|
||
|
};
|