Files
ai-doc-template-system/docs/AI文档模板系统-像素级任务清单-20260701.md
T
2026-07-01 20:25:48 +08:00

1339 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI 文档模板系统 — 像素级任务清单
> 每个任务 ~0.25–0.5 天,可独立分配,验收精确到文件/函数/交互效果。
> 聚焦:模板标注配置页(第一阶段 MVP)。
> UI 库:Ant Design Vue
---
## 第一阶段:MVP 任务总表(73 个)
| # | 任务名 | 端 | 预估 | 状态 |
|---|--------|:--:|:----:|:--:|
| | **环境搭建** | | | |
| 001 | 初始化 FastAPI 项目 + main.py + health 接口 | 后端 | 0.25d | ✅ 已完成 |
| 002 | 配置 MySQL 连接 + SQLAlchemy session | 后端 | 0.25d | ✅ 已完成 |
| 003 | 配置 MinIO 客户端 | 后端 | 0.25d | ✅ 已完成 |
| 004 | Alembic 初始化 + 第一个迁移脚本 | 后端 | 0.25d | ✅ 已完成 |
| 005 | Docker Compose 编排(MySQL + MinIO | 后端 | 0.25d | ✅ 已完成 |
| 006 | 初始化 Vue3 + Vite 项目 | 前端 | 0.25d | ✅ 已完成 |
| 007 | 安装 Ant Design Vue + 全局注册 | 前端 | 0.25d | ✅ 已完成 |
| 008 | 安装 Vue Router + 配置路由结构 | 前端 | 0.25d | ✅ 已完成 |
| 009 | 安装 Pinia + 创建根 store | 前端 | 0.25d | ✅ 已完成 |
| 010 | 安装 Axios + 创建 API 客户端 base.ts | 前端 | 0.25d | ✅ 已完成 |
| 011 | 定义全局 CSS 变量 | 前端 | 0.25d | ✅ 已完成 |
| | **数据库建表(3 个)** | | | |
| 012 | 创建 template 表 + SQLAlchemy Model | 后端 | 0.25d | ✅ 已完成 |
| 013 | 创建 template_block 表 + SQLAlchemy Model | 后端 | 0.25d | ✅ 已完成 |
| 014 | 创建 block_config 表 + SQLAlchemy Model(含 unique 约束) | 后端 | 0.25d | ✅ 已完成 |
| | **模板上传** | | | |
| 015 | 实现文件接收 + .docx 格式校验 + 大小校验 | 后端 | 0.25d | ⏳ 未完成 |
| 016 | 实现文件存储到 MinIO | 后端 | 0.25d | ⏳ 未完成 |
| 017 | 实现 template 表 insert | 后端 | 0.25d | ⏳ 未完成 |
| 018 | 组装上传接口 `POST /api/templates/upload` | 后端 | 0.25d | ⏳ 未完成 |
| | **Word 解析** | | | |
| 019 | 实现 docx 文件打开 + 逐段落遍历 | 后端 | 0.25d | ⏳ 未完成 |
| 020 | 实现标题识别(Heading 1-6)→ type=heading | 后端 | 0.25d | ⏳ 未完成 |
| 021 | 实现段落识别 → type=paragraph | 后端 | 0.25d | ⏳ 未完成 |
| 022 | 实现表格识别 + 行数列数提取 → type=table | 后端 | 0.25d | ⏳ 未完成 |
| 023 | 实现 block_id 生成器(顺序编号) | 后端 | 0.25d | ⏳ 未完成 |
| 024 | 实现层级构建(parent_block_id+ 结构树 tree 输出 | 后端 | 0.25d | ⏳ 未完成 |
| 025 | 将解析结果批量写入 template_block 表 | 后端 | 0.25d | ⏳ 未完成 |
| | **HTML 预览生成** | | | |
| 026 | 标题 → `<h2 data-block-id>` 转换 + 居中加粗样式 | 后端 | 0.25d | ⏳ 未完成 |
| 027 | 段落 → `<p data-block-id>` 转换 + 首行缩进样式 | 后端 | 0.25d | ⏳ 未完成 |
| 028 | 表格 → `<table data-block-id>` 转换 + 边框样式 | 后端 | 0.25d | ⏳ 未完成 |
| 029 | 拼接完整 HTML 字符串(带内联 CSS) | 后端 | 0.25d | ⏳ 未完成 |
| | **查询 + 配置接口** | | | |
| 030 | 实现 `GET /api/templates/{id}` 组装全部数据 | 后端 | 0.5d | ⏳ 未完成 |
| 031 | 实现 `POST /api/templates/{id}/blocks/{blockId}/config` upsert | 后端 | 0.25d | ⏳ 未完成 |
| 032 | 实现 `GET /api/data-sources` 返回预设列表 | 后端 | 0.25d | ⏳ 未完成 |
| 033 | 实现 `POST /api/data-sources` 新增数据源 | 后端 | 0.25d | ⏳ 未完成 |
| | **四栏布局** | | | |
| 034 | 实现四栏 CSS Grid 布局 | 前端 | 0.5d | ⏳ 未完成 |
| 035 | 实现左侧菜单 shell 组件 | 前端 | 0.25d | ⏳ 未完成 |
| 036 | 实现顶部工具栏 shell 组件 | 前端 | 0.25d | ⏳ 未完成 |
| 037 | 实现配置面板 shell 组件 | 前端 | 0.25d | ⏳ 未完成 |
| 038 | 实现预览区 shell 组件(A4 纸效果) | 前端 | 0.25d | ⏳ 未完成 |
| 039 | 实现结构树 shell 组件 | 前端 | 0.25d | ⏳ 未完成 |
| | **左侧菜单** | | | |
| 040 | 菜单数据模型(JSON+ 子菜单配置 | 前端 | 0.25d | ⏳ 未完成 |
| 041 | 菜单展开/收起交互 + 箭头旋转动画 | 前端 | 0.25d | ⏳ 未完成 |
| 042 | 菜单选中高亮(蓝色左侧竖条) | 前端 | 0.25d | ⏳ 未完成 |
| | **顶部工具栏** | | | |
| 043 | 面包屑导航渲染 | 前端 | 0.25d | ⏳ 未完成 |
| 044 | 模板名称 + 版本号标签展示 | 前端 | 0.25d | ⏳ 未完成 |
| 045 | 保存状态指示器(已保存/未保存 圆点切换) | 前端 | 0.25d | ⏳ 未完成 |
| 046 | 操作按钮区(预览/保存/生成测试/导出模板/全屏) | 前端 | 0.25d | ⏳ 未完成 |
| | **Word 预览区** | | | |
| 047 | v-html 渲染后端 HTML + 为 [data-block-id] 添加 doc-block class | 前端 | 0.25d | ⏳ 未完成 |
| 048 | 点击事件委托 + block_id 提取 | 前端 | 0.25d | ⏳ 未完成 |
| 049 | 选中高亮(蓝色虚线 outline + 浅蓝背景) | 前端 | 0.25d | ⏳ 未完成 |
| 050 | 区域类型颜色映射(蓝/黄/绿/紫/灰)+ 保存后更新 | 前端 | 0.5d | ⏳ 未完成 |
| 051 | block_id 标签 absolute 定位 + hover 显示 | 前端 | 0.25d | ⏳ 未完成 |
| 052 | 显示/隐藏标签 toggle 开关 | 前端 | 0.25d | ⏳ 未完成 |
| 053 | 缩放控制(70%/100%/150% scale 切换) | 前端 | 0.25d | ⏳ 未完成 |
| | **结构树** | | | |
| 054 | 递归树组件 + 数据绑定 | 前端 | 0.5d | ⏳ 未完成 |
| 055 | 展开/折叠交互 + 图标 | 前端 | 0.25d | ⏳ 未完成 |
| 056 | 状态圆点(已配置绿/待审核黄/已禁用红/未配置灰) | 前端 | 0.25d | ⏳ 未完成 |
| 057 | 点击树节点 → 预览区 scrollIntoView + 闪烁高亮 | 前端 | 0.5d | ⏳ 未完成 |
| | **配置面板** | | | |
| 058 | 区域名称 input(默认取 text_preview | 前端 | 0.25d | ⏳ 未完成 |
| 059 | 区域类型 select | 前端 | 0.25d | ⏳ 未完成 |
| 060 | 数据来源 tag chips 多选组件 | 前端 | 0.5d | ⏳ 未完成 |
| 061 | 提示词 textarea + 字数统计 | 前端 | 0.5d | ⏳ 未完成 |
| 062 | 输出格式 select | 前端 | 0.25d | ⏳ 未完成 |
| 063 | 是否需要审核 radio | 前端 | 0.25d | ⏳ 未完成 |
| 064 | 备注 textarea + 字数统计 | 前端 | 0.25d | ⏳ 未完成 |
| 065 | 保存按钮 + loading 状态 + 成功/失败提示 | 前端 | 0.25d | ⏳ 未完成 |
| 066 | 未选中区域时的空状态占位提示 | 前端 | 0.25d | ⏳ 未完成 |
| | **Pinia Store** | | | |
| 067 | templateStore | 前端 | 0.5d | ⏳ 未完成 |
| 068 | selectionStore | 前端 | 0.25d | ⏳ 未完成 |
| 069 | uiStore | 前端 | 0.25d | ⏳ 未完成 |
| | **三区联动** | | | |
| 070 | watch selectedBlockId → 配置面板加载 | 前端 | 0.5d | ⏳ 未完成 |
| 071 | watch selectedBlockId → 结构树节点高亮 | 前端 | 0.25d | ⏳ 未完成 |
| 072 | 页面初始化 loading(全页遮罩 + a-spin | 前端 | 0.25d | ⏳ 未完成 |
| 073 | 加载失败错误页 + 重新加载按钮 | 前端 | 0.25d | ⏳ 未完成 |
| | **总计** | | **~18.25d** | |
---
## 第一阶段 MVP 任务卡(逐个)
---
### 环境搭建(001011
---
#### 001 · 初始化 FastAPI 项目 + main.py + health 接口
**端**:后端 **预估**0.25d **产出**`app/main.py`
**做什么**
- 创建 `app/` 目录,创建 `app/main.py`
- 创建 FastAPI 实例,添加 `GET /health` 返回 `{"status": "ok"}`
- 添加 CORS 中间件(允许前端 localhost 访问)
- 创建目录结构:`api/`, `models/`, `services/`, `schemas/`
**验收**
- `uvicorn app.main:app --reload` 启动成功
- 访问 `/docs` 显示 Swagger 页面
- 访问 `/health` 返回 200
---
#### 002 · 配置 MySQL 连接 + SQLAlchemy session
**端**:后端 **预估**0.25d **产出**`app/database.py`
**做什么**
- 创建 `app/database.py`
- 配置 SQLAlchemy engine(读取环境变量 `DB_HOST/DB_PORT/DB_USER/DB_PASS/DB_NAME`
- 创建 `SessionLocal` 工厂
- 创建 `Base` 基类
- 创建 `get_db` 依赖注入函数
**验收**
- 应用启动时不会报数据库连接错误
---
#### 003 · 配置 MinIO 客户端
**端**:后端 **预估**0.25d **产出**`app/services/storage.py`
**做什么**
- 创建 `app/services/storage.py`
- 初始化 `minio.Minio` 客户端
- 实现 `upload_file(bucket, file_path, content)` 函数
- 实现 `download_file(bucket, file_path)` 函数
- 实现 `ensure_bucket(bucket_name)` 自动创建 bucket
**验收**
- MinIO 未启动时降级为本地文件存储
---
#### 004 · Alembic 初始化 + 第一个迁移脚本
**端**:后端 **预估**0.25d **产出**`alembic.ini`, `migrations/`
**做什么**
- 运行 `alembic init alembic`
- 配置 `alembic.ini` 中的数据库连接
- 配置 `env.py` 导入 `Base`
- 生成第一个空迁移
**验收**
- `alembic upgrade head` 成功执行
- `alembic history` 显示初始迁移
---
#### 005 · Docker Compose 编排
**端**:后端 **预估**0.25d **产出**`docker-compose.yml`
**做什么**
- 创建 `docker-compose.yml`
- MySQL 8.0 服务(端口 3306,初始化数据库 `ai_doc_template`
- MinIO 服务(端口 9000 + 9001 console
- 网络配置,服务间可通信
**验收**
- `docker-compose up -d` 启动成功
---
#### 006 · 初始化 Vue3 + Vite 项目
**端**:前端 **预估**0.25d **产出**:前端项目根目录
**做什么**
- 运行 `npm create vite@latest`Vue3 + TypeScript
- 安装依赖 `npm install`
- 确认 `npm run dev` 可启动
- 清理默认 App.vue 内容,保留 Hello World
**验收**
- `npm run dev` 启动成功
---
#### 007 · 安装 Ant Design Vue + 全局注册
**端**:前端 **预估**0.25d **产出**`src/main.ts`
**做什么**
- 运行 `npm install ant-design-vue`
-`main.ts``import Antd from 'ant-design-vue'` + `app.use(Antd)`
- 全局导入样式:`import 'ant-design-vue/dist/reset.css'`
- 验证:在 App.vue 中使用一个 `<a-button>` 看能否渲染
**验收**
- Ant Design Vue 按钮可正常显示
- 无控制台报错
---
#### 008 · 安装 Vue Router + 配置路由结构
**端**:前端 **预估**0.25d **产出**`src/router/index.ts`
**做什么**
- 运行 `npm install vue-router@4`
- 创建 `src/router/index.ts`
- 配置路由:`/` → TemplateCenter, `/template/:id` → TemplateEdit
-`main.ts``app.use(router)`
- 添加 `router-view` 到 App.vue
**验收**
- 访问 `/` 显示模板中心页
- 访问 `/template/1` 显示模板编辑页
---
#### 009 · 安装 Pinia + 创建根 store
**端**:前端 **预估**0.25d **产出**`src/stores/index.ts`
**做什么**
- 运行 `npm install pinia`
-`main.ts``app.use(createPinia())`
- 创建一个简单的 counter store 验证可用
**验收**
- Pinia 正常注册,无控制台报错
---
#### 010 · 安装 Axios + 创建 API 客户端
**端**:前端 **预估**0.25d **产出**`src/api/base.ts`
**做什么**
- 运行 `npm install axios`
- 创建 `src/api/base.ts`
- 创建 axios 实例,baseURL 设为 `/api`
- 添加请求/响应拦截器
**验收**
- 调用 `api.get('/health')` 能收到后端响应
---
#### 011 · 定义全局 CSS 变量
**端**:前端 **预估**0.25d **产出**`src/styles/variables.css`
**做什么**
- 创建 `src/styles/variables.css`
- 定义 `:root` 变量:
```css
:root {
--primary: #5b5bd6;
--primary-hover: #4a4ac0;
--primary-soft: #eeeefb;
--text: #1a1d24;
--text-2: #5b626e;
--text-3: #9aa1ad;
--border: #e0e2e6;
--border-light: #eaecef;
--bg: #f5f6f8;
--bg-card: #fff;
--bg-soft: #f0f1f3;
--success: #1a8c4a;
--warn: #d48a00;
--danger: #d33;
--font: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
--fs-body: 13px;
--fs-sm: 12px;
--radius: 6px;
--sidebar-w: 220px;
--config-panel-w: 380px;
--tree-panel-w: 240px;
--toolbar-h: 48px;
}
```
-`main.ts` 中 import
**验收**
- 全局样式变量可在任意组件中使用 `var(--primary)`
---
### 数据库建表(012014
---
#### 012 · 创建 template 表 + Model
**端**:后端 **预估**0.25d **产出**`app/models/template.py`
**做什么**
- 定义 `Template` Model(字段见附录)
- 生成 Alembic 迁移并执行
**验收**
- 表创建成功,可通过 Model 插入一条记录
---
#### 013 · 创建 template_block 表 + Model
**端**:后端 **预估**0.25d **产出**`app/models/template_block.py`
**做什么**
- 定义 `TemplateBlock` Model
- 外键关联到 `template.id`
- 生成迁移并执行
**验收**
- 插入数据时外键约束生效
---
#### 014 · 创建 block_config 表 + Model
**端**:后端 **预估**0.25d **产出**`app/models/block_config.py`
**做什么**
- 定义 `BlockConfig` Model
- 添加唯一约束 `(template_id, block_id)`
- 生成迁移并执行
**验收**
- 重复 `(template_id, block_id)` 插入时更新而非报错
---
### 模板上传(015018
---
#### 015 · 文件接收 + 格式校验 + 大小校验
**端**:后端 **预估**0.25d **产出**`app/services/template_service.py` 中的 `validate_file()`
**做什么**
- 校验扩展名为 `.docx`
- 校验 Content-Type
- 校验文件大小 ≤ 50MB
**验收**
- 上传 `.txt` 返回 400
- 上传 >50MB 返回 413
---
#### 016 · 文件存储到 MinIO
**端**:后端 **预估**0.25d **产出**`app/services/template_service.py` 中的 `save_file()`
**做什么**
- 调用 storage 服务的 upload_file
- 路径格式:`/templates/{template_id}/{filename}`
- MinIO 不可用时降级本地存储
**验收**
- 文件保存到指定路径
---
#### 017 · template 表 insert
**端**:后端 **预估**0.25d **产出**`app/services/template_service.py` 中的 `create_template_record()`
**做什么**
- 写入 template 表
- 返回创建的 template 对象
**验收**
- 数据库有正确记录
---
#### 018 · 组装上传接口
**端**:后端 **预估**0.25d **产出**`app/api/templates.py` 中的 upload 路由
**做什么**
- 创建 `app/api/templates.py`
- 实现 `POST /api/templates/upload` 路由
- 调用 015→016→017 三个步骤
**验收**
- Postman 上传成功,返回 `{"template_id": 1, "name": "...", "status": "uploaded"}`
---
### Word 解析(019025
---
#### 019 · docx 文件打开 + 逐段落遍历
**端**:后端 **预估**0.25d **产出**`app/services/doc_parser.py` 中的 `parse_document()`
**做什么**
- 使用 python-docx 打开文件
- 遍历 `document.paragraphs`
**验收**
- 返回段落列表
---
#### 020 · 标题识别(Heading 1-6
**端**:后端 **预估**0.25d **产出**`app/services/doc_parser.py` 中的 `classify_paragraph()`
**做什么**
- 判断 `paragraph.style.name` 是否以 "Heading" 开头
- 提取标题级别和文本
**验收**
- Heading 1 → level 1
- Heading 2 → level 2
---
#### 021 · 段落识别
**端**:后端 **预估**0.25d **产出**`app/services/doc_parser.py`
**做什么**
- 非标题段落 → type="paragraph"
- 提取文本和样式名称
**验收**
- 正文段落 type=paragraph
---
#### 022 · 表格识别 + 行数列数提取
**端**:后端 **预估**0.25d **产出**`app/services/doc_parser.py` 中的 `parse_tables()`
**做什么**
- 遍历 `document.tables`
- 提取行数、列数、表头
**验收**
- 表格行数列数正确
---
#### 023 · block_id 生成器
**端**:后端 **预估**0.25d **产出**`app/services/doc_parser.py` 中的 `generate_block_id()`
**做什么**
- 格式:`block_001`, `block_002`, ... 递增
**验收**
- 输入 1 → `block_001`
- 输入 12 → `block_012`
---
#### 024 · 层级构建 + 结构树输出
**端**:后端 **预估**0.25d **产出**`app/services/doc_parser.py` 中的 `build_tree()`
**做什么**
- 根据 heading level 确定父子关系
- 填充 `parent_block_id`
- 输出嵌套 tree 结构
**验收**
- 三级标题层级正确
- 表格归属于正确的父章节
---
#### 025 · 解析结果批量写入 template_block 表
**端**:后端 **预估**0.25d **产出**`app/services/doc_parser.py` 中的 `save_blocks()`
**做什么**
- 批量 insert 到 template_block 表
- 先删除旧记录(重解析场景)
**验收**
- 数据库有对应的 block 记录
---
### HTML 预览生成(026029
---
#### 026 · 标题 → `<h2 data-block-id>` 转换
**端**:后端 **预估**0.25d **产出**`app/services/html_generator.py` 中的 `render_heading()`
**做什么**
- 输出 `<h2 data-block-id="block_002" style="...">标题内容</h2>`
**验收**
- 标签带 data-block-id,样式居中加粗
---
#### 027 · 段落 → `<p data-block-id>` 转换
**端**:后端 **预估**0.25d **产出**`app/services/html_generator.py` 中的 `render_paragraph()`
**做什么**
- 输出 `<p data-block-id="block_003" style="text-indent:2em;...">段落内容</p>`
**验收**
- 首行缩进,行高 1.8
---
#### 028 · 表格 → `<table data-block-id>` 转换
**端**:后端 **预估**0.25d **产出**`app/services/html_generator.py` 中的 `render_table()`
**做什么**
- 输出完整 HTML `<table>` 带 data-block-id
**验收**
- 表格正确渲染,边框可见
---
#### 029 · 拼接完整 HTML 字符串
**端**:后端 **预估**0.25d **产出**`app/services/html_generator.py` 中的 `generate_preview_html()`
**做什么**
- 按 sort_order 排序
- 遍历 blocks 调用对应的 render 函数
- 拼接为完整 HTML
**验收**
- 返回字符串可被前端 v-html 渲染
---
### 查询 + 配置接口(030033
---
#### 030 · 模板详情查询接口
**端**:后端 **预估**0.5d **产出**`app/api/templates.py` 中的 GET 路由
**做什么**
- 实现 `GET /api/templates/{template_id}`
- 查询 template 表 + template_block 表 + block_config 表
- 调用 html_generator 生成 preview_html
- 返回完整 JSON
**验收**
- 返回数据包含 template, preview_html, blocks, tree, configs
---
#### 031 · 保存区域配置接口
**端**:后端 **预估**0.25d **产出**`app/api/blocks.py` 中的 POST 路由
**做什么**
- 实现 `POST /api/templates/{template_id}/blocks/{block_id}/config`
- upsert 语义
**验收**
- 保存后数据库记录正确
- 重复调用更新记录
---
#### 032 · 获取数据源列表
**端**:后端 **预估**0.25d **产出**`app/api/data_sources.py` 中的 GET 路由
**做什么**
- 返回预设列表:财务部、运行部、生技部、安全监察部等
**验收**
- 返回 JSON 数组
---
#### 033 · 新增数据源
**端**:后端 **预估**0.25d **产出**`app/api/data_sources.py` 中的 POST 路由
**做什么**
- 接收 `{name, category}` 并存储
**验收**
- 新增后 GET 列表包含新数据源
---
### 四栏布局(034039
---
#### 034 · 四栏 CSS Grid 布局
**端**:前端 **预估**0.5d **产出**`src/views/TemplateEdit.vue`
**做什么**
```css
.layout {
display: grid;
grid-template-columns: var(--sidebar-w) var(--config-panel-w) 1fr var(--tree-panel-w);
grid-template-rows: var(--toolbar-h) 1fr;
height: 100vh;
}
```
- 工具栏 grid-column: 1 / -1
- 左侧菜单 grid-column: 1
- 配置面板 grid-column: 2
- 预览区 grid-column: 3
- 结构树 grid-column: 4
**验收**
- 页面渲染为四栏布局
- 各区域宽度正确
---
#### 035 · 左侧菜单 shell
**端**:前端 **预估**0.25d **产出**`src/components/TemplateEdit/LeftMenu.vue`
**做什么**
- 固定宽度 220px
- overflow-y: auto, border-right, 白色背景
**验收**
- 渲染在正确位置
---
#### 036 · 顶部工具栏 shell
**端**:前端 **预估**0.25d **产出**`src/components/TemplateEdit/TopToolbar.vue`
**做什么**
- 固定高度 48px, flex 布局, border-bottom
**验收**
- 渲染在顶部
---
#### 037 · 配置面板 shell
**端**:前端 **预估**0.25d **产出**`src/components/TemplateEdit/ConfigPanel.vue`
**做什么**
- 固定宽度 380px, overflow-y: auto
- border-right: 1px solid
**验收**
- 渲染在正确位置
---
#### 038 · 预览区 shell
**端**:前端 **预估**0.25d **产出**`src/components/TemplateEdit/WordPreview.vue`
**做什么**
- flex: 1, overflow-y: auto, 背景色 `#f0f1f3`
- 内部 A4 纸容器:794px 宽, 白色背景, box-shadow
**验收**
- A4 纸效果可见
---
#### 039 · 结构树 shell
**端**:前端 **预估**0.25d **产出**`src/components/TemplateEdit/StructureTree.vue`
**做什么**
- 固定宽度 240px, overflow-y: auto
- border-left: 1px solid
**验收**
- 渲染在右侧
---
### 左侧菜单(040042
---
#### 040 · 菜单数据模型
**端**:前端 **预估**0.25d **产出**`src/components/TemplateEdit/menu-data.ts`
**做什么**
```ts
interface MenuItem {
label: string
icon: string
children?: MenuItem[]
route?: string
}
```
- 完整菜单数据(模板中心/生成文档/历史记录/系统配置 及子菜单)
**验收**
- 数据结构可被菜单组件消费
---
#### 041 · 菜单展开/收起 + 箭头动画
**端**:前端 **预估**0.25d **产出**`LeftMenu.vue`
**做什么**
- 渲染菜单树
- 有子菜单的项显示箭头图标
- 点击切换展开/收起,箭头旋转动画
**验收**
- 点击展开子菜单,箭头旋转 90°,动画流畅
---
#### 042 · 菜单选中高亮
**端**:前端 **预估**0.25d **产出**`LeftMenu.vue`
**做什么**
- 当前选中菜单添加 `active` class
- 高亮样式:`background: #eeeefb; color: #5b5bd6;`
- 左侧竖条 3px`border-left: 3px solid #5b5bd6;`
**验收**
- 点击菜单项高亮,切换时旧高亮移除
---
### 顶部工具栏(043046
---
#### 043 · 面包屑导航
**端**:前端 **预估**0.25d **产出**`TopToolbar.vue`
**做什么**
- 显示面包屑:`模板中心 / 模板名称 / 区域配置`
- 每段用 `/` 分隔,最后一段加粗
**验收**
- 面包屑渲染正确
---
#### 044 · 模板名称 + 版本号
**端**:前端 **预估**0.25d **产出**`TopToolbar.vue`
**做什么**
- 从 Pinia store 读取模板名称显示
- 版本号显示为灰色小标签
**验收**
- 模板名称正确读取
---
#### 045 · 保存状态指示器
**端**:前端 **预估**0.25d **产出**`TopToolbar.vue`
**做什么**
- 小圆点 + 文字:绿色"已保存" / 黄色"未保存"
- 从 uiStore 读取 saveStatus
**验收**
- 初始"未保存",保存成功后变"已保存"
---
#### 046 · 操作按钮区
**端**:前端 **预估**0.25d **产出**`TopToolbar.vue`
**做什么**
- 使用 Ant Design Vue `<a-button size="small">`
- 按钮:预览、保存(primary)、生成测试、导出模板、全屏
- 按钮间距 8px
**验收**
- 5 个按钮分布整齐,保存按钮高亮
---
### Word 预览区(047053
---
#### 047 · v-html 渲染后端 HTML
**端**:前端 **预估**0.25d **产出**`WordPreview.vue`
**做什么**
- `<div v-html="previewHtml" class="doc-content" @click="handleClick"></div>`
- 在 mounted 中为所有 `[data-block-id]` 元素添加 class `doc-block`
**验收**
- HTML 内容渲染正确
---
#### 048 · 点击事件委托 + block_id 提取
**端**:前端 **预估**0.25d **产出**`WordPreview.vue` handleClick
**做什么**
```ts
function handleClick(event: MouseEvent) {
const target = (event.target as HTMLElement).closest('[data-block-id]')
if (!target) { selectionStore.cancelSelection(); return }
const blockId = target.getAttribute('data-block-id')
selectionStore.selectBlock(blockId)
}
```
**验收**
- 点击段落提取到正确的 block_id
- 点击空白区域取消选中
---
#### 049 · 选中高亮
**端**:前端 **预估**0.25d **产出**`WordPreview.vue`
**做什么**
- 选中 block 添加 class `active-region`
- CSS`outline: 2px solid #5b5bd6; outline-offset: -2px; background: #f0f0ff;`
**验收**
- 选中后蓝色虚线边框可见
---
#### 050 · 区域类型颜色映射
**端**:前端 **预估**0.5d **产出**`WordPreview.vue`
**做什么**
- 根据 region_type 添加颜色 class
- `ai_generate` → 浅蓝 `#e8f0fe` + 蓝色左边框
- `manual` → 浅黄 `#fef7e0` + 黄色左边框
- `fixed` → 浅绿 `#e6f4ea` + 绿色左边框
- `table` → 浅紫 `#f3e8ff` + 紫色左边框
- 保存配置后立即更新
**验收**
- 保存后预览区颜色变化正确
---
#### 051 · block_id 标签
**端**:前端 **预估**0.25d **产出**`WordPreview.vue`
**做什么**
- 每个 block 左下角显示标签 `block_003`
- 标签样式:`position: absolute; left: -80px; font-size: 11px;`
- hover 时透明度 0→1CSS transition
**验收**
- 鼠标移入时标签可见
---
#### 052 · 显示/隐藏标签 toggle
**端**:前端 **预估**0.25d **产出**`TopToolbar.vue` + `WordPreview.vue`
**做什么**
- 工具栏添加 Ant Design Vue `<a-switch>`
- 标签:`显示区域标识`
- 切换时更新 uiStore.showLabels
**验收**
- 关闭后所有标签隐藏
---
#### 053 · 缩放控制
**端**:前端 **预估**0.25d **产出**`WordPreview.vue`
**做什么**
- 工具栏缩放控制:`<a-button>-</a-button> 100% <a-button>+</a-button>`
- 级别:70% / 100% / 150%
-`.doc-content` 应用 `transform: scale(N)`
**验收**
- 缩放后内容清晰,交互正常
---
### 结构树(054057
---
#### 054 · 递归树组件 + 数据绑定
**端**:前端 **预估**0.5d **产出**`StructureTree.vue`
**做什么**
- 递归组件,接收 tree 数据
- 渲染文档章节层级
**验收**
- 树结构与解析结果一致,递归渲染无报错
---
#### 055 · 展开/折叠 + 图标
**端**:前端 **预估**0.25d **产出**`StructureTree.vue`
**做什么**
- 箭头旋转 90° 表示展开
- 图标:heading→📄, paragraph→📝, table→📊
**验收**
- 点击箭头展开/折叠,图标正确
---
#### 056 · 状态圆点
**端**:前端 **预估**0.25d **产出**`StructureTree.vue`
**做什么**
- 从 Pinia store 的 configs 读取配置
- 圆点:已配置=绿色 / 待审核=黄色 / 已禁用=红色 / 未配置=灰色
- 圆点样式:`width: 8px; height: 8px; border-radius: 50%;`
**验收**
- 保存后圆点变为绿色
---
#### 057 · 树节点 → 预览区滚动定位
**端**:前端 **预估**0.5d **产出**`StructureTree.vue` + `WordPreview.vue`
**做什么**
- 点击树节点:
1. `selectionStore.selectBlock(blockId)`
2. `document.querySelector([data-block-id="${blockId}"])?.scrollIntoView({ behavior: 'smooth', block: 'center' })`
- 闪烁效果:添加 class `flash`0.3s 后移除
**验收**
- 点击树节点后预览区滚动到正确位置
---
### 配置面板(058066
---
#### 058 · 区域名称 input
**端**:前端 **预估**0.25d **产出**`ConfigPanel.vue`
**做什么**
- `<a-input v-model:value="form.regionName" placeholder="区域名称" :disabled="!selectedBlockId" />`
- 选中 block 时默认填充 text_preview
**验收**
- 选中后显示 block 名称,可编辑
---
#### 059 · 区域类型 select
**端**:前端 **预估**0.25d **产出**`ConfigPanel.vue`
**做什么**
- `<a-select v-model:value="form.regionType">`
- 选项:AI 生成区域 / 人工填写区域 / 固定区域 / 表格生成区域
**验收**
- 切换选项值变化
---
#### 060 · 数据来源 tag chips
**端**:前端 **预估**0.5d **产出**`ConfigPanel.vue`
**做什么**
- 已选项显示为 `<a-tag closable @close="removeSource(item)">财务部</a-tag>`
- 末尾" + 添加"按钮,点击弹出 `<a-select>` 下拉
- 已选的不可重复添加
**验收**
- 标签显示正确,可删除可添加
---
#### 061 · 提示词 textarea + 字数统计
**端**:前端 **预估**0.5d **产出**`ConfigPanel.vue`
**做什么**
- `<a-textarea v-model:value="form.prompt" :rows="4" :maxlength="1000" show-count />`
- 超过 850 字时数字变橙色,超过 1000 变红
**验收**
- 输入字数实时统计
---
#### 062 · 输出格式 select
**端**:前端 **预估**0.25d **产出**`ConfigPanel.vue`
**做什么**
- `<a-select v-model:value="form.outputFormat">`
- 选项:正式报告段落 / 分条说明 / 表格数据 / JSON 数据 / 标题文本
**验收**
- 选项切换正常
---
#### 063 · 是否需要审核 radio
**端**:前端 **预估**0.25d **产出**`ConfigPanel.vue`
**做什么**
- `<a-radio-group v-model:value="form.needReview">`
- `<a-radio :value="true">是</a-radio>`
- `<a-radio :value="false">否</a-radio>`
**验收**
- 切换值变化
---
#### 064 · 备注 textarea
**端**:前端 **预估**0.25d **产出**`ConfigPanel.vue`
**做什么**
- `<a-textarea v-model:value="form.remark" :rows="2" :maxlength="200" show-count />`
**验收**
- 输入正常,字数统计正确
---
#### 065 · 保存按钮 + 流程
**端**:前端 **预估**0.25d **产出**`ConfigPanel.vue`
**做什么**
- `<a-button type="primary" @click="saveConfig" :loading="saving" style="width:100%">保存该区域配置</a-button>`
- 点击后:
1. `saving = true`
2. 调用 POST 接口
3. 成功 → `saving = false` + `message.success('保存成功')`
4. 失败 → `saving = false` + `message.error('保存失败')`
**验收**
- 保存成功/失败有对应提示
---
#### 066 · 未选中空状态
**端**:前端 **预估**0.25d **产出**`ConfigPanel.vue`
**做什么**
- `selectedBlockId === null` 时显示:
- 居中图标 + "点击文档中的区域开始配置"
- 隐藏所有表单字段
**验收**
- 初始显示空状态,选中后显示表单
---
### Pinia Store067069
---
#### 067 · templateStore
**端**:前端 **预估**0.5d **产出**`src/stores/templateStore.ts`
**做什么**
- state: `template`, `previewHtml`, `blocks`, `tree`, `configs`, `dataSources`
- actions: `fetchTemplate(id)`, `updateConfig(blockId, config)`
- getters: `getBlockById(blockId)`, `getConfigByBlockId(blockId)`
**验收**
- 调用 fetchTemplate 后所有数据填充
---
#### 068 · selectionStore
**端**:前端 **预估**0.25d **产出**`src/stores/selectionStore.ts`
**做什么**
- state: `selectedBlockId: string | null`
- actions: `selectBlock(id)`, `cancelSelection()`
**验收**
- selectBlock 后 selectedBlockId 更新
---
#### 069 · uiStore
**端**:前端 **预估**0.25d **产出**`src/stores/uiStore.ts`
**做什么**
- state: `zoomLevel: 100`, `showLabels: true`, `saveStatus: 'saved' | 'unsaved' | 'saving'`
- actions: `setZoom(level)`, `toggleLabels()`, `setSaveStatus(status)`
**验收**
- 状态可读可写,所有组件响应变化
---
### 三区联动(070073
---
#### 070 · watch selectedBlockId → 配置加载
**端**:前端 **预估**0.5d **产出**`ConfigPanel.vue`
**做什么**
```ts
watch(() => selectionStore.selectedBlockId, (blockId) => {
if (!blockId) { form.value = {}; return }
const config = templateStore.configs[blockId]
if (config) {
form.value = { ...config }
} else {
const block = templateStore.getBlockById(blockId)
form.value = {
regionName: block?.text_preview || '',
regionType: 'ai_generate',
dataSources: [],
prompt: '',
outputFormat: 'formal_paragraph',
needReview: true,
remark: '',
enabled: true
}
}
})
```
**验收**
- 已配置的 block 加载已有配置
- 未配置的 block 显示默认值
---
#### 071 · watch selectedBlockId → 树节点高亮
**端**:前端 **预估**0.25d **产出**`StructureTree.vue`
**做什么**
```ts
watch(() => selectionStore.selectedBlockId, (blockId) => {
highlightNode(blockId)
if (blockId) expandParents(blockId)
})
```
**验收**
- 选中后树节点高亮,父级自动展开
---
#### 072 · 页面初始化 loading
**端**:前端 **预估**0.25d **产出**`TemplateEdit.vue`
**做什么**
- 页面 mounted 时:
1. `loading = true`
2. 调用 `templateStore.fetchTemplate(id)`
3. 成功 → `loading = false`
- loading 状态:全屏半透明遮罩 + `<a-spin>` 加载动画
**验收**
- 加载时显示 loading,完成后内容出现
---
#### 073 · 加载失败错误页
**端**:前端 **预估**0.25d **产出**`TemplateEdit.vue`
**做什么**
- 加载失败时显示:错误图标 + "加载失败" + 错误信息 + 重新加载按钮
- 点击重新加载重试 fetchTemplate
**验收**
- 模拟网络断开时显示错误页
---
## 附录:数据库表结构
### template 表
```sql
CREATE TABLE template (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
type VARCHAR(50) DEFAULT 'report',
version VARCHAR(20) DEFAULT 'v1.0.0',
original_file_path VARCHAR(500) NOT NULL,
status TINYINT DEFAULT 1,
created_by VARCHAR(50),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
### template_block 表
```sql
CREATE TABLE template_block (
id INT AUTO_INCREMENT PRIMARY KEY,
template_id INT NOT NULL,
block_id VARCHAR(50) NOT NULL,
parent_block_id VARCHAR(50),
block_type VARCHAR(20) NOT NULL,
block_name VARCHAR(200),
text_preview VARCHAR(500),
level INT DEFAULT 0,
sort_order INT DEFAULT 0,
table_rows INT,
table_cols INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (template_id) REFERENCES template(id) ON DELETE CASCADE,
INDEX idx_template_id (template_id)
);
```
### block_config 表
```sql
CREATE TABLE block_config (
id INT AUTO_INCREMENT PRIMARY KEY,
template_id INT NOT NULL,
block_id VARCHAR(50) NOT NULL,
region_name VARCHAR(200),
region_type VARCHAR(30) DEFAULT 'ai_generate',
data_sources TEXT,
prompt TEXT,
output_format VARCHAR(30) DEFAULT 'formal_paragraph',
need_review TINYINT DEFAULT 1,
remark TEXT,
enabled TINYINT DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (template_id) REFERENCES template(id) ON DELETE CASCADE,
UNIQUE KEY uk_template_block (template_id, block_id)
);
```
---
## 附录:第一阶段接口清单
| 方法 | 路径 | 任务 |
|------|------|:----:|
| GET | `/health` | 001 |
| POST | `/api/templates/upload` | 018 |
| GET | `/api/templates/{template_id}` | 030 |
| POST | `/api/templates/{template_id}/blocks/{block_id}/config` | 031 |
| GET | `/api/data-sources` | 032 |
| POST | `/api/data-sources` | 033 |
---
## 附录:Ant Design Vue 组件对照
| 用途 | Ant Design Vue 组件 |
|------|-------------------|
| 按钮 | `<a-button>` |
| 文本输入 | `<a-input v-model:value>` |
| 下拉选择 | `<a-select v-model:value>` |
| 多行文本 | `<a-textarea v-model:value>`(自带 `show-count` 字数统计) |
| 标签 | `<a-tag closable>` |
| 单选框 | `<a-radio-group> <a-radio>` |
| 开关 | `<a-switch>` |
| 加载动画 | `<a-spin>` |
| 消息提示 | `message.success()` / `message.error()`(从 `ant-design-vue` import |