# 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 | 标题 → `
` 转换 + 居中加粗样式 | 后端 | 0.25d | ✅ 已完成 |
| 027 | 段落 → `
` 转换 + 首行缩进样式 | 后端 | 0.25d | ✅ 已完成 |
| 028 | 表格 → `
` 转换 + 边框样式 | 后端 | 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 任务卡(逐个)
---
### 环境搭建(001–011)
---
#### 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 中使用一个 `` 看能否渲染
**验收**
- 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)`
---
### 数据库建表(012–014)
---
#### 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)` 插入时更新而非报错
---
### 模板上传(015–018)
---
#### 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 解析(019–025)
---
#### 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 预览生成(026–029)
---
#### 026 · 标题 → `` 转换
**端**:后端 **预估**:0.25d **产出**:`app/services/html_generator.py` 中的 `render_heading()`
**做什么**
- 输出 `标题内容
`
**验收**
- 标签带 data-block-id,样式居中加粗
---
#### 027 · 段落 → `
` 转换
**端**:后端 **预估**:0.25d **产出**:`app/services/html_generator.py` 中的 `render_paragraph()`
**做什么**
- 输出 `
段落内容
`
**验收**
- 首行缩进,行高 1.8
---
#### 028 · 表格 → `` 转换
**端**:后端 **预估**:0.25d **产出**:`app/services/html_generator.py` 中的 `render_table()`
**做什么**
- 输出完整 HTML `` 带 data-block-id
**验收**
- 表格正确渲染,边框可见
---
#### 029 · 拼接完整 HTML 字符串
**端**:后端 **预估**:0.25d **产出**:`app/services/html_generator.py` 中的 `generate_preview_html()`
**做什么**
- 按 sort_order 排序
- 遍历 blocks 调用对应的 render 函数
- 拼接为完整 HTML
**验收**
- 返回字符串可被前端 v-html 渲染
---
### 查询 + 配置接口(030–033)
---
#### 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 列表包含新数据源
---
### 四栏布局(034–039)
---
#### 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
**验收**
- 渲染在右侧
---
### 左侧菜单(040–042)
---
#### 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;`
**验收**
- 点击菜单项高亮,切换时旧高亮移除
---
### 顶部工具栏(043–046)
---
#### 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 ``
- 按钮:预览、保存(primary)、生成测试、导出模板、全屏
- 按钮间距 8px
**验收**
- 5 个按钮分布整齐,保存按钮高亮
---
### Word 预览区(047–053)
---
#### 047 · v-html 渲染后端 HTML
**端**:前端 **预估**:0.25d **产出**:`WordPreview.vue`
**做什么**
- ``
- 在 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→1(CSS transition)
**验收**
- 鼠标移入时标签可见
---
#### 052 · 显示/隐藏标签 toggle
**端**:前端 **预估**:0.25d **产出**:`TopToolbar.vue` + `WordPreview.vue`
**做什么**
- 工具栏添加 Ant Design Vue ``
- 标签:`显示区域标识`
- 切换时更新 uiStore.showLabels
**验收**
- 关闭后所有标签隐藏
---
#### 053 · 缩放控制
**端**:前端 **预估**:0.25d **产出**:`WordPreview.vue`
**做什么**
- 工具栏缩放控制:`- 100% +`
- 级别:70% / 100% / 150%
- 对 `.doc-content` 应用 `transform: scale(N)`
**验收**
- 缩放后内容清晰,交互正常
---
### 结构树(054–057)
---
#### 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 后移除
**验收**
- 点击树节点后预览区滚动到正确位置
---
### 配置面板(058–066)
---
#### 058 · 区域名称 input
**端**:前端 **预估**:0.25d **产出**:`ConfigPanel.vue`
**做什么**
- ``
- 选中 block 时默认填充 text_preview
**验收**
- 选中后显示 block 名称,可编辑
---
#### 059 · 区域类型 select
**端**:前端 **预估**:0.25d **产出**:`ConfigPanel.vue`
**做什么**
- ``
- 选项:AI 生成区域 / 人工填写区域 / 固定区域 / 表格生成区域
**验收**
- 切换选项值变化
---
#### 060 · 数据来源 tag chips
**端**:前端 **预估**:0.5d **产出**:`ConfigPanel.vue`
**做什么**
- 已选项显示为 `财务部`
- 末尾" + 添加"按钮,点击弹出 `` 下拉
- 已选的不可重复添加
**验收**
- 标签显示正确,可删除可添加
---
#### 061 · 提示词 textarea + 字数统计
**端**:前端 **预估**:0.5d **产出**:`ConfigPanel.vue`
**做什么**
- ``
- 超过 850 字时数字变橙色,超过 1000 变红
**验收**
- 输入字数实时统计
---
#### 062 · 输出格式 select
**端**:前端 **预估**:0.25d **产出**:`ConfigPanel.vue`
**做什么**
- ``
- 选项:正式报告段落 / 分条说明 / 表格数据 / JSON 数据 / 标题文本
**验收**
- 选项切换正常
---
#### 063 · 是否需要审核 radio
**端**:前端 **预估**:0.25d **产出**:`ConfigPanel.vue`
**做什么**
- ``
- `是`
- `否`
**验收**
- 切换值变化
---
#### 064 · 备注 textarea
**端**:前端 **预估**:0.25d **产出**:`ConfigPanel.vue`
**做什么**
- ``
**验收**
- 输入正常,字数统计正确
---
#### 065 · 保存按钮 + 流程
**端**:前端 **预估**:0.25d **产出**:`ConfigPanel.vue`
**做什么**
- `保存该区域配置`
- 点击后:
1. `saving = true`
2. 调用 POST 接口
3. 成功 → `saving = false` + `message.success('保存成功')`
4. 失败 → `saving = false` + `message.error('保存失败')`
**验收**
- 保存成功/失败有对应提示
---
#### 066 · 未选中空状态
**端**:前端 **预估**:0.25d **产出**:`ConfigPanel.vue`
**做什么**
- `selectedBlockId === null` 时显示:
- 居中图标 + "点击文档中的区域开始配置"
- 隐藏所有表单字段
**验收**
- 初始显示空状态,选中后显示表单
---
### Pinia Store(067–069)
---
#### 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)`
**验收**
- 状态可读可写,所有组件响应变化
---
### 三区联动(070–073)
---
#### 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 状态:全屏半透明遮罩 + `` 加载动画
**验收**
- 加载时显示 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 组件 |
|------|-------------------|
| 按钮 | `` |
| 文本输入 | `` |
| 下拉选择 | `` |
| 多行文本 | ``(自带 `show-count` 字数统计) |
| 标签 | `` |
| 单选框 | ` ` |
| 开关 | `` |
| 加载动画 | `` |
| 消息提示 | `message.success()` / `message.error()`(从 `ant-design-vue` import) |