|
@@ -1,4 +1,4 @@
|
|
|
-import React, { useState, useCallback } from 'react';
|
|
|
|
|
|
|
+import React, { useState, useCallback, useEffect } from 'react';
|
|
|
import { Upload, Progress, message, Tag, Space, Typography, Button } from 'antd';
|
|
import { Upload, Progress, message, Tag, Space, Typography, Button } from 'antd';
|
|
|
import { UploadOutlined, CloseOutlined, CheckCircleOutlined, SyncOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
import { UploadOutlined, CloseOutlined, CheckCircleOutlined, SyncOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
|
import { App } from 'antd';
|
|
import { App } from 'antd';
|
|
@@ -44,6 +44,7 @@ const AliyunOSSUploader: React.FC<AliyunOSSUploaderProps> = ({
|
|
|
const { message: antdMessage } = App.useApp();
|
|
const { message: antdMessage } = App.useApp();
|
|
|
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
|
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
|
|
const [uploadingFiles, setUploadingFiles] = useState<Set<string>>(new Set());
|
|
const [uploadingFiles, setUploadingFiles] = useState<Set<string>>(new Set());
|
|
|
|
|
+ const [ossPolicy, setOssPolicy] = useState<UploadPolicyResponse | null>(null);
|
|
|
|
|
|
|
|
// 处理上传进度
|
|
// 处理上传进度
|
|
|
const handleProgress = useCallback((uid: string, percent: number) => {
|
|
const handleProgress = useCallback((uid: string, percent: number) => {
|
|
@@ -146,105 +147,10 @@ const AliyunOSSUploader: React.FC<AliyunOSSUploaderProps> = ({
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- // 重试上传
|
|
|
|
|
- const handleRetry = (file: UploadFile) => {
|
|
|
|
|
- if (file.originFileObj) {
|
|
|
|
|
- customRequest({
|
|
|
|
|
- file,
|
|
|
|
|
- onSuccess: () => {},
|
|
|
|
|
- onError: () => {}
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 自定义上传逻辑
|
|
|
|
|
- const customRequest = async (options: {
|
|
|
|
|
- file: UploadFile,
|
|
|
|
|
- onSuccess: () => void,
|
|
|
|
|
- onError: (error: Error) => void
|
|
|
|
|
- }) => {
|
|
|
|
|
- const { file, onSuccess, onError } = options;
|
|
|
|
|
- const uid = file.uid;
|
|
|
|
|
- const originFile = file.originFileObj as File;
|
|
|
|
|
-
|
|
|
|
|
- // 添加到文件列表
|
|
|
|
|
- setFileList(prev => [
|
|
|
|
|
- ...prev.filter(item => item.uid !== uid),
|
|
|
|
|
- {
|
|
|
|
|
- uid,
|
|
|
|
|
- name: file.name,
|
|
|
|
|
- size: file.size,
|
|
|
|
|
- type: file.type,
|
|
|
|
|
- status: 'uploading' as UploadFileStatus,
|
|
|
|
|
- percent: 0,
|
|
|
|
|
- }
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- // 添加到上传中集合
|
|
|
|
|
- setUploadingFiles(prev => new Set(prev).add(uid));
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- // 获取上传策略
|
|
|
|
|
- handleProgress(uid, 5); // 标记为获取策略中
|
|
|
|
|
- const policyResponse = await getUploadPolicy(originFile);
|
|
|
|
|
- const { uploadPolicy } = policyResponse;
|
|
|
|
|
-
|
|
|
|
|
- // 创建表单数据
|
|
|
|
|
- const formData = new FormData();
|
|
|
|
|
- formData.append('key', uploadPolicy.key);
|
|
|
|
|
- formData.append('x-amz-algorithm', uploadPolicy['x-amz-algorithm']);
|
|
|
|
|
- formData.append('x-amz-credential', uploadPolicy['x-amz-credential']);
|
|
|
|
|
- formData.append('x-amz-date', uploadPolicy['x-amz-date']);
|
|
|
|
|
- formData.append('policy', uploadPolicy.policy);
|
|
|
|
|
- formData.append('x-amz-signature', uploadPolicy['x-amz-signature']);
|
|
|
|
|
- if (uploadPolicy['x-amz-security-token']) {
|
|
|
|
|
- formData.append('x-amz-security-token', uploadPolicy['x-amz-security-token']);
|
|
|
|
|
- }
|
|
|
|
|
- formData.append('file', originFile);
|
|
|
|
|
-
|
|
|
|
|
- // 创建XMLHttpRequest对象上传文件
|
|
|
|
|
- const xhr = new XMLHttpRequest();
|
|
|
|
|
- xhr.open('POST', uploadPolicy.host, true);
|
|
|
|
|
-
|
|
|
|
|
- // 监听进度事件
|
|
|
|
|
- xhr.upload.addEventListener('progress', (e) => {
|
|
|
|
|
- if (e.lengthComputable) {
|
|
|
|
|
- const percent = Math.round((e.loaded / e.total) * 100);
|
|
|
|
|
- // 加上之前的5%作为获取策略的进度
|
|
|
|
|
- const adjustedPercent = 5 + percent * 0.95;
|
|
|
|
|
- handleProgress(uid, adjustedPercent);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 监听完成事件
|
|
|
|
|
- xhr.addEventListener('load', () => {
|
|
|
|
|
- if (xhr.status >= 200 && xhr.status < 300) {
|
|
|
|
|
- handleComplete(uid, {
|
|
|
|
|
- fileKey: uploadPolicy.key,
|
|
|
|
|
- fileUrl: `${uploadPolicy.host}/${uploadPolicy.key}`
|
|
|
|
|
- }, originFile);
|
|
|
|
|
- onSuccess();
|
|
|
|
|
- } else {
|
|
|
|
|
- throw new Error(`上传失败,状态码: ${xhr.status}`);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 监听错误事件
|
|
|
|
|
- xhr.addEventListener('error', () => {
|
|
|
|
|
- throw new Error('网络错误,上传失败');
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 监听中断事件
|
|
|
|
|
- xhr.addEventListener('abort', () => {
|
|
|
|
|
- throw new Error('上传已取消');
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 发送请求
|
|
|
|
|
- xhr.send(formData);
|
|
|
|
|
-
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- handleError(uid, error instanceof Error ? error : new Error('未知错误'), originFile);
|
|
|
|
|
- onError(error instanceof Error ? error : new Error('未知错误'));
|
|
|
|
|
|
|
+ // 处理上传进度
|
|
|
|
|
+ const handleUploadProgress: UploadProps['onProgress'] = ({ percent }, file) => {
|
|
|
|
|
+ if (percent) {
|
|
|
|
|
+ handleProgress(file.uid, percent);
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -253,14 +159,42 @@ const AliyunOSSUploader: React.FC<AliyunOSSUploaderProps> = ({
|
|
|
setFileList(prev => prev.filter(item => item.uid !== uid));
|
|
setFileList(prev => prev.filter(item => item.uid !== uid));
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- // 验证文件大小
|
|
|
|
|
- const beforeUpload = (file: File) => {
|
|
|
|
|
|
|
+ // 验证文件大小并获取上传策略
|
|
|
|
|
+ const beforeUpload = async (file: File) => {
|
|
|
|
|
+ // 验证文件大小
|
|
|
const fileSizeMB = file.size / (1024 * 1024);
|
|
const fileSizeMB = file.size / (1024 * 1024);
|
|
|
if (fileSizeMB > maxSize!) {
|
|
if (fileSizeMB > maxSize!) {
|
|
|
message.error(`文件 "${file.name}" 大小超过 ${maxSize}MB 限制`);
|
|
message.error(`文件 "${file.name}" 大小超过 ${maxSize}MB 限制`);
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
- return true;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 获取上传策略
|
|
|
|
|
+ const policy = await getUploadPolicy(file);
|
|
|
|
|
+ setOssPolicy(policy);
|
|
|
|
|
+
|
|
|
|
|
+ // 添加到文件列表
|
|
|
|
|
+ setFileList(prev => [
|
|
|
|
|
+ ...prev.filter(item => item.uid !== file.uid),
|
|
|
|
|
+ {
|
|
|
|
|
+ uid: file.uid,
|
|
|
|
|
+ name: file.name,
|
|
|
|
|
+ size: file.size,
|
|
|
|
|
+ type: file.type,
|
|
|
|
|
+ status: 'uploading' as UploadFileStatus,
|
|
|
|
|
+ percent: 0,
|
|
|
|
|
+ }
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ // 添加到上传中集合
|
|
|
|
|
+ setUploadingFiles(prev => new Set(prev).add(file.uid));
|
|
|
|
|
+ return true;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ const message = error instanceof Error ? error.message : '获取上传策略失败';
|
|
|
|
|
+ antdMessage.error(message);
|
|
|
|
|
+ onUploadError?.(error instanceof Error ? error : new Error(message), file);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// 渲染上传状态
|
|
// 渲染上传状态
|
|
@@ -301,11 +235,38 @@ const AliyunOSSUploader: React.FC<AliyunOSSUploaderProps> = ({
|
|
|
return (
|
|
return (
|
|
|
<div className="aliyun-oss-uploader">
|
|
<div className="aliyun-oss-uploader">
|
|
|
<Upload.Dragger
|
|
<Upload.Dragger
|
|
|
- name="files"
|
|
|
|
|
|
|
+ name="file"
|
|
|
accept={accept}
|
|
accept={accept}
|
|
|
multiple={multiple}
|
|
multiple={multiple}
|
|
|
- customRequest={customRequest}
|
|
|
|
|
|
|
+ action={ossPolicy?.uploadPolicy.host}
|
|
|
|
|
+ data={(file) => {
|
|
|
|
|
+ if (!ossPolicy?.uploadPolicy) return {};
|
|
|
|
|
+
|
|
|
|
|
+ const policy = ossPolicy.uploadPolicy;
|
|
|
|
|
+ return {
|
|
|
|
|
+ key: policy.key,
|
|
|
|
|
+ 'x-amz-algorithm': policy['x-amz-algorithm'],
|
|
|
|
|
+ 'x-amz-credential': policy['x-amz-credential'],
|
|
|
|
|
+ 'x-amz-date': policy['x-amz-date'],
|
|
|
|
|
+ policy: policy.policy,
|
|
|
|
|
+ 'x-amz-signature': policy['x-amz-signature'],
|
|
|
|
|
+ ...(policy['x-amz-security-token'] && {
|
|
|
|
|
+ 'x-amz-security-token': policy['x-amz-security-token']
|
|
|
|
|
+ })
|
|
|
|
|
+ };
|
|
|
|
|
+ }}
|
|
|
beforeUpload={beforeUpload}
|
|
beforeUpload={beforeUpload}
|
|
|
|
|
+ onProgress={handleUploadProgress}
|
|
|
|
|
+ onChange={({ file }) => {
|
|
|
|
|
+ if (file.status === 'done') {
|
|
|
|
|
+ handleComplete(file.uid, {
|
|
|
|
|
+ fileKey: ossPolicy?.uploadPolicy.key || '',
|
|
|
|
|
+ fileUrl: ossPolicy ? `${ossPolicy.uploadPolicy.host}/${ossPolicy.uploadPolicy.key}` : ''
|
|
|
|
|
+ }, file.originFileObj as File);
|
|
|
|
|
+ } else if (file.status === 'error') {
|
|
|
|
|
+ handleError(file.uid, new Error(file.error?.message || '上传失败'), file.originFileObj as File);
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
showUploadList={false}
|
|
showUploadList={false}
|
|
|
disabled={uploadingFiles.size > 0 && !multiple}
|
|
disabled={uploadingFiles.size > 0 && !multiple}
|
|
|
>
|
|
>
|