init project
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
# Default ignored files
|
||||||
|
.idea
|
||||||
|
.idea/**
|
||||||
|
node_modules
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
# AGENTS.md — AI 编码助手指引
|
||||||
|
|
||||||
|
> 本文档供 Claude Code / Copilot / Cursor 等 AI 编码助手读取,确保生成的代码符合项目约定。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
AI 文档模板生成系统(第一阶段 MVP),定位是一个 **Word 模板标注器**。
|
||||||
|
|
||||||
|
核心流程:上传 Word → 解析标题/段落/表格 → 生成 HTML 预览(含 data-block-id)→ 用户点击区域 → 配置提示词 → 保存配置。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- **前端**:Vue 3 + TypeScript + Vite + Element Plus + Pinia + Vue Router 4
|
||||||
|
- **后端**:Python 3.11+ + FastAPI + SQLAlchemy + Alembic + MySQL 8.0
|
||||||
|
- **存储**:MinIO(文件)/ MySQL(结构化数据)
|
||||||
|
- **关键词**:block_id, template, region_type, upsert
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 代码约定
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
- 路由文件在 `app/api/`,服务逻辑在 `app/services/`,Model 在 `app/models/`
|
||||||
|
- 所有接口返回 JSON,统一格式 `{"data": ..., "message": "ok"}`
|
||||||
|
- 错误返回 `{"detail": "错误信息"}`
|
||||||
|
- 使用 Python类型注解
|
||||||
|
- 数据库 session 通过依赖注入 `get_db` 获取
|
||||||
|
- Model 使用 `sqlalchemy.orm.DeclarativeBase`
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- 组件使用 Composition API + `<script setup lang="ts">`
|
||||||
|
- 全局状态走 Pinia,不滥用 props/emit 跨多层传递
|
||||||
|
- API 请求走 `src/api/base.ts` 的 axios 实例
|
||||||
|
- 样式优先使用全局 CSS 变量(`var(--c-primary)`),避免硬编码色值
|
||||||
|
- 组件文件命名:`PascalCase.vue`
|
||||||
|
|
||||||
|
### 数据库
|
||||||
|
|
||||||
|
- 表名:snake_case(如 `template_block`)
|
||||||
|
- 字段名:snake_case(Model 中用 `__tablename__` 映射)
|
||||||
|
- 迁移使用 Alembic auto-generation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心数据模型
|
||||||
|
|
||||||
|
### template
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Template(Base):
|
||||||
|
__tablename__ = "template"
|
||||||
|
id: int # PK, auto
|
||||||
|
name: str # 模板名称
|
||||||
|
type: str # 报告类/公文类
|
||||||
|
version: str # v1.0.0
|
||||||
|
original_file_path: str # 文件存储路径
|
||||||
|
status: int # 1=启用 0=停用
|
||||||
|
created_by: str
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
```
|
||||||
|
|
||||||
|
### template_block
|
||||||
|
|
||||||
|
```python
|
||||||
|
class TemplateBlock(Base):
|
||||||
|
__tablename__ = "template_block"
|
||||||
|
id: int # PK
|
||||||
|
template_id: int # FK → template.id
|
||||||
|
block_id: str # "block_001"
|
||||||
|
parent_block_id: str | None
|
||||||
|
block_type: str # "title" | "heading" | "paragraph" | "table"
|
||||||
|
block_name: str | None
|
||||||
|
text_preview: str | None
|
||||||
|
level: int
|
||||||
|
sort_order: int
|
||||||
|
table_rows: int | None
|
||||||
|
table_cols: int | None
|
||||||
|
```
|
||||||
|
|
||||||
|
### block_config
|
||||||
|
|
||||||
|
```python
|
||||||
|
class BlockConfig(Base):
|
||||||
|
__tablename__ = "block_config"
|
||||||
|
id: int # PK
|
||||||
|
template_id: int # FK
|
||||||
|
block_id: str # "block_003"
|
||||||
|
region_name: str | None
|
||||||
|
region_type: str # "ai_generate" | "manual" | "fixed" | "table"
|
||||||
|
data_sources: str | None # JSON 数组字符串
|
||||||
|
prompt: str | None
|
||||||
|
output_format: str # "formal_paragraph" | ...
|
||||||
|
need_review: int # 0/1
|
||||||
|
remark: str | None
|
||||||
|
enabled: int # 0/1
|
||||||
|
# UNIQUE(template_id, block_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 关键接口
|
||||||
|
|
||||||
|
### 上传模板
|
||||||
|
```
|
||||||
|
POST /api/templates/upload
|
||||||
|
multipart/form-data: file + name + type
|
||||||
|
→ {"template_id": 1, "name": "...", "status": "uploaded"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模板详情(核心接口)
|
||||||
|
```
|
||||||
|
GET /api/templates/{id}
|
||||||
|
→ {
|
||||||
|
template: {...},
|
||||||
|
preview_html: "<div ...>",
|
||||||
|
blocks: [...],
|
||||||
|
tree: [...],
|
||||||
|
configs: {"block_003": {...}}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 保存区域配置
|
||||||
|
```
|
||||||
|
POST /api/templates/{id}/blocks/{blockId}/config
|
||||||
|
{region_name, region_type, data_sources, prompt, output_format, need_review, remark, enabled}
|
||||||
|
→ {"message": "saved"}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第一阶段不做
|
||||||
|
|
||||||
|
- ❌ 完整在线 Word 编辑
|
||||||
|
- ❌ 多人协同
|
||||||
|
- ❌ 权限审批
|
||||||
|
- ❌ 复杂表格智能填充
|
||||||
|
- ❌ 移动端适配
|
||||||
|
- ❌ 提示词历史版本(后续阶段)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 项目启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 后端
|
||||||
|
docker compose up -d mysql minio
|
||||||
|
cd backend && pip install -r requirements.txt
|
||||||
|
alembic upgrade head
|
||||||
|
uvicorn app.main:app --reload
|
||||||
|
|
||||||
|
# 前端
|
||||||
|
cd frontend && npm install && npm run dev
|
||||||
|
```
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
# AI 文档模板生成系统
|
||||||
|
|
||||||
|
> 通用型 AI 文档模板配置与生成平台 — 上传 Word 模板 → 自动解析结构 → 配置 AI 生成规则 → 分章节生成内容 → 回填 Word 模板 → 导出 Word/PDF。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一句话定位
|
||||||
|
|
||||||
|
**Word 模板标注器**:上传 Word 后,系统在页面中间展示文档预览,为每个标题/段落/表格生成唯一 block_id。用户点击预览区某个区域后,左侧显示该区域的提示词配置表单,可设置区域类型、数据来源、提示词、输出格式和审核要求。右侧显示文档结构树,与预览区联动。保存配置后,该区域成为 AI 可生成区域。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
| 层 | 技术 | 版本要求 |
|
||||||
|
|---|------|---------|
|
||||||
|
| 前端框架 | Vue 3 | ^3.4 |
|
||||||
|
| 构建工具 | Vite | ^5.x |
|
||||||
|
| UI 组件库 | Element Plus | ^2.8 |
|
||||||
|
| 状态管理 | Pinia | ^2.x |
|
||||||
|
| 路由 | Vue Router 4 | ^4.x |
|
||||||
|
| 后端框架 | Python FastAPI | ^0.110 |
|
||||||
|
| Python 版本 | Python 3.11+ | |
|
||||||
|
| 数据库 | MySQL 8.0 | |
|
||||||
|
| 缓存 | Redis 7.x | 可选 |
|
||||||
|
| 对象存储 | MinIO | |
|
||||||
|
| 文档解析 | python-docx | ^1.1 |
|
||||||
|
| 模板回填 | python-docx-template | ^1.0 |
|
||||||
|
| PDF 转换 | LibreOffice 无头模式 | |
|
||||||
|
| 容器化 | Docker + Docker Compose | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
ai-doc-template/
|
||||||
|
├── README.md
|
||||||
|
├── SKILL.md # Hermes 技能文件
|
||||||
|
├── AGENTS.md # AI 编码助手指引
|
||||||
|
├── .gitignore
|
||||||
|
├── .env.example
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── backend/
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ ├── requirements.txt
|
||||||
|
│ └── app/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── main.py # 应用入口 + FastAPI 实例
|
||||||
|
│ ├── database.py # 数据库连接 + Session
|
||||||
|
│ ├── models/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── template.py # template 表 Model
|
||||||
|
│ │ ├── template_block.py
|
||||||
|
│ │ └── block_config.py
|
||||||
|
│ ├── api/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── templates.py # 模板上传/查询接口
|
||||||
|
│ │ ├── blocks.py # 区域配置接口
|
||||||
|
│ │ └── data_sources.py # 数据源管理接口
|
||||||
|
│ ├── services/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── storage.py # MinIO 文件存储
|
||||||
|
│ │ ├── template_service.py
|
||||||
|
│ │ ├── doc_parser.py # Word 解析引擎
|
||||||
|
│ │ └── html_generator.py # HTML 预览生成
|
||||||
|
│ └── schemas/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── template.py # Pydantic 模型
|
||||||
|
├── frontend/
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── vite.config.ts
|
||||||
|
│ ├── tsconfig.json
|
||||||
|
│ ├── index.html
|
||||||
|
│ └── src/
|
||||||
|
│ ├── main.ts # 入口 + 插件注册
|
||||||
|
│ ├── App.vue
|
||||||
|
│ ├── router/
|
||||||
|
│ │ └── index.ts # 路由配置
|
||||||
|
│ ├── stores/
|
||||||
|
│ │ ├── templateStore.ts
|
||||||
|
│ │ ├── selectionStore.ts
|
||||||
|
│ │ └── uiStore.ts
|
||||||
|
│ ├── api/
|
||||||
|
│ │ └── base.ts # Axios 实例
|
||||||
|
│ ├── styles/
|
||||||
|
│ │ └── variables.css # 全局 CSS 变量
|
||||||
|
│ ├── views/
|
||||||
|
│ │ ├── TemplateCenter.vue
|
||||||
|
│ │ └── TemplateEdit.vue
|
||||||
|
│ └── components/
|
||||||
|
│ └── TemplateEdit/
|
||||||
|
│ ├── LeftMenu.vue
|
||||||
|
│ ├── TopToolbar.vue
|
||||||
|
│ ├── WordPreview.vue
|
||||||
|
│ ├── ConfigPanel.vue
|
||||||
|
│ └── StructureTree.vue
|
||||||
|
└── docs/
|
||||||
|
└── architecture.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 前置依赖
|
||||||
|
|
||||||
|
- Docker & Docker Compose
|
||||||
|
- Python 3.11+
|
||||||
|
- Node.js 18+
|
||||||
|
- LibreOffice(可选,PDF 导出需要)
|
||||||
|
|
||||||
|
### 启动后端
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 启动 MySQL + MinIO
|
||||||
|
docker compose up -d mysql minio
|
||||||
|
|
||||||
|
# 2. 安装后端依赖
|
||||||
|
cd backend
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 3. 复制环境变量
|
||||||
|
cp ../.env.example .env
|
||||||
|
|
||||||
|
# 4. 初始化数据库
|
||||||
|
alembic upgrade head
|
||||||
|
|
||||||
|
# 5. 启动后端
|
||||||
|
uvicorn app.main:app --reload --port 8000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 启动前端
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
pnpm install
|
||||||
|
pnpm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 访问
|
||||||
|
|
||||||
|
- 前端页面:http://localhost:5173
|
||||||
|
- API 文档:http://localhost:8000/docs
|
||||||
|
- MinIO Console:http://localhost:9001
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 开发阶段
|
||||||
|
|
||||||
|
| 阶段 | 目标 | 预估 |
|
||||||
|
|------|------|:----:|
|
||||||
|
| 第一阶段:模板标注 MVP | 上传 Word → 解析 → 预览 → 点击配置 → 保存 | 3-4 周 |
|
||||||
|
| 第二阶段:生成测试 | 上传资料 → AI 逐区域生成 → 预览结果 | 1-2 周 |
|
||||||
|
| 第三阶段:回填导出 | block 回填 → Word/PDF 导出 | 1-2 周 |
|
||||||
|
| 第四阶段:高级功能 | 提示词历史、数据源管理、审核流程等 | 2-3 周 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心流程
|
||||||
|
|
||||||
|
### 创建模板
|
||||||
|
|
||||||
|
```
|
||||||
|
上传 Word → 保存原始文件 → 解析标题/段落/表格 → 生成 block_id
|
||||||
|
→ 生成 HTML 预览 → 用户点击段落 → 配置提示词 → 保存配置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生成文档
|
||||||
|
|
||||||
|
```
|
||||||
|
选择模板 → 上传资料 → AI 提取结构化数据 → 按区域逐块生成
|
||||||
|
→ 人工审核修改 → 回填 Word 模板 → 导出 Word/PDF
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据库核心表
|
||||||
|
|
||||||
|
| 表名 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| template | 模板基本信息、文件路径、版本 |
|
||||||
|
| template_block | 解析出的文档区域(标题/段落/表格),含 block_id 和层级 |
|
||||||
|
| block_config | 用户为每个区域配置的提示词、类型、数据来源等 |
|
||||||
|
| data_source | 数据源配置 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 设计原则
|
||||||
|
|
||||||
|
1. **Word 保持版式,系统管理结构,AI 生成内容**
|
||||||
|
2. **HTML 只负责预览和交互,原始 docx 负责最终导出**
|
||||||
|
3. **block_id 是关键索引**:所有功能(点击、配置、生成、回填)都围绕 block_id
|
||||||
|
4. **先提取、再生成、再审核、再回填**,不把资料一次性扔给 AI
|
||||||
|
5. **第一版不做在线 Word 编辑**,聚焦标注和配置
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
---
|
||||||
|
name: ai-doc-template-system
|
||||||
|
description: "通用型 AI 文档模板生成系统 — Word 模板标注与 AI 文档生成平台。触发:需要建设/开发/维护 AI 文档模板生成平台时使用。"
|
||||||
|
version: 1.0.0
|
||||||
|
metadata:
|
||||||
|
hermes:
|
||||||
|
tags: [文档生成, AI, Word模板, 系统建设, FastAPI, Vue3]
|
||||||
|
related_skills: [requirements-analysis, task-splitting-methodology, prototype-design]
|
||||||
|
---
|
||||||
|
|
||||||
|
# AI 文档模板生成系统
|
||||||
|
|
||||||
|
> 一个面向多类文档的"模板管理 + 提示词配置 + AI 内容生成 + Word 样式回填"平台。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 系统定位
|
||||||
|
|
||||||
|
本系统**不是一个**纯 Word 在线编辑器,而是一个 Word 模板标注器:
|
||||||
|
|
||||||
|
1. 用户上传 Word → 系统自动解析标题/段落/表格 → 生成预览 HTML
|
||||||
|
2. 用户点击文档中的某个区域 → 左侧显示配置表单
|
||||||
|
3. 用户配置区域名称、区域类型、数据来源、提示词、输出格式、审核要求
|
||||||
|
4. 保存配置后,该区域成为 AI 可生成区域
|
||||||
|
5. 后续生成文档时,AI 根据各区域提示词生成内容并回填到原 Word
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术架构
|
||||||
|
|
||||||
|
| 层 | 选型 | 说明 |
|
||||||
|
|---|------|------|
|
||||||
|
| 前端 | Vue3 + Vite + Element Plus + Pinia | 组件化单页应用 |
|
||||||
|
| 后端 | Python FastAPI | RESTful API |
|
||||||
|
| 数据库 | MySQL 8.0 + SQLAlchemy + Alembic | 关系型数据 |
|
||||||
|
| 文件存储 | MinIO / 本地文件系统 | 模板和资料文件 |
|
||||||
|
| 文档解析 | python-docx | 读取 Word 结构 |
|
||||||
|
| 模板回填 | python-docx-template | 内容回填 Word |
|
||||||
|
| AI 集成 | 大模型 API + LangChain | 内容生成 |
|
||||||
|
| 容器化 | Docker + Docker Compose | 一键部署 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
ai-doc-template/
|
||||||
|
├── backend/
|
||||||
|
│ ├── app/
|
||||||
|
│ │ ├── main.py # FastAPI 入口 + CORS
|
||||||
|
│ │ ├── database.py # MySQL + SQLAlchemy
|
||||||
|
│ │ ├── models/ # SQLAlchemy Model 定义
|
||||||
|
│ │ ├── api/ # RESTful 路由
|
||||||
|
│ │ ├── services/ # 业务逻辑层
|
||||||
|
│ │ └── schemas/ # Pydantic 请求/响应
|
||||||
|
│ ├── requirements.txt
|
||||||
|
│ └── Dockerfile
|
||||||
|
├── frontend/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── views/ # 页面级组件
|
||||||
|
│ │ ├── components/ # 业务组件
|
||||||
|
│ │ ├── stores/ # Pinia 状态管理
|
||||||
|
│ │ ├── api/ # Axios API 客户端
|
||||||
|
│ │ └── styles/ # 全局样式变量
|
||||||
|
│ └── package.json
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── .env.example
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据库表
|
||||||
|
|
||||||
|
详见 `后端开发 > 数据模型`,核心三张表:
|
||||||
|
|
||||||
|
### template — 模板
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| id, name, type, version | 基本信息 |
|
||||||
|
| original_file_path | 原始 docx 存储路径 |
|
||||||
|
| status | 1=启用, 0=停用 |
|
||||||
|
|
||||||
|
### template_block — 文档区域
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| template_id | 所属模板 |
|
||||||
|
| block_id | 唯一标识(block_001) |
|
||||||
|
| block_type | title/heading/paragraph/table |
|
||||||
|
| level | 层级(0/1/2) |
|
||||||
|
| parent_block_id | 父级 |
|
||||||
|
| table_rows, table_cols | 表格行列 |
|
||||||
|
|
||||||
|
### block_config — 区域配置
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| template_id + block_id | 联合唯一键 |
|
||||||
|
| region_type | ai_generate/manual/fixed/table |
|
||||||
|
| prompt | 提示词 |
|
||||||
|
| data_sources | 数据来源列表(JSON) |
|
||||||
|
| need_review | 是否需要审核 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API 接口清单
|
||||||
|
|
||||||
|
### 模板
|
||||||
|
- `POST /api/templates/upload` — 上传 docx
|
||||||
|
- `GET /api/templates/{id}` — 模板详情(含预览 HTML + blocks + 配置)
|
||||||
|
|
||||||
|
### 区域配置
|
||||||
|
- `POST /api/templates/{id}/blocks/{blockId}/config` — 保存/更新区域配置
|
||||||
|
- `GET /api/data-sources` — 数据源列表
|
||||||
|
- `POST /api/data-sources` — 新增数据源
|
||||||
|
|
||||||
|
### 生成(后续阶段)
|
||||||
|
- `POST /api/templates/{id}/generate-test` — 生成测试
|
||||||
|
- `GET /api/tasks/{id}/download-docx` — 下载 Word
|
||||||
|
- `GET /api/tasks/{id}/download-pdf` — 下载 PDF
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 开发任务(第一阶段 MVP)
|
||||||
|
|
||||||
|
共 **73 个任务**,按模块分组:
|
||||||
|
|
||||||
|
| 模块 | 任务数 | 核心产出 |
|
||||||
|
|------|:------:|---------|
|
||||||
|
| 环境搭建 | 11 | FastAPI + Vue3 项目骨架 + Docker |
|
||||||
|
| 数据库建表 | 3 | template, template_block, block_config |
|
||||||
|
| 模板上传 | 4 | 文件接收校验 → 存储 → 入库 |
|
||||||
|
| Word 解析 | 7 | 标题/段落/表格识别 → block_id → 结构树 |
|
||||||
|
| HTML 预览 | 4 | heading/paragraph/table 转 HTML |
|
||||||
|
| 查询+配置接口 | 4 | 详情查询、配置保存、数据源 |
|
||||||
|
| 四栏布局 | 6 | 菜单/工具栏/预览/配置/树 shell |
|
||||||
|
| 左侧菜单 | 3 | 数据模型、展开收起、高亮 |
|
||||||
|
| 工具栏 | 4 | 面包屑、版本号、保存状态、操作按钮 |
|
||||||
|
| Word 预览区 | 7 | v-html渲染、点击、高亮、颜色、标签、缩放 |
|
||||||
|
| 结构树 | 4 | 递归组件、展开折叠、状态圆点、滚动联动 |
|
||||||
|
| 配置面板 | 9 | 名称/类型/数据源/提示词/审核/备注/保存 |
|
||||||
|
| Pinia Store | 3 | templateStore, selectionStore, uiStore |
|
||||||
|
| 三区联动 | 4 | watch 驱动的配置加载、树同步、loading |
|
||||||
|
|
||||||
|
详见 `docs/tasks-v3.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 开发阶段规划
|
||||||
|
|
||||||
|
### 第一阶段:模板标注 MVP(3-4 周)
|
||||||
|
|
||||||
|
核心流程:上传 Word → 解析 → 预览 → 点击配置 → 保存
|
||||||
|
|
||||||
|
交付物:
|
||||||
|
- 可用的模板编辑页面(四栏布局)
|
||||||
|
- Word 解析服务(标题/段落/表格)
|
||||||
|
- 区域配置面板(类型/提示词/数据来源)
|
||||||
|
- 结构树联动
|
||||||
|
|
||||||
|
### 第二阶段:生成测试(1-2 周)
|
||||||
|
|
||||||
|
- 上传资料 → AI 逐区域生成 → 预览结果 → 人工编辑
|
||||||
|
|
||||||
|
### 第三阶段:回填导出(1-2 周)
|
||||||
|
|
||||||
|
- block 回填原始 Word → Word/PDF 导出
|
||||||
|
|
||||||
|
### 第四阶段:高级功能(2-3 周)
|
||||||
|
|
||||||
|
- 提示词历史、数据源管理、审核流程
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 关键设计决策
|
||||||
|
|
||||||
|
| 决策 | 原因 |
|
||||||
|
|------|------|
|
||||||
|
| 不做 Word→HTML→Word 转换 | 格式丢失严重,Word 做底座 |
|
||||||
|
| HTML 只预览,docx 负责导出 | 分离预览和最终输出 |
|
||||||
|
| block_id 作为统一索引 | 解析、配置、生成、回填共享同一定位 |
|
||||||
|
| 分阶段迭代 | MVP 先跑通标注流程 |
|
||||||
|
| python-docx 而非 docxtpl | 第一版需要精确位置控制 |
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import os
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker, DeclarativeBase
|
||||||
|
|
||||||
|
DB_HOST = os.getenv("DB_HOST", "localhost")
|
||||||
|
DB_PORT = os.getenv("DB_PORT", "3306")
|
||||||
|
DB_USER = os.getenv("DB_USER", "root")
|
||||||
|
DB_PASS = os.getenv("DB_PASS", "root123")
|
||||||
|
DB_NAME = os.getenv("DB_NAME", "ai_doc_template")
|
||||||
|
|
||||||
|
DATABASE_URL = f"mysql+pymysql://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}?charset=utf8mb4"
|
||||||
|
|
||||||
|
engine = create_engine(DATABASE_URL, echo=False)
|
||||||
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
|
||||||
|
class Base(DeclarativeBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
app = FastAPI(title="AI 文档模板生成系统", version="0.1.0")
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["http://localhost:5173", "http://localhost:3000"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health():
|
||||||
|
return {"status": "ok", "service": "ai-doc-template"}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, UniqueConstraint
|
||||||
|
from ..database import Base
|
||||||
|
|
||||||
|
|
||||||
|
class BlockConfig(Base):
|
||||||
|
__tablename__ = "block_config"
|
||||||
|
__table_args__ = (
|
||||||
|
UniqueConstraint("template_id", "block_id", name="uq_template_block"),
|
||||||
|
)
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
template_id = Column(Integer, ForeignKey("template.id", ondelete="CASCADE"), nullable=False)
|
||||||
|
block_id = Column(String(50), nullable=False)
|
||||||
|
region_name = Column(String(200), nullable=True, comment="区域名称")
|
||||||
|
region_type = Column(String(30), default="ai_generate", comment="ai_generate/manual/fixed/table")
|
||||||
|
data_sources = Column(Text, nullable=True, comment="数据来源 JSON 数组")
|
||||||
|
prompt = Column(Text, nullable=True, comment="提示词")
|
||||||
|
output_format = Column(String(30), default="formal_paragraph", comment="输出格式")
|
||||||
|
need_review = Column(Integer, default=1, comment="1=需要审核")
|
||||||
|
remark = Column(Text, nullable=True, comment="备注")
|
||||||
|
enabled = Column(Integer, default=1, comment="1=启用")
|
||||||
|
created_at = Column(DateTime, default=datetime.now)
|
||||||
|
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
|
||||||
|
from ..database import Base
|
||||||
|
|
||||||
|
|
||||||
|
class Template(Base):
|
||||||
|
__tablename__ = "template"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
name = Column(String(200), nullable=False, comment="模板名称")
|
||||||
|
type = Column(String(50), default="report", comment="报告类/公文类/总结类/合同类")
|
||||||
|
version = Column(String(20), default="v1.0.0", comment="版本号")
|
||||||
|
original_file_path = Column(String(500), nullable=False, comment="原始 Word 文件路径")
|
||||||
|
status = Column(Integer, default=1, comment="1=启用 0=停用")
|
||||||
|
created_by = Column(String(50), comment="创建人")
|
||||||
|
created_at = Column(DateTime, default=datetime.now)
|
||||||
|
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
|
||||||
|
from ..database import Base
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateBlock(Base):
|
||||||
|
__tablename__ = "template_block"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
template_id = Column(Integer, ForeignKey("template.id", ondelete="CASCADE"), nullable=False, comment="所属模板")
|
||||||
|
block_id = Column(String(50), nullable=False, comment="唯一标识 block_001")
|
||||||
|
parent_block_id = Column(String(50), nullable=True, comment="父级 block_id")
|
||||||
|
block_type = Column(String(20), nullable=False, comment="title/heading/paragraph/table")
|
||||||
|
block_name = Column(String(200), nullable=True, comment="区域名称")
|
||||||
|
text_preview = Column(String(500), nullable=True, comment="文本预览")
|
||||||
|
level = Column(Integer, default=0, comment="0=文档标题 1=一级 2=二级")
|
||||||
|
sort_order = Column(Integer, default=0, comment="排序号")
|
||||||
|
table_rows = Column(Integer, nullable=True, comment="表格行数")
|
||||||
|
table_cols = Column(Integer, nullable=True, comment="表格列数")
|
||||||
|
created_at = Column(DateTime, default=datetime.now)
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
"""Word 文档解析引擎"""
|
||||||
|
from docx import Document
|
||||||
|
from docx.paragraph import Paragraph
|
||||||
|
from docx.table import Table as DocxTable
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
def parse_document(file_path: str) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
解析 Word 文档,返回 block 列表(不含表格数据,仅标记位置)。
|
||||||
|
每个 block 包含:type, text, level, style
|
||||||
|
"""
|
||||||
|
doc = Document(file_path)
|
||||||
|
blocks = []
|
||||||
|
|
||||||
|
for para in doc.paragraphs:
|
||||||
|
block = classify_paragraph(para)
|
||||||
|
if block:
|
||||||
|
blocks.append(block)
|
||||||
|
|
||||||
|
return blocks
|
||||||
|
|
||||||
|
|
||||||
|
def classify_paragraph(para: Paragraph) -> Dict[str, Any] | None:
|
||||||
|
"""识别段落类型"""
|
||||||
|
text = para.text.strip()
|
||||||
|
style_name = para.style.name if para.style else ""
|
||||||
|
|
||||||
|
if not text and "Heading" not in style_name:
|
||||||
|
return None # 跳过空段落
|
||||||
|
|
||||||
|
if "Heading" in style_name:
|
||||||
|
level = int(style_name.replace("Heading", "").strip()) if style_name.replace("Heading", "").strip().isdigit() else 1
|
||||||
|
return {"type": "heading", "text": text, "level": level, "style": style_name}
|
||||||
|
else:
|
||||||
|
return {"type": "paragraph", "text": text, "level": 0, "style": style_name}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_tables(doc: Document) -> List[Dict[str, Any]]:
|
||||||
|
"""解析 Word 表格"""
|
||||||
|
tables = []
|
||||||
|
for i, table in enumerate(doc.tables):
|
||||||
|
headers = [cell.text.strip() for cell in table.rows[0].cells] if table.rows else []
|
||||||
|
tables.append({
|
||||||
|
"type": "table",
|
||||||
|
"text": " | ".join(headers),
|
||||||
|
"level": 0,
|
||||||
|
"rows": len(table.rows),
|
||||||
|
"cols": len(table.columns),
|
||||||
|
"headers": headers,
|
||||||
|
"table_index": i,
|
||||||
|
})
|
||||||
|
return tables
|
||||||
|
|
||||||
|
|
||||||
|
def generate_block_id(index: int) -> str:
|
||||||
|
"""生成 block_id,格式:block_001"""
|
||||||
|
return f"block_{index:03d}"
|
||||||
|
|
||||||
|
|
||||||
|
def build_tree(blocks: List[Dict]) -> Dict:
|
||||||
|
"""构建层级树结构"""
|
||||||
|
tree = []
|
||||||
|
stack = [] # 存父级节点
|
||||||
|
|
||||||
|
for block in blocks:
|
||||||
|
node = {"block_id": block["block_id"], "text": block["text"][:50], "children": []}
|
||||||
|
level = block.get("level", 0)
|
||||||
|
|
||||||
|
# 回退栈
|
||||||
|
while stack and stack[-1]["level"] >= level:
|
||||||
|
stack.pop()
|
||||||
|
|
||||||
|
if stack:
|
||||||
|
parent = stack[-1]["node"]
|
||||||
|
parent["children"].append(node)
|
||||||
|
block["parent_block_id"] = parent["block_id"]
|
||||||
|
else:
|
||||||
|
tree.append(node)
|
||||||
|
block["parent_block_id"] = None
|
||||||
|
|
||||||
|
# 非叶子节点入栈
|
||||||
|
if level < 2 or block["type"] == "heading":
|
||||||
|
stack.append({"level": level, "node": node})
|
||||||
|
|
||||||
|
return {"blocks": tree}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
"""HTML 预览生成服务"""
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
def generate_preview_html(blocks: List[Dict[str, Any]]) -> str:
|
||||||
|
"""
|
||||||
|
将 block 列表转为带 data-block-id 的 HTML 预览字符串。
|
||||||
|
每个元素都会带上 data-block-id 属性供前端点击交互。
|
||||||
|
"""
|
||||||
|
html_parts = ['<div class="doc-preview-content">']
|
||||||
|
|
||||||
|
for block in blocks:
|
||||||
|
block_id = block.get("block_id", "")
|
||||||
|
block_type = block.get("type", "")
|
||||||
|
text = block.get("text", "")
|
||||||
|
|
||||||
|
if block_type == "heading":
|
||||||
|
html = f'<h2 data-block-id="{block_id}" style="font-size:16px;font-weight:600;margin:20px 0 10px;padding-bottom:6px;border-bottom:2px solid #1a1d24">{text}</h2>'
|
||||||
|
elif block_type == "table":
|
||||||
|
html = f'<div data-block-id="{block_id}" style="margin:12px 0;padding:8px;background:#f7f8fa;border:1px dashed #ccc;border-radius:4px;color:#5b626e">📊 {text[:80]}</div>'
|
||||||
|
else:
|
||||||
|
html = f'<p data-block-id="{block_id}" style="text-indent:2em;margin:8px 0;line-height:1.8;text-align:justify">{text}</p>'
|
||||||
|
|
||||||
|
html_parts.append(html)
|
||||||
|
|
||||||
|
html_parts.append('</div>')
|
||||||
|
return "\n".join(html_parts)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import os
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
# 先实现本地文件存储,MinIO 作为选项
|
||||||
|
STORAGE_BASE = os.getenv("LOCAL_STORAGE_PATH", "./data/files")
|
||||||
|
|
||||||
|
|
||||||
|
def save_file(content: bytes, file_path: str) -> str:
|
||||||
|
"""保存文件到本地存储,返回完整路径"""
|
||||||
|
full_path = os.path.join(STORAGE_BASE, file_path)
|
||||||
|
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
||||||
|
with open(full_path, "wb") as f:
|
||||||
|
f.write(content)
|
||||||
|
return full_path
|
||||||
|
|
||||||
|
|
||||||
|
def read_file(file_path: str) -> bytes:
|
||||||
|
"""读取文件内容"""
|
||||||
|
full_path = os.path.join(STORAGE_BASE, file_path)
|
||||||
|
with open(full_path, "rb") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def delete_file(file_path: str):
|
||||||
|
"""删除文件"""
|
||||||
|
full_path = os.path.join(STORAGE_BASE, file_path)
|
||||||
|
if os.path.exists(full_path):
|
||||||
|
os.remove(full_path)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"""模板相关业务逻辑"""
|
||||||
|
from fastapi import UploadFile
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from ..models.template import Template
|
||||||
|
|
||||||
|
|
||||||
|
ALLOWED_EXTENSIONS = {".docx"}
|
||||||
|
MAX_FILE_SIZE = 50 * 1024 * 1024 # 50MB
|
||||||
|
|
||||||
|
|
||||||
|
def validate_file(file: UploadFile):
|
||||||
|
"""校验文件格式和大小"""
|
||||||
|
ext = "." + file.filename.rsplit(".", 1)[-1].lower() if "." in file.filename else ""
|
||||||
|
if ext not in ALLOWED_EXTENSIONS:
|
||||||
|
raise ValueError(f"不支持的文件格式: {ext},仅支持 .docx")
|
||||||
|
# 读取文件头校验大小
|
||||||
|
content = file.file.read()
|
||||||
|
file.file.seek(0)
|
||||||
|
if len(content) > MAX_FILE_SIZE:
|
||||||
|
raise ValueError(f"文件大小超过限制 (50MB)")
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def create_template_record(db: Session, name: str, file_path: str, type: str = "report") -> Template:
|
||||||
|
"""创建模板记录"""
|
||||||
|
tmpl = Template(name=name, type=type, original_file_path=file_path)
|
||||||
|
db.add(tmpl)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(tmpl)
|
||||||
|
return tmpl
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fastapi==0.110.0
|
||||||
|
uvicorn[standard]==0.27.0
|
||||||
|
sqlalchemy==2.0.25
|
||||||
|
pymysql==1.1.0
|
||||||
|
cryptography==42.0.0
|
||||||
|
alembic==1.13.0
|
||||||
|
python-multipart==0.0.6
|
||||||
|
python-docx==1.1.0
|
||||||
|
minio==7.2.0
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
pydantic==2.5.0
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: ai-doc-mysql
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: root123
|
||||||
|
MYSQL_DATABASE: ai_doc_template
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql
|
||||||
|
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||||
|
|
||||||
|
minio:
|
||||||
|
image: minio/minio:latest
|
||||||
|
container_name: ai-doc-minio
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: minioadmin
|
||||||
|
MINIO_ROOT_PASSWORD: minioa...n
|
||||||
|
ports:
|
||||||
|
- "9000:9000" # API
|
||||||
|
- "9001:9001" # Console
|
||||||
|
volumes:
|
||||||
|
- minio_data:/data
|
||||||
|
command: server /data --console-address ":9001"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
||||||
|
minio_data:
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
# 系统架构设计
|
||||||
|
|
||||||
|
> AI 文档模板生成系统 — 架构决策与模块设计
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 整体架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ 前端 (Vue3) │
|
||||||
|
│ TemplateEdit.vue │
|
||||||
|
│ ├─ LeftMenu.vue 左侧菜单 │
|
||||||
|
│ ├─ TopToolbar.vue 顶部工具栏 │
|
||||||
|
│ ├─ WordPreview.vue 中间文档预览 │
|
||||||
|
│ ├─ ConfigPanel.vue 左侧配置面板 │
|
||||||
|
│ └─ StructureTree.vue 右侧结构树 │
|
||||||
|
├────────────────── HTTP/REST ────────────────────────┤
|
||||||
|
│ 后端 (FastAPI) │
|
||||||
|
│ ├─ api/ RESTful 接口 │
|
||||||
|
│ ├─ services/ 业务逻辑层 │
|
||||||
|
│ │ ├─ doc_parser.py Word 解析引擎 │
|
||||||
|
│ │ ├─ html_generator.py HTML 预览生成 │
|
||||||
|
│ │ ├─ template_service.py 模板业务 │
|
||||||
|
│ │ └─ storage.py 文件存储 │
|
||||||
|
│ └─ models/ SQLAlchemy 数据模型 │
|
||||||
|
├─────────────────── 数据层 ───────────────────────────┤
|
||||||
|
│ MySQL 8.0 (结构化) MinIO (文件) │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心流程 数据流
|
||||||
|
|
||||||
|
### 上传模板
|
||||||
|
|
||||||
|
```
|
||||||
|
用户 → 上传 .docx
|
||||||
|
→ POST /api/templates/upload
|
||||||
|
→ validate_file() 校验格式和大小
|
||||||
|
→ save_file() 保存到 MinIO/本地
|
||||||
|
→ create_template_record() 写入 template 表
|
||||||
|
→ parse_document() 解析标题/段落
|
||||||
|
→ parse_tables() 解析表格
|
||||||
|
→ generate_block_id() 生成唯一 ID
|
||||||
|
→ save_blocks() 写入 template_block 表
|
||||||
|
← 返回 template_id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 进入编辑页
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/templates/{id}
|
||||||
|
→ 查询 template 表 → 基本信息
|
||||||
|
→ 查询 template_block 表 → blocks + tree
|
||||||
|
→ 调用 html_generator → preview_html
|
||||||
|
→ 查询 block_config 表 → 已有配置 keyed by block_id
|
||||||
|
← 返回完整数据
|
||||||
|
前端渲染四栏布局
|
||||||
|
```
|
||||||
|
|
||||||
|
### 保存配置
|
||||||
|
|
||||||
|
```
|
||||||
|
用户配置区域 → 点击保存
|
||||||
|
→ POST /api/templates/{id}/blocks/{blockId}/config
|
||||||
|
→ upsert block_config 表
|
||||||
|
→ 更新 configs 缓存
|
||||||
|
← 返回成功
|
||||||
|
前端更新保存状态 + 树节点颜色
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 关键设计
|
||||||
|
|
||||||
|
### block_id 是统一索引
|
||||||
|
|
||||||
|
```
|
||||||
|
Word 解析 → block_id → 数据库存储
|
||||||
|
点击交互 → block_id → 定位 + 高亮
|
||||||
|
配置保存 → block_id → 存储提示词
|
||||||
|
AI 生成 → block_id → 绑定数据来源
|
||||||
|
Word 回填 → block_id → 定位原始位置
|
||||||
|
```
|
||||||
|
|
||||||
|
所有模块通过 block_id 串联,不依赖 Word 内 XML 路径。
|
||||||
|
|
||||||
|
### HTML 预览 vs Word 回填
|
||||||
|
|
||||||
|
| 维度 | HTML 预览 | Word 回填 |
|
||||||
|
|------|----------|----------|
|
||||||
|
| 用途 | 前端展示+交互 | 最终导出 |
|
||||||
|
| 数据来源 | 解析结果 | 原始 docx |
|
||||||
|
| 样式要求 | 接近即可 | 尽量保留 |
|
||||||
|
| 操作 | 点击选中+配置 | 替换内容 |
|
||||||
|
| 技术 | python-docx → HTML | python-docx 直接操作 |
|
||||||
|
|
||||||
|
### 分阶段策略
|
||||||
|
|
||||||
|
第一阶段只做预览+标注+配置。
|
||||||
|
HTML 预览只需"看到内容+点击",不需要"和 Word 一模一样"。
|
||||||
|
Word 回填保留给第三阶段,那时再精细化处理样式映射。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据流时序
|
||||||
|
|
||||||
|
### 点击区域 → 配置联动
|
||||||
|
|
||||||
|
```
|
||||||
|
用户点击预览区段落
|
||||||
|
→ WordPreview 捕获 click 事件
|
||||||
|
→ event.target.closest('[data-block-id]')
|
||||||
|
→ selectionStore.selectBlock(blockId)
|
||||||
|
→ watch 触发:
|
||||||
|
├─ ConfigPanel: 加载该 block 的 config (已有/默认)
|
||||||
|
├─ StructureTree: 高亮对应节点
|
||||||
|
└─ WordPreview: 添加高亮样式
|
||||||
|
```
|
||||||
|
|
||||||
|
### 保存配置
|
||||||
|
|
||||||
|
```
|
||||||
|
ConfigPanel 点击保存
|
||||||
|
→ saving=true
|
||||||
|
→ POST /api/templates/{id}/blocks/{blockId}/config
|
||||||
|
→ 成功:
|
||||||
|
├─ saving=false
|
||||||
|
├─ uiStore.saveStatus = 'saved'
|
||||||
|
├─ templateStore.updateConfig(blockId, config)
|
||||||
|
├─ WordPreview 更新区域颜色
|
||||||
|
└─ StructureTree 更新状态圆点
|
||||||
|
→ 失败:
|
||||||
|
├─ saving=false
|
||||||
|
└─ ElMessage.error('保存失败')
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user