# 合同起草功能实施清单
## 阶段一:数据库设计(1天)
### 1.1 创建数据库表
- [ ] 创建 `drafted_contracts` 表
```sql
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' CHECK (status IN ('draft', 'completed', 'archived')),
created_by INTEGER REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_drafted_contracts_template_id ON drafted_contracts(template_id);
CREATE INDEX idx_drafted_contracts_created_by ON drafted_contracts(created_by);
CREATE INDEX idx_drafted_contracts_status ON drafted_contracts(status);
```
### 1.2 扩展模板表
- [ ] 为 `contract_templates` 表添加占位符配置字段
```sql
ALTER TABLE contract_templates
ADD COLUMN placeholder_schema JSONB;
-- 示例数据
UPDATE contract_templates
SET placeholder_schema = '{
"fields": [
{"key": "甲方名称", "label": "甲方名称", "type": "text", "required": true, "group": "甲方信息"},
{"key": "甲方地址", "label": "甲方地址", "type": "text", "required": true, "group": "甲方信息"},
{"key": "甲方法定代表人", "label": "法定代表人", "type": "text", "required": true, "group": "甲方信息"},
{"key": "甲方联系电话", "label": "联系电话", "type": "tel", "required": true, "group": "甲方信息"},
{"key": "乙方名称", "label": "乙方名称", "type": "text", "required": true, "group": "乙方信息"},
{"key": "乙方地址", "label": "乙方地址", "type": "text", "required": true, "group": "乙方信息"},
{"key": "乙方法定代表人", "label": "法定代表人", "type": "text", "required": true, "group": "乙方信息"},
{"key": "乙方联系电话", "label": "联系电话", "type": "tel", "required": true, "group": "乙方信息"},
{"key": "合同金额", "label": "合同金额(元)", "type": "number", "required": true, "group": "合同条款"},
{"key": "签订日期", "label": "签订日期", "type": "date", "required": true, "group": "合同条款"},
{"key": "合同编号", "label": "合同编号", "type": "text", "required": false, "group": "基本信息"}
]
}'::jsonb
WHERE id = 1;
```
---
## 阶段二:后端 API 开发(2-3天)
### 2.1 文件复制服务
- [ ] 创建 `app/api/contracts/draft-service.server.ts`
- [ ] 实现 `copyTemplateFile` 函数
- 从 MinIO 下载模板文件
- 重命名文件(添加时间戳、用户ID)
- 上传到草稿目录
- 返回新文件路径
### 2.2 创建起草合同 API
- [ ] 创建路由 `app/routes/api.contracts.draft.tsx`
- [ ] POST action:创建起草记录
```typescript
export async function action({ request }: ActionFunctionArgs) {
const { templateId, title } = await request.json();
// 1. 获取模板信息
// 2. 复制模板文件
// 3. 创建 drafted_contracts 记录
// 4. 返回新记录 ID 和文件路径
}
```
### 2.3 保存占位符值 API
- [ ] 创建路由 `app/routes/api.contracts.draft.$id.placeholders.tsx`
- [ ] PUT action:更新占位符值
```typescript
export async function action({ request, params }: ActionFunctionArgs) {
const { placeholders } = await request.json();
// 更新 drafted_contracts 表的 placeholder_values 字段
}
```
### 2.4 完成起草 API
- [ ] 创建路由 `app/routes/api.contracts.draft.$id.complete.tsx`
- [ ] POST action:标记为已完成
```typescript
export async function action({ request, params }: ActionFunctionArgs) {
// 1. 检查是否还有未替换的占位符
// 2. 更新状态为 'completed'
// 3. 可选:移动文件到正式合同目录
}
```
### 2.5 查询草稿列表 API
- [ ] 扩展 `app/routes/api.contracts.drafts.tsx`
- [ ] GET loader:查询当前用户的草稿列表
- [ ] 支持分页和筛选
---
## 阶段三:前端组件开发(3-4天)
### 3.1 模板详情页增加按钮
- [ ] 修改 `app/routes/contract-template.detail.$id.tsx`
- [ ] 在操作按钮区域添加"起草合同"按钮
```tsx
```
- [ ] 实现 `handleStartDraft` 函数
- 调用创建草稿 API
- 导航到起草页面
### 3.2 创建起草页面路由
- [ ] 创建 `app/routes/contract-draft.$draftId.tsx`
- [ ] Loader:加载草稿信息和模板信息
```typescript
export async function loader({ params, request }: LoaderFunctionArgs) {
const draftId = params.draftId!;
// 1. 获取草稿记录
// 2. 获取模板信息(占位符配置)
// 3. 返回数据
return { draft, template };
}
```
### 3.3 创建占位符表单组件
- [ ] 创建 `app/components/contracts/PlaceholderForm.tsx`
- [ ] Props 定义
```typescript
interface PlaceholderFormProps {
schema: PlaceholderSchema;
values: Record;
onChange: (values: Record) => void;
onBatchReplace: () => void;
onSaveDraft: () => void;
onComplete: () => void;
isReplacing: boolean;
isSaving: boolean;
}
```
- [ ] 渲染表单字段
- 根据 `schema.fields` 动态生成表单
- 按 `group` 分组显示
- 支持不同字段类型(text, number, date, tel)
- [ ] 表单验证
- 必填字段验证
- 数据格式验证
- [ ] 操作按钮
- "一键替换"按钮
- "保存草稿"按钮
- "完成起草"按钮
### 3.4 创建起草页面布局
- [ ] 实现 `app/routes/contract-draft.$draftId.tsx` 页面组件
- [ ] 布局结构
```tsx
{/* 左侧:文档预览(60%) */}
{/* 右侧:占位符表单(40%) */}
```
### 3.5 实现替换逻辑
- [ ] 在起草页面实现 `handleBatchReplace` 函数
```typescript
const handleBatchReplace = async () => {
setIsReplacing(true);
try {
// 获取 CollaboraViewer 引用
const collaboraRef = filePreviewRef.current?.collaboraViewerRef;
if (!collaboraRef?.isReady) {
toastService.warning('文档尚未加载完成');
return;
}
// 批量替换所有占位符
for (const [key, value] of Object.entries(placeholderValues)) {
if (value) { // 只替换有值的字段
const placeholder = `{{${key}}}`;
await collaboraRef.unoCommands.replaceAll(placeholder, value);
}
}
toastService.success('占位符替换完成');
} catch (error) {
console.error('替换失败:', error);
toastService.error('替换失败');
} finally {
setIsReplacing(false);
}
};
```
- [ ] 实现 `handleSaveDraft` 函数
```typescript
const handleSaveDraft = async () => {
setIsSaving(true);
try {
await fetch(`/api/contracts/draft/${draftId}/placeholders`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ placeholders: placeholderValues })
});
toastService.success('草稿已保存');
} catch (error) {
toastService.error('保存失败');
} finally {
setIsSaving(false);
}
};
```
- [ ] 实现 `handleComplete` 函数
```typescript
const handleComplete = async () => {
// 1. 检查必填字段
const missingFields = schema.fields
.filter(f => f.required && !placeholderValues[f.key])
.map(f => f.label);
if (missingFields.length > 0) {
toastService.error(`请填写必填字段:${missingFields.join('、')}`);
return;
}
// 2. 检查是否还有未替换的占位符
// (可选:调用 API 检查文档内容)
// 3. 标记为完成
await fetch(`/api/contracts/draft/${draftId}/complete`, {
method: 'POST'
});
toastService.success('起草完成');
navigate('/contracts/drafts'); // 跳转到草稿列表
};
```
### 3.6 FilePreview 组件增强
- [ ] 修改 `app/components/reviews/FilePreview.tsx`
- [ ] 导出 `collaboraViewerRef` 引用
```typescript
export interface FilePreviewHandle {
collaboraViewerRef: RefObject;
}
export const FilePreview = forwardRef(
function FilePreview(props, ref) {
const collaboraViewerRef = useRef(null);
useImperativeHandle(ref, () => ({
collaboraViewerRef
}));
// ...
}
);
```
---
## 阶段四:CollaboraViewer 替换功能完善(1-2天)
### 4.1 检查现有替换功能
- [ ] 查看 `app/components/collabora/lib/` 中的替换相关方法
- `unoReplaceAll` - 全局替换
- `unoReplaceCurrent` - 替换当前匹配
- `replaceTextInPage` - 页面定点替换
### 4.2 增强 replaceAll 方法
- [ ] 修改 `app/components/collabora/hooks.ts`
- [ ] 确保 `replaceAll` 方法可用
```typescript
const replaceAll = useCallback(async (searchText: string, replaceText: string) => {
if (!iframeRef.current?.contentWindow) {
throw new Error('iframe 不可用');
}
await unoReplaceAll(iframeRef.current.contentWindow, searchText, replaceText);
}, [iframeRef]);
return useMemo(
() => ({
scrollToTop,
replaceAll // 确保导出
}),
[scrollToTop, replaceAll]
);
```
### 4.3 添加批量替换方法
- [ ] 在 `CollaboraViewer.tsx` 中添加批量替换方法
```typescript
useImperativeHandle(ref, () => ({
isReady: documentReady,
unoCommands: {
scrollToTop,
replaceAll
},
// 新增批量替换方法
batchReplace: async (replacements: Array<{ search: string; replace: string }>) => {
for (const { search, replace } of replacements) {
await replaceAll(search, replace);
// 添加延迟避免 Collabora 响应不过来
await new Promise(resolve => setTimeout(resolve, 100));
}
},
clearAllHighlights: async () => {
await clearHighlights(iframeWindowRef.current);
}
}));
```
---
## 阶段五:样式和体验优化(1-2天)
### 5.1 占位符表单样式
- [ ] 创建 `app/styles/components/placeholder-form.css`
- 表单分组样式
- 字段输入框样式
- 按钮样式
- 响应式布局
### 5.2 起草页面样式
- [ ] 创建 `app/styles/pages/contract-draft.css`
- 左右分栏布局
- 固定高度和滚动
- 响应式设计
### 5.3 交互优化
- [ ] 添加加载状态指示
- 文档加载中
- 替换进行中
- 保存中
- [ ] 添加进度提示
- "正在替换占位符... (3/10)"
- 替换完成提示
- [ ] 添加确认对话框
- 完成起草前确认
- 离开页面前提示未保存
### 5.4 错误处理
- [ ] 网络错误提示
- [ ] 文件加载失败处理
- [ ] 替换失败回滚
---
## 阶段六:草稿管理功能(1-2天)
### 6.1 草稿列表页面
- [ ] 创建 `app/routes/contracts.drafts.tsx`
- [ ] 显示用户的所有草稿
- [ ] 支持筛选(模板、状态、日期)
- [ ] 支持搜索
- [ ] 操作按钮
- 继续编辑
- 删除草稿
- 查看详情
### 6.2 草稿卡片组件
- [ ] 创建 `app/components/contracts/DraftCard.tsx`
- 显示草稿信息
- 标题
- 基于的模板
- 创建时间
- 状态
- 操作按钮
---
## 阶段七:测试和优化(2-3天)
### 7.1 功能测试
- [ ] 创建草稿流程测试
- [ ] 占位符替换测试
- 单个替换
- 批量替换
- 部分替换
- 重复替换
- [ ] 保存草稿测试
- [ ] 完成起草测试
- [ ] 边界情况测试
- 占位符不存在
- 替换值为空
- 特殊字符处理
### 7.2 性能测试
- [ ] 大文档替换性能
- [ ] 多占位符替换时间
- [ ] 文件复制速度
### 7.3 用户体验测试
- [ ] 操作流畅性
- [ ] 错误提示清晰度
- [ ] 响应式布局适配
---
## 阶段八:文档和部署(1天)
### 8.1 开发文档
- [ ] 编写功能使用说明
- [ ] 编写 API 文档
- [ ] 编写占位符配置说明
### 8.2 用户手册
- [ ] 如何创建模板(添加占位符)
- [ ] 如何起草合同
- [ ] 常见问题解答
### 8.3 部署准备
- [ ] 数据库迁移脚本
- [ ] 环境变量配置
- [ ] 测试环境部署
- [ ] 生产环境部署
---
## 预计总工期
- **阶段一**:数据库设计 - 1天
- **阶段二**:后端 API 开发 - 2-3天
- **阶段三**:前端组件开发 - 3-4天
- **阶段四**:CollaboraViewer 完善 - 1-2天
- **阶段五**:样式优化 - 1-2天
- **阶段六**:草稿管理 - 1-2天
- **阶段七**:测试优化 - 2-3天
- **阶段八**:文档部署 - 1天
**总计:12-18天**
---
## 依赖关系
```
阶段一(数据库) → 阶段二(后端 API) → 阶段三(前端开发)
↓
阶段四(Collabora 完善)
↓
阶段五(样式优化) + 阶段六(草稿管理)
↓
阶段七(测试) → 阶段八(部署)
```
---
## 关键里程碑
1. **里程碑1**:数据库和 API 完成(第3天)
2. **里程碑2**:基本起草功能完成(第7天)
3. **里程碑3**:完整功能开发完成(第12天)
4. **里程碑4**:测试和部署完成(第18天)
---
## 风险评估
| 风险 | 等级 | 应对措施 |
|------|------|----------|
| CollaboraViewer 替换性能不足 | 中 | 提前测试,准备降级方案(使用 docxtemplater) |
| 文件复制失败 | 低 | 添加重试机制,记录详细日志 |
| 占位符格式不统一 | 中 | 制定严格的占位符规范,提供模板检查工具 |
| 用户数据丢失 | 中 | 实现自动保存,添加草稿历史记录 |
---
## 可选增强功能(后续迭代)
- [ ] 模板变量智能推荐
- [ ] 历史数据自动填充
- [ ] 合同模板市场
- [ ] 审批流程集成
- [ ] 电子签名集成
- [ ] 导出为 PDF
- [ ] 版本控制和对比