Browse Source

✨ feat(preview): add automatic image renaming feature

- add "auto-rename images to A1,A2,A3..." toggle switch
- implement image sorting and renaming functionality for each folder
- process images in alphabetical order and rename them to A1,A2,A3...
- add template placeholder usage instructions for both modes

📝 docs(usage): add automatic image renaming documentation

- create new documentation file for image renaming feature
- add detailed usage instructions for automatic renaming mode
- update UI guidance and help text for image naming rules
- add examples for A1,A2,A3... placeholder usage in templates
yourname 3 months ago
parent
commit
33e1dc791e
2 changed files with 199 additions and 23 deletions
  1. 99 0
      docs/自动重命名图片模板示例.md
  2. 100 23
      src/client/home/pages/WordPreview.tsx

+ 99 - 0
docs/自动重命名图片模板示例.md

@@ -0,0 +1,99 @@
+# 自动重命名图片模板示例
+
+## 模板说明
+
+当启用"自动重命名图片为A1,A2,A3..."功能时,系统会自动将每个文件夹中的图片按顺序重命名,并在Word模板中使用对应的占位符。
+
+## 压缩包结构
+
+```
+图片压缩包.zip
+├── 1/          # 对应Excel第1行
+│   ├── 任意名称的图片1.jpg
+│   ├── 任意名称的图片2.png
+│   └── 任意名称的图片3.webp
+├── 2/          # 对应Excel第2行
+│   ├── 照片1.jpg
+│   ├── 照片2.jpg
+│   └── 照片3.jpg
+└── 3/          # 对应Excel第3行
+    ├── image1.png
+    ├── image2.png
+    └── image3.png
+```
+
+## Word模板占位符使用
+
+在Word模板中,使用以下格式的占位符:
+
+```
+{%A1%} - 对应文件夹中的第1张图片
+{%A2%} - 对应文件夹中的第2张图片  
+{%A3%} - 对应文件夹中的第3张图片
+...以此类推
+```
+
+## 示例模板内容
+
+```html
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>自动重命名图片模板示例</title>
+</head>
+<body>
+
+<h1>产品展示文档</h1>
+
+<table border="1" style="width: 100%; border-collapse: collapse;">
+  <tr>
+    <th>产品名称</th>
+    <th>产品图片1</th>
+    <th>产品图片2</th>
+    <th>产品图片3</th>
+    <th>产品描述</th>
+  </tr>
+  <tr>
+    <td>{产品名称}</td>
+    <td>{%A1%}</td>
+    <td>{%A2%}</td>
+    <td>{%A3%}</td>
+    <td>{产品描述}</td>
+  </tr>
+</table>
+
+<h2>详细图片展示</h2>
+
+<p><strong>主图:</strong>{%A1%}</p>
+<p><strong>细节图1:</strong>{%A2%}</p> 
+<p><strong>细节图2:</strong>{%A3%}</p>
+<p><strong>使用场景图:</strong>{%A4%}</p>
+
+</body>
+</html>
+```
+
+## Excel数据格式
+
+Excel表格应该包含与Word模板中字段名匹配的列:
+
+| 产品名称 | 产品描述 | ...其他字段 |
+|---------|---------|------------|
+| 产品A   | 这是产品A的描述 | ...        |
+| 产品B   | 这是产品B的描述 | ...        |
+| 产品C   | 这是产品C的描述 | ...        |
+
+## 使用流程
+
+1. **准备图片**:将每个产品/条目的图片放入对应序号的文件夹中
+2. **启用自动重命名**:在界面上勾选"自动重命名图片为A1,A2,A3..."选项
+3. **上传文件**:上传Word模板、Excel数据和图片压缩包
+4. **生成文档**:系统会自动处理并生成包含正确图片的文档
+
+## 注意事项
+
+- 图片将按文件名的字母顺序进行排序和重命名
+- 如果某个序号缺少图片,对应的占位符位置将留空
+- 支持jpg、jpeg、png、gif、bmp、webp格式的图片
+- 建议图片文件名使用有意义的命名以便于排序(如01.jpg, 02.jpg等)

+ 100 - 23
src/client/home/pages/WordPreview.tsx

@@ -83,6 +83,9 @@ export default function WordPreview() {
   const [imageSizeSettings, setImageSizeSettings] = useState<ImageSizeSettings>({ width: 200, height: 150 });
   const [showSizeSettings, setShowSizeSettings] = useState(false);
   
+  // 新增状态:自动重命名图片功能
+  const [autoRenameImages, setAutoRenameImages] = useState(true);
+  
   // 新增状态:选择下载功能
   const [selectedFiles, setSelectedFiles] = useState<Set<number>>(new Set());
   const [isDownloading, setIsDownloading] = useState(false);
@@ -212,42 +215,98 @@ export default function WordPreview() {
       const newImagePreviewUrls: Record<string, Record<string, string>> = {};
       const allImages: Array<{ url: string; name: string; folder: string }> = [];
 
-      // 解析文件夹结构:第一层为序号,第二层为图片
-      for (const [path, zipEntry] of Object.entries(zipContent.files)) {
-        if (!zipEntry.dir && isImageFile(path)) {
-          const pathParts = path.split('/');
-          if (pathParts.length >= 2) {
-            const folderIndex = pathParts[0]; // 序号文件夹
-            const imageName = pathParts[pathParts.length - 1].split('.')[0]; // 去掉扩展名的文件名
-            const fullImageName = pathParts[pathParts.length - 1]; // 完整文件名
-            
-            if (!newImageMappings[folderIndex]) {
-              newImageMappings[folderIndex] = {};
-              newImagePreviewUrls[folderIndex] = {};
+      if (autoRenameImages) {
+        // 自动重命名模式:按文件夹分组图片并按顺序重命名为A1,A2,A3...
+        const folderImages: Record<string, Array<{ path: string; zipEntry: JSZip.JSZipObject }>> = {};
+
+        // 首先收集所有图片并按文件夹分组
+        for (const [path, zipEntry] of Object.entries(zipContent.files)) {
+          if (!zipEntry.dir && isImageFile(path)) {
+            const pathParts = path.split('/');
+            if (pathParts.length >= 2) {
+              const folderIndex = pathParts[0];
+              if (!folderImages[folderIndex]) {
+                folderImages[folderIndex] = [];
+              }
+              folderImages[folderIndex].push({ path, zipEntry });
             }
+          }
+        }
+
+        // 对每个文件夹的图片按文件名排序,然后重命名为A1,A2,A3...
+        for (const [folderIndex, images] of Object.entries(folderImages)) {
+          // 按文件名排序以确保顺序一致
+          images.sort((a, b) => a.path.localeCompare(b.path));
+          
+          if (!newImageMappings[folderIndex]) {
+            newImageMappings[folderIndex] = {};
+            newImagePreviewUrls[folderIndex] = {};
+          }
+
+          // 为每个图片分配A1,A2,A3...的命名
+          for (let i = 0; i < images.length; i++) {
+            const { zipEntry } = images[i];
+            const newImageName = `A${i + 1}`; // A1, A2, A3...
             
             const imageFile = await zipEntry.async('blob');
-            newImageMappings[folderIndex][imageName] = new File([imageFile], imageName, {
-              type: getImageMimeType(path)
+            newImageMappings[folderIndex][newImageName] = new File([imageFile], newImageName, {
+              type: getImageMimeType(images[i].path)
             });
-            
+
             // 创建预览URL
             const previewUrl = URL.createObjectURL(imageFile);
-            newImagePreviewUrls[folderIndex][imageName] = previewUrl;
-            
+            newImagePreviewUrls[folderIndex][newImageName] = previewUrl;
+
             allImages.push({
               url: previewUrl,
-              name: fullImageName,
+              name: newImageName,
               folder: folderIndex
             });
           }
         }
+      } else {
+        // 原有模式:保持图片原始文件名
+        for (const [path, zipEntry] of Object.entries(zipContent.files)) {
+          if (!zipEntry.dir && isImageFile(path)) {
+            const pathParts = path.split('/');
+            if (pathParts.length >= 2) {
+              const folderIndex = pathParts[0]; // 序号文件夹
+              const imageName = pathParts[pathParts.length - 1].split('.')[0]; // 去掉扩展名的文件名
+              const fullImageName = pathParts[pathParts.length - 1]; // 完整文件名
+              
+              if (!newImageMappings[folderIndex]) {
+                newImageMappings[folderIndex] = {};
+                newImagePreviewUrls[folderIndex] = {};
+              }
+              
+              const imageFile = await zipEntry.async('blob');
+              newImageMappings[folderIndex][imageName] = new File([imageFile], imageName, {
+                type: getImageMimeType(path)
+              });
+              
+              // 创建预览URL
+              const previewUrl = URL.createObjectURL(imageFile);
+              newImagePreviewUrls[folderIndex][imageName] = previewUrl;
+              
+              allImages.push({
+                url: previewUrl,
+                name: fullImageName,
+                folder: folderIndex
+              });
+            }
+          }
+        }
       }
 
       setImageMappings(newImageMappings);
       setImagePreviewUrls(newImagePreviewUrls);
       setAllImages(allImages);
-      toast.success(`成功解析 ${Object.keys(newImageMappings).length} 个文件夹的图片`);
+      
+      if (autoRenameImages) {
+        toast.success(`成功解析 ${Object.keys(newImageMappings).length} 个文件夹的图片,已自动重命名为A1,A2,A3...`);
+      } else {
+        toast.success(`成功解析 ${Object.keys(newImageMappings).length} 个文件夹的图片`);
+      }
     } catch (error) {
       toast.error('图片压缩文件解析失败');
       console.error('Image zip parsing error:', error);
@@ -913,6 +972,20 @@ export default function WordPreview() {
             </CardDescription>
           </CardHeader>
           <CardContent className="space-y-4">
+            {/* 自动重命名开关 */}
+            <div className="flex items-center space-x-2">
+              <input
+                type="checkbox"
+                id="auto-rename"
+                checked={autoRenameImages}
+                onChange={(e) => setAutoRenameImages(e.target.checked)}
+                className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
+              />
+              <Label htmlFor="auto-rename" className="text-sm font-medium">
+                自动重命名图片为A1,A2,A3...
+              </Label>
+            </div>
+            
             <div className="grid w-full items-center gap-1.5">
               <Label htmlFor="image-zip">图片压缩文件</Label>
               <Input
@@ -1403,14 +1476,17 @@ export default function WordPreview() {
             <div>
               <h4 className="font-medium mb-1">4. 准备图片压缩包</h4>
               <p className="text-muted-foreground">
-                压缩包结构:第一层为序号文件夹(1,2,3...对应Excel行),第二层为图片文件(文件名对应模板中的图片名)
+                压缩包结构:第一层为序号文件夹(1,2,3...对应Excel行),第二层为图片文件
               </p>
             </div>
             
             <div>
               <h4 className="font-medium mb-1">5. 图片命名规则</h4>
               <p className="text-muted-foreground">
-                例如:模板中使用 {'{%logo}'},则图片文件应命名为 logo.jpg/png等
+                <strong>自动重命名模式:</strong>启用"自动重命名图片为A1,A2,A3..."选项,系统会自动按图片顺序重命名,在Word模板中使用 {'{%A1%}'}, {'{%A2%}'}, {'{%A3%}'} 等占位符
+              </p>
+              <p className="text-muted-foreground mt-2">
+                <strong>手动命名模式:</strong>禁用自动重命名,图片文件名必须与模板中的图片占位符匹配(不含扩展名),例如:模板中使用 {'{%logo%}'},则图片文件应命名为 logo.jpg/png等
               </p>
             </div>
           </div>
@@ -1428,11 +1504,12 @@ export default function WordPreview() {
         <CardContent>
           <div className="space-y-2 text-sm">
             <p>• Word模板中的字段名必须与Excel表头完全匹配</p>
-            <p>• 图片文件名必须与模板中的图片占位符匹配(不含扩展名)</p>
+            <p>• 启用自动重命名时,图片将按顺序命名为A1,A2,A3...,模板中使用 {'{%A1%}'}, {'{%A2%}'}, {'{%A3%}'} 等占位符</p>
+            <p>• 禁用自动重命名时,图片文件名必须与模板中的图片占位符匹配(不含扩展名)</p>
             <p>• 文件夹序号必须与Excel行号对应(第1行对应文件夹"1")</p>
             <p>• 如果图片不存在,对应位置将留空</p>
             <p>• 支持jpg、jpeg、png、gif、bmp、webp格式图片</p>
-            <p>• 图片占位符使用 {'{%图片名%}'} 格式,如 {'{%logo%}'}</p>
+            <p>• 图片占位符使用 {'{%图片名%}'} 格式,如 {'{%logo%}'} 或 {'{%A1%}'}</p>
           </div>
         </CardContent>
       </Card>