|
|
@@ -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>
|