feat: 完成T-403结果回写与汇总

新增excel.write_back_results,按原Excel文件、工作表和行号回写新标题、新封面图片路径与更新状态。

③更新完成后自动用WriteBackWorker(mode=results)回写结果,并弹窗汇总成功、失败、略过数量;文件被占用时提示关闭Excel后手动重试。

补充Excel结果回写、GUI自动回写/汇总/锁文件提示和模块契约测试,同步任务看板、API、流程和当前状态文档。
This commit is contained in:
chengma
2026-06-27 17:28:33 +08:00
parent 04ff06a2e3
commit 3bc1776d07
10 changed files with 465 additions and 30 deletions
+59 -1
View File
@@ -32,7 +32,10 @@ OUTPUT_HEADERS = {
"update_status": "更新状态",
}
OLD_WRITE_BACK_FIELDS = ("old_title", "old_cover_path")
RESULT_WRITE_BACK_FIELDS = ("new_title", "new_cover_path", "update_status")
WRITEABLE_STAGES = {"collected", "generated", "applied"}
RESULT_WRITEABLE_STAGES = {"generated", "applied"}
RESULT_WRITEABLE_STATUSES = {"failed", "skipped", "cancelled"}
LOCK_WINERRORS = {5, 32, 33}
LOCK_ERRNOS = {13, 16}
@@ -310,7 +313,21 @@ def _filter_tasks_by_excel_path(tasks, excel_path):
return filtered
def _is_result_fields(fields) -> bool:
return any(field in RESULT_WRITE_BACK_FIELDS for field in fields)
def _has_write_back_data(task, fields) -> bool:
if _is_result_fields(fields):
if getattr(task, "stage", None) in RESULT_WRITEABLE_STAGES:
return True
if getattr(task, "status", None) in RESULT_WRITEABLE_STATUSES:
return True
return any(
getattr(task, field, None) not in (None, "")
for field in ("new_title", "new_cover_path")
if field in fields
)
if getattr(task, "stage", None) in WRITEABLE_STAGES:
return True
return any(getattr(task, field, None) not in (None, "") for field in fields)
@@ -367,6 +384,32 @@ def _ensure_output_columns(sheet, fields):
return columns
def _update_status(task) -> str:
error = getattr(task, "last_error", None)
status = getattr(task, "status", None)
stage = getattr(task, "stage", None)
committed = bool(getattr(task, "committed", 0))
if stage == "applied" and committed:
return "成功"
if status == "failed":
return f"失败:{error}" if error else "失败"
if status == "skipped":
return f"略过:{error}" if error else "略过"
if status == "running":
return "处理中"
if status == "cancelled":
return f"已取消:{error}" if error else "已取消"
if stage == "generated":
return "待更新"
return ""
def _field_value(task, field):
if field == "update_status":
return _update_status(task)
return getattr(task, field, None) or ""
def _write_tasks_to_workbook(workbook, tasks, fields) -> int:
rows = 0
missing_sheets = []
@@ -387,7 +430,7 @@ def _write_tasks_to_workbook(workbook, tasks, fields) -> int:
sheet.cell(
row=int(task.source_row),
column=columns[field],
).value = getattr(task, field, None) or ""
).value = _field_value(task, field)
rows += 1
if missing_sheets:
@@ -450,6 +493,21 @@ def write_back(batch_id, excel_path=None, path=None) -> dict:
)
def write_back_results(batch_id, excel_path=None, path=None) -> dict:
"""Write generated title/cover and update status back to the original Excel file."""
_require_openpyxl()
fields = RESULT_WRITE_BACK_FIELDS
tasks = _filter_tasks_by_excel_path(_batch_tasks(batch_id, path=path), excel_path)
groups = _group_by_source_file(tasks, fields)
return _write_result(
batch_id,
groups,
lambda source_path: source_path,
fields,
)
def _is_excel_output_path(path_value) -> bool:
return _excel_suffix(path_value) in {".xlsx", ".xlsm"}