|
|
@@ -359,17 +359,27 @@ export default function WordPreview() {
|
|
|
const arrayBuffer = await wordFile.arrayBuffer();
|
|
|
const zip = new PizZip(arrayBuffer);
|
|
|
|
|
|
- // 预加载所有图片数据,避免Promise问题
|
|
|
const folderIndex = (rowIndex + 1).toString();
|
|
|
const imageDataMap: Record<string, ArrayBuffer> = {};
|
|
|
|
|
|
- // 预加载当前文件夹的所有图片
|
|
|
+ // 预加载当前文件夹的所有图片 - 使用更健壮的方式
|
|
|
if (imageMappings[folderIndex]) {
|
|
|
for (const [imageName, imageFile] of Object.entries(imageMappings[folderIndex])) {
|
|
|
try {
|
|
|
- imageDataMap[imageName] = await imageFile.arrayBuffer();
|
|
|
+ // 创建文件的副本,避免原始文件引用丢失
|
|
|
+ const fileCopy = new File([imageFile], imageFile.name, { type: imageFile.type });
|
|
|
+ imageDataMap[imageName] = await fileCopy.arrayBuffer();
|
|
|
} catch (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 () => {
|
|
|
if (!selectedWordFile || !selectedExcelFile || excelData.length === 0) {
|
|
|
toast.error('请先选择Word模板和Excel数据文件');
|
|
|
@@ -509,11 +519,34 @@ export default function WordPreview() {
|
|
|
|
|
|
try {
|
|
|
const generatedFiles: ProcessingResult['generatedFiles'] = [];
|
|
|
+ const maxRetries = 3;
|
|
|
|
|
|
for (let i = 0; i < excelData.length; 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}`;
|
|
|
generatedFiles.push({
|
|
|
name: fileName,
|
|
|
@@ -534,8 +567,16 @@ export default function WordPreview() {
|
|
|
toast.success(`成功生成 ${generatedFiles.length} 个文档`);
|
|
|
|
|
|
} catch (error) {
|
|
|
- toast.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 {
|
|
|
setIsLoading(false);
|
|
|
setProcessingProgress(0);
|