diff --git a/backend/routers/templates.py b/backend/routers/templates.py index 2bd0314..8651a10 100644 --- a/backend/routers/templates.py +++ b/backend/routers/templates.py @@ -6,11 +6,13 @@ from datetime import datetime from io import BytesIO from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile -from sqlalchemy import func, select +from sqlalchemy import delete, func, select from sqlalchemy.ext.asyncio import AsyncSession from config import settings from database import get_db +from models.document import Document +from models.generation_log import GenerationLog from models.paragraph import Paragraph from models.template import Template from schemas.schemas import Response, TemplateSave @@ -194,8 +196,14 @@ async def save_template_paragraphs( paragraph_map = {item.id: item for item in existing_paragraphs} incoming_ids = {config.id for config in body.paragraphs if config.id} + print(f"[SAVE] template_id={template_id}, incoming_ids={incoming_ids}, existing_ids={[p.id for p in existing_paragraphs]}") + for paragraph in existing_paragraphs: if paragraph.id not in incoming_ids: + print(f"[SAVE] Deleting paragraph id={paragraph.id} title={paragraph.title}") + await db.execute( + delete(GenerationLog).where(GenerationLog.paragraph_id == paragraph.id) + ) await db.delete(paragraph) for index, config in enumerate(body.paragraphs, start=1): @@ -229,7 +237,23 @@ async def delete_template(template_id: int, db: AsyncSession = Depends(get_db)): raise HTTPException(status_code=404, detail="模板不存在") result = await db.execute(select(Paragraph).where(Paragraph.template_id == template_id)) - for paragraph in result.scalars().all(): + paragraphs_to_delete = result.scalars().all() + paragraph_ids = [p.id for p in paragraphs_to_delete] + + doc_result = await db.execute(select(Document).where(Document.template_id == template_id)) + documents_to_delete = doc_result.scalars().all() + + if paragraph_ids: + await db.execute( + delete(GenerationLog).where(GenerationLog.paragraph_id.in_(paragraph_ids)) + ) + for document in documents_to_delete: + await db.execute( + delete(GenerationLog).where(GenerationLog.document_id == document.id) + ) + await db.delete(document) + + for paragraph in paragraphs_to_delete: await db.delete(paragraph) file_path = template.file_path or "" diff --git a/backend/services/document_export.py b/backend/services/document_export.py index 86c0aef..fdba938 100644 --- a/backend/services/document_export.py +++ b/backend/services/document_export.py @@ -32,6 +32,43 @@ def _delete_block(block): parent.remove(element) +def _delete_heading_section(heading: Paragraph): + blocks = [heading] + current = heading._element.getnext() + while current is not None: + if isinstance(current, CT_P): + para = Paragraph(current, heading._parent) + if _is_heading(para): + break + blocks.append(para) + elif isinstance(current, CT_Tbl): + blocks.append(Table(current, heading._parent)) + current = current.getnext() + for block in blocks: + _delete_block(block) + + +def _remove_unreferenced_headings(document: DocumentObject, referenced_anchors: set[str]): + headings_to_remove: list[Paragraph] = [] + found_first_heading = False + pre_heading_blocks: list = [] + print(f"[EXPORT] referenced_anchors: {referenced_anchors}") + for block in _iter_block_items(document): + if isinstance(block, Paragraph) and _is_heading(block): + found_first_heading = True + text = block.text.strip() + if text not in referenced_anchors: + print(f"[EXPORT] Unreferenced heading found, will remove: '{text}'") + headings_to_remove.append(block) + elif not found_first_heading: + pre_heading_blocks.append(block) + for heading in headings_to_remove: + _delete_heading_section(heading) + if not referenced_anchors: + for block in pre_heading_blocks: + _delete_block(block) + + def _clear_paragraph(paragraph: Paragraph): element = paragraph._element for child in list(element): @@ -332,10 +369,22 @@ def _replace_section_group( def export_document_bytes(template_bytes: bytes, logs: list[dict]) -> bytes: document = Document(BytesIO(template_bytes)) + + referenced_anchors: set[str] = set() + for item in logs: + for key in ("anchor_title", "title"): + val = (item.get(key) or "").strip() + if val: + referenced_anchors.add(val) + + print(f"[EXPORT] logs count={len(logs)}, anchor_titles={[(l.get('anchor_title'), l.get('title')) for l in logs]}") + last_heading_element = None for group in _group_logs(logs): last_heading_element = _replace_section_group(document, group, last_heading_element) + _remove_unreferenced_headings(document, referenced_anchors) + output = BytesIO() document.save(output) return output.getvalue() diff --git a/web/src/views/TemplateEditor.vue b/web/src/views/TemplateEditor.vue index cb6152d..c024c39 100644 --- a/web/src/views/TemplateEditor.vue +++ b/web/src/views/TemplateEditor.vue @@ -33,6 +33,17 @@ {{ paragraph.edit_mode === 'ai' ? 'AI 生成' : '人工编辑' }} + + + + + + + + + + + @@ -61,6 +72,17 @@