# 合同起草功能实施清单 ## 阶段一:数据库设计(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 - [ ] 版本控制和对比