34 KiB
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 任务卡(逐个)
环境搭建(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 中使用一个
<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变量:
: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
做什么
- 定义
TemplateModel(字段见附录) - 生成 Alembic 迁移并执行
验收
- 表创建成功,可通过 Model 插入一条记录
013 · 创建 template_block 表 + Model
端:后端 预估:0.25d 产出:app/models/template_block.py
做什么
- 定义
TemplateBlockModel - 外键关联到
template.id - 生成迁移并执行
验收
- 插入数据时外键约束生效
014 · 创建 block_config 表 + Model
端:后端 预估:0.25d 产出:app/models/block_config.py
做什么
- 定义
BlockConfigModel - 添加唯一约束
(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 · 标题 → <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 渲染
查询 + 配置接口(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
做什么
.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
做什么
interface MenuItem {
label: string
icon: string
children?: MenuItem[]
route?: string
}
- 完整菜单数据(模板中心/生成文档/历史记录/系统配置 及子菜单)
验收
- 数据结构可被菜单组件消费
041 · 菜单展开/收起 + 箭头动画
端:前端 预估:0.25d 产出:LeftMenu.vue
做什么
- 渲染菜单树
- 有子菜单的项显示箭头图标
- 点击切换展开/收起,箭头旋转动画
验收
- 点击展开子菜单,箭头旋转 90°,动画流畅
042 · 菜单选中高亮
端:前端 预估:0.25d 产出:LeftMenu.vue
做什么
- 当前选中菜单添加
activeclass - 高亮样式:
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
<a-button size="small"> - 按钮:预览、保存(primary)、生成测试、导出模板、全屏
- 按钮间距 8px
验收
- 5 个按钮分布整齐,保存按钮高亮
Word 预览区(047–053)
047 · v-html 渲染后端 HTML
端:前端 预估:0.25d 产出:WordPreview.vue
做什么
<div v-html="previewHtml" class="doc-content" @click="handleClick"></div>- 在 mounted 中为所有
[data-block-id]元素添加 classdoc-block
验收
- HTML 内容渲染正确
048 · 点击事件委托 + block_id 提取
端:前端 预估:0.25d 产出:WordPreview.vue handleClick
做什么
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
<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)
验收
- 缩放后内容清晰,交互正常
结构树(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
做什么
- 点击树节点:
selectionStore.selectBlock(blockId)document.querySelector([data-block-id="${blockId}"])?.scrollIntoView({ behavior: 'smooth', block: 'center' })
- 闪烁效果:添加 class
flash,0.3s 后移除
验收
- 点击树节点后预览区滚动到正确位置
配置面板(058–066)
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>- 点击后:
saving = true- 调用 POST 接口
- 成功 →
saving = false+message.success('保存成功') - 失败 →
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
做什么
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
做什么
watch(() => selectionStore.selectedBlockId, (blockId) => {
highlightNode(blockId)
if (blockId) expandParents(blockId)
})
验收
- 选中后树节点高亮,父级自动展开
072 · 页面初始化 loading
端:前端 预估:0.25d 产出:TemplateEdit.vue
做什么
- 页面 mounted 时:
loading = true- 调用
templateStore.fetchTemplate(id) - 成功 →
loading = false
- loading 状态:全屏半透明遮罩 +
<a-spin>加载动画
验收
- 加载时显示 loading,完成后内容出现
073 · 加载失败错误页
端:前端 预估:0.25d 产出:TemplateEdit.vue
做什么
- 加载失败时显示:错误图标 + "加载失败" + 错误信息 + 重新加载按钮
- 点击重新加载重试 fetchTemplate
验收
- 模拟网络断开时显示错误页
附录:数据库表结构
template 表
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 表
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 表
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) |