react中,使用antd的Upload组件切片上传.zip文件及压缩包的下载
页面效果
JS
import type { TableProps, UploadProps } from 'antd';
import http from '@utils/http';
import type { UploadProps } from 'antd';
const [showUploadModal, setShowUploadModal] = useState<boolean>(false);
const [percent, setPercent] = useState(0);
const [showProgressBar, setShowProgressBar] = useState(false);
const [isDownloading, setIsDownloading] = useState(false); // 正在下载模板
const isCancelUpload = useRef<boolean>(false); // 是否取消上传
// 模板下载
const handleExport = async () => {
try {
setIsDownloading(true);
templateDownload()
.then((res) => {
console.log('templateDownload res=>', res);
if (res.type === 'application/zip') {
const b = new Blob([res], { type: res.type });
let a: HTMLAnchorElement | null = document.createElement('a');
a.href = URL.createObjectURL(b);
a.download = '模板.zip';
a.click();
a = null;
}
})
.catch((err: ErrorObj) => {
showErrorMsg(err);
})
.finally(() => {
setIsDownloading(false);
});
} catch (error) {
setIsDownloading(false);
console.error('/log/export ~ error:', error);
showErrorMsg({ msg: '模板下载失败,请重试!' });
}
};
// 辅助函数:检查文件是否为zip格式
function isZipFile(file: File): boolean {
const fileType = file.type;
return fileType === 'application/zip' || file.name.endsWith('.zip');
}
// 分片大小,单位字节
const CHUNK_SIZE = configData?.sliceSize * 1024 * 1024; // configData?.sliceSize是后端返回的切片大小值,存储在Store中
// 创建分片
const createChunks = (file: File) => {
const chunks = [];
for (let i = 0; i < file.size; i += CHUNK_SIZE) {
chunks.push(file.slice(i, Math.min(i + CHUNK_SIZE, file.size)));
}
return chunks;
};
const uploadFileInChunks = async (file: File) => {
const chunks = createChunks(file);
console.log('chunks=>', chunks);
setShowProgressBar(true);
let fileldId = '';
setPercent(0);
try {
for (let i = 0; i < chunks.length; i++) {
// 如果弹窗关闭了,则不继续上传,并提示用户上传取消
if (!isCancelUpload.current) {
message.error('已取消上传');
return;
}
const formData = new FormData();
if (i !== 0) { // 第一次上传不用传fileldId字段
formData.append('fileId', fileldId);
}
formData.append('partIndex', i.toString());
formData.append('partSize', chunks[i].size.toString());
formData.append('totalParts', chunks.length.toString());
formData.append('totalSize', file.size.toString());
formData.append('partFile', new File([chunks[i]], file.name, { type: file.type })); // 需要把Blob格式转成File格式,不然后端收到的partFile值为空
// eslint-disable-next-line
const resFileldId = (await http.request({
method: 'post',
url: `/***/file/slice-upload`, // 上传接口地址
data: formData,
})) as any;
fileldId = resFileldId;
if (typeof resFileldId === 'string') {
if (i === chunks.length - 1) {
setPercent(100);
message.success('上传成功');
searchByFilter(); // 请求列表数据
} else {
setPercent(Math.floor(((i + 1) / chunks.length) * 100));
}
}
}
} catch (error: any) {
console.log('error=>', error);
message.error(error.msg);
setPercent(0);
setShowProgressBar(false);
searchByFilter();
}
};
const props: UploadProps = {
name: 'file',
multiple: false,
action: ``, // 因为是自定义上传逻辑,所以action值就不需要了
accept: '.zip',
showUploadList: false,
disabled: percent !== 100 && showProgressBar, // 上传进行中,禁用上传组件
onChange(info) {
console.log('info=>', info);
},
beforeUpload(file) {
const isZip = isZipFile(file);
const maxSize = 200 * 1024 * 1024;
if (!isZip) {
message.error('请上传一个压缩包文件。');
} else if (file.size > maxSize) {
message.error('文件大小不能超过200兆。');
}
if (isZip && file.size <= maxSize) {
uploadFileInChunks(file); // 自定义上传逻辑
}
return false; // 禁止组件默认的上传逻辑
},
};
HTML
<Modal
open={showUploadModal}
maskClosable={false}
footer={null}
width={500}
onCancel={() => {
setShowUploadModal(false);
isCancelUpload.current = false;
setPercent(0);
setShowProgressBar(false);
setIsDownloading(false);
}}
centered
>
<Spin spinning={isDownloading} tip="正在下载">
<div className={style.content}>
<Dragger {...props}>
<p className={style.upload_icon}>
<CloudUploadOutlined />
</p>
<p>
<span className={style.tip_txt1}>将文件拖到此处,或</span>
<Typography.Link disabled={percent !== 100 && showProgressBar}>点击上传</Typography.Link>
</p>
<p>
<span className={style.tip_txt2}>文件详情,请</span>
<Typography.Link
onClick={(event) => {
event.stopPropagation();
handleExport();
}}
>
查看模板
</Typography.Link>
</p>
{showProgressBar && (
<div>
<Progress size={'small'} percent={percent} />
<div className={style.red_txt}>注意:关闭弹窗、刷新页面将导致上传失败,需要再次上传。</div>
</div>
)}
</Dragger>
<div className={style.footer_txt}>
请将所有文件以一个压缩包的形式上传,支持*.zip文件格式,文件不大于200MB,需要上传随路信息文件、控制文件、音频文件。音频文件支持主流格式如mp4、wav等。
</div>
</div>
</Spin>
</Modal>
CSS
.content {
padding: 25px;
.upload_icon {
font-size: 36px;
color: #adadb0;
}
.tip_txt1 {
color: #8a8a8c;
}
.red_txt {
color: red;
font-size: 12px;
text-align: left;
}
.footer_txt {
margin: 10px 0;
font-size: 12px;
color: #6c6c6c;
}
}