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 生成' : '人工编辑' }}
+
+