Files
leaudit-platform-frontend/docs/docxtemplater-placeholder-extraction.md
T
2025-12-05 00:09:32 +08:00

407 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 从 DOCX 文件自动提取占位符
## 功能说明
系统会自动从 docx 文档中提取 `{{占位符}}` 格式的变量,并生成表单字段,无需在数据库中手动配置。
---
## 实现原理
### 技术栈
- **pizzip** - 用于解压 docx 文件(docx 本质是 zip 格式)
- **正则表达式** - 提取占位符(避免 docxtemplater 编译时的格式化标签分割问题)
### 工作流程
```
1. 用户点击"起草合同"
2. Loader 读取 docx 文件
3. PizZip 解压文件,读取 word/document.xml
4. 移除 XML 标签,提取纯文本
5. 正则匹配所有 {{...}} 占位符
6. 根据占位符名称自动推测:
- 字段类型(text/number/date/textarea
- 字段分组(甲方信息/乙方信息/合同条款)
- 是否必填
7. 生成 PlaceholderSchema
8. 渲染表单
```
---
## 核心代码
### docx-parser.server.ts
```typescript
import PizZip from 'pizzip';
import fs from 'fs';
/**
* 从 docx 文件中提取占位符
*/
export async function extractPlaceholdersFromDocx(
filePath: string
): Promise<string[]> {
// 1. 读取文件
const content = fs.readFileSync(filePath, 'binary');
// 2. 使用 PizZip 解压
const zip = new PizZip(content);
// 3. 读取 document.xml 文件(不使用 docxtemplater,避免格式化文本的标签分割问题)
const documentXml = zip.file('word/document.xml');
if (!documentXml) {
throw new Error('无法找到 word/document.xml 文件');
}
// 4. 获取 XML 文本内容
const xmlContent = documentXml.asText();
// 5. 移除所有 XML 标签,只保留纯文本
const fullText = xmlContent.replace(/<[^>]+>/g, '');
// 6. 使用正则表达式提取所有 {{...}} 占位符
const placeholderRegex = /\{\{([^}]+)\}\}/g;
const matches = fullText.matchAll(placeholderRegex);
// 7. 去重并返回
const placeholders = new Set<string>();
for (const match of matches) {
placeholders.add(match[1].trim());
}
return Array.from(placeholders);
}
/**
* 生成默认的 PlaceholderSchema
*/
export function generateDefaultSchema(
placeholders: string[]
): PlaceholderSchema {
return {
fields: placeholders.map(placeholder => ({
key: placeholder,
label: placeholder,
type: inferType(placeholder), // 推测类型
required: inferRequired(placeholder), // 推测是否必填
group: inferGroup(placeholder) // 推测分组
}))
};
}
```
### contract-draft.$draftId.tsx (loader)
```typescript
export async function loader({ params, request }: LoaderFunctionArgs) {
// ... 获取用户信息和草稿信息 ...
// 从 docx 文件中提取占位符
const testDocPath = path.join(process.cwd(), 'public', 'testWork', '买卖合同 (1).docx');
// 提取占位符
const placeholders = await extractPlaceholdersFromDocx(testDocPath);
console.log('提取到的占位符:', placeholders);
// 生成默认 schema
const placeholderSchema = generateDefaultSchema(placeholders);
return json({
draft,
template: {
...template,
placeholder_schema: placeholderSchema
}
});
}
```
---
## 自动推测规则
### 字段类型推测
| 占位符名称包含 | 推测类型 | 示例 |
|--------------|---------|------|
| 金额、价格、数量、amount、price | `number` | {{合同金额}}、{{price}} |
| 日期、时间、date、time | `date` | {{签订日期}}、{{date}} |
| 地址、说明、备注、address、description | `textarea` | {{甲方地址}}、{{remark}} |
| 其他 | `text` | {{甲方名称}}、{{项目名称}} |
### 字段分组推测
| 占位符名称包含 | 推测分组 | 示例 |
|--------------|---------|------|
| 甲方、partyA | 甲方信息 | {{甲方名称}}、{{partyA_address}} |
| 乙方、partyB | 乙方信息 | {{乙方代表}}、{{partyB_phone}} |
| 金额、价格、数量 | 合同条款 | {{合同金额}}、{{数量}} |
| 日期、时间 | 日期信息 | {{签订日期}}、{{生效日期}} |
| 其他 | 基本信息 | {{项目名称}}、{{编号}} |
### 必填字段推测
| 占位符名称包含 | 是否必填 |
|--------------|---------|
| "可选"、"optional" | ❌ 否 |
| 其他 | ✅ 是 |
---
## 使用示例
### 1. 准备模板文档
在 Word 文档中添加占位符:
```
买卖合同
甲方名称:{{甲方名称}}
甲方地址:{{甲方地址}}
甲方代表:{{甲方代表}}
乙方名称:{{乙方名称}}
乙方地址:{{乙方地址}}
乙方代表:{{乙方代表}}
合同金额:{{合同金额}} 元
签订日期:{{签订日期}}
备注:{{备注说明(可选)}}
```
### 2. 系统自动提取
提取结果:
```json
[
"甲方名称",
"甲方地址",
"甲方代表",
"乙方名称",
"乙方地址",
"乙方代表",
"合同金额",
"签订日期",
"备注说明(可选)"
]
```
### 3. 自动生成表单配置
```json
{
"fields": [
{
"key": "甲方名称",
"label": "甲方名称",
"type": "text",
"required": true,
"group": "甲方信息"
},
{
"key": "甲方地址",
"label": "甲方地址",
"type": "textarea",
"required": true,
"group": "甲方信息"
},
{
"key": "合同金额",
"label": "合同金额",
"type": "number",
"required": true,
"group": "合同条款"
},
{
"key": "签订日期",
"label": "签订日期",
"type": "date",
"required": true,
"group": "日期信息"
},
{
"key": "备注说明(可选)",
"label": "备注说明(可选)",
"type": "textarea",
"required": false,
"group": "基本信息"
}
]
}
```
### 4. 渲染表单
表单自动分组显示:
```
【甲方信息】
甲方名称: [_________] *
甲方地址: [_________] *
甲方代表: [_________] *
【乙方信息】
乙方名称: [_________] *
乙方地址: [_________] *
乙方代表: [_________] *
【合同条款】
合同金额: [_________] 元 *
【日期信息】
签订日期: [日期选择器] *
【基本信息】
备注说明(可选): [_________]
```
---
## 测试验证
### 当前测试文档
文件路径:`public/testWork/买卖合同 (1).docx`
### 测试步骤
1. **启动开发服务器**
```bash
npm run dev
```
2. **创建测试草稿**
- 访问模板详情页
- 点击"起草合同"
- 输入标题
3. **查看控制台输出**
```
[DOCX Parser] 开始读取文件: D:\remix_project\docreview\public\testWork\买卖合同 (1).docx
[DOCX Parser] 文档文本长度: 1234
[DOCX Parser] 提取到的占位符: ["甲方名称", "乙方名称", "合同金额", ...]
[Loader] 生成的 schema: {...}
```
4. **验证表单渲染**
- 检查右侧表单是否正确渲染
- 检查字段分组是否合理
- 检查字段类型是否正确
---
## 优势总结
### 1. 无需手动配置
- ✅ 自动提取占位符
- ✅ 自动推测字段类型
- ✅ 自动分组
- ✅ 节省配置时间
### 2. 灵活性高
- ✅ 修改文档即可,无需改代码
- ✅ 支持任意数量的占位符
- ✅ 支持任意命名规则
### 3. 用户友好
- ✅ 表单自动生成
- ✅ 智能分组展示
- ✅ 类型自动匹配
### 4. 维护简单
- ✅ 模板即配置
- ✅ 无需同步数据库
- ✅ 减少维护成本
---
## 扩展功能
### 1. 支持更复杂的占位符
```
{{甲方名称:text:required:甲方信息}}
{{合同金额:number:required:合同条款}}
{{备注:textarea:optional:基本信息}}
```
可以解析占位符中的元数据:
```typescript
const [key, type, required, group] = placeholder.split(':');
```
### 2. 支持默认值
```
{{甲方名称|默认公司}}
{{合同金额|100000}}
```
### 3. 支持枚举选项
```
{{付款方式|现金,转账,支票}}
```
渲染为下拉选择框。
### 4. 支持条件显示
```
{{#if 需要担保人}}
担保人姓名:{{担保人姓名}}
{{/if}}
```
---
## 注意事项
### 1. 占位符命名规范
- ✅ 使用有意义的名称
- ✅ 避免特殊字符
- ✅ 保持一致的命名风格
### 2. 性能考虑
- ✅ 提取占位符很快(毫秒级)
- ✅ 结果可以缓存
- ⚠️ 大文件(>10MB)可能较慢
### 3. 错误处理
- ✅ 文件不存在 → 使用空 schema
- ✅ 解析失败 → 回退到数据库配置
- ✅ 无占位符 → 显示提示信息
---
## 总结
使用 docxtemplater 自动提取占位符是一个优雅的解决方案:
1.**零配置** - 模板即配置
2.**自动化** - 智能推测字段属性
3.**灵活** - 修改文档即可
4.**可靠** - 专业工具,提取准确
功能已实现,可以开始测试!🎉