Pārlūkot izejas kodu

✨ feat(word): 增强文档生成稳定性和错误处理能力

- 图片加载优化:创建文件副本避免原始文件引用丢失,增加加载失败重试机制
- 添加文档处理重试逻辑:最多3次重试,每次重试间隔递增
- 优化错误提示:根据不同错误类型显示更具体的错误信息
- 增加错误分类处理:区分权限错误、模板格式错误和其他未知错误
yourname 3 mēneši atpakaļ
vecāks
revīzija
261f41363a
1 mainītis faili ar 48 papildinājumiem un 7 dzēšanām
  1. 48 7
      src/client/home/pages/WordPreview.tsx

+ 48 - 7
src/client/home/pages/WordPreview.tsx

@@ -359,17 +359,27 @@ export default function WordPreview() {
       const arrayBuffer = await wordFile.arrayBuffer();
       const arrayBuffer = await wordFile.arrayBuffer();
       const zip = new PizZip(arrayBuffer);
       const zip = new PizZip(arrayBuffer);
       
       
-      // 预加载所有图片数据,避免Promise问题
       const folderIndex = (rowIndex + 1).toString();
       const folderIndex = (rowIndex + 1).toString();
       const imageDataMap: Record<string, ArrayBuffer> = {};
       const imageDataMap: Record<string, ArrayBuffer> = {};
       
       
-      // 预加载当前文件夹的所有图片
+      // 预加载当前文件夹的所有图片 - 使用更健壮的方式
       if (imageMappings[folderIndex]) {
       if (imageMappings[folderIndex]) {
         for (const [imageName, imageFile] of Object.entries(imageMappings[folderIndex])) {
         for (const [imageName, imageFile] of Object.entries(imageMappings[folderIndex])) {
           try {
           try {
-            imageDataMap[imageName] = await imageFile.arrayBuffer();
+            // 创建文件的副本,避免原始文件引用丢失
+            const fileCopy = new File([imageFile], imageFile.name, { type: imageFile.type });
+            imageDataMap[imageName] = await fileCopy.arrayBuffer();
           } catch (error) {
           } catch (error) {
             console.warn(`Failed to load image ${imageName}:`, error);
             console.warn(`Failed to load image ${imageName}:`, error);
+            // 如果加载失败,尝试重新从原始映射获取
+            try {
+              const originalFile = imageMappings[folderIndex][imageName];
+              if (originalFile) {
+                imageDataMap[imageName] = await originalFile.arrayBuffer();
+              }
+            } catch (retryError) {
+              console.warn(`Retry failed for image ${imageName}:`, retryError);
+            }
           }
           }
         }
         }
       }
       }
@@ -497,7 +507,7 @@ export default function WordPreview() {
     }
     }
   };
   };
 
 
-  // 处理文件
+  // 处理文件(带重试机制)
   const processFiles = async () => {
   const processFiles = async () => {
     if (!selectedWordFile || !selectedExcelFile || excelData.length === 0) {
     if (!selectedWordFile || !selectedExcelFile || excelData.length === 0) {
       toast.error('请先选择Word模板和Excel数据文件');
       toast.error('请先选择Word模板和Excel数据文件');
@@ -509,11 +519,34 @@ export default function WordPreview() {
     
     
     try {
     try {
       const generatedFiles: ProcessingResult['generatedFiles'] = [];
       const generatedFiles: ProcessingResult['generatedFiles'] = [];
+      const maxRetries = 3;
       
       
       for (let i = 0; i < excelData.length; i++) {
       for (let i = 0; i < excelData.length; i++) {
         const row = excelData[i];
         const row = excelData[i];
-        const processedBlob = await replaceFieldsInWord(selectedWordFile, row, i);
-        
+        let processedBlob: Blob | null = null;
+        let retryCount = 0;
+        let lastError: Error | null = null;
+
+        // 重试机制
+        while (retryCount < maxRetries && !processedBlob) {
+          try {
+            processedBlob = await replaceFieldsInWord(selectedWordFile, row, i);
+          } catch (error) {
+            lastError = error as Error;
+            retryCount++;
+            console.warn(`处理第 ${i + 1} 行数据失败,第 ${retryCount} 次重试:`, error);
+            
+            if (retryCount < maxRetries) {
+              // 等待一段时间后重试
+              await new Promise(resolve => setTimeout(resolve, 500 * retryCount));
+            }
+          }
+        }
+
+        if (!processedBlob) {
+          throw new Error(`无法处理第 ${i + 1} 行数据: ${lastError?.message || '未知错误'}`);
+        }
+
         const fileName = `processed_${i + 1}_${selectedWordFile.name}`;
         const fileName = `processed_${i + 1}_${selectedWordFile.name}`;
         generatedFiles.push({
         generatedFiles.push({
           name: fileName,
           name: fileName,
@@ -534,8 +567,16 @@ export default function WordPreview() {
       toast.success(`成功生成 ${generatedFiles.length} 个文档`);
       toast.success(`成功生成 ${generatedFiles.length} 个文档`);
       
       
     } catch (error) {
     } catch (error) {
-      toast.error('文档处理失败');
       console.error('Processing error:', error);
       console.error('Processing error:', error);
+      const errorMessage = error instanceof Error ? error.message : '文档处理失败';
+      
+      if (errorMessage.includes('权限') || errorMessage.includes('permission')) {
+        toast.error('文件权限错误,请重新选择文件后重试');
+      } else if (errorMessage.includes('模板格式')) {
+        toast.error('Word模板格式错误,请检查模板中的占位符格式');
+      } else {
+        toast.error(`文档处理失败: ${errorMessage}`);
+      }
     } finally {
     } finally {
       setIsLoading(false);
       setIsLoading(false);
       setProcessingProgress(0);
       setProcessingProgress(0);