8.2 KiB
8.2 KiB
合同起草功能实施方案
功能概述
基于合同模板创建新合同,通过占位符替换和在线编辑实现合同起草。
方案对比
方案A:docxtemplater + 服务端生成
技术栈:
docxtemplater- DOCX 模板引擎- 服务端处理(Node.js)
- 生成新文档后上传到 MinIO
工作流程:
- 模板中预先定义占位符:
{甲方名称}、{合同金额} - 用户填写表单
- 服务端使用 docxtemplater 替换占位符
- 生成新 DOCX 文件
- 上传到 MinIO
- 前端加载新文件用 Collabora 展示和编辑
优点:
- ✅ 模板语法强大(支持循环、条件、表格等)
- ✅ 替换精确可控
- ✅ 可以在服务端验证数据
- ✅ 支持复杂的模板逻辑
缺点:
- ❌ 需要安装新依赖
- ❌ 需要开发服务端 API
- ❌ 模板需要预先制作占位符
- ❌ 生成→上传→加载流程较长
- ❌ 无法实时预览替换效果
方案B:CollaboraViewer 实时替换(推荐)
技术栈:
- 现有的
CollaboraViewer组件 replaceTextInPage/unoReplaceAll功能- Collabora Online 编辑模式
工作流程:
- 模板中约定占位符格式:
{{甲方名称}}、{{合同金额}} - 用户点击"起草合同",复制模板文件
- 在 Collabora 中以编辑模式打开复制的文件
- 用户填写右侧表单
- 实时调用
replaceTextInPage或unoReplaceAll替换占位符 - 用户可以继续手动编辑文档
- 保存生成的新合同
优点:
- ✅ 无需新依赖,复用现有代码
- ✅ 实时预览替换效果
- ✅ 用户可以继续手动编辑
- ✅ 开发速度快
- ✅ 所见即所得
缺点:
- ❌ 依赖 Collabora Online 服务
- ❌ 替换逻辑相对简单(基于查找替换)
- ❌ 需要处理异步替换
方案C:混合方案
技术栈:
- 简单模板:使用 CollaboraViewer 实时替换
- 复杂模板:使用 docxtemplater 服务端生成
工作流程:
- 模板元数据标记是否为"复杂模板"
- 简单模板走方案B流程
- 复杂模板走方案A流程
优点:
- ✅ 灵活性最高
- ✅ 简单场景快速响应
- ✅ 复杂场景功能完整
缺点:
- ❌ 开发成本高
- ❌ 维护两套逻辑
推荐方案:方案B(CollaboraViewer 实时替换)
理由
- 快速落地:复用现有代码,开发周期短
- 用户体验好:所见即所得,实时预览
- 灵活性高:替换后用户可以继续编辑
- 维护成本低:不引入新依赖
技术细节
占位符约定
使用双花括号格式,便于识别和替换:
{{甲方名称}}
{{甲方地址}}
{{甲方法定代表人}}
{{甲方联系电话}}
{{乙方名称}}
{{乙方地址}}
{{乙方法定代表人}}
{{乙方联系电话}}
{{合同金额}}
{{签订日期}}
{{合同编号}}
替换实现
使用 CollaboraViewer 的 unoReplaceAll 方法:
// 批量替换所有占位符
async function replacePlaceholders(
collaboraRef: CollaboraViewerHandle,
values: Record<string, string>
) {
for (const [key, value] of Object.entries(values)) {
const placeholder = `{{${key}}}`;
await collaboraRef.unoCommands.replaceAll(placeholder, value);
}
}
文件复制策略
选项1:服务端复制(推荐)
- API:
POST /api/contracts/draft - 请求参数:
{ templateId: string } - 返回:
{ newFileId: string, newFilePath: string } - 服务端将模板文件复制为新文件
选项2:客户端复制
- 下载模板文件 Blob
- 重命名后重新上传
- 成本高,不推荐
数据库设计
新增表:drafted_contracts (起草的合同)
CREATE TABLE drafted_contracts (
id SERIAL PRIMARY KEY,
template_id INTEGER REFERENCES contract_templates(id),
file_path TEXT NOT NULL, -- 起草后的文件路径
title TEXT NOT NULL, -- 合同标题
placeholder_values JSONB, -- 占位符填充值
status TEXT DEFAULT 'draft', -- draft | completed | archived
created_by INTEGER REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
API 设计
1. 创建起草合同
POST /api/contracts/draft
请求体:
{
"templateId": 123,
"title": "智慧法务平台采购合同-草稿"
}
响应:
{
"id": 456,
"filePath": "drafts/contract_456_20250104.docx",
"title": "智慧法务平台采购合同-草稿",
"templateId": 123
}
2. 保存占位符值
PUT /api/contracts/draft/:id/placeholders
请求体:
{
"placeholders": {
"甲方名称": "广东省烟草专卖局",
"合同金额": "500000"
}
}
3. 完成起草
POST /api/contracts/draft/:id/complete
将草稿标记为已完成,可选地移动到正式合同目录。
UI/UX 设计
页面结构
/contract-template/draft/:templateId
├── 左侧(60%):FilePreview(Collabora 编辑模式)
└── 右侧(40%):占位符表单
├── 基本信息
│ ├── 合同标题
│ └── 合同编号
├── 甲方信息
│ ├── 甲方名称
│ ├── 甲方地址
│ ├── 法定代表人
│ └── 联系电话
├── 乙方信息
│ ├── 乙方名称
│ ├── 乙方地址
│ ├── 法定代表人
│ └── 联系电话
├── 合同条款
│ ├── 合同金额
│ ├── 签订日期
│ └── 其他自定义字段
└── 操作按钮
├── 一键替换
├── 保存草稿
└── 完成起草
交互流程
- 用户在模板详情页点击"起草合同"
- 系统复制模板文件,创建草稿记录
- 跳转到起草页面
- 左侧加载文档(Collabora 编辑模式)
- 右侧显示占位符表单(从模板中提取或预定义)
- 用户填写表单
- 点击"一键替换"批量替换所有占位符
- 用户可以继续手动编辑文档
- 点击"保存草稿"保存当前状态
- 点击"完成起草"将合同标记为完成
占位符提取方案
方案1:预定义字段(推荐)
在模板表中增加字段:
ALTER TABLE contract_templates
ADD COLUMN placeholder_schema JSONB;
示例数据:
{
"fields": [
{
"key": "甲方名称",
"label": "甲方名称",
"type": "text",
"required": true,
"group": "甲方信息"
},
{
"key": "合同金额",
"label": "合同金额(元)",
"type": "number",
"required": true,
"group": "合同条款"
},
{
"key": "签订日期",
"label": "签订日期",
"type": "date",
"required": true,
"group": "合同条款"
}
]
}
方案2:动态提取
从 DOCX 文件中提取所有 {{xxx}} 占位符:
- 需要解析 DOCX 文件
- 服务端实现
- 成本较高
技术风险与应对
风险1:Collabora 替换性能
风险:多个占位符替换可能耗时较长
应对:
- 使用
unoReplaceAll而不是逐个替换 - 显示替换进度提示
- 考虑批量替换 API
风险2:并发编辑冲突
风险:用户同时编辑和替换可能冲突
应对:
- 替换前提示用户
- 替换时禁用编辑
- 使用防抖避免频繁替换
风险3:占位符未完全替换
风险:用户忘记填写某些字段
应对:
- 完成前检查是否还有
{{}}占位符 - 高亮显示未替换的占位符
- 必填字段验证
扩展功能(可选)
- 模板变量管理:管理员可以配置模板的占位符字段
- 历史记录:记录每次替换的历史
- 模板版本:支持模板版本管理
- 审批流程:起草完成后进入审批流程
- 电子签名:集成电子签名功能
总结
推荐使用 方案B(CollaboraViewer 实时替换),理由:
- 开发成本低,复用现有代码
- 用户体验好,所见即所得
- 灵活性高,支持后续手动编辑
- 维护成本低,不引入新依赖
如果未来需要支持复杂模板逻辑(循环、条件等),可以再引入 docxtemplater 作为补充。