feat(export): 批量导出后生成 AI 穿搭 Excel,每印花子目录一行 (§17.23)

添加印花批量导出完成后,自动在输出目录生成与时间戳目录同名的 xlsx
文件(docs/02 §6.12);每个有效印花子目录一行,C 列指向该子目录路径,
AI 穿搭页直接选取即可触发目录扇出逻辑(§19.12),无需手工填写 Excel。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 09:33:28 +08:00
co-authored by Claude Sonnet 4.6
parent e83dd07dc0
commit a3f4c39821
5 changed files with 144 additions and 3 deletions
+23
View File
@@ -1,4 +1,5 @@
import logging
import os
from dataclasses import dataclass
from pathlib import Path
from typing import List, Optional, Tuple
@@ -23,6 +24,7 @@ from PySide6.QtWidgets import (
from core.composer import compose, image_size, resolve_transform
from core.models import BatchMode, ExportOptions, ImageAsset, Template, TransformState
from services.excel_service import write_outfit_source_excel
from services.file_service import (
get_output_dir,
make_safe_output_path,
@@ -393,6 +395,27 @@ class QueuePanel(QWidget):
self._running = False
self._batch_btn.setText("开始批量导出")
self._write_outfit_excel(run_dir)
def _write_outfit_excel(self, run_dir):
"""Generate an AI-outfit source Excel alongside *run_dir* (docs/02 §6.12)."""
run_path = Path(run_dir)
if not run_path.is_dir():
return
_img_exts = {".png", ".jpg", ".jpeg", ".webp", ".gif"}
rows = []
for subdir in sorted(run_path.iterdir(), key=lambda p: p.name.lower()):
if not subdir.is_dir():
continue
if any(f.suffix.lower() in _img_exts for f in subdir.iterdir() if f.is_file()):
rows.append((subdir.name, str(subdir) + os.sep))
if not rows:
return
excel_path = run_path.with_suffix(".xlsx")
try:
write_outfit_source_excel(excel_path, rows)
except Exception as exc:
logger.error("Failed to write outfit source Excel: %s", exc)
def _image_size(self, path):
"""Return (w, h) for path, caching within a run. None if unreadable."""
+23 -2
View File
@@ -1,8 +1,8 @@
import logging
from pathlib import Path
from typing import List
from typing import List, Sequence, Tuple
from openpyxl import load_workbook
from openpyxl import Workbook, load_workbook
from core.models import OutfitResult, OutfitTask
@@ -149,6 +149,27 @@ def read_all_rows(excel_path):
workbook.close()
_OUTFIT_SOURCE_HEADERS = ["标题", "货号", "衣服图路径", "生成结果图片路径", "完成状态", "失败原因"]
def write_outfit_source_excel(excel_path, rows):
"""Create an AI-outfit source Excel alongside a compose run directory (docs/02 §6.12).
rows: ordered sequence of (print_name, subdir_path) pairs.
subdir_path should end with a path separator so AI穿搭 treats it as a directory.
Columns: A=print_name, B=empty(货号可空), C=subdir_path, D/E/F=empty (AI穿搭 writes back).
"""
rows = list(rows)
wb = Workbook()
ws = wb.active
ws.append(_OUTFIT_SOURCE_HEADERS)
for print_name, subdir_path in rows:
ws.append([print_name, None, subdir_path, None, None, None])
wb.save(str(excel_path))
wb.close()
logger.info("Wrote outfit source Excel: %s (%d rows)", excel_path, len(rows))
def write_outfit_result(excel_path, result):
"""Write one outfit result to columns D/E/F and save immediately."""
if not isinstance(result, OutfitResult):