在 Axonix 平台中,添加类似豆包的富文本编辑器功能,服务于以下场景:
| 层次 | 技术 |
|---|---|
| 前端框架 | React 18 + TypeScript + Vite + Ant Design |
| 编辑器核心 | Slate.js |
| 状态管理 | Zustand |
| 后端框架 | FastAPI(Python 3.10+) |
| ORM | SQLAlchemy 2.0 |
| 数据库 | PostgreSQL |
阶段 0 打通基础链路(当前)
mod-chat 中 AI 生成文档 → 编辑器打开 → 编辑 → 保存回 Chat
约 1.5 周
阶段 1 Word 模板 + 工作流文档编辑(近期目标)
上传 Word 模板 → 工作流生成文档 → 编辑器打开编辑 → 下载保持模板格式
约 2 周
阶段 2 oil-agent 跨平台集成
编辑器微前端化 → oil-agent 免登录接入 → 跨平台数据同步
约 2 周
阶段 3 编辑器增强 + 协同编辑
富文本增强、版本管理、实时多人协同
约 4 周
跑通 mod-chat 中"AI 生成文档 → 编辑器打开 → 编辑 → 保存回 Chat"的完整闭环。
从 Chat 打开编辑器
基础编辑器
保存并下载
.doc)文档 API
用户在 Chat 中查看 AI 生成的文档
↓
点击"编辑"按钮
↓
POST /api/v1/documents 创建文档记录,获取 documentId
↓
打开编辑器 Modal,加载文档内容
↓
用户编辑 → 计算变动块(标题 level + index)→ 写入本地缓存
↓
判断文档大小
├── 小文件(< 200KB)→ 防抖 3s → PUT /api/v1/documents/{id}(局部块更新)
└── 大文件(≥ 200KB)→ 仅写 IndexedDB,不自动推后端
↓
点击"保存"按钮 → 弹出保存对话框
↓
用户填写文件名,选择格式(.doc)→ 确认
↓
POST /api/v1/export/doc 提交文件名、格式、文档内容
↓
后端生成 .doc 文件,返回下载链接
↓
前端自动触发浏览器下载
┌─────────────────────────────────┐
│ 保存文档 │
├─────────────────────────────────┤
│ 文件名 [季度报告_2026Q2 ] │
│ 格式 [.doc ▼ ] │
│ (目前仅支持 .doc) │
├─────────────────────────────────┤
│ [取消] [确认下载] │
└─────────────────────────────────┘
mod-chat/src/
components/
editor/
DocumentEditor.tsx // 编辑器 Modal 主组件
EditorToolbar.tsx // 工具栏(含保存按钮)
EditorPreview.tsx // Markdown 分屏预览
SaveDialog.tsx // 保存对话框(文件名 + 格式选择)
store/
editorStore.ts // 编辑器 Zustand store
api/
documentApi.ts // 文档 API 封装
exportApi.ts // 导出下载 API 封装
backend/
app/
main.py // FastAPI 应用入口,注册路由
config.py // 环境变量配置(DB URL 等)
core/
database.py // AsyncSession 连接池
dependencies.py // get_db 依赖注入
exceptions.py // 自定义异常 + 全局 handler
models/
document.py // documents 表 ORM 模型
schemas/
document.py // DocumentCreate / Update / Response
services/
document_service.py // CRUD 业务逻辑
export_service.py // .doc 文件生成
api/
v1/
documents.py // CRUD 路由(5 个端点)
export.py // 导出下载路由(1 个端点)
migrations/
versions/
001_init.py // 建表迁移(Alembic)
requirements.txt
.env.example
| 任务 | 预计工时 |
|---|---|
| 编辑器组件搭建(Slate.js 集成) | 3 天 |
| 文档 CRUD API(FastAPI + PostgreSQL) | 2 天 |
| 保存对话框 + 导出 .doc API | 2 天 |
| Chat 集成(打开编辑器) | 1 天 |
| 联调测试 | 1 天 |
| 合计 | 约 1.5 周 |
支持工作流上传 Word 模板,工作流根据模板生成文档后,用户可在编辑器中打开、修改,并下载,下载后的 DOCX 文件格式、字体、标题样式与原模板完全一致。
Word 模板管理
工作流生成文档
编辑器打开工作流文档
下载(保持模板格式)
核心技术:
python-docx:解析模板样式、生成最终 DOCXdocxtpl(可选):基于 Jinja2 模板语法填充变量,适合工作流场景上传模板阶段:
上传 .docx → 解析样式表(标题/正文/字体/间距)→ 存储元数据 + 原始文件
生成文档阶段:
工作流触发 → 基于模板填充内容 → 生成文档记录(内容存 DB)
编辑器打开阶段:
加载文档内容 → 查询关联模板样式 → 编辑器 UI 应用对应样式
下载阶段:
读取编辑器最新内容 → 加载原始模板文件 → 用 python-docx 将内容写入模板 → 返回 DOCX 文件流
// 模板表
interface DocumentTemplate {
id: string;
name: string;
fileKey: string; // 模板文件存储路径
styles: { // 从模板解析出的样式元数据
headings: HeadingStyle[];
defaultFont: string;
pageMargins: PageMargins;
hasHeader: boolean;
hasFooter: boolean;
};
createdAt: number;
}
// documents 表新增字段
interface Document {
// ...原有字段
templateId?: string; // 关联模板 ID(工作流文档必填)
source: 'chat' | 'workflow';
}
工作流中上传 Word 模板
↓
后端解析模板样式,存储模板文件和元数据
↓
工作流根据业务数据 + 模板生成文档内容,POST /api/v1/documents(携带 templateId)
↓
用户点击"在编辑器中打开"
↓
编辑器加载文档内容,同时拉取模板样式,渲染时应用标题字体等样式
↓
用户修改内容 → 自动保存
↓
点击"下载" → POST /api/v1/export/docx
↓
后端:读取最新文档内容 + 加载原始模板 → python-docx 合并 → 返回 DOCX 文件流
↓
浏览器下载,文件格式与模板一致
mod-chat/src/
components/
editor/
TemplateStyleProvider.tsx // 加载模板样式并注入 CSS 变量(新增)
EditorToolbar.tsx // 扩展:工具栏样式选项与模板标题级别对应
api/
templateApi.ts // 模板信息查询 API 封装(新增)
backend/app/
models/
document_template.py // document_templates 表 ORM 模型(新增)
schemas/
template.py // TemplateCreate / TemplateResponse(新增)
services/
template_service.py // 模板上传、样式解析、文件存储(新增)
export_service.py // 扩展:新增 export_docx_with_template()
api/
v1/
templates.py // 模板管理路由(新增,2 个端点)
export.py // 扩展:新增 /export/docx 端点
utils/
docx_parser.py // python-docx 样式解析工具函数(新增)
| 任务 | 预计工时 |
|---|---|
| 模板上传 + 样式解析 API | 2 天 |
| 工作流文档生成 API | 1 天 |
| 编辑器应用模板样式(前端) | 2 天 |
| DOCX 下载导出(python-docx 合并) | 2 天 |
| 联调测试 | 1 天 |
| 合计 | 约 2 周 |
将编辑器组件微前端化,供 oil-agent 平台通过免登录 Token 接入,两个平台共用同一份文档数据。
编辑器微前端化
免登录 Token 机制
跨平台数据同步
oil-agent 请求编辑某文档
↓
调用后端 POST /api/v1/edit-sessions,生成一次性 Token
↓
返回编辑器 URL(含 Token)
↓
oil-agent 打开新窗口,编辑器凭 Token 加载文档
↓
用户编辑保存 → 后端 Webhook 通知 oil-agent
↓
oil-agent 拉取最新文档内容
mod-editor/src/ // 新建独立微前端包
components/
editor/
DocumentEditor.tsx // 从 mod-chat 迁移,支持 platform prop
EditorToolbar.tsx
EditorPreview.tsx
SaveDialog.tsx
transport/
EditorTransport.ts // SSE / WebSocket 通信适配层(新增)
store/
editorStore.ts
api/
documentApi.ts
exportApi.ts
editSessionApi.ts // Token 会话 API 封装(新增)
vite.config.ts // Module Federation 打包配置(新增)
backend/app/
core/
cache.py // Redis 连接池(新增)
models/
edit_session.py // edit_sessions 表 ORM 模型(新增)
schemas/
edit_session.py // CreateEditSessionRequest / Response(新增)
services/
token_service.py // JWT 生成/验证/撤销 + Redis 双重校验(新增)
webhook_service.py // 触发 Webhook、签名、重试(新增)
api/
v1/
edit_sessions.py // Token 会话路由(新增,3 个端点)
webhooks.py // Webhook 配置路由(新增)
| 任务 | 预计工时 |
|---|---|
| Module Federation 打包配置 | 1 天 |
| Token 服务(JWT + Redis) | 2 天 |
| 编辑会话 API | 1 天 |
| Webhook 通知 | 1 天 |
| oil-agent 接入联调 | 2 天 |
| 合计 | 约 2 周 |
提升编辑器的编辑体验,并在有需要时支持多人实时协同。
富文本增强
PDF 导出
版本管理
实时协同(可选,按需启用)
阶段 3 完整架构图见 技术架构文档 - 阶段 3,新增依赖:Yjs、y-websocket、S3/MinIO(大文档迁移)、API Gateway。
mod-editor/src/
components/
editor/
CollabCursors.tsx // 协同光标渲染(在线用户实时位置)(新增)
OnlineUserList.tsx // 在线用户列表展示(新增)
toolbar/
FormatToolbar.tsx // 完整富文本工具栏(H1-H6、代码块、表格等)(新增)
FindReplacePanel.tsx // 查找替换面板(新增)
version/
VersionHistoryPanel.tsx // 版本历史列表(新增)
VersionDiffView.tsx // 两版本对比 Diff 视图(新增)
plugins/
withCollaboration.ts // Slate + Yjs 协同插件(新增)
withHistory.ts // 撤销/重做扩展
withImages.ts // 图片上传插入插件(新增)
api/
versionApi.ts // 版本历史 API 封装(新增)
exportApi.ts // 扩展:新增 PDF 导出
backend/app/
models/
document_version.py // document_versions 表 ORM 模型(新增)
schemas/
version.py // VersionResponse / DiffResponse(新增)
services/
version_service.py // 版本快照、回滚、Diff 逻辑(新增)
collab_service.py // y-websocket 会话管理(新增)
export_service.py // 扩展:新增 export_pdf()
api/
v1/
versions.py // 版本管理路由(新增,3 个端点)
export.py // 扩展:新增 /export/pdf 端点
collab.py // WebSocket 协同端点(新增)
| 任务 | 预计工时 |
|---|---|
| 富文本工具栏增强 | 3 天 |
| PDF 导出 | 2 天 |
| 版本管理(DB 设计 + API + 前端) | 3 天 |
| 实时协同(Yjs + WebSocket) | 5 天 |
| 合计 | 约 3-4 周 |
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 文档唯一 ID |
| title | string | 标题 |
| content | text | 文档内容(Markdown / JSON),上限 200KB |
| format | string | markdown(阶段 0/1);阶段 3 支持 json |
| template_id | string | 关联模板 ID,工作流文档必填(阶段 1 引入) |
| source | string | chat / workflow |
| session_id | string | 关联聊天会话 ID(可选) |
| created_by | string | 创建者 |
| created_at | timestamp | |
| updated_at | timestamp |
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 模板唯一 ID |
| name | string | 模板名称 |
| file_key | string | 模板文件存储路径 |
| styles | jsonb | 解析出的样式元数据 |
| created_at | timestamp |
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 会话 ID |
| document_id | string | 文档 ID |
| token_hash | string | Token 哈希(不存明文) |
| platform | string | mod-chat / oil-agent |
| status | string | active / closed |
| expires_at | timestamp |
| 接口 | 方法 | 引入阶段 | 说明 |
|---|---|---|---|
| /api/v1/documents | POST | 阶段 0 | 创建文档 |
| /api/v1/documents/{id} | GET | 阶段 0 | 获取文档 |
| /api/v1/documents/{id} | PUT | 阶段 0 | 更新文档 |
| /api/v1/documents/{id} | DELETE | 阶段 0 | 删除文档 |
| /api/v1/export/doc | POST | 阶段 0 | 导出 .doc 并返回下载链接 |
| /api/v1/templates | POST | 阶段 1 | 上传 Word 模板 |
| /api/v1/templates/{id} | GET | 阶段 1 | 获取模板信息 |
| /api/v1/export/docx | POST | 阶段 1 | 下载 DOCX(保持模板格式) |
| /api/v1/edit-sessions | POST | 阶段 2 | 创建编辑会话,返回 Token |
| /api/v1/edit-sessions/{id}/document | GET | 阶段 2 | 凭 Token 获取文档 |
| /api/v1/edit-sessions/{id} | DELETE | 阶段 2 | 关闭会话 |
| /api/v1/export/pdf | POST | 阶段 3 | 导出 PDF |
| /api/v1/documents/{id}/versions | GET | 阶段 3 | 版本历史 |
以 200KB 为分界线:
| 文档大小 | 自动保存行为 |
|---|---|
| 小文件(< 200KB) | 防抖 3 秒后直接 PUT 到后端,本地不做额外缓存 |
| 大文件(≥ 200KB) | 写入前端 IndexedDB 缓存,不立即推后端;用户手动点"保存"时才同步到后端 |
| 存储类型 | 用途 | 说明 |
|---|---|---|
| Cookies | 小型临时状态 | 存储编辑器 UI 状态(当前 documentId、预览模式开关等),TTL 与会话一致 |
| localStorage | 小文件草稿块缓存 | 按标题块存储变更内容,key 格式见 9.3,浏览器关闭后仍保留 |
| IndexedDB | 大文件内容缓存 | 存储 ≥ 200KB 的文档内容,异步读写,不阻塞主线程 |
不管文档大小,每次编辑都按标题等级定位到具体块做局部更新,而不是全量覆盖整个文档。
块的定义:以标题节点(H1/H2/H3...)为分割点,每个标题及其下属正文构成一个块,用"第 N 个 K 级标题"来唯一定位。
文档结构示例:
# 第一章 ← H1[0]
## 1.1 背景 ← H2[0]
## 1.2 目标 ← H2[1]
# 第二章 ← H1[1]
## 2.1 方案 ← H2[2]
用户只修改了"1.2 目标"下的正文
↓
定位到块:{ level: 2, index: 1 } 即"第 2 个 H2 标题"
↓
只更新该块,其余块不变
本地存储 key 格式:
localStorage key: doc:{documentId}:h{level}:{index}
示例: doc:doc-abc123:h2:1
后端局部更新请求(见 API 文档 2.3):
{
"blocks": [
{
"level": 2,
"index": 1,
"content": "更新后的内容..."
}
]
}
用户编辑内容
↓
计算变动块(level + index)
↓
写入 localStorage(小文件)或 IndexedDB(大文件)
↓
判断文档大小
├── 小文件(< 200KB)
│ 防抖 3 秒 → PUT /api/v1/documents/{id}(携带变动块)
│ 后端更新成功 → 清除对应本地缓存块
│ 后端更新失败 → 保留缓存,下次打开时提示"有未同步的草稿"
│
└── 大文件(≥ 200KB)
只写 IndexedDB,不自动推后端
用户手动点"保存" → 全量或按块同步到后端
打开编辑器,传入 documentId
↓
检查 localStorage / IndexedDB 是否有本地缓存块
├── 有缓存 → 对比本地 updatedAt 与服务端 updatedAt
│ ├── 本地更新 → 提示"发现未同步的草稿,是否恢复?"
│ │ 用户确认 → 合并本地块覆盖服务端内容
│ │ 用户拒绝 → 丢弃本地缓存,加载服务端内容
│ └── 服务端更新 → 直接加载服务端内容,清除本地缓存
└── 无缓存 → 直接从服务端加载文档内容
| 风险 | 影响 | 应对措施 |
|---|---|---|
| Word 模板样式复杂,解析不完整 | 下载格式与模板不一致 | 阶段 1 先支持标题/正文/字体基础样式,复杂样式(表格边框、页眉图片等)逐步覆盖 |
| 编辑器富文本节点与 DOCX 样式映射困难 | 导出排版错乱 | 建立明确的节点类型 ↔ DOCX 样式映射表,作为开发规范 |
| 大文档(> 200KB)阶段 0 无法存储 | 工作流生成超长文档时失败 | 阶段 0 强制限制,阶段 1 引入文件存储后解除 |
| oil-agent Token 安全 | 数据泄露 | JWT + Redis 双重验证,Token 与文档 ID 绑定,默认 1 小时过期 |
| 协同编辑并发冲突 | 数据不一致 | 引入成熟 CRDT 库(Yjs),阶段 3 单独评估 |
文档版本: v4.0 最后更新: 2026-06-11 维护者: Axonix 前端团队