Просмотр исходного кода

♻️ refactor(uploader): 重构阿里云OSS上传组件实现

- 移除自定义XMLHttpRequest实现,改用Ant Design Upload组件内置上传机制
- 将上传策略获取逻辑移至beforeUpload钩子中
- 优化文件上传状态管理和进度更新逻辑
- 简化上传数据处理,使用Upload组件的data属性配置表单数据
- 移除冗余的重试上传功能,统一由Upload组件处理上传状态

✨ feat(uploader): 增强文件上传错误处理能力

- 添加获取上传策略失败时的错误提示
- 完善文件上传各阶段的错误处理流程
- 提供更详细的错误信息反馈给用户

🐛 fix(uploader): 修复文件上传进度计算不准确问题

- 移除自定义的进度百分比调整逻辑
- 使用Upload组件原生进度回调,确保进度显示准确
yourname 8 месяцев назад
Родитель
Сommit
b8bbcd8e3e
1 измененных файлов с 66 добавлено и 105 удалено
  1. 66 105
      src/client/admin/components/AliyunOSSUploader.tsx

+ 66 - 105
src/client/admin/components/AliyunOSSUploader.tsx

@@ -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 { UploadOutlined, CloseOutlined, CheckCircleOutlined, SyncOutlined, ReloadOutlined } from '@ant-design/icons';
 import { App } from 'antd';
@@ -44,6 +44,7 @@ const AliyunOSSUploader: React.FC<AliyunOSSUploaderProps> = ({
   const { message: antdMessage } = App.useApp();
   const [fileList, setFileList] = useState<UploadFile[]>([]);
   const [uploadingFiles, setUploadingFiles] = useState<Set<string>>(new Set());
+  const [ossPolicy, setOssPolicy] = useState<UploadPolicyResponse | null>(null);
 
   // 处理上传进度
   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));
   };
 
-  // 验证文件大小
-  const beforeUpload = (file: File) => {
+  // 验证文件大小并获取上传策略
+  const beforeUpload = async (file: File) => {
+    // 验证文件大小
     const fileSizeMB = file.size / (1024 * 1024);
     if (fileSizeMB > maxSize!) {
       message.error(`文件 "${file.name}" 大小超过 ${maxSize}MB 限制`);
       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 (
     <div className="aliyun-oss-uploader">
       <Upload.Dragger
-        name="files"
+        name="file"
         accept={accept}
         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}
+        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}
         disabled={uploadingFiles.size > 0 && !multiple}
       >