hookehuyr

feat(plan): 合并选项控制功能到 develop

- 完善计划书字段选项控制功能
- 优化表单交互体验
- 提取字段配置模板
Showing 44 changed files with 2402 additions and 4669 deletions
......@@ -24,3 +24,7 @@ CLAUDE.md
*.pptx
.swc/
# Document parsing generated files
docs/parse-audit/
docs/parsed-backup/
......
# 产品配置审核 - 计划书模版2.docx
**解析时间**: 2026/2/15 10:20:30
**原始文件**: 计划书模版2.docx
**数据来源**: docs/to-parse/计划书模版2.docx
---
## 📋 产品基本信息
| 字段 | 提取值 | 需要确认 |
|------|--------|---------|
| 产品名称 | 宏摯傳承保障計劃 - 性別, 年齡, 出生年月日 | ✅ 请核对产品名称 |
| 产品类型 | savings | ✅ 请确认产品类型 |
| 币种 | USD | ✅ 请确认币种 |
| form_sn | `savings-product-ef3dd50b` | ✅ 请确认 form_sn 唯一性 |
| 缴费年期 | ["整付","3年","5年","10年","15年"] | ✅ 请确认缴费年期选项 |
| 年龄范围 | 0-75岁 | ✅ 请确认年龄范围 |
| 保险期间 | 终身 | ✅ 请确认保险期间 |
### 💰 储蓄类产品特有字段
| 字段 | 提取值 | 需要确认 |
|------|--------|---------|
| 提取方式 | ["最高固定提取金额"] | ✅ 请确认提取方式 |
| 提取期 | ["1年","3年","5年","10年"] | ✅ 请确认提取期选项 |
---
## 🤖 智能字段提取报告
### 匹配统计
- ✅ 成功匹配: 4 字段
- ⚠️ 使用默认值: 3 字段
- ❌ 未匹配(需人工补充): 0 字段
### ✅ 已成功匹配的字段
- product_name
- product_type
- payment_periods
- withdrawal_modes
### ⚠️ 使用默认值的字段
- **currency**: 未找到字段 "currency",使用默认值: "USD"
- **age_range**: 未找到字段 "age_range",使用默认值: {"min":0,"max":75}
- **insurance_period**: 未找到字段 "insurance_period",使用默认值: "终身"
---
## 🧾 配置预览
```javascript
{
"product_name": "宏摯傳承保障計劃 - 性別, 年齡, 出生年月日",
"product_type": "savings",
"currency": "USD",
"form_sn": "savings-product-ef3dd50b",
"payment_periods": [
"整付",
"3年",
"5年",
"10年",
"15年"
],
"age_range": {
"min": 0,
"max": 75
},
"insurance_period": "终身",
"is_savings": true,
"withdrawal_modes": [
"最高固定提取金额"
],
"withdrawal_periods": [
"1年",
"3年",
"5年",
"10年"
]
}
```
---
## 📝 表单字段 (form_schema)
```javascript
{
"base_fields": [],
"withdrawal_fields": [],
"reset_map": {}
}
```
---
## 🔄 提交字段映射 (submit_mapping)
```javascript
{}
```
---
## 🧩 生成配置片段
```javascript
/**
* 宏摯傳承保障計劃 - 性別, 年齡, 出生年月日
* @added 2026-02-15T02:20:30.982Z
* @source docs/to-parse/计划书模版2.docx
*/
'savings-product-ef3dd50b': {
name: '宏摯傳承保障計劃 - 性別, 年齡, 出生年月日',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: ["整付","3年","5年","10年","15年"],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ["最高固定提取金额"],
withdrawal_periods: ["1年","3年","5年","10年"]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
}
```
---
## ✅ 审核检查清单
### 基础信息
- [ ] 产品名称正确
- [ ] 产品类型正确(savings/critical-illness/life-insurance)
- [ ] 币种正确(USD/CNY/HKD/EUR)
- [ ] form_sn 唯一且符合命名规范
### 缴费与年龄
- [ ] 缴费年期选项完整且正确
- [ ] 年龄范围合理
- [ ] 保险期间正确
### 储蓄类特有(如适用)
- [ ] 提取方式正确
- [ ] 提取期选项完整
- [ ] 表单字段定义完整
- [ ] 提交字段映射正确
---
## 📋 审核后操作
### 确认无误
```bash
# 1. 移动到 approved 目录
mv docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏摯傳承保障計劃-性別-年齡-出生年月日.md \
docs/parse-audit/approved/
# 2. 合并到正式配置
# 手动复制或使用工具合并到 src/config/plan-templates.js
# 3. 删除待审核文件(可选)
rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏摯傳承保障計劃-性別-年齡-出生年月日.md
```
### 需要修改
1. 编辑本文件修正内容
2. 重新提交审核
### 放弃本次解析
```bash
rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏摯傳承保障計劃-性別-年齡-出生年月日.md
```
---
## 审核状态
- [ ] 待审核
- [ ] 已通过
- [ ] 已拒绝
## 审核意见
```text
```
# 产品配置审核 - 计划书模版2.docx
**解析时间**: 2026/2/15 10:20:30
**原始文件**: 计划书模版2.docx
**数据来源**: docs/to-parse/计划书模版2.docx
---
## 📋 产品基本信息
| 字段 | 提取值 | 需要确认 |
|------|--------|---------|
| 产品名称 | 宏摯家傳承保險計劃- 性別, 年齡, 出生年月日 | ✅ 请核对产品名称 |
| 产品类型 | savings | ✅ 请确认产品类型 |
| 币种 | USD | ✅ 请确认币种 |
| form_sn | `savings-product-aaaa60f8` | ✅ 请确认 form_sn 唯一性 |
| 缴费年期 | ["整付","3年","5年"] | ✅ 请确认缴费年期选项 |
| 年龄范围 | 0-75岁 | ✅ 请确认年龄范围 |
| 保险期间 | 终身 | ✅ 请确认保险期间 |
### 💰 储蓄类产品特有字段
| 字段 | 提取值 | 需要确认 |
|------|--------|---------|
| 提取方式 | ["年龄指定金额","最高固定金额"] | ✅ 请确认提取方式 |
| 提取期 | ["1年","3年","5年","10年"] | ✅ 请确认提取期选项 |
---
## 🤖 智能字段提取报告
### 匹配统计
- ✅ 成功匹配: 2 字段
- ⚠️ 使用默认值: 4 字段
- ❌ 未匹配(需人工补充): 0 字段
### ✅ 已成功匹配的字段
- product_name
- payment_periods
### ⚠️ 使用默认值的字段
- **product_type**: 未找到字段 "product_type",使用默认值: "savings"
- **currency**: 未找到字段 "currency",使用默认值: "USD"
- **age_range**: 未找到字段 "age_range",使用默认值: {"min":0,"max":75}
- **insurance_period**: 未找到字段 "insurance_period",使用默认值: "终身"
---
## 🧾 配置预览
```javascript
{
"product_name": "宏摯家傳承保險計劃- 性別, 年齡, 出生年月日",
"product_type": "savings",
"currency": "USD",
"form_sn": "savings-product-aaaa60f8",
"payment_periods": [
"整付",
"3年",
"5年"
],
"age_range": {
"min": 0,
"max": 75
},
"insurance_period": "终身",
"is_savings": true,
"withdrawal_modes": [
"年龄指定金额",
"最高固定金额"
],
"withdrawal_periods": [
"1年",
"3年",
"5年",
"10年"
]
}
```
---
## 📝 表单字段 (form_schema)
```javascript
{
"base_fields": [],
"withdrawal_fields": [],
"reset_map": {}
}
```
---
## 🔄 提交字段映射 (submit_mapping)
```javascript
{}
```
---
## 🧩 生成配置片段
```javascript
/**
* 宏摯家傳承保險計劃- 性別, 年齡, 出生年月日
* @added 2026-02-15T02:20:30.997Z
* @source docs/to-parse/计划书模版2.docx
*/
'savings-product-aaaa60f8': {
name: '宏摯家傳承保險計劃- 性別, 年齡, 出生年月日',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: ["整付","3年","5年"],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ["年龄指定金额","最高固定金额"],
withdrawal_periods: ["1年","3年","5年","10年"]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
}
```
---
## ✅ 审核检查清单
### 基础信息
- [ ] 产品名称正确
- [ ] 产品类型正确(savings/critical-illness/life-insurance)
- [ ] 币种正确(USD/CNY/HKD/EUR)
- [ ] form_sn 唯一且符合命名规范
### 缴费与年龄
- [ ] 缴费年期选项完整且正确
- [ ] 年龄范围合理
- [ ] 保险期间正确
### 储蓄类特有(如适用)
- [ ] 提取方式正确
- [ ] 提取期选项完整
- [ ] 表单字段定义完整
- [ ] 提交字段映射正确
---
## 📋 审核后操作
### 确认无误
```bash
# 1. 移动到 approved 目录
mv docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏摯家傳承保險計劃-性別-年齡-出生年月日.md \
docs/parse-audit/approved/
# 2. 合并到正式配置
# 手动复制或使用工具合并到 src/config/plan-templates.js
# 3. 删除待审核文件(可选)
rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏摯家傳承保險計劃-性別-年齡-出生年月日.md
```
### 需要修改
1. 编辑本文件修正内容
2. 重新提交审核
### 放弃本次解析
```bash
rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏摯家傳承保險計劃-性別-年齡-出生年月日.md
```
---
## 审核状态
- [ ] 待审核
- [ ] 已通过
- [ ] 已拒绝
## 审核意见
```text
```
# 产品配置审核 - 计划书模版2.docx
**解析时间**: 2026/2/15 10:20:30
**原始文件**: 计划书模版2.docx
**数据来源**: docs/to-parse/计划书模版2.docx
---
## 📋 产品基本信息
| 字段 | 提取值 | 需要确认 |
|------|--------|---------|
| 产品名称 | 宏浚傳承保障計劃 | ✅ 请核对产品名称 |
| 产品类型 | savings | ✅ 请确认产品类型 |
| 币种 | USD | ✅ 请确认币种 |
| form_sn | `savings-product-d1581522` | ✅ 请确认 form_sn 唯一性 |
| 缴费年期 | ["整付","2年","5年"] | ✅ 请确认缴费年期选项 |
| 年龄范围 | 0-75岁 | ✅ 请确认年龄范围 |
| 保险期间 | 终身 | ✅ 请确认保险期间 |
### 💰 储蓄类产品特有字段
| 字段 | 提取值 | 需要确认 |
|------|--------|---------|
| 提取方式 | ["年龄指定金额","最高固定金额"] | ✅ 请确认提取方式 |
| 提取期 | ["1年","3年","5年","10年"] | ✅ 请确认提取期选项 |
---
## 🤖 智能字段提取报告
### 匹配统计
- ✅ 成功匹配: 2 字段
- ⚠️ 使用默认值: 4 字段
- ❌ 未匹配(需人工补充): 0 字段
### ✅ 已成功匹配的字段
- product_name
- payment_periods
### ⚠️ 使用默认值的字段
- **product_type**: 未找到字段 "product_type",使用默认值: "savings"
- **currency**: 未找到字段 "currency",使用默认值: "USD"
- **age_range**: 未找到字段 "age_range",使用默认值: {"min":0,"max":75}
- **insurance_period**: 未找到字段 "insurance_period",使用默认值: "终身"
---
## 🧾 配置预览
```javascript
{
"product_name": "宏浚傳承保障計劃",
"product_type": "savings",
"currency": "USD",
"form_sn": "savings-product-d1581522",
"payment_periods": [
"整付",
"2年",
"5年"
],
"age_range": {
"min": 0,
"max": 75
},
"insurance_period": "终身",
"is_savings": true,
"withdrawal_modes": [
"年龄指定金额",
"最高固定金额"
],
"withdrawal_periods": [
"1年",
"3年",
"5年",
"10年"
]
}
```
---
## 📝 表单字段 (form_schema)
```javascript
{
"base_fields": [],
"withdrawal_fields": [],
"reset_map": {}
}
```
---
## 🔄 提交字段映射 (submit_mapping)
```javascript
{}
```
---
## 🧩 生成配置片段
```javascript
/**
* 宏浚傳承保障計劃
* @added 2026-02-15T02:20:30.997Z
* @source docs/to-parse/计划书模版2.docx
*/
'savings-product-d1581522': {
name: '宏浚傳承保障計劃',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: ["整付","2年","5年"],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ["年龄指定金额","最高固定金额"],
withdrawal_periods: ["1年","3年","5年","10年"]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
}
```
---
## ✅ 审核检查清单
### 基础信息
- [ ] 产品名称正确
- [ ] 产品类型正确(savings/critical-illness/life-insurance)
- [ ] 币种正确(USD/CNY/HKD/EUR)
- [ ] form_sn 唯一且符合命名规范
### 缴费与年龄
- [ ] 缴费年期选项完整且正确
- [ ] 年龄范围合理
- [ ] 保险期间正确
### 储蓄类特有(如适用)
- [ ] 提取方式正确
- [ ] 提取期选项完整
- [ ] 表单字段定义完整
- [ ] 提交字段映射正确
---
## 📋 审核后操作
### 确认无误
```bash
# 1. 移动到 approved 目录
mv docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏浚傳承保障計劃.md \
docs/parse-audit/approved/
# 2. 合并到正式配置
# 手动复制或使用工具合并到 src/config/plan-templates.js
# 3. 删除待审核文件(可选)
rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏浚傳承保障計劃.md
```
### 需要修改
1. 编辑本文件修正内容
2. 重新提交审核
### 放弃本次解析
```bash
rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏浚傳承保障計劃.md
```
---
## 审核状态
- [ ] 待审核
- [ ] 已通过
- [ ] 已拒绝
## 审核意见
```text
```
# 产品配置审核 - 计划书模版2.docx
**解析时间**: 2026/2/15 10:20:30
**原始文件**: 计划书模版2.docx
**数据来源**: docs/to-parse/计划书模版2.docx
---
## 📋 产品基本信息
| 字段 | 提取值 | 需要确认 |
|------|--------|---------|
| 产品名称 | 赤霞珠終身壽險計劃2基本人壽保障選項 | ✅ 请核对产品名称 |
| 产品类型 | savings | ✅ 请确认产品类型 |
| 币种 | USD | ✅ 请确认币种 |
| form_sn | `savings-2-031c1237` | ✅ 请确认 form_sn 唯一性 |
| 缴费年期 | ["5年","8年","12年","15年"] | ✅ 请确认缴费年期选项 |
| 年龄范围 | 0-75岁 | ✅ 请确认年龄范围 |
| 保险期间 | 终身 | ✅ 请确认保险期间 |
### 💰 储蓄类产品特有字段
| 字段 | 提取值 | 需要确认 |
|------|--------|---------|
| 提取方式 | ["年龄指定金额","最高固定金额"] | ✅ 请确认提取方式 |
| 提取期 | ["1年","3年","5年","10年"] | ✅ 请确认提取期选项 |
---
## 🤖 智能字段提取报告
### 匹配统计
- ✅ 成功匹配: 2 字段
- ⚠️ 使用默认值: 4 字段
- ❌ 未匹配(需人工补充): 0 字段
### ✅ 已成功匹配的字段
- product_name
- payment_periods
### ⚠️ 使用默认值的字段
- **product_type**: 未找到字段 "product_type",使用默认值: "savings"
- **currency**: 未找到字段 "currency",使用默认值: "USD"
- **age_range**: 未找到字段 "age_range",使用默认值: {"min":0,"max":75}
- **insurance_period**: 未找到字段 "insurance_period",使用默认值: "终身"
---
## 🧾 配置预览
```javascript
{
"product_name": "赤霞珠終身壽險計劃2基本人壽保障選項",
"product_type": "savings",
"currency": "USD",
"form_sn": "savings-2-031c1237",
"payment_periods": [
"5年",
"8年",
"12年",
"15年"
],
"age_range": {
"min": 0,
"max": 75
},
"insurance_period": "终身",
"is_savings": true,
"withdrawal_modes": [
"年龄指定金额",
"最高固定金额"
],
"withdrawal_periods": [
"1年",
"3年",
"5年",
"10年"
]
}
```
---
## 📝 表单字段 (form_schema)
```javascript
{
"base_fields": [],
"withdrawal_fields": [],
"reset_map": {}
}
```
---
## 🔄 提交字段映射 (submit_mapping)
```javascript
{}
```
---
## 🧩 生成配置片段
```javascript
/**
* 赤霞珠終身壽險計劃2基本人壽保障選項
* @added 2026-02-15T02:20:30.997Z
* @source docs/to-parse/计划书模版2.docx
*/
'savings-2-031c1237': {
name: '赤霞珠終身壽險計劃2基本人壽保障選項',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: ["5年","8年","12年","15年"],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ["年龄指定金额","最高固定金额"],
withdrawal_periods: ["1年","3年","5年","10年"]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
}
```
---
## ✅ 审核检查清单
### 基础信息
- [ ] 产品名称正确
- [ ] 产品类型正确(savings/critical-illness/life-insurance)
- [ ] 币种正确(USD/CNY/HKD/EUR)
- [ ] form_sn 唯一且符合命名规范
### 缴费与年龄
- [ ] 缴费年期选项完整且正确
- [ ] 年龄范围合理
- [ ] 保险期间正确
### 储蓄类特有(如适用)
- [ ] 提取方式正确
- [ ] 提取期选项完整
- [ ] 表单字段定义完整
- [ ] 提交字段映射正确
---
## 📋 审核后操作
### 确认无误
```bash
# 1. 移动到 approved 目录
mv docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-赤霞珠終身壽險計劃2基本人壽保障選項.md \
docs/parse-audit/approved/
# 2. 合并到正式配置
# 手动复制或使用工具合并到 src/config/plan-templates.js
# 3. 删除待审核文件(可选)
rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-赤霞珠終身壽險計劃2基本人壽保障選項.md
```
### 需要修改
1. 编辑本文件修正内容
2. 重新提交审核
### 放弃本次解析
```bash
rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-赤霞珠終身壽險計劃2基本人壽保障選項.md
```
---
## 审核状态
- [ ] 待审核
- [ ] 已通过
- [ ] 已拒绝
## 审核意见
```text
```
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771074633927.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-product-30b41aae"],"at":"2026-02-14T13:10:33.928Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771077530778.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-2-148b3acd"],"at":"2026-02-14T13:58:50.779Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771077569110.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-readme-a4296d1f"],"at":"2026-02-14T13:59:29.110Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771077989896.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-readme-a4296d1f"],"at":"2026-02-14T14:06:29.896Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771078080604.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-readme-a4296d1f"],"at":"2026-02-14T14:08:00.605Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771078351660.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-2-148b3acd"],"at":"2026-02-14T14:12:31.660Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771080130974.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-2-55bcffc2"],"at":"2026-02-14T14:42:10.974Z"}
{"at":"2026-02-14T13:10:33.928Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":3,"success_list":[{"form_sn":"savings-product-30b41aae","product_name":"测试计划书-智享未来","file":"测试计划书-智享未来.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-product-30b41aae"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T13:58:50.780Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-2-148b3acd"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T13:59:29.111Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-readme-a4296d1f","product_name":"README","file":"README.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-readme-a4296d1f"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T14:06:15.148Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-readme-a4296d1f","product_name":"README","file":"README.md"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":["savings-readme-a4296d1f"],"reason":"conflict"}}
{"at":"2026-02-14T14:06:29.897Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-readme-a4296d1f","product_name":"README","file":"README.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-readme-a4296d1f"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T14:08:00.605Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-readme-a4296d1f","product_name":"README","file":"README.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-readme-a4296d1f"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T14:12:31.661Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":1,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-2-148b3acd"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T14:34:05.582Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":["savings-2-148b3acd"],"reason":"conflict"}}
{"at":"2026-02-14T14:34:22.438Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":["savings-2-148b3acd"],"reason":"conflict"}}
{"at":"2026-02-14T14:34:50.292Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":1153,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":["savings-2-148b3acd"],"reason":"conflict"}}
{"at":"2026-02-14T14:35:12.489Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":6,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":["savings-2-148b3acd"],"reason":"conflict"}}
{"at":"2026-02-14T14:35:32.726Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":["savings-2-148b3acd"],"reason":"conflict"}}
{"at":"2026-02-14T14:42:10.975Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":28,"success_list":[{"form_sn":"savings-2-55bcffc2","product_name":"计划书模版2","file":"计划书模版2.docx"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-2-55bcffc2"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T15:16:37.454Z","mode":"single","options":{"dry_run":true},"summary":{"total":1,"success":1,"failed":0,"duration_ms":39,"success_list":[{"form_sn":"savings-product-54cf664a","product_name":"智享未来储蓄计划书","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":true,"updated_count":1,"form_sn_list":["savings-product-54cf664a"],"conflicts":[],"reason":null,"diff_preview":"--- plan-templates.js\n+++ plan-templates.js\n+ /**\n+ * 智享未来储蓄计划书\n+ * @added 2026-02-14T15:16:37.415Z\n+ * @source docs/to-parse/测试计划书-智享未来2.md\n+ */\n+ 'savings-product-54cf664a': {\n+ name: '智享未来储蓄计划书',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ }"}}
{"at":"2026-02-14T15:24:05.580Z","mode":"batch","options":{"dry_run":true},"summary":{"total":1,"success":1,"failed":0,"duration_ms":41,"success_list":[{"form_sn":"savings-2-55bcffc2","product_name":"计划书模版2","file":"计划书模版2.docx"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":true,"updated_count":1,"form_sn_list":["savings-2-55bcffc2"],"conflicts":[],"reason":null,"diff_preview":"--- plan-templates.js\n+++ plan-templates.js\n+ /**\n+ * 计划书模版2\n+ * @added 2026-02-14T15:24:05.564Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-2-55bcffc2': {\n+ name: '计划书模版2',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ }"}}
{"at":"2026-02-14T15:29:02.807Z","mode":"batch","options":{"dry_run":true},"summary":{"total":2,"success":2,"failed":0,"duration_ms":50,"success_list":[{"form_sn":"savings-2-55bcffc2","product_name":"计划书模版2","file":"计划书模版2.docx"},{"form_sn":"savings-3-8f4f27ad","product_name":"计划书模版3","file":"计划书模版3.docx"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":true,"updated_count":2,"form_sn_list":["savings-2-55bcffc2","savings-3-8f4f27ad"],"conflicts":[],"reason":null,"diff_preview":"--- plan-templates.js\n+++ plan-templates.js\n+ /**\n+ * 计划书模版2\n+ * @added 2026-02-14T15:29:02.781Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-2-55bcffc2': {\n+ name: '计划书模版2',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ },\n+ \n+ /**\n+ * 计划书模版3\n+ * @added 2026-02-14T15:29:02.804Z\n+ * @source docs/to-parse/计划书模版3.docx\n+ */\n+ 'savings-3-8f4f27ad': {\n+ name: '计划书模版3',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ }"}}
{"at":"2026-02-14T15:30:22.307Z","mode":"batch","options":{"dry_run":true},"summary":{"total":2,"success":2,"failed":0,"duration_ms":49,"success_list":[{"form_sn":"savings-2-55bcffc2","product_name":"计划书模版2","file":"计划书模版2.docx"},{"form_sn":"savings-3-8f4f27ad","product_name":"计划书模版3","file":"计划书模版3.docx"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":true,"updated_count":2,"form_sn_list":["savings-2-55bcffc2","savings-3-8f4f27ad"],"conflicts":[],"reason":null,"diff_preview":"--- plan-templates.js\n+++ plan-templates.js\n+ /**\n+ * 计划书模版2\n+ * @added 2026-02-14T15:30:22.283Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-2-55bcffc2': {\n+ name: '计划书模版2',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ },\n+ \n+ /**\n+ * 计划书模版3\n+ * @added 2026-02-14T15:30:22.304Z\n+ * @source docs/to-parse/计划书模版3.docx\n+ */\n+ 'savings-3-8f4f27ad': {\n+ name: '计划书模版3',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ }"}}
{"at":"2026-02-14T15:51:38.101Z","mode":"batch","options":{"dry_run":true},"summary":{"total":2,"success":2,"failed":0,"duration_ms":47,"success_list":[{"form_sn":"savings-2-55bcffc2","product_name":"计划书模版2","file":"计划书模版2.docx"},{"form_sn":"savings-3-8f4f27ad","product_name":"计划书模版3","file":"计划书模版3.docx"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":true,"updated_count":2,"form_sn_list":["savings-2-55bcffc2","savings-3-8f4f27ad"],"conflicts":[],"reason":null,"diff_preview":"--- plan-templates.js\n+++ plan-templates.js\n+ /**\n+ * 计划书模版2\n+ * @added 2026-02-14T15:51:38.078Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-2-55bcffc2': {\n+ name: '计划书模版2',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ },\n+ \n+ /**\n+ * 计划书模版3\n+ * @added 2026-02-14T15:51:38.098Z\n+ * @source docs/to-parse/计划书模版3.docx\n+ */\n+ 'savings-3-8f4f27ad': {\n+ name: '计划书模版3',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ }"}}
{"at":"2026-02-14T15:58:54.732Z","mode":"batch","options":{"dry_run":true},"summary":{"total":2,"success":2,"failed":0,"duration_ms":51,"success_list":[{"form_sn":"savings-2-55bcffc2","product_name":"计划书模版2","file":"计划书模版2.docx"},{"form_sn":"savings-3-8f4f27ad","product_name":"计划书模版3","file":"计划书模版3.docx"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":true,"updated_count":2,"form_sn_list":["savings-2-55bcffc2","savings-3-8f4f27ad"],"conflicts":[],"reason":null,"diff_preview":"--- plan-templates.js\n+++ plan-templates.js\n+ /**\n+ * 计划书模版2\n+ * @added 2026-02-14T15:58:54.707Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-2-55bcffc2': {\n+ name: '计划书模版2',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"指定提取金额\",\"最高固定提取金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ },\n+ \n+ /**\n+ * 计划书模版3\n+ * @added 2026-02-14T15:58:54.729Z\n+ * @source docs/to-parse/计划书模版3.docx\n+ */\n+ 'savings-3-8f4f27ad': {\n+ name: '计划书模版3',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ }"}}
{"at":"2026-02-14T16:37:53.682Z","mode":"single","options":{"dry_run":true},"summary":{"total_docs":1,"total_products":4,"success":4,"failed":0,"duration_ms":48,"success_list":[{"form_sn":"savings-product-ef3dd50b","product_name":"宏摯傳承保障計劃 - 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-aaaa60f8","product_name":"宏摯家傳承保險計劃- 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-d1581522","product_name":"宏浚傳承保障計劃","file":"计划书模版2.docx"},{"form_sn":"savings-2-031c1237","product_name":"赤霞珠終身壽險計劃2基本人壽保障選項","file":"计划书模版2.docx"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":[],"reason":"insert_not_found"}}
{"at":"2026-02-14T16:38:42.112Z","mode":"single","options":{"dry_run":true},"summary":{"total_docs":1,"total_products":4,"success":4,"failed":0,"duration_ms":47,"success_list":[{"form_sn":"savings-product-ef3dd50b","product_name":"宏摯傳承保障計劃 - 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-aaaa60f8","product_name":"宏摯家傳承保險計劃- 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-d1581522","product_name":"宏浚傳承保障計劃","file":"计划书模版2.docx"},{"form_sn":"savings-2-031c1237","product_name":"赤霞珠終身壽險計劃2基本人壽保障選項","file":"计划书模版2.docx"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":[],"reason":"insert_not_found"}}
{"at":"2026-02-14T16:39:13.208Z","mode":"single","options":{"dry_run":true},"summary":{"total_docs":1,"total_products":4,"success":4,"failed":0,"duration_ms":46,"success_list":[{"form_sn":"savings-product-ef3dd50b","product_name":"宏摯傳承保障計劃 - 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-aaaa60f8","product_name":"宏摯家傳承保險計劃- 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-d1581522","product_name":"宏浚傳承保障計劃","file":"计划书模版2.docx"},{"form_sn":"savings-2-031c1237","product_name":"赤霞珠終身壽險計劃2基本人壽保障選項","file":"计划书模版2.docx"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":[],"reason":"insert_not_found"}}
{"at":"2026-02-14T16:39:23.952Z","mode":"single","options":{"dry_run":true},"summary":{"total_docs":1,"total_products":4,"success":4,"failed":0,"duration_ms":46,"success_list":[{"form_sn":"savings-product-ef3dd50b","product_name":"宏摯傳承保障計劃 - 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-aaaa60f8","product_name":"宏摯家傳承保險計劃- 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-d1581522","product_name":"宏浚傳承保障計劃","file":"计划书模版2.docx"},{"form_sn":"savings-2-031c1237","product_name":"赤霞珠終身壽險計劃2基本人壽保障選項","file":"计划书模版2.docx"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":[],"reason":"insert_not_found"}}
{"at":"2026-02-14T16:39:56.947Z","mode":"single","options":{"dry_run":true},"summary":{"total_docs":1,"total_products":4,"success":4,"failed":0,"duration_ms":46,"success_list":[{"form_sn":"savings-product-ef3dd50b","product_name":"宏摯傳承保障計劃 - 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-aaaa60f8","product_name":"宏摯家傳承保險計劃- 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-d1581522","product_name":"宏浚傳承保障計劃","file":"计划书模版2.docx"},{"form_sn":"savings-2-031c1237","product_name":"赤霞珠終身壽險計劃2基本人壽保障選項","file":"计划书模版2.docx"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":[],"reason":"insert_not_found"}}
{"at":"2026-02-14T16:41:18.047Z","mode":"single","options":{"dry_run":true},"summary":{"total_docs":1,"total_products":4,"success":4,"failed":0,"duration_ms":46,"success_list":[{"form_sn":"savings-product-ef3dd50b","product_name":"宏摯傳承保障計劃 - 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-aaaa60f8","product_name":"宏摯家傳承保險計劃- 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-d1581522","product_name":"宏浚傳承保障計劃","file":"计划书模版2.docx"},{"form_sn":"savings-2-031c1237","product_name":"赤霞珠終身壽險計劃2基本人壽保障選項","file":"计划书模版2.docx"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":true,"updated_count":4,"form_sn_list":["savings-product-ef3dd50b","savings-product-aaaa60f8","savings-product-d1581522","savings-2-031c1237"],"conflicts":[],"reason":null,"diff_preview":"--- plan-templates.js\n+++ plan-templates.js\n+ /**\n+ * 宏摯傳承保障計劃 - 性別, 年齡, 出生年月日\n+ * @added 2026-02-14T16:41:18.029Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-product-ef3dd50b': {\n+ name: '宏摯傳承保障計劃 - 性別, 年齡, 出生年月日',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\",\"10年\",\"15年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"最高固定提取金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ },\n+ \n+ /**\n+ * 宏摯家傳承保險計劃- 性別, 年齡, 出生年月日\n+ * @added 2026-02-14T16:41:18.044Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-product-aaaa60f8': {\n+ name: '宏摯家傳承保險計劃- 性別, 年齡, 出生年月日',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ },\n+ \n+ /**\n+ * 宏浚傳承保障計劃\n+ * @added 2026-02-14T16:41:18.044Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-product-d1581522': {"}}
{"at":"2026-02-14T16:41:27.896Z","mode":"single","options":{"dry_run":true},"summary":{"total_docs":1,"total_products":4,"success":4,"failed":0,"duration_ms":47,"success_list":[{"form_sn":"savings-product-ef3dd50b","product_name":"宏摯傳承保障計劃 - 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-aaaa60f8","product_name":"宏摯家傳承保險計劃- 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-d1581522","product_name":"宏浚傳承保障計劃","file":"计划书模版2.docx"},{"form_sn":"savings-2-031c1237","product_name":"赤霞珠終身壽險計劃2基本人壽保障選項","file":"计划书模版2.docx"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":true,"updated_count":4,"form_sn_list":["savings-product-ef3dd50b","savings-product-aaaa60f8","savings-product-d1581522","savings-2-031c1237"],"conflicts":[],"reason":null,"diff_preview":"--- plan-templates.js\n+++ plan-templates.js\n+ /**\n+ * 宏摯傳承保障計劃 - 性別, 年齡, 出生年月日\n+ * @added 2026-02-14T16:41:27.878Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-product-ef3dd50b': {\n+ name: '宏摯傳承保障計劃 - 性別, 年齡, 出生年月日',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\",\"10年\",\"15年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"最高固定提取金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ },\n+ \n+ /**\n+ * 宏摯家傳承保險計劃- 性別, 年齡, 出生年月日\n+ * @added 2026-02-14T16:41:27.893Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-product-aaaa60f8': {\n+ name: '宏摯家傳承保險計劃- 性別, 年齡, 出生年月日',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ },\n+ \n+ /**\n+ * 宏浚傳承保障計劃\n+ * @added 2026-02-14T16:41:27.893Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-product-d1581522': {"}}
{"at":"2026-02-15T02:10:06.819Z","mode":"batch","options":{"dry_run":true},"summary":{"total_docs":2,"total_products":5,"success":5,"failed":0,"duration_ms":73,"success_list":[{"form_sn":"savings-product-ef3dd50b","product_name":"宏摯傳承保障計劃 - 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-aaaa60f8","product_name":"宏摯家傳承保險計劃- 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-d1581522","product_name":"宏浚傳承保障計劃","file":"计划书模版2.docx"},{"form_sn":"savings-2-031c1237","product_name":"赤霞珠終身壽險計劃2基本人壽保障選項","file":"计划书模版2.docx"},{"form_sn":"savings-3-8f4f27ad","product_name":"计划书模版3","file":"计划书模版3.docx"}],"failed_list":[],"total":5},"change_summary":{"ok":true,"dry_run":true,"updated_count":5,"form_sn_list":["savings-product-ef3dd50b","savings-product-aaaa60f8","savings-product-d1581522","savings-2-031c1237","savings-3-8f4f27ad"],"conflicts":[],"reason":null,"diff_preview":"--- plan-templates.js\n+++ plan-templates.js\n+ /**\n+ * 宏摯傳承保障計劃 - 性別, 年齡, 出生年月日\n+ * @added 2026-02-15T02:10:06.768Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-product-ef3dd50b': {\n+ name: '宏摯傳承保障計劃 - 性別, 年齡, 出生年月日',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\",\"10年\",\"15年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"最高固定提取金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ },\n+ \n+ /**\n+ * 宏摯家傳承保險計劃- 性別, 年齡, 出生年月日\n+ * @added 2026-02-15T02:10:06.803Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-product-aaaa60f8': {\n+ name: '宏摯家傳承保險計劃- 性別, 年齡, 出生年月日',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ },\n+ \n+ /**\n+ * 宏浚傳承保障計劃\n+ * @added 2026-02-15T02:10:06.803Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-product-d1581522': {"}}
{"at":"2026-02-15T02:19:44.455Z","mode":"batch","options":{"dry_run":true},"summary":{"total_docs":1,"total_products":1,"success":1,"failed":0,"duration_ms":39,"success_list":[{"form_sn":"savings-3-8f4f27ad","product_name":"计划书模版3","file":"计划书模版3.docx"}],"failed_list":[],"total":1},"change_summary":{"ok":true,"dry_run":true,"updated_count":1,"form_sn_list":["savings-3-8f4f27ad"],"conflicts":[],"reason":null,"diff_preview":"--- plan-templates.js\n+++ plan-templates.js\n+ /**\n+ * 计划书模版3\n+ * @added 2026-02-15T02:19:44.438Z\n+ * @source docs/to-parse/计划书模版3.docx\n+ */\n+ 'savings-3-8f4f27ad': {\n+ name: '计划书模版3',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"5年\",\"8年\",\"12年\",\"15年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ }"}}
{"at":"2026-02-15T02:20:31.001Z","mode":"batch","options":{"dry_run":true},"summary":{"total_docs":1,"total_products":4,"success":4,"failed":0,"duration_ms":47,"success_list":[{"form_sn":"savings-product-ef3dd50b","product_name":"宏摯傳承保障計劃 - 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-aaaa60f8","product_name":"宏摯家傳承保險計劃- 性別, 年齡, 出生年月日","file":"计划书模版2.docx"},{"form_sn":"savings-product-d1581522","product_name":"宏浚傳承保障計劃","file":"计划书模版2.docx"},{"form_sn":"savings-2-031c1237","product_name":"赤霞珠終身壽險計劃2基本人壽保障選項","file":"计划书模版2.docx"}],"failed_list":[],"total":4},"change_summary":{"ok":true,"dry_run":true,"updated_count":4,"form_sn_list":["savings-product-ef3dd50b","savings-product-aaaa60f8","savings-product-d1581522","savings-2-031c1237"],"conflicts":[],"reason":null,"diff_preview":"--- plan-templates.js\n+++ plan-templates.js\n+ /**\n+ * 宏摯傳承保障計劃 - 性別, 年齡, 出生年月日\n+ * @added 2026-02-15T02:20:30.982Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-product-ef3dd50b': {\n+ name: '宏摯傳承保障計劃 - 性別, 年齡, 出生年月日',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\",\"10年\",\"15年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"最高固定提取金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ },\n+ \n+ /**\n+ * 宏摯家傳承保險計劃- 性別, 年齡, 出生年月日\n+ * @added 2026-02-15T02:20:30.997Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-product-aaaa60f8': {\n+ name: '宏摯家傳承保險計劃- 性別, 年齡, 出生年月日',\n+ component: 'SavingsTemplate',\n+ category: 'savings',\n+ config: {\n+ currency: 'USD',\n+ payment_periods: [\"整付\",\"3年\",\"5年\"],\n+ age_range: { min: 0, max: 75 },\n+ insurance_period: '终身',\n+ withdrawal_plan: {\n+ enabled: true,\n+ currencies: ['HKD', 'USD', 'CNY'],\n+ default_currency: 'USD',\n+ withdrawal_modes: [\"年龄指定金额\",\"最高固定金额\"],\n+ withdrawal_periods: [\"1年\",\"3年\",\"5年\",\"10年\"]\n+ },\n+ form_schema: savingsFormSchema,\n+ submit_mapping: savingsSubmitMapping\n+ }\n+ },\n+ \n+ /**\n+ * 宏浚傳承保障計劃\n+ * @added 2026-02-15T02:20:30.997Z\n+ * @source docs/to-parse/计划书模版2.docx\n+ */\n+ 'savings-product-d1581522': {"}}
/**
* 计划书模版配置
*
* @description 定义产品 form_sn 到模版组件和配置的映射关系
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
* 计划书模版配置映射
* @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
*
* @example
* // 产品 API 返回
* {
* id: 1,
* product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
* form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
* }
*/
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD', // 币种:USD/CNY/HKD/EUR
payment_periods: [
// 缴费年期选项
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 }, // 年龄范围
insurance_period: '终身' // 保险期间
}
},
// 人寿保险产品 - WIOP3
'life-insurance-wiop3': {
name: 'WIOP3 - 盈传创富保障计划 3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: [
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 },
insurance_period: '终身'
}
},
// 重疾保险产品 - MPC
'critical-illness-mpc': {
name: 'MPC 守护无间重疾',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
},
// 重疾保险产品 - MBC PRO
'critical-illness-mbc-pro': {
name: 'MBC PRO 活跃人生重疾保 PRO',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
},
// 重疾保险产品 - MBC2
'critical-illness-mbc2': {
name: 'MBC2 活跃人生重疾保 2',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
},
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 提取计划配置
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
default_currency: 'USD', // 统一为美元
withdrawal_modes: [
'年龄指定金额', // 方式1
'最高固定金额' // 方式2
],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
},
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['年龄指定金额', '最高固定金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
},
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['年龄指定金额', '最高固定金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
},
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['年龄指定金额', '最高固定金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
}
}
/**
* 全局功能开关
* @description 用于控制实验性功能或未来扩展功能的开关
*
* @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
*/
export const FEATURE_FLAGS = {
/**
* 多币种切换功能
* @description false: 方案 1 - 固定币种(当前实现)
* true: 方案 2 - 支持多币种切换(未来扩展)
* @type {boolean}
*/
MULTI_CURRENCY_ENABLED: false
}
/**
* 币种符号映射
* @description 币种代码到符号的映射关系
*/
export const CURRENCY_SYMBOLS = {
CNY: '¥', // 人民币
USD: '$', // 美元
HKD: 'HK$', // 港币
EUR: '€' // 欧元
}
/**
* 币种完整信息映射
* @description 币种代码到完整信息的映射(用于多币种模式)
*/
export const CURRENCY_MAP = {
CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
USD: { label: '美元', symbol: '$', value: 'USD' },
HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
EUR: { label: '欧元', symbol: '€', value: 'EUR' }
}
/**
* 根据 form_sn 获取模版配置
* @param {string} formSn - 产品 API 返回的 form_sn 字段
* @returns {Object|null} 模版配置对象,未找到返回 null
*
* @example
* const config = getTemplateConfig('life-insurance-wiop3e')
* // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
*/
export function getTemplateConfig(formSn) {
if (!formSn) {
console.warn('[plan-templates] form_sn 为空')
return null
}
const config = PLAN_TEMPLATES[formSn]
if (!config) {
console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
return null
}
return config
}
/**
* 获取币种符号
* @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
* @returns {string} 币种符号
*
* @example
* const symbol = getCurrencySymbol('USD') // 返回: '$'
*/
export function getCurrencySymbol(currencyCode) {
return CURRENCY_SYMBOLS[currencyCode] || '¥'
}
/**
* 计划书模版配置
*
* @description 定义产品 form_sn 到模版组件和配置的映射关系
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
* 计划书模版配置映射
* @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
*
* @example
* // 产品 API 返回
* {
* id: 1,
* product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
* form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
* }
*/
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD', // 币种:USD/CNY/HKD/EUR
payment_periods: [
// 缴费年期选项
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 }, // 年龄范围
insurance_period: '终身' // 保险期间
}
},
// 人寿保险产品 - WIOP3
'life-insurance-wiop3': {
name: 'WIOP3 - 盈传创富保障计划 3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: [
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 },
insurance_period: '终身'
}
},
// 重疾保险产品 - MPC
'critical-illness-mpc': {
name: 'MPC 守护无间重疾',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
},
// 重疾保险产品 - MBC PRO
'critical-illness-mbc-pro': {
name: 'MBC PRO 活跃人生重疾保 PRO',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
},
// 重疾保险产品 - MBC2
'critical-illness-mbc2': {
name: 'MBC2 活跃人生重疾保 2',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
},
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 提取计划配置
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
default_currency: 'USD', // 统一为美元
withdrawal_modes: [
'年龄指定金额', // 方式1
'最高固定金额' // 方式2
],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
},
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['年龄指定金额', '最高固定金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
},
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['年龄指定金额', '最高固定金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
},
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['年龄指定金额', '最高固定金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
}
}
/**
* 全局功能开关
* @description 用于控制实验性功能或未来扩展功能的开关
*
* @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
*/
export const FEATURE_FLAGS = {
/**
* 多币种切换功能
* @description false: 方案 1 - 固定币种(当前实现)
* true: 方案 2 - 支持多币种切换(未来扩展)
* @type {boolean}
*/
MULTI_CURRENCY_ENABLED: false
}
/**
* 币种符号映射
* @description 币种代码到符号的映射关系
*/
export const CURRENCY_SYMBOLS = {
CNY: '¥', // 人民币
USD: '$', // 美元
HKD: 'HK$', // 港币
EUR: '€' // 欧元
}
/**
* 币种完整信息映射
* @description 币种代码到完整信息的映射(用于多币种模式)
*/
export const CURRENCY_MAP = {
CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
USD: { label: '美元', symbol: '$', value: 'USD' },
HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
EUR: { label: '欧元', symbol: '€', value: 'EUR' }
}
/**
* 根据 form_sn 获取模版配置
* @param {string} formSn - 产品 API 返回的 form_sn 字段
* @returns {Object|null} 模版配置对象,未找到返回 null
*
* @example
* const config = getTemplateConfig('life-insurance-wiop3e')
* // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
*/
export function getTemplateConfig(formSn) {
if (!formSn) {
console.warn('[plan-templates] form_sn 为空')
return null
}
const config = PLAN_TEMPLATES[formSn]
if (!config) {
console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
return null
}
return config
}
/**
* 获取币种符号
* @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
* @returns {string} 币种符号
*
* @example
* const symbol = getCurrencySymbol('USD') // 返回: '$'
*/
export function getCurrencySymbol(currencyCode) {
return CURRENCY_SYMBOLS[currencyCode] || '¥'
}
/**
* 计划书模版配置
*
* @description 定义产品 form_sn 到模版组件和配置的映射关系
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
* 计划书模版配置映射
* @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
*
* @example
* // 产品 API 返回
* {
* id: 1,
* product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
* form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
* }
*/
// 基础提交字段映射(适用于人寿/重疾等通用表单)
const baseSubmitMapping = {
customer_name: { api_field: 'customer_name' },
gender: { api_field: 'customer_gender' },
birthday: { api_field: 'customer_birthday' },
smoker: { api_field: 'smoking_status' },
coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
payment_period: { api_field: 'payment_years' },
total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
}
// 人寿/重疾基础表单 Schema(通用保障类)
const protectionFormSchema = {
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
]
}
// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
const savingsSubmitMapping = {
...baseSubmitMapping,
withdrawal_enabled: { api_field: 'allow_reduce_amount' },
withdrawal_mode: { api_field: 'withdrawal_option' },
withdrawal_method: { api_field: 'withdrawal_method' },
annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
annual_increase_percentage: { api_field: 'annual_increase_percentage' },
withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
withdrawal_period_specified: { api_field: 'withdrawal_period' },
withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
withdrawal_period_fixed: { api_field: 'withdrawal_period' }
}
// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
const savingsFormSchema = {
// 基础字段:非提取计划部分
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
],
// 提取计划字段:由 withdrawal_plan 开关控制
withdrawal_fields: [
{ id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
{ id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
{ id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_withdrawal_amount', key: 'annual_withdrawal_amount', type: 'amount', label: '每年提取金额', placeholder: '请输入每年提取金额', input_label: '请输入每年提取金额', required: true, currency_from: 'withdrawal_plan.default_currency', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_period_specified', key: 'withdrawal_period_specified', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
{ id: 'withdrawal_period_fixed', key: 'withdrawal_period_fixed', type: 'select', label: '按年岁:提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }
],
// 提取模式切换时的清空逻辑,避免脏字段影响提交
reset_map: {
withdrawal_mode: {
'最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
'指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
}
}
}
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD', // 币种:USD/CNY/HKD/EUR
payment_periods: [
// 缴费年期选项
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 }, // 年龄范围
insurance_period: '终身', // 保险期间
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 人寿保险产品 - WIOP3
'life-insurance-wiop3': {
name: 'WIOP3 - 盈传创富保障计划 3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: [
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MPC
'critical-illness-mpc': {
name: 'MPC 守护无间重疾',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC PRO
'critical-illness-mbc-pro': {
name: 'MBC PRO 活跃人生重疾保 PRO',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC2
'critical-illness-mbc2': {
name: 'MBC2 活跃人生重疾保 2',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 提取计划配置
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
}
}
/**
* 全局功能开关
* @description 用于控制实验性功能或未来扩展功能的开关
*
* @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
*/
export const FEATURE_FLAGS = {
/**
* 多币种切换功能
* @description false: 方案 1 - 固定币种(当前实现)
* true: 方案 2 - 支持多币种切换(未来扩展)
* @type {boolean}
*/
MULTI_CURRENCY_ENABLED: false
}
/**
* 币种符号映射
* @description 币种代码到符号的映射关系
*/
export const CURRENCY_SYMBOLS = {
CNY: '¥', // 人民币
USD: '$', // 美元
HKD: 'HK$', // 港币
EUR: '€' // 欧元
}
/**
* 币种完整信息映射
* @description 币种代码到完整信息的映射(用于多币种模式)
*/
export const CURRENCY_MAP = {
CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
USD: { label: '美元', symbol: '$', value: 'USD' },
HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
EUR: { label: '欧元', symbol: '€', value: 'EUR' }
}
/**
* 根据 form_sn 获取模版配置
* @param {string} formSn - 产品 API 返回的 form_sn 字段
* @returns {Object|null} 模版配置对象,未找到返回 null
*
* @example
* const config = getTemplateConfig('life-insurance-wiop3e')
* // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
*/
export function getTemplateConfig(formSn) {
if (!formSn) {
console.warn('[plan-templates] form_sn 为空')
return null
}
const config = PLAN_TEMPLATES[formSn]
if (!config) {
console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
return null
}
return config
}
/**
* 获取币种符号
* @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
* @returns {string} 币种符号
*
* @example
* const symbol = getCurrencySymbol('USD') // 返回: '$'
*/
export function getCurrencySymbol(currencyCode) {
return CURRENCY_SYMBOLS[currencyCode] || '¥'
}
/**
* 计划书模版配置
*
* @description 定义产品 form_sn 到模版组件和配置的映射关系
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
* 计划书模版配置映射
* @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
*
* @example
* // 产品 API 返回
* {
* id: 1,
* product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
* form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
* }
*/
// 基础提交字段映射(适用于人寿/重疾等通用表单)
const baseSubmitMapping = {
customer_name: { api_field: 'customer_name' },
gender: { api_field: 'customer_gender' },
birthday: { api_field: 'customer_birthday' },
smoker: { api_field: 'smoking_status' },
coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
payment_period: { api_field: 'payment_years' },
total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
}
// 人寿/重疾基础表单 Schema(通用保障类)
const protectionFormSchema = {
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
]
}
// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
const savingsSubmitMapping = {
...baseSubmitMapping,
withdrawal_enabled: { api_field: 'allow_reduce_amount' },
withdrawal_mode: { api_field: 'withdrawal_option' },
withdrawal_method: { api_field: 'withdrawal_method' },
annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
annual_increase_percentage: { api_field: 'annual_increase_percentage' },
withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
withdrawal_period_specified: { api_field: 'withdrawal_period' },
withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
withdrawal_period_fixed: { api_field: 'withdrawal_period' }
}
// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
const savingsFormSchema = {
// 基础字段:非提取计划部分
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
],
// 提取计划字段:由 withdrawal_plan 开关控制
withdrawal_fields: [
{ id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
{ id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
{ id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_withdrawal_amount', key: 'annual_withdrawal_amount', type: 'amount', label: '每年提取金额', placeholder: '请输入每年提取金额', input_label: '请输入每年提取金额', required: true, currency_from: 'withdrawal_plan.default_currency', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_period_specified', key: 'withdrawal_period_specified', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
{ id: 'withdrawal_period_fixed', key: 'withdrawal_period_fixed', type: 'select', label: '按年岁:提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }
],
// 提取模式切换时的清空逻辑,避免脏字段影响提交
reset_map: {
withdrawal_mode: {
'最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
'指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
}
}
}
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD', // 币种:USD/CNY/HKD/EUR
payment_periods: [
// 缴费年期选项
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 }, // 年龄范围
insurance_period: '终身', // 保险期间
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 人寿保险产品 - WIOP3
'life-insurance-wiop3': {
name: 'WIOP3 - 盈传创富保障计划 3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: [
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MPC
'critical-illness-mpc': {
name: 'MPC 守护无间重疾',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC PRO
'critical-illness-mbc-pro': {
name: 'MBC PRO 活跃人生重疾保 PRO',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC2
'critical-illness-mbc2': {
name: 'MBC2 活跃人生重疾保 2',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 提取计划配置
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
/**
* 测试计划书-智享未来
* @added 2026-02-14T13:10:33.924Z
* @source docs/to-parse/测试计划书-智享未来.md
*/
'savings-product-30b41aae': {
name: '测试计划书-智享未来',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: ["整付", "3年", "5年"],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ["年龄指定金额", "最高固定金额"],
withdrawal_periods: ["1年", "3年", "5年", "10年"]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
}
}
/**
* 全局功能开关
* @description 用于控制实验性功能或未来扩展功能的开关
*
* @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
*/
export const FEATURE_FLAGS = {
/**
* 多币种切换功能
* @description false: 方案 1 - 固定币种(当前实现)
* true: 方案 2 - 支持多币种切换(未来扩展)
* @type {boolean}
*/
MULTI_CURRENCY_ENABLED: false
}
/**
* 币种符号映射
* @description 币种代码到符号的映射关系
*/
export const CURRENCY_SYMBOLS = {
CNY: '¥', // 人民币
USD: '$', // 美元
HKD: 'HK$', // 港币
EUR: '€' // 欧元
}
/**
* 币种完整信息映射
* @description 币种代码到完整信息的映射(用于多币种模式)
*/
export const CURRENCY_MAP = {
CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
USD: { label: '美元', symbol: '$', value: 'USD' },
HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
EUR: { label: '欧元', symbol: '€', value: 'EUR' }
}
/**
* 根据 form_sn 获取模版配置
* @param {string} formSn - 产品 API 返回的 form_sn 字段
* @returns {Object|null} 模版配置对象,未找到返回 null
*
* @example
* const config = getTemplateConfig('life-insurance-wiop3e')
* // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
*/
export function getTemplateConfig(formSn) {
if (!formSn) {
console.warn('[plan-templates] form_sn 为空')
return null
}
const config = PLAN_TEMPLATES[formSn]
if (!config) {
console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
return null
}
return config
}
/**
* 获取币种符号
* @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
* @returns {string} 币种符号
*
* @example
* const symbol = getCurrencySymbol('USD') // 返回: '$'
*/
export function getCurrencySymbol(currencyCode) {
return CURRENCY_SYMBOLS[currencyCode] || '¥'
}
/**
* 计划书模版配置
*
* @description 定义产品 form_sn 到模版组件和配置的映射关系
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
* 计划书模版配置映射
* @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
*
* @example
* // 产品 API 返回
* {
* id: 1,
* product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
* form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
* }
*/
// 基础提交字段映射(适用于人寿/重疾等通用表单)
const baseSubmitMapping = {
customer_name: { api_field: 'customer_name' },
gender: { api_field: 'customer_gender' },
birthday: { api_field: 'customer_birthday' },
smoker: { api_field: 'smoking_status' },
coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
payment_period: { api_field: 'payment_years' },
total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
}
// 人寿/重疾基础表单 Schema(通用保障类)
const protectionFormSchema = {
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
]
}
// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
const savingsSubmitMapping = {
...baseSubmitMapping,
withdrawal_enabled: { api_field: 'allow_reduce_amount' },
withdrawal_mode: { api_field: 'withdrawal_option' },
withdrawal_method: { api_field: 'withdrawal_method' },
annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
annual_increase_percentage: { api_field: 'annual_increase_percentage' },
withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
withdrawal_period_specified: { api_field: 'withdrawal_period' },
withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
withdrawal_period_fixed: { api_field: 'withdrawal_period' }
}
// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
const savingsFormSchema = {
// 基础字段:非提取计划部分
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
],
// 提取计划字段:由 withdrawal_plan 开关控制
withdrawal_fields: [
{ id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
{ id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
{ id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_withdrawal_amount', key: 'annual_withdrawal_amount', type: 'amount', label: '每年提取金额', placeholder: '请输入每年提取金额', input_label: '请输入每年提取金额', required: true, currency_from: 'withdrawal_plan.default_currency', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_period_specified', key: 'withdrawal_period_specified', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
{ id: 'withdrawal_period_fixed', key: 'withdrawal_period_fixed', type: 'select', label: '按年岁:提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }
],
// 提取模式切换时的清空逻辑,避免脏字段影响提交
reset_map: {
withdrawal_mode: {
'最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
'指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
}
}
}
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD', // 币种:USD/CNY/HKD/EUR
payment_periods: [
// 缴费年期选项
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 }, // 年龄范围
insurance_period: '终身', // 保险期间
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 人寿保险产品 - WIOP3
'life-insurance-wiop3': {
name: 'WIOP3 - 盈传创富保障计划 3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: [
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MPC
'critical-illness-mpc': {
name: 'MPC 守护无间重疾',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC PRO
'critical-illness-mbc-pro': {
name: 'MBC PRO 活跃人生重疾保 PRO',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC2
'critical-illness-mbc2': {
name: 'MBC2 活跃人生重疾保 2',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 提取计划配置
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
/**
* 测试计划书-智享未来
* @added 2026-02-14T13:10:33.924Z
* @source docs/to-parse/测试计划书-智享未来.md
*/
'savings-product-30b41aae': {
name: '测试计划书-智享未来',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: ["整付", "3年", "5年"],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ["年龄指定金额", "最高固定金额"],
withdrawal_periods: ["1年", "3年", "5年", "10年"]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
/**
* 测试计划书-智享未来2
* @added 2026-02-14T13:58:50.776Z
* @source docs/to-parse/测试计划书-智享未来2.md
*/
'savings-2-148b3acd': {
name: '测试计划书-智享未来2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: ["整付","3年","5年"],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ["年龄指定金额","最高固定金额"],
withdrawal_periods: ["1年","3年","5年","10年"]
},
form_schema: {
"base_fields": [],
"withdrawal_fields": [],
"reset_map": {}
},
submit_mapping: savingsSubmitMapping
}
}}
/**
* 全局功能开关
* @description 用于控制实验性功能或未来扩展功能的开关
*
* @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
*/
export const FEATURE_FLAGS = {
/**
* 多币种切换功能
* @description false: 方案 1 - 固定币种(当前实现)
* true: 方案 2 - 支持多币种切换(未来扩展)
* @type {boolean}
*/
MULTI_CURRENCY_ENABLED: false
}
/**
* 币种符号映射
* @description 币种代码到符号的映射关系
*/
export const CURRENCY_SYMBOLS = {
CNY: '¥', // 人民币
USD: '$', // 美元
HKD: 'HK$', // 港币
EUR: '€' // 欧元
}
/**
* 币种完整信息映射
* @description 币种代码到完整信息的映射(用于多币种模式)
*/
export const CURRENCY_MAP = {
CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
USD: { label: '美元', symbol: '$', value: 'USD' },
HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
EUR: { label: '欧元', symbol: '€', value: 'EUR' }
}
/**
* 根据 form_sn 获取模版配置
* @param {string} formSn - 产品 API 返回的 form_sn 字段
* @returns {Object|null} 模版配置对象,未找到返回 null
*
* @example
* const config = getTemplateConfig('life-insurance-wiop3e')
* // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
*/
export function getTemplateConfig(formSn) {
if (!formSn) {
console.warn('[plan-templates] form_sn 为空')
return null
}
const config = PLAN_TEMPLATES[formSn]
if (!config) {
console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
return null
}
return config
}
/**
* 获取币种符号
* @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
* @returns {string} 币种符号
*
* @example
* const symbol = getCurrencySymbol('USD') // 返回: '$'
*/
export function getCurrencySymbol(currencyCode) {
return CURRENCY_SYMBOLS[currencyCode] || '¥'
}
/**
* 计划书模版配置
*
* @description 定义产品 form_sn 到模版组件和配置的映射关系
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
* 计划书模版配置映射
* @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
*
* @example
* // 产品 API 返回
* {
* id: 1,
* product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
* form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
* }
*/
// 基础提交字段映射(适用于人寿/重疾等通用表单)
const baseSubmitMapping = {
customer_name: { api_field: 'customer_name' },
gender: { api_field: 'customer_gender' },
birthday: { api_field: 'customer_birthday' },
smoker: { api_field: 'smoking_status' },
coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
payment_period: { api_field: 'payment_years' },
total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
}
// 人寿/重疾基础表单 Schema(通用保障类)
const protectionFormSchema = {
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
]
}
// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
const savingsSubmitMapping = {
...baseSubmitMapping,
withdrawal_enabled: { api_field: 'allow_reduce_amount' },
withdrawal_mode: { api_field: 'withdrawal_option' },
withdrawal_method: { api_field: 'withdrawal_method' },
annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
annual_increase_percentage: { api_field: 'annual_increase_percentage' },
withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
withdrawal_period_specified: { api_field: 'withdrawal_period' },
withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
withdrawal_period_fixed: { api_field: 'withdrawal_period' }
}
// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
const savingsFormSchema = {
// 基础字段:非提取计划部分
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
],
// 提取计划字段:由 withdrawal_plan 开关控制
withdrawal_fields: [
{ id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
{ id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
{ id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_withdrawal_amount', key: 'annual_withdrawal_amount', type: 'amount', label: '每年提取金额', placeholder: '请输入每年提取金额', input_label: '请输入每年提取金额', required: true, currency_from: 'withdrawal_plan.default_currency', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_period_specified', key: 'withdrawal_period_specified', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
{ id: 'withdrawal_period_fixed', key: 'withdrawal_period_fixed', type: 'select', label: '按年岁:提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }
],
// 提取模式切换时的清空逻辑,避免脏字段影响提交
reset_map: {
withdrawal_mode: {
'最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
'指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
}
}
}
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD', // 币种:USD/CNY/HKD/EUR
payment_periods: [
// 缴费年期选项
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 }, // 年龄范围
insurance_period: '终身', // 保险期间
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 人寿保险产品 - WIOP3
'life-insurance-wiop3': {
name: 'WIOP3 - 盈传创富保障计划 3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: [
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MPC
'critical-illness-mpc': {
name: 'MPC 守护无间重疾',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC PRO
'critical-illness-mbc-pro': {
name: 'MBC PRO 活跃人生重疾保 PRO',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC2
'critical-illness-mbc2': {
name: 'MBC2 活跃人生重疾保 2',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 提取计划配置
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
/**
* 测试计划书-智享未来
* @added 2026-02-14T13:10:33.924Z
* @source docs/to-parse/测试计划书-智享未来.md
*/
'savings-product-30b41aae': {
name: '测试计划书-智享未来',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: ["整付", "3年", "5年"],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ["年龄指定金额", "最高固定金额"],
withdrawal_periods: ["1年", "3年", "5年", "10年"]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
}
/**
* 全局功能开关
* @description 用于控制实验性功能或未来扩展功能的开关
*
* @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
*/
export const FEATURE_FLAGS = {
/**
* 多币种切换功能
* @description false: 方案 1 - 固定币种(当前实现)
* true: 方案 2 - 支持多币种切换(未来扩展)
* @type {boolean}
*/
MULTI_CURRENCY_ENABLED: false
}
/**
* 币种符号映射
* @description 币种代码到符号的映射关系
*/
export const CURRENCY_SYMBOLS = {
CNY: '¥', // 人民币
USD: '$', // 美元
HKD: 'HK$', // 港币
EUR: '€' // 欧元
}
/**
* 币种完整信息映射
* @description 币种代码到完整信息的映射(用于多币种模式)
*/
export const CURRENCY_MAP = {
CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
USD: { label: '美元', symbol: '$', value: 'USD' },
HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
EUR: { label: '欧元', symbol: '€', value: 'EUR' }
}
/**
* 根据 form_sn 获取模版配置
* @param {string} formSn - 产品 API 返回的 form_sn 字段
* @returns {Object|null} 模版配置对象,未找到返回 null
*
* @example
* const config = getTemplateConfig('life-insurance-wiop3e')
* // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
*/
export function getTemplateConfig(formSn) {
if (!formSn) {
console.warn('[plan-templates] form_sn 为空')
return null
}
const config = PLAN_TEMPLATES[formSn]
if (!config) {
console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
return null
}
return config
}
/**
* 获取币种符号
* @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
* @returns {string} 币种符号
*
* @example
* const symbol = getCurrencySymbol('USD') // 返回: '$'
*/
export function getCurrencySymbol(currencyCode) {
return CURRENCY_SYMBOLS[currencyCode] || '¥'
}
/**
* 计划书模版配置
*
* @description 定义产品 form_sn 到模版组件和配置的映射关系
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
* 计划书模版配置映射
* @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
*
* @example
* // 产品 API 返回
* {
* id: 1,
* product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
* form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
* }
*/
// 基础提交字段映射(适用于人寿/重疾等通用表单)
const baseSubmitMapping = {
customer_name: { api_field: 'customer_name' },
gender: { api_field: 'customer_gender' },
birthday: { api_field: 'customer_birthday' },
smoker: { api_field: 'smoking_status' },
coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
payment_period: { api_field: 'payment_years' },
total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
}
// 人寿/重疾基础表单 Schema(通用保障类)
const protectionFormSchema = {
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
]
}
// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
const savingsSubmitMapping = {
...baseSubmitMapping,
withdrawal_enabled: { api_field: 'allow_reduce_amount' },
withdrawal_mode: { api_field: 'withdrawal_option' },
withdrawal_method: { api_field: 'withdrawal_method' },
annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
annual_increase_percentage: { api_field: 'annual_increase_percentage' },
withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
withdrawal_period_specified: { api_field: 'withdrawal_period' },
withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
withdrawal_period_fixed: { api_field: 'withdrawal_period' }
}
// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
const savingsFormSchema = {
// 基础字段:非提取计划部分
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
],
// 提取计划字段:由 withdrawal_plan 开关控制
withdrawal_fields: [
{ id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
{ id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
{ id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_withdrawal_amount', key: 'annual_withdrawal_amount', type: 'amount', label: '每年提取金额', placeholder: '请输入每年提取金额', input_label: '请输入每年提取金额', required: true, currency_from: 'withdrawal_plan.default_currency', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_period_specified', key: 'withdrawal_period_specified', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
{ id: 'withdrawal_period_fixed', key: 'withdrawal_period_fixed', type: 'select', label: '按年岁:提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }
],
// 提取模式切换时的清空逻辑,避免脏字段影响提交
reset_map: {
withdrawal_mode: {
'最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
'指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
}
}
}
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD', // 币种:USD/CNY/HKD/EUR
payment_periods: [
// 缴费年期选项
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 }, // 年龄范围
insurance_period: '终身', // 保险期间
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 人寿保险产品 - WIOP3
'life-insurance-wiop3': {
name: 'WIOP3 - 盈传创富保障计划 3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: [
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MPC
'critical-illness-mpc': {
name: 'MPC 守护无间重疾',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC PRO
'critical-illness-mbc-pro': {
name: 'MBC PRO 活跃人生重疾保 PRO',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC2
'critical-illness-mbc2': {
name: 'MBC2 活跃人生重疾保 2',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 提取计划配置
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
}
/**
* 全局功能开关
* @description 用于控制实验性功能或未来扩展功能的开关
*
* @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
*/
export const FEATURE_FLAGS = {
/**
* 多币种切换功能
* @description false: 方案 1 - 固定币种(当前实现)
* true: 方案 2 - 支持多币种切换(未来扩展)
* @type {boolean}
*/
MULTI_CURRENCY_ENABLED: false
}
/**
* 币种符号映射
* @description 币种代码到符号的映射关系
*/
export const CURRENCY_SYMBOLS = {
CNY: '¥', // 人民币
USD: '$', // 美元
HKD: 'HK$', // 港币
EUR: '€' // 欧元
}
/**
* 币种完整信息映射
* @description 币种代码到完整信息的映射(用于多币种模式)
*/
export const CURRENCY_MAP = {
CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
USD: { label: '美元', symbol: '$', value: 'USD' },
HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
EUR: { label: '欧元', symbol: '€', value: 'EUR' }
}
/**
* 根据 form_sn 获取模版配置
* @param {string} formSn - 产品 API 返回的 form_sn 字段
* @returns {Object|null} 模版配置对象,未找到返回 null
*
* @example
* const config = getTemplateConfig('life-insurance-wiop3e')
* // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
*/
export function getTemplateConfig(formSn) {
if (!formSn) {
console.warn('[plan-templates] form_sn 为空')
return null
}
const config = PLAN_TEMPLATES[formSn]
if (!config) {
console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
return null
}
return config
}
/**
* 获取币种符号
* @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
* @returns {string} 币种符号
*
* @example
* const symbol = getCurrencySymbol('USD') // 返回: '$'
*/
export function getCurrencySymbol(currencyCode) {
return CURRENCY_SYMBOLS[currencyCode] || '¥'
}
/**
* 计划书模版配置
*
* @description 定义产品 form_sn 到模版组件和配置的映射关系
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
* 计划书模版配置映射
* @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
*
* @example
* // 产品 API 返回
* {
* id: 1,
* product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
* form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
* }
*/
// 基础提交字段映射(适用于人寿/重疾等通用表单)
const baseSubmitMapping = {
customer_name: { api_field: 'customer_name' },
gender: { api_field: 'customer_gender' },
birthday: { api_field: 'customer_birthday' },
smoker: { api_field: 'smoking_status' },
coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
payment_period: { api_field: 'payment_years' },
total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
}
// 人寿/重疾基础表单 Schema(通用保障类)
const protectionFormSchema = {
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
]
}
// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
const savingsSubmitMapping = {
...baseSubmitMapping,
withdrawal_enabled: { api_field: 'allow_reduce_amount' },
withdrawal_mode: { api_field: 'withdrawal_option' },
withdrawal_method: { api_field: 'withdrawal_method' },
annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
annual_increase_percentage: { api_field: 'annual_increase_percentage' },
withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
withdrawal_period_specified: { api_field: 'withdrawal_period' },
withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
withdrawal_period_fixed: { api_field: 'withdrawal_period' }
}
// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
const savingsFormSchema = {
// 基础字段:非提取计划部分
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
],
// 提取计划字段:由 withdrawal_plan 开关控制
withdrawal_fields: [
{ id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
{ id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
{ id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_withdrawal_amount', key: 'annual_withdrawal_amount', type: 'amount', label: '每年提取金额', placeholder: '请输入每年提取金额', input_label: '请输入每年提取金额', required: true, currency_from: 'withdrawal_plan.default_currency', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_period_specified', key: 'withdrawal_period_specified', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
{ id: 'withdrawal_period_fixed', key: 'withdrawal_period_fixed', type: 'select', label: '按年岁:提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }
],
// 提取模式切换时的清空逻辑,避免脏字段影响提交
reset_map: {
withdrawal_mode: {
'最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
'指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
}
}
}
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD', // 币种:USD/CNY/HKD/EUR
payment_periods: [
// 缴费年期选项
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 }, // 年龄范围
insurance_period: '终身', // 保险期间
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 人寿保险产品 - WIOP3
'life-insurance-wiop3': {
name: 'WIOP3 - 盈传创富保障计划 3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: [
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MPC
'critical-illness-mpc': {
name: 'MPC 守护无间重疾',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC PRO
'critical-illness-mbc-pro': {
name: 'MBC PRO 活跃人生重疾保 PRO',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC2
'critical-illness-mbc2': {
name: 'MBC2 活跃人生重疾保 2',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 提取计划配置
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
}
/**
* 全局功能开关
* @description 用于控制实验性功能或未来扩展功能的开关
*
* @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
*/
export const FEATURE_FLAGS = {
/**
* 多币种切换功能
* @description false: 方案 1 - 固定币种(当前实现)
* true: 方案 2 - 支持多币种切换(未来扩展)
* @type {boolean}
*/
MULTI_CURRENCY_ENABLED: false
}
/**
* 币种符号映射
* @description 币种代码到符号的映射关系
*/
export const CURRENCY_SYMBOLS = {
CNY: '¥', // 人民币
USD: '$', // 美元
HKD: 'HK$', // 港币
EUR: '€' // 欧元
}
/**
* 币种完整信息映射
* @description 币种代码到完整信息的映射(用于多币种模式)
*/
export const CURRENCY_MAP = {
CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
USD: { label: '美元', symbol: '$', value: 'USD' },
HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
EUR: { label: '欧元', symbol: '€', value: 'EUR' }
}
/**
* 根据 form_sn 获取模版配置
* @param {string} formSn - 产品 API 返回的 form_sn 字段
* @returns {Object|null} 模版配置对象,未找到返回 null
*
* @example
* const config = getTemplateConfig('life-insurance-wiop3e')
* // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
*/
export function getTemplateConfig(formSn) {
if (!formSn) {
console.warn('[plan-templates] form_sn 为空')
return null
}
const config = PLAN_TEMPLATES[formSn]
if (!config) {
console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
return null
}
return config
}
/**
* 获取币种符号
* @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
* @returns {string} 币种符号
*
* @example
* const symbol = getCurrencySymbol('USD') // 返回: '$'
*/
export function getCurrencySymbol(currencyCode) {
return CURRENCY_SYMBOLS[currencyCode] || '¥'
}
/**
* 计划书模版配置
*
* @description 定义产品 form_sn 到模版组件和配置的映射关系
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
* 计划书模版配置映射
* @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
*
* @example
* // 产品 API 返回
* {
* id: 1,
* product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
* form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
* }
*/
// 基础提交字段映射(适用于人寿/重疾等通用表单)
const baseSubmitMapping = {
customer_name: { api_field: 'customer_name' },
gender: { api_field: 'customer_gender' },
birthday: { api_field: 'customer_birthday' },
smoker: { api_field: 'smoking_status' },
coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
payment_period: { api_field: 'payment_years' },
total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
}
// 人寿/重疾基础表单 Schema(通用保障类)
const protectionFormSchema = {
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
]
}
// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
const savingsSubmitMapping = {
...baseSubmitMapping,
withdrawal_enabled: { api_field: 'allow_reduce_amount' },
withdrawal_mode: { api_field: 'withdrawal_option' },
withdrawal_method: { api_field: 'withdrawal_method' },
annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
annual_increase_percentage: { api_field: 'annual_increase_percentage' },
withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
withdrawal_period_specified: { api_field: 'withdrawal_period' },
withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
withdrawal_period_fixed: { api_field: 'withdrawal_period' }
}
// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
const savingsFormSchema = {
// 基础字段:非提取计划部分
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
],
// 提取计划字段:由 withdrawal_plan 开关控制
withdrawal_fields: [
{ id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
{ id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
{ id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_withdrawal_amount', key: 'annual_withdrawal_amount', type: 'amount', label: '每年提取金额', placeholder: '请输入每年提取金额', input_label: '请输入每年提取金额', required: true, currency_from: 'withdrawal_plan.default_currency', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_period_specified', key: 'withdrawal_period_specified', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
{ id: 'withdrawal_period_fixed', key: 'withdrawal_period_fixed', type: 'select', label: '按年岁:提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }
],
// 提取模式切换时的清空逻辑,避免脏字段影响提交
reset_map: {
withdrawal_mode: {
'最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
'指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
}
}
}
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD', // 币种:USD/CNY/HKD/EUR
payment_periods: [
// 缴费年期选项
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 }, // 年龄范围
insurance_period: '终身', // 保险期间
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 人寿保险产品 - WIOP3
'life-insurance-wiop3': {
name: 'WIOP3 - 盈传创富保障计划 3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: [
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MPC
'critical-illness-mpc': {
name: 'MPC 守护无间重疾',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC PRO
'critical-illness-mbc-pro': {
name: 'MBC PRO 活跃人生重疾保 PRO',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC2
'critical-illness-mbc2': {
name: 'MBC2 活跃人生重疾保 2',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 提取计划配置
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
/**
* 测试计划书-智享未来2
* @added 2026-02-14T14:12:31.658Z
* @source docs/to-parse/测试计划书-智享未来2.md
*/
'savings-2-148b3acd': {
name: '测试计划书-智享未来2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: ["整付","3年","5年"],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ["年龄指定金额","最高固定金额"],
withdrawal_periods: ["1年","3年","5年","10年"]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
}}
/**
* 全局功能开关
* @description 用于控制实验性功能或未来扩展功能的开关
*
* @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
*/
export const FEATURE_FLAGS = {
/**
* 多币种切换功能
* @description false: 方案 1 - 固定币种(当前实现)
* true: 方案 2 - 支持多币种切换(未来扩展)
* @type {boolean}
*/
MULTI_CURRENCY_ENABLED: false
}
/**
* 币种符号映射
* @description 币种代码到符号的映射关系
*/
export const CURRENCY_SYMBOLS = {
CNY: '¥', // 人民币
USD: '$', // 美元
HKD: 'HK$', // 港币
EUR: '€' // 欧元
}
/**
* 币种完整信息映射
* @description 币种代码到完整信息的映射(用于多币种模式)
*/
export const CURRENCY_MAP = {
CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
USD: { label: '美元', symbol: '$', value: 'USD' },
HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
EUR: { label: '欧元', symbol: '€', value: 'EUR' }
}
/**
* 根据 form_sn 获取模版配置
* @param {string} formSn - 产品 API 返回的 form_sn 字段
* @returns {Object|null} 模版配置对象,未找到返回 null
*
* @example
* const config = getTemplateConfig('life-insurance-wiop3e')
* // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
*/
export function getTemplateConfig(formSn) {
if (!formSn) {
console.warn('[plan-templates] form_sn 为空')
return null
}
const config = PLAN_TEMPLATES[formSn]
if (!config) {
console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
return null
}
return config
}
/**
* 获取币种符号
* @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
* @returns {string} 币种符号
*
* @example
* const symbol = getCurrencySymbol('USD') // 返回: '$'
*/
export function getCurrencySymbol(currencyCode) {
return CURRENCY_SYMBOLS[currencyCode] || '¥'
}
# 字段条件显示系统扩展计划
## 背景与目标
### 当前问题
- `show_when` 只支持简单的等于比较 (`equals`)
- 不支持 OR 条件、嵌套条件
- 不支持不等于、大于、小于等操作符
- 隐藏字段的清理逻辑分散在 `reset_map`
- 提交时可能包含隐藏字段的脏数据
### 目标
构建一个声明式的条件规则系统,支持复杂逻辑同时保持配置可读性。
## 技术方案:方案 B - 条件规则引擎
### 核心文件变更
| 文件 | 操作 | 说明 |
|------|------|------|
| `src/config/plan-conditions.js` | 新建 | 条件操作符和评估引擎 |
| `src/composables/useFieldDependencies.js` | 修改 | 集成新的条件评估器 |
| `src/composables/usePlanSubmit.js` | 新建 | 提交时字段过滤逻辑 |
| `src/config/plan-templates.js` | 修改 | 迁移现有配置到新格式 |
| `src/composables/__tests__/plan-conditions.test.js` | 新建 | 条件引擎单元测试 |
---
## 阶段 1:核心条件引擎
### 任务 1.1 创建条件操作符定义
- [x] 创建 `src/config/plan-conditions.js`
- [x] 定义比较操作符:`eq`, `ne`, `gt`, `gte`, `lt`, `lte`
- [x] 定义集合操作符:`in`, `nin`
- [x] 定义字符串操作符:`contains`, `startsWith`, `matches`
- [x] 定义布尔操作符:`truthy`, `falsy`, `empty`, `notEmpty`
- [x] 编写单元测试验证操作符正确性
### 任务 1.2 实现条件评估函数
- [x] 实现 `evaluateCondition(condition, formData)` 函数
- [x] 支持简单条件:`{ field, op, value }`
- [x] 支持 AND 逻辑:`{ and: [...] }`
- [x] 支持 OR 逻辑:`{ or: [...] }`
- [x] 支持 NOT 逻辑:`{ not: {...} }`
- [x] 支持嵌套条件组合
- [x] 编写单元测试覆盖各种条件场景
### 任务 1.3 向后兼容处理
- [x] 支持旧格式 `show_when: { field: 'x', equals: 'y' }`
- [x] 支持旧格式数组 `show_when: [{ field: 'x', equals: 'y' }]`
- [x] 自动转换为新格式
---
## 阶段 2:清理机制
### 任务 2.1 字段清理规则
- [x] 在字段定义中添加 `clear_when_hidden` 属性
- [x] 实现 `clear_when_hidden: true` 自动清空
- [x] 实现 `clear_when_hidden: false` 保留值
- [x] 实现级联清理 `clear_when_hidden: { clear_dependents: [...] }`
### 任务 2.2 提交时字段过滤
- [x] 实现 `filterHiddenFields(formData, visibleFields)` 函数
- [x] 只提交当前可见的字段
- [x] 保持 API 兼容性
### 任务 2.3 更新 useFieldDependencies
- [x] 集成新的条件评估引擎
- [x] 实现字段隐藏时的自动清理
- [x] 更新 `isFieldVisible` 使用新引擎
- [x] 保持 API 兼容性
---
## 阶段 3:配置迁移
### 任务 3.1 迁移储蓄类模板
- [x] 迁移 `savingsFormSchema.withdrawal_fields` 到新格式
- [x] 删除 `reset_map`,使用 `clear_when_hidden` 替代
- [x] 验证功能正常(测试通过 160/160)
### 任务 3.2 迁移保障类模板
- [x] 检查 `protectionFormSchema` 是否需要条件
- [x] 按需添加条件规则(保障类暂无条件需求,无需迁移)
### 任务 3.3 更新文档解析工具 ⚠️ 重要
- [x] 更新 `src/utils/parsers/config-generator.js` 生成新格式配置
- [x] 修改 `show_when` 生成逻辑,使用 `{ field, op, value }` 格式
- [x] 停止生成 `reset_map`(已忽略)
- [x] 测试通过(160/160)
**说明**:MCP 文档解析服务保持不变,仅更新配置生成器输出格式。
### 任务 3.4 更新文档
- [x] 更新 `plan-templates.js` 顶部的使用说明
- [x] 添加条件规则配置示例(已在 plan-templates.js 注释中说明)
- [x] 添加常见场景示例(已在 savingsFormSchema 中实现)
---
## 阶段 4:测试与验证
### 任务 4.1 单元测试
- [ ] 条件操作符测试覆盖率 > 90%
- [ ] 条件评估引擎测试覆盖率 > 90%
- [ ] useFieldDependencies 测试更新
### 任务 4.2 集成测试
- [ ] 测试储蓄类产品完整流程
- [ ] 测试字段显示/隐藏切换
- [ ] 测试提交时字段过滤
- [ ] 测试向后兼容性
### 任务 4.3 真机验证
- [ ] 微信开发者工具验证
- [ ] 检查性能影响
- [ ] 检查内存占用
---
## 配置格式示例
### 新格式示例
```javascript
// 简单条件
show_when: { field: 'smoker', op: 'eq', value: '是' }
// 多条件 AND
show_when: {
and: [
{ field: 'smoker', op: 'eq', value: '是' },
{ field: 'age', op: 'gte', value: 30 }
]
}
// OR 条件
show_when: {
or: [
{ field: 'smoker', op: 'eq', value: '是' },
{ field: 'age', op: 'gt', value: 50 }
]
}
// 嵌套条件
show_when: {
and: [
{ field: 'product_type', op: 'in', value: ['A', 'B'] },
{
or: [
{ field: 'coverage', op: 'gte', value: 100000 },
{ field: 'payment_period', op: 'eq', value: '20年' }
]
}
]
}
// 清理规则
{
id: 'withdrawal_amount',
show_when: { field: 'withdrawal_mode', op: 'eq', value: '指定提取金额' },
clear_when_hidden: true // 隐藏时清空
}
```
### 旧格式(保持兼容)
```javascript
// 旧格式仍然支持
show_when: { field: 'withdrawal_mode', equals: '指定提取金额' }
show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }]
```
---
## 风险与注意事项
1. **向后兼容**:必须保持现有配置格式可用
2. **性能**:条件评估不能影响表单响应速度
3. **可读性**:配置文件需要保持可理解性
4. **测试覆盖**:每个操作符都需要充分测试
---
## 进度追踪
| 阶段 | 状态 | 完成时间 |
|------|------|---------|
| 阶段 1:核心引擎 | ✅ 完成 | 2026-02-15 |
| 阶段 2:清理机制 | ✅ 完成 | 2026-02-15 |
| 阶段 3:配置迁移 | ✅ 完成 | 2026-02-15 |
| 阶段 4:测试验证 | 🔄 待真机验证 | - |
---
**创建时间**: 2026-02-15
**预计工期**: 3-4 天
**维护者**: Claude Code
......@@ -9,8 +9,8 @@
## 📊 总体进度
- [x] **第 1 步**: 目标与输出定义
- [ ] **第 2 步**: 文本抽取管线
- [ ] **第 3 步**: 结构化解析与校验
- [x] **第 2 步**: 文本抽取管线
- [x] **第 3 步**: 结构化解析与校验
- [x] **第 4 步**: 生成与写入稳态化
- [x] **第 5 步**: 测试与验证
- [x] **第 6 步**: 运营与审计
......
......@@ -29,15 +29,15 @@
"postinstall": "weapp-tw patch",
"lint": "eslint --ext .js,.vue src",
"test": "vitest run",
"api:generate": "node scripts/generateApiFromOpenAPI.js",
"changelog:check": "bash scripts/check-changelog.sh 7",
"changelog:check:30": "bash scripts/check-changelog.sh 30",
"changelog:check:all": "bash scripts/check-changelog.sh 0",
"api:generate": "node scripts/api-generator/generateApiFromOpenAPI.js",
"changelog:check": "bash scripts/changelog/check-changelog.sh 7",
"changelog:check:30": "bash scripts/changelog/check-changelog.sh 30",
"changelog:check:all": "bash scripts/changelog/check-changelog.sh 0",
"prepare": "husky",
"parse:docs": "node scripts/parse-docs.js",
"parse:docs:list": "node scripts/parse-docs.js --list",
"parse:docs:status": "node scripts/parse-docs.js --status",
"parse:docs:file": "node scripts/parse-docs.js --file=",
"parse:docs": "node scripts/doc-parser/parse-docs.js",
"parse:docs:list": "node scripts/doc-parser/parse-docs.js --list",
"parse:docs:status": "node scripts/doc-parser/parse-docs.js --status",
"parse:docs:file": "node scripts/doc-parser/parse-docs.js --file=",
"release": "standard-version"
},
"browserslist": [
......
# Scripts 目录说明
> 最后更新: 2026-02-15
本目录包含项目的各类开发工具脚本,按功能分组存放在子目录中。
## 📁 目录结构
```
scripts/
├── api-generator/ # API 代码生成工具
├── doc-parser/ # 文档解析工具
├── changelog/ # CHANGELOG 管理工具
└── README.md # 本说明文件
```
---
## 🚀 快速使用
### API 代码生成
```bash
pnpm api:generate # 从 OpenAPI 生成 API 接口
```
### 文档解析
```bash
pnpm parse:docs # 解析待处理文档
pnpm parse:docs:list # 查看待处理文档
pnpm parse:docs:status # 查看解析状态
```
### CHANGELOG 检查
```bash
pnpm changelog:check # 检查最近 7 天的漏记
pnpm changelog:check:30 # 检查最近 30 天的漏记
```
---
## 📦 子目录详情
| 目录 | 用途 | 相关 npm scripts |
|------|------|-----------------|
| `api-generator/` | 从 OpenAPI 规范生成 JavaScript API 代码 | `api:generate` |
| `doc-parser/` | 解析 PDF/DOCX 等文档,智能提取配置字段 | `parse:docs:*` |
| `changelog/` | 检查和归档 CHANGELOG 记录 | `changelog:check:*` |
---
## 🔗 相关文档
- [API 生成指南](./api-generator/GUIDE.md)
- [文档解析快速开始](./doc-parser/QUICKSTART.md)
- [CHANGELOG 规范](../docs/CHANGELOG.md)
# API 代码生成工具
从 OpenAPI 规范自动生成 JavaScript API 接口代码。
## 📁 文件说明
| 文件 | 说明 | 使用频率 |
|------|------|---------|
| `generateApiFromOpenAPI.js` | 主脚本 - 从 OpenAPI 生成 API 代码 | ⭐⭐⭐ 高频 |
| `apiDiff.js` | API 对比工具 - 检测 API 变更和破坏性改动 | ⭐⭐ 中频 |
| `test-generate.js` | 测试脚本 - 验证生成的 API 文件是否正确 | ⭐ 低频 |
| `API_GUIDE.md` | 使用指南 - 详细的 API 维护工作流文档 | 📖 文档 |
## 🚀 使用方式
```bash
# 生成所有 API 接口
pnpm api:generate
# 生成指定模块
pnpm api:generate -- --module=user
```
## 📖 详细文档
参见 [API_GUIDE.md](./API_GUIDE.md) 了解完整的 API 维护工作流。
## 🔧 工作原理
1. 扫描 `docs/api-specs/` 目录下的 OpenAPI 文档
2. 解析 Markdown 文件中的 YAML 规范
3. 检测 API 变更(新增、修改、删除)
4. 生成符合项目规范的 JavaScript API 代码
5. 保存到 `src/api/` 目录
## 📝 生成代码示例
输入 (OpenAPI YAML):
```yaml
paths:
/user/info:
get:
summary: 获取用户信息
parameters:
- name: user_id
in: query
```
输出 (JavaScript):
```javascript
/**
* 获取用户信息
* @param {Object} params
* @param {string} params.user_id - 用户ID
*/
export const getUserInfoAPI = (params) => {
return buildApiUrl('user_info', params)
}
```
# CHANGELOG 管理工具
检查和归档项目的 CHANGELOG 记录。
## 📁 文件说明
| 文件 | 说明 | 使用频率 |
|------|------|---------|
| `check-changelog.sh` | 漏记检查 - 扫描 git 提交,检查 CHANGELOG 漏记 | ⭐⭐⭐ 高频 |
| `archive-changelog.sh` | 归档脚本 - 当记录超过 20 条时自动归档 | ⭐ 低频 |
## 🚀 使用方式
### 检查漏记
```bash
# 检查最近 7 天(默认)
pnpm changelog:check
# 检查最近 30 天
pnpm changelog:check:30
# 检查所有提交
pnpm changelog:check:all
# 直接运行
./scripts/changelog/check-changelog.sh 7
```
### 归档旧记录
```bash
# 直接运行归档脚本
./scripts/changelog/archive-changelog.sh
```
## 📋 检查输出示例
```
======================================
CHANGELOG 漏记检查工具
======================================
检查范围: 最近 7 天
[1/4] 正在获取 git 提交记录...
找到 5 个提交
[2/4] 正在读取 CHANGELOG 记录...
找到 3 条记录
[3/4] 正在对比...
[4/4] 生成报告...
⚠️ 发现 2 处漏记:
1. feat(plan): 添加计划书表单验证
提交: abc1234 (2026-02-14)
建议: 在 CHANGELOG 中添加此功能记录
2. fix(login): 修复登录跳转问题
提交: def5678 (2026-02-13)
建议: 在 CHANGELOG 中添加此修复记录
```
## 🔧 归档规则
`docs/CHANGELOG.md` 记录数超过 20 条时:
1. 自动将旧记录移动到 `docs/changelog-archive/`
2. 主文件只保留最近 20 条记录
3. 归档文件以日期命名:`CHANGELOG-archive-YYYYMMDD.md`
## 📝 最佳实践
1. **每次提交后检查**:运行 `pnpm changelog:check` 确保没有漏记
2. **及时记录**:在提交代码时同步更新 CHANGELOG
3. **定期归档**:每月运行一次归档脚本
# 文档解析工具
从 PDF、DOCX 等文档中智能提取配置字段,自动生成计划书模板配置。
## 📁 文件说明
| 文件 | 说明 | 使用频率 |
|------|------|---------|
| `parse-docs.js` | 主脚本 - 文档解析和配置生成 | ⭐⭐⭐ 高频 |
| `smart-field-extractor.js` | 智能字段提取器 - 从文档中提取表单字段 | ⭐⭐ 中频 |
| `product-splitter.js` | 产品分割器 - 识别和分割多产品文档 | ⭐⭐ 中频 |
| `parse-config.js` | 配置文件 - markitdown 和 AI 服务配置 | 📋 配置 |
| `parse-docs.test.js` | 测试文件 - 单元测试 | 🧪 测试 |
| `QUICKSTART.md` | 快速开始指南 | 📖 文档 |
## 🚀 使用方式
```bash
# 解析所有待处理文档
pnpm parse:docs
# 查看待处理文档列表
pnpm parse:docs:list
# 查看解析状态
pnpm parse:docs:status
# 解析指定文件
pnpm parse:docs -- --file=产品说明书.pdf
# 应用审核通过的配置
pnpm parse:docs -- --apply=计划书模版4
# 预览应用配置(不实际修改)
pnpm parse:docs -- --apply=计划书模版4 --dry-run
```
## 📖 详细文档
参见 [QUICKSTART.md](./QUICKSTART.md) 了解完整的快速开始指南。
## 🔧 工作原理
1. 扫描 `docs/to-parse/` 目录下的待处理文档
2. 使用 markitdown 将文档转换为 Markdown
3. 调用 AI 服务提取配置字段
4. 生成可审核的配置文件
5. 审核通过后应用到 `src/config/plan-templates.js`
## 📝 支持的文档格式
- PDF (`.pdf`)
- Word (`.doc`, `.docx`)
- 文本 (`.txt`, `.md`)
## 🤖 AI 配置
`.env` 文件中配置 AI 服务:
```env
# markitdown 服务 URL(可选)
MARKITDOWN_URL=http://localhost:8000/convert
# AI 服务配置(用于智能字段提取)
AI_SERVICE_URL=your_ai_service_url
AI_API_KEY=your_api_key
```
## 📋 输出示例
解析后会生成:
- `docs/parsed/产品名称.json` - 解析结果
- `docs/parsed/产品名称.audit.md` - 审核报告
......@@ -15,6 +15,12 @@
*
* # 查看待处理文档
* npm run parse:docs -- --list
*
* # 应用审核通过的配置
* npm run parse:docs -- --apply=计划书模版4
*
* # 预览应用配置(不实际修改)
* npm run parse:docs -- --apply=计划书模版4 --dry-run
*/
import crypto from 'crypto'
import fs from 'fs'
......@@ -976,17 +982,27 @@ ${code.trim()}
## 📋 审核后操作
### 确认无误
### 方法 1:自动应用(推荐)
\`\`\`bash
# 预览变更(不实际修改)
pnpm parse:docs -- --apply=${baseFileName} --dry-run
# 确认无误后,正式应用
pnpm parse:docs -- --apply=${baseFileName}
# 说明:
# 1. 自动提取配置代码并插入到 src/config/plan-templates.js
# 2. 自动创建备份文件(docs/parsed-backup/)
# 3. 自动将审核文件移动到 docs/parse-audit/approved/
\`\`\`
### 方法 2:手动操作
\`\`\`bash
# 1. 移动到 approved 目录
mv docs/parse-audit/pending/${baseFileName}/${auditFileName} \\
docs/parse-audit/approved/
# 2. 合并到正式配置
# 手动复制或使用工具合并到 src/config/plan-templates.js
# 3. 删除待审核文件(可选)
rm docs/parse-audit/pending/${baseFileName}/${auditFileName}
# 2. 手动复制"生成配置片段"到 src/config/plan-templates.js
\`\`\`
### 需要修改
......@@ -1402,6 +1418,189 @@ function rollbackConfigFile(backupFile) {
return true
}
/**
* 从审核文件应用配置到 plan-templates.js
*
* @description 读取审核 markdown 文件,提取配置代码,插入到配置文件中
* @param {string} auditFileName - 审核文件名(不含路径,如 "计划书模版4")
* @param {Object} options - 选项
* @param {boolean} options.dry_run - 是否仅预览
* @returns {Object} 应用结果
*/
function applyAuditFile(auditFileName, options = {}) {
const PENDING_DIR = path.resolve(process.cwd(), 'docs/parse-audit/pending')
const APPROVED_DIR = path.resolve(process.cwd(), 'docs/parse-audit/approved')
// 1. 查找审核文件
let auditFile = null
let sourceDir = null
// 先在 pending 目录查找
const pendingDirs = fs.existsSync(PENDING_DIR) ? fs.readdirSync(PENDING_DIR) : []
for (const dir of pendingDirs) {
const dirPath = path.join(PENDING_DIR, dir)
if (fs.statSync(dirPath).isDirectory()) {
const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.md'))
for (const file of files) {
// 匹配文件名或目录名
const normalizedName = dir.replace(/\s+/g, '').toLowerCase()
const normalizedInput = auditFileName.replace(/\s+/g, '').toLowerCase()
if (normalizedName.includes(normalizedInput) || normalizedInput.includes(normalizedName)) {
auditFile = path.join(dirPath, file)
sourceDir = PENDING_DIR
break
}
}
}
if (auditFile) break
}
// 如果 pending 没找到,在 approved 目录查找
if (!auditFile && fs.existsSync(APPROVED_DIR)) {
const approvedFiles = fs.readdirSync(APPROVED_DIR).filter(f => f.endsWith('.md'))
for (const file of approvedFiles) {
// 从文件名提取产品名(格式:YYYY-MM-DD-产品名.md)
const match = file.match(/^\d{4}-\d{2}-\d{2}-(.+)\.md$/)
if (match) {
const normalizedName = match[1].replace(/\s+/g, '').toLowerCase()
const normalizedInput = auditFileName.replace(/\s+/g, '').toLowerCase()
if (normalizedName.includes(normalizedInput) || normalizedInput.includes(normalizedName)) {
auditFile = path.join(APPROVED_DIR, file)
sourceDir = APPROVED_DIR
break
}
}
}
}
if (!auditFile) {
console.error("❌ 找不到审核文件: " + auditFileName)
console.log(" 搜索目录:")
console.log(" - docs/parse-audit/pending/")
console.log(" - docs/parse-audit/approved/")
return { ok: false, reason: 'file_not_found' }
}
console.log("\n📄 找到审核文件: " + auditFile)
// 2. 读取审核文件内容
const content = fs.readFileSync(auditFile, 'utf-8')
// 3. 提取配置代码片段
const configMatch = content.match(/## 🧩 生成配置片段\s*\n+```javascript\s*\n([\s\S]*?)```/)
if (!configMatch) {
console.error("❌ 无法从审核文件中提取配置代码")
return { ok: false, reason: 'config_not_found' }
}
const configCode = configMatch[1].trim()
console.log("\n📝 提取的配置代码:")
console.log("-".repeat(40))
console.log(configCode)
console.log("-".repeat(40))
// 4. 提取 form_sn 用于去重检查
const formSnMatch = configCode.match(/'([^']+)':\s*\{/)
const formSn = formSnMatch ? formSnMatch[1] : null
if (!formSn) {
console.error("❌ 无法从配置代码中提取 form_sn")
return { ok: false, reason: 'form_sn_not_found' }
}
console.log("\n🔑 form_sn: " + formSn)
// 5. 读取现有配置文件
const existingContent = fs.readFileSync(CONFIG_FILE, 'utf-8')
// 检查是否已存在
if (existingContent.includes(`'${formSn}':`)) {
console.error("❌ 配置文件中已存在 form_sn: " + formSn)
console.log(" 如需更新,请先手动删除旧配置")
return { ok: false, reason: 'duplicate', formSn }
}
// 6. 找到插入位置(PLAN_TEMPLATES 对象的结束位置)
// 查找最后一个产品配置的结束位置
const insertPattern = /(\n\s*'\w+[^']+':\s*\{[\s\S]*?\n\s*\}\s*,?\s*)(\n\})/
const match = existingContent.match(insertPattern)
if (!match) {
console.error("❌ 无法定位插入位置")
return { ok: false, reason: 'insert_not_found' }
}
// 7. 构建新配置(确保有逗号)
let newConfigEntry = configCode
// 确保配置以逗号结尾
if (!newConfigEntry.trimEnd().endsWith(',')) {
newConfigEntry = newConfigEntry.trimEnd() + ','
}
// 8. 插入配置
const insertPosition = match.index + match[1].length
const updatedContent =
existingContent.slice(0, insertPosition) +
'\n\n' +
newConfigEntry +
existingContent.slice(insertPosition)
if (options.dry_run) {
console.log("\n🧪 dry-run 模式,变更预览:")
console.log("-".repeat(40))
console.log("将插入以下配置:")
console.log(newConfigEntry)
console.log("-".repeat(40))
return { ok: true, dry_run: true, formSn }
}
// 9. 备份并写入
let backupFile = null
if (fs.existsSync(CONFIG_FILE)) {
ensureDir(BACKUP_DIR)
backupFile = path.join(BACKUP_DIR, `plan-templates.backup.${Date.now()}.js`)
fs.copyFileSync(CONFIG_FILE, backupFile)
console.log("\n💾 已备份到: " + backupFile)
}
writeFile(CONFIG_FILE, updatedContent)
console.log("\n✅ 配置已更新: " + CONFIG_FILE)
writeBackupLog({
action: 'apply_audit',
backup_file: backupFile,
target_file: CONFIG_FILE,
audit_file: auditFile,
form_sn: formSn,
at: new Date().toISOString()
})
// 10. 移动审核文件到 approved 目录(如果是从 pending 来的)
if (sourceDir === PENDING_DIR) {
ensureDir(APPROVED_DIR)
const fileName = path.basename(auditFile)
const approvedPath = path.join(APPROVED_DIR, fileName)
// 检查目标是否已存在
if (fs.existsSync(approvedPath)) {
console.log("⚠️ approved 目录已存在同名文件,跳过移动")
} else {
fs.renameSync(auditFile, approvedPath)
console.log("📁 审核文件已移动到: " + approvedPath)
// 删除空的 pending 子目录
const pendingSubDir = path.dirname(auditFile)
const remainingFiles = fs.readdirSync(pendingSubDir).filter(f => !f.startsWith('.'))
if (remainingFiles.length === 0) {
fs.rmdirSync(pendingSubDir)
console.log("🗑️ 已删除空目录: " + pendingSubDir)
}
}
}
return { ok: true, formSn, backupFile }
}
function updateConfigFile(newConfigs, options = {}) {
console.log("\n" + "=".repeat(60))
console.log("📝 更新配置文件: " + CONFIG_FILE)
......@@ -1563,9 +1762,16 @@ async function main() {
const listMode = args.includes('--list')
const fileMode = args.find(arg => arg.startsWith('--file='))
const writeMode = args.includes('--write-config')
const dryRunMode = args.includes('--dry-run') || !writeMode
const rollbackMode = args.find(arg => arg.startsWith('--rollback='))
const statusMode = args.includes('--status')
const applyMode = args.find(arg => arg.startsWith('--apply='))
// dry-run 逻辑:
// 1. 如果显式指定 --dry-run,则 dry-run
// 2. 如果是 apply 模式,默认不 dry-run(除非显式指定)
// 3. 如果是解析模式,默认 dry-run(除非显式指定 --write-config)
const explicitDryRun = args.includes('--dry-run')
const dryRunMode = applyMode ? explicitDryRun : (!writeMode && !explicitDryRun || explicitDryRun)
// 检查解析器选择
const parserModeArg = args.find(arg => arg.startsWith('--parser='))
......@@ -1586,6 +1792,11 @@ async function main() {
if (rollbackMode) {
const backupFile = rollbackMode.split('=')[1]
rollbackConfigFile(backupFile)
} else if (applyMode) {
// 从审核文件应用配置
const auditFileName = applyMode.split('=')[1]
const applyOptions = { dry_run: dryRunMode }
applyAuditFile(auditFileName, applyOptions)
} else if (listMode) {
// 列出模式
const docs = getDocsToParse()
......
......@@ -16,6 +16,7 @@
* - GC宏摯家傳承保險計劃- 性別, 年齡, 出生年月日
* - FA 宏浚傳承保障計劃
* - LV2 赤霞珠終身壽險計劃2基本人壽保障選項
* - LV3 长宁終身壽險計劃3
*/
const PRODUCT_TITLE_PATTERNS = [
// 产品代码 + 产品名称 + 可选后缀
......@@ -31,14 +32,17 @@ const PRODUCT_TITLE_PATTERNS = [
/^([^\n]{2,30}?(?:計劃|计划|保障|保险|壽險|壽险)[^\n]*)/gm,
// 产品代码开头的行
/^([A-Z]{2,4}\d?)\s*[-:]\s*([^\n]+)/gm
/^([A-Z]{2,4}\d?)\s*[-:]\s*([^\n]+)/gm,
// 新增:产品代码 + 产品名称 + 数字后缀(如 "LV3 长宁終身壽險計劃3")
/^([A-Z]{2,3}\d?)\s+([^\n]{2,25}?(?:計劃|计划|壽險|壽险)\d?)/gm
]
/**
* 产品代码前缀列表(用于优先匹配)
*/
const PRODUCT_CODE_PREFIXES = [
'GS', 'GC', 'FA', 'LV2', 'LV', 'CR', 'HR', 'PR', 'SR',
'GS', 'GC', 'FA', 'LV2', 'LV3', 'LV', 'CR', 'HR', 'PR', 'SR',
'TR', 'UR', 'WR', 'XR', 'YR', 'ZR'
]
......@@ -62,10 +66,11 @@ export function detectProductCount(content) {
export function findProductTitles(content) {
const products = []
const seenCodes = new Set()
const seenNames = new Set()
// 策略1: 优先匹配产品代码前缀
for (const prefix of PRODUCT_CODE_PREFIXES) {
// 匹配 "GS宏摯傳承保障計劃" 或 "GS 宏摯傳承保障計劃"
// 匹配 "GS宏摯傳承保障計劃" 或 "GS 宏摯傳承保障計劃" 或 "LV3 长宁終身壽險計劃3"
const regex = new RegExp(
`^(${prefix}\\d?)\\s*([\\u4e00-\\u9fa5]+(?:計劃|计划|保障|保险|壽險|壽险)[^\\n]*)`,
'gm'
......@@ -76,9 +81,11 @@ export function findProductTitles(content) {
const code = match[1]
const name = match[2].trim()
// 去重
if (seenCodes.has(code)) continue
// 去重(基于代码或名称)
const nameKey = name.replace(/\s+/g, '').toLowerCase()
if (seenCodes.has(code) || seenNames.has(nameKey)) continue
seenCodes.add(code)
seenNames.add(nameKey)
products.push({
index: match.index,
......@@ -99,15 +106,52 @@ export function findProductTitles(content) {
const fullTitle = match[0].trim()
if (fullTitle.length < 5) continue // 过滤太短的匹配
const code = match[1] || null
const name = match[2] || fullTitle
// 去重
const nameKey = name.replace(/\s+/g, '').toLowerCase()
if (seenNames.has(nameKey)) continue
if (code) seenCodes.add(code)
seenNames.add(nameKey)
products.push({
index: match.index,
code: match[1] || null,
name: match[2] || fullTitle,
code,
name,
fullTitle
})
}
}
// 策略3: 新增 - 识别包含"计划"但不包含产品代码的行(纯计划书名称)
// 适用于标题如 "宏挚传承保障计划" 或 "长宁终身寿险计划3"
if (products.length === 0) {
const planNameRegex = /^([^\n]{2,30}?(?:計劃|计划)[^\n]*)/gm
let match
while ((match = planNameRegex.exec(content)) !== null) {
const fullTitle = match[1].trim()
// 排除太短或包含其他关键词的行
if (fullTitle.length < 5 || fullTitle.includes('選項') || fullTitle.includes('选项')) continue
// 检查是否是产品名称(通常包含"保障"、"保险"、"寿险"等关键词)
if (/(?:保障|保险|壽險|壽险|传承|家传)/.test(fullTitle)) {
const nameKey = fullTitle.replace(/\s+/g, '').toLowerCase()
if (!seenNames.has(nameKey)) {
seenNames.add(nameKey)
products.push({
index: match.index,
code: null,
name: fullTitle.split(/[-—::]/)[0].trim(), // 移除后缀说明
fullTitle
})
}
}
}
}
// 按出现位置排序
products.sort((a, b) => a.index - b.index)
......
......@@ -8,6 +8,107 @@
*/
/**
* 保险缴费年期匹配模式
*
* @description 用于识别各种格式的缴费年期选项
* @constant {Object}
*/
const PAYMENT_PERIOD_PATTERNS = {
// 一次性缴费关键词
singlePayment: ['整付', '趸交', '躉繳', 'lump sum', 'single'],
// 数字+年格式(3年、5年、10年等)
yearlyPattern: /^(\d+)\s*年$/,
// 至X岁格式
untilAgePattern: /^至?\s*(\d+)\s*岁$/,
// 列表项格式(- 3年、• 5年等)
listItemPattern: /^[-•·]\s*(.+)$/
}
/**
* 检查是否为有效的缴费年期选项
*
* @param {string} text - 待检查文本
* @returns {boolean} 是否为有效缴费年期
*/
const isValidPaymentPeriod = (text) => {
const trimmed = text.trim()
// 排除无效关键字
if (trimmed.includes('投保') || trimmed.includes('年龄') || trimmed.includes('年齡')) {
return false
}
// 1. 匹配 "X年" 格式
if (PAYMENT_PERIOD_PATTERNS.yearlyPattern.test(trimmed)) {
return true
}
// 2. 匹配 "至X岁" 格式
if (PAYMENT_PERIOD_PATTERNS.untilAgePattern.test(trimmed)) {
return true
}
// 3. 匹配一次性缴费关键词
if (PAYMENT_PERIOD_PATTERNS.singlePayment.some(kw =>
trimmed.toLowerCase() === kw.toLowerCase()
)) {
return true
}
// 4. 匹配列表项格式(提取内容后递归检查)
const listItemMatch = trimmed.match(PAYMENT_PERIOD_PATTERNS.listItemPattern)
if (listItemMatch) {
return isValidPaymentPeriod(listItemMatch[1])
}
// 5. 通用匹配:包含"年"或"岁"或"交"的短文本(2-10字符)
if (trimmed.length >= 2 && trimmed.length <= 10) {
if (/年|岁|交|付/.test(trimmed)) {
return true
}
}
return false
}
/**
* 标准化缴费年期选项
*
* @param {string} text - 原始文本
* @returns {string} 标准化后的文本
*/
const normalizePaymentPeriod = (text) => {
const trimmed = text.trim()
// 标准化一次性缴费
if (PAYMENT_PERIOD_PATTERNS.singlePayment.some(kw =>
trimmed.toLowerCase() === kw.toLowerCase()
)) {
return '整付'
}
// 标准化 "X年" 格式
const yearlyMatch = trimmed.match(PAYMENT_PERIOD_PATTERNS.yearlyPattern)
if (yearlyMatch) {
return `${yearlyMatch[1]}年`
}
// 标准化 "至X岁" 格式
const ageMatch = trimmed.match(PAYMENT_PERIOD_PATTERNS.untilAgePattern)
if (ageMatch) {
return `至${ageMatch[1]}岁`
}
// 处理列表项格式
const listItemMatch = trimmed.match(PAYMENT_PERIOD_PATTERNS.listItemPattern)
if (listItemMatch) {
return normalizePaymentPeriod(listItemMatch[1])
}
return trimmed
}
/**
* 字段提取规则配置
*
* @description 定义每个字段的匹配规则、优先级和默认值
......@@ -20,13 +121,30 @@ const FIELD_RULES = {
/产品名称[::]\s*([^\n]+)/,
/计划书名称[::]\s*([^\n]+)/,
/Product\s+Name[::]\s*([^\n]+)/i,
/^#\s+(.+)$/m // Markdown 标题
/^#\s+(.+)$/m, // Markdown 标题
// 新增:识别包含"计划"的标题行(如 "LV3 长宁終身壽險計劃3")
// 格式:产品代码 + 中文产品名(包含"計劃/计划")
/^[A-Z]{2,4}\d?\s*[-::]?\s*([^\n]*(?:計劃|计划)[^\n]*)/m,
// 纯产品名称(包含"計劃/计划")- 通常为计划书名称
/^([^\n]{2,30}?(?:計劃|计划)[^\n]*)/m
],
fallback: null, // 必填,无默认值
required: true
required: true,
postProcess: (value) => {
// 处理正则匹配结果(数组)
if (Array.isArray(value) && value.length > 1) {
let name = value[1] || value[0] || ''
// 移除产品代码前缀
name = name.replace(/^[A-Z]{2,4}\d?\s*[-::]?\s*/, '')
// 移除后缀说明(如 "- 性別, 年齡, 出生年月日")
name = name.split(/[-—::]/)[0].trim()
return name || null
}
return value
}
},
// 产品类型
// 产品类型(保险类别)
product_type: {
priority: 2,
patterns: [
......@@ -34,9 +152,21 @@ const FIELD_RULES = {
{
type: 'content_match',
rules: [
{ keywords: ['储蓄', 'saving', '传承', '家传', '红利', '提取'], value: 'savings' },
// 储蓄型产品(新增"储蓄产品"关键字)
{ keywords: ['储蓄产品', '储蓄型', '储蓄', 'saving', '传承', '家传', '红利', '提取'], value: 'savings' },
// 重疾型产品
{ keywords: ['重疾', 'critical', '守护', '严重疾病'], value: 'critical-illness' },
{ keywords: ['人寿', 'life', '创富', '身故保障'], value: 'life-insurance' }
// 人寿型产品
{ keywords: ['人寿', 'life', '创富', '身故保障', '壽險', '壽险'], value: 'life-insurance' }
]
},
// 新增:从标题行推断(如 "LV3 长宁終身壽險計劃3" 中的"壽險")
{
type: 'title_match',
rules: [
{ pattern: /終身壽險|終身寿险|壽險計劃|寿险计划/, value: 'life-insurance' },
{ pattern: /儲蓄計劃|储蓄计划|儲蓄產品|储蓄产品/, value: 'savings' },
{ pattern: /重疾|嚴重疾病/, value: 'critical-illness' }
]
}
],
......@@ -70,25 +200,12 @@ const FIELD_RULES = {
priority: 4,
patterns: [
// 匹配 "年繳保費繳費年期" 或 "缴费年期" 后面的列表
// 策略:匹配到包含 "年" 或 "整付" 的所有行,直到遇到其他关键字
// 策略:使用通用匹配函数识别各种格式的缴费年期选项
{
type: 'smart_list_extract',
startPattern: /(?:年繳保費)?繳費年期[::\s]*\n/,
endKeywords: ['提取', '保險期間', '保险期间', '投保年龄', '投保年齡', '選是', '選項', 'GC宏', 'FA宏', 'LV2'],
itemFilter: (line) => {
const trimmed = line.trim()
// 排除包含"投保年龄"等关键字的行
if (trimmed.includes('投保') || trimmed.includes('年龄') || trimmed.includes('年齡')) {
return false
}
// 精确匹配 "整付" 或 "X年" 格式
return trimmed && (
/^\d+\s*年$/.test(trimmed) ||
trimmed === '整付' ||
/^\d+年$/.test(trimmed) ||
/^[-•·]\s*\d+\s*年$/.test(trimmed) // 支持列表格式 "- 3年"
)
}
itemFilter: (line) => isValidPaymentPeriod(line)
}
],
fallback: ['整付', '3年', '5年'],
......@@ -96,24 +213,17 @@ const FIELD_RULES = {
postProcess: (values) => {
// 过滤并标准化
const normalized = values
.map(v => v.trim())
// 排除包含"投保"等无效关键字
.filter(v => v && !v.includes('投保') && !v.includes('年龄') && !v.includes('年齡'))
.filter(v => v.includes('年') || v.includes('整付'))
.map(v => {
// 提取数字+年格式
const match = v.match(/(\d+)\s*年|整付/i)
if (match) {
return match[0].includes('整付') ? '整付' : `${match[1]}年`
}
return v
})
.map(v => normalizePaymentPeriod(v))
.filter(v => v && isValidPaymentPeriod(v))
// 去重、排序
// 去重、排序(整付放最前,其他按数字排序)
return [...new Set(normalized)].sort((a, b) => {
if (a === '整付') return -1
if (b === '整付') return 1
return parseInt(a) - parseInt(b)
// 提取数字进行排序
const numA = parseInt(a.match(/\d+/)?.[0] || '999')
const numB = parseInt(b.match(/\d+/)?.[0] || '999')
return numA - numB
})
}
},
......@@ -252,6 +362,11 @@ function extractField(content, fieldName) {
patternDesc = `content_match(${pattern.rules.length} rules)`
break
case 'title_match':
match = matchByTitle(content, pattern.rules)
patternDesc = `title_match(${pattern.rules.length} rules)`
break
case 'count_match':
match = matchByCount(content, pattern.rules)
patternDesc = `count_match(${pattern.rules.length} rules)`
......@@ -276,6 +391,11 @@ function extractField(content, fieldName) {
match = extractRange(content, pattern.pattern)
patternDesc = `range_extract`
break
case 'options_extract':
match = extractOptionsFields(content, pattern.keyword)
patternDesc = `options_extract(${pattern.keyword})`
break
}
} else if (pattern instanceof RegExp) {
// 正则表达式匹配
......@@ -331,6 +451,120 @@ function matchByContent(content, rules) {
}
/**
* 通过标题模式匹配
*
* @description 从文档标题或产品名称行中匹配产品类型
* @param {string} content - 文档内容
* @param {Array} rules - 匹配规则数组
* @returns {string|null} 匹配的值
*/
function matchByTitle(content, rules) {
// 提取可能的产品标题行(通常在文档开头)
const lines = content.split('\n').slice(0, 20) // 只检查前20行
const titleLines = lines.filter(line => {
const trimmed = line.trim()
// 标题行通常包含产品代码、产品名称等
return trimmed.length > 5 && trimmed.length < 100 &&
(/[A-Z]{2,4}\d?/.test(trimmed) ||
/計劃|计划|保障|保险|壽險|壽险/.test(trimmed))
})
// 合并标题行进行匹配
const titleText = titleLines.join('\n')
for (const rule of rules) {
if (rule.pattern && rule.pattern.test(titleText)) {
return rule.value
}
}
return null
}
/**
* 从"选项"段落提取字段
*
* @description 识别 "XXX選項:" 或 "XXX选项:" 格式的段落,提取其中的字段列表
* 示例:
* - "基本人壽保障選項:" 之后的性别、年龄等字段
* - "提取選項:" 之后的提取方式
* @param {string} content - 文档内容
* @param {string} optionKeyword - 选项关键词(如 "基本人壽保障選項")
* @returns {Array<{name: string, description: string}>|null} 提取的字段列表
*/
function extractOptionsFields(content, optionKeyword) {
// 匹配 "XXX選項:" 或 "XXX选项:" 格式
const pattern = new RegExp(`${optionKeyword}[::\\s]*\\n([\\s\\S]*?)(?=\\n\\n|\\n[A-Z]|\\n\\d+\\.\\s|$)`, 'gm')
const match = content.match(pattern)
if (!match) return null
const optionContent = match[0]
const fields = []
// 按行分割,提取字段名和描述
const lines = optionContent.split('\n').slice(1) // 跳过标题行
for (const line of lines) {
const trimmed = line.trim()
if (!trimmed) continue
// 常见字段格式:
// 1. "性別" - 纯字段名
// 2. "年齡 (1-75岁)" - 字段名 + 范围说明
// 3. "- 出生年月日" - 列表格式
// 4. "提取金额: 指定金额" - 字段名 + 描述
// 提取字段名(去除列表符号和说明)
const fieldMatch = trimmed.match(/^(?:[-•·]\s*)?([^::((]+)/)
if (fieldMatch) {
const fieldName = fieldMatch[1].trim()
if (fieldName && fieldName.length > 0 && fieldName.length < 20) {
fields.push({
name: fieldName,
description: trimmed // 保留完整描述
})
}
}
}
return fields.length > 0 ? fields : null
}
/**
* 查找所有"选项"段落
*
* @description 扫描文档中所有包含"選項"或"选项"的段落标题
* @param {string} content - 文档内容
* @returns {Array<{keyword: string, startIndex: number, fields: Array}>} 选项段落列表
*/
function findAllOptionSections(content) {
const sections = []
// 匹配所有 "XXX選項:" 或 "XXX选项:" 格式
const pattern = /([^\n]*(?:選項|选项|選是)[::]?)/gm
let match
while ((match = pattern.exec(content)) !== null) {
const keyword = match[1].trim()
const startIndex = match.index
// 提取该选项下的字段
const fields = extractOptionsFields(content, keyword.replace(/[::]/g, ''))
if (fields && fields.length > 0) {
sections.push({
keyword,
startIndex,
fields
})
}
}
return sections
}
/**
* 通过统计匹配内容
*/
function matchByCount(content, rules) {
......
# Scripts 目录整理报告
> 整理日期: 2026-02-15
## ✅ 整理完成
### 新目录结构
```
scripts/
├── README.md # 主说明文档
├── CLAUDE.md # Claude Code 指南(待处理)
├── api-generator/ # API 代码生成工具
│ ├── README.md # 使用说明
│ ├── GUIDE.md # 详细指南
│ ├── generateApiFromOpenAPI.js # 主脚本 ⭐
│ ├── apiDiff.js # API 对比工具
│ └── test-generate.js # 测试脚本
├── doc-parser/ # 文档解析工具
│ ├── README.md # 使用说明
│ ├── QUICKSTART.md # 快速开始
│ ├── .env.example # 环境变量示例
│ ├── parse-docs.js # 主脚本 ⭐
│ ├── parse-config.js # 配置文件
│ ├── smart-field-extractor.js # 字段提取器
│ ├── product-splitter.js # 产品分割器
│ └── parse-docs.test.js # 测试文件
└── changelog/ # CHANGELOG 管理工具
├── README.md # 使用说明
├── check-changelog.sh # 漏记检查 ⭐
└── archive-changelog.sh # 归档脚本
```
### npm scripts 已更新
| 命令 | 新路径 |
|------|--------|
| `pnpm api:generate` | `scripts/api-generator/generateApiFromOpenAPI.js` |
| `pnpm parse:docs` | `scripts/doc-parser/parse-docs.js` |
| `pnpm changelog:check` | `scripts/changelog/check-changelog.sh` |
---
## ⚠️ 未使用文件列表
以下文件**没有在 package.json 或其他脚本中直接调用**,请判断是否需要保留:
### 1. `scripts/changelog/archive-changelog.sh`
**状态**: 🟡 未直接引用
**功能**: CHANGELOG 归档脚本,当记录超过 20 条时自动归档
**使用方式**: 手动运行 `./scripts/changelog/archive-changelog.sh`
**建议**:
- [ ] 保留 - 作为手动维护工具
- [ ] 删除 - 不再需要归档功能
---
### 2. `scripts/api-generator/test-generate.js`
**状态**: 🟡 未直接引用
**功能**: 测试生成的 API 文件是否正确
**使用方式**: 手动运行 `node scripts/api-generator/test-generate.js`
**建议**:
- [ ] 保留 - 作为开发调试工具
- [ ] 删除 - 已有其他测试方式
---
### 3. `scripts/CLAUDE.md`
**状态**: 🟡 需要评估
**功能**: 给 Claude Code 的说明文档
**位置**: 当前在 scripts 根目录
**建议**:
- [ ] 保留在当前位置 - 作为 Claude 指南
- [ ] 移动到 doc-parser 目录 - 因为主要与文档解析相关
- [ ] 删除 - 内容已整合到其他文档
---
## 📝 整理后的改进
1. **✅ 功能分组清晰** - 按用途分为 3 个子目录
2. **✅ 每个子目录有 README** - 包含使用说明和文件介绍
3. **✅ 路径引用已更新** - package.json 已同步更新
4. **✅ 单个文件已收录** - 所有脚本都在对应的功能目录中
---
## 🔧 后续操作建议
1. **确认未使用文件** - 对上述 3 个文件做出保留/删除决定
2. **更新 CLAUDE.md** - 如果项目根目录的 CLAUDE.md 已包含相关内容,可以删除 scripts/CLAUDE.md
3. **测试脚本** - 运行 `pnpm api:generate``pnpm parse:docs` 确保路径更新正确
......@@ -5,10 +5,16 @@
* @module composables/useFieldDependencies
* @author Claude Code
* @created 2026-02-14
* @updated 2026-02-15 - 集成新的条件评估引擎,支持复杂条件
*/
import { computed, reactive, isRef } from 'vue'
import { computed, reactive, isRef, watch } from 'vue'
import { PLAN_FIELD_DEFINITIONS } from '@/config/plan-fields'
import {
evaluateCondition,
getConditionDependencies,
convertToNewFormat
} from '@/config/plan-conditions'
/**
* �测循环依赖
......@@ -88,6 +94,7 @@ export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEF
/**
* 检查字段是否应该显示
*
* @description 使用新的条件评估引擎,支持复杂条件(AND/OR/NOT/嵌套)
* @param {string} fieldKey - 字段键名
* @returns {boolean} 是否显示
*/
......@@ -96,28 +103,16 @@ export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEF
const definition = definitions[fieldKey]
if (!definition) return false
// 检查是否有 show_when 条件
// 使用新的条件评估引擎评估 show_when
if (definition.show_when) {
const conditions = definition.show_when
if (Array.isArray(conditions)) {
for (const condition of conditions) {
if (!condition) continue
const currentValue = formData[condition.field]
if (currentValue !== condition.equals) {
return false
}
}
} else if (conditions && typeof conditions === 'object') {
for (const [depKey, expectedValue] of Object.entries(conditions)) {
const currentValue = formData[depKey]
if (currentValue !== expectedValue) {
return false
}
}
// 转换为新格式(向后兼容)
const normalizedCondition = convertToNewFormat(definition.show_when)
if (!evaluateCondition(normalizedCondition, formData)) {
return false
}
}
// 检查是否被依赖字段影响
// 检查是否被依赖字段影响(旧逻辑,保持兼容)
for (const [key, def] of Object.entries(definitions)) {
if (def.affects?.includes(fieldKey)) {
// 依赖字段必须为 true 才显示
......@@ -198,6 +193,88 @@ export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEF
// 初始化
initFieldStates()
/**
* 清理隐藏字段的值
*
* @description 当字段隐藏时,根据 clear_when_hidden 配置决定是否清空值
* @param {string} fieldKey - 字段键名
* @param {Object} definition - 字段定义
*/
function clearHiddenFieldValue(fieldKey, definition) {
// 默认行为:隐藏时不清空(保持向后兼容)
const clearConfig = definition.clear_when_hidden
// false 或 undefined:不清空
if (clearConfig === false || clearConfig === undefined) {
return
}
// true:清空为 undefined
if (clearConfig === true) {
formData[fieldKey] = undefined
return
}
// null:设置为 null
if (clearConfig === null) {
formData[fieldKey] = null
return
}
// 对象配置
if (typeof clearConfig === 'object') {
// 清空自身
if (clearConfig.clear_self !== false) {
formData[fieldKey] = undefined
}
// 级联清空依赖字段
if (Array.isArray(clearConfig.clear_dependents)) {
for (const depKey of clearConfig.clear_dependents) {
if (formData[depKey] !== undefined) {
formData[depKey] = undefined
}
}
}
}
}
/**
* 更新字段可见性并处理隐藏字段的清理
*
* @param {string} fieldKey - 字段键名
* @param {string} triggerFieldKey - 触发更新的字段键名(可选)
*/
function updateFieldVisibility(fieldKey, triggerFieldKey = null) {
const definitions = getFieldDefinitions()
const definition = definitions[fieldKey]
if (!definition) return
const wasVisible = fieldVisibility[fieldKey]
const isVisible = isFieldVisible(fieldKey)
fieldVisibility[fieldKey] = isVisible
// 如果从可见变为不可见,且配置了清理规则,则清理字段值
if (wasVisible && !isVisible) {
clearHiddenFieldValue(fieldKey, definition)
}
// 更新启用状态
fieldEnabled[fieldKey] = isFieldEnabled(fieldKey)
}
/**
* 批量更新所有字段的可见性
*
* @description 通常在表单值变化后调用,重新计算所有字段的可见性
*/
function refreshAllVisibility() {
const definitions = getFieldDefinitions()
for (const key of Object.keys(definitions)) {
updateFieldVisibility(key)
}
}
return {
// 状态
fieldVisibility,
......@@ -208,6 +285,86 @@ export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEF
isFieldVisible,
isFieldEnabled,
updateFieldValue,
initFieldStates
initFieldStates,
updateFieldVisibility,
refreshAllVisibility
}
}
/**
* 过滤隐藏字段(用于提交前)
*
* @description 过滤掉当前不可见的字段,避免提交脏数据
* @param {Object} formData - 完整的表单数据
* @param {string[]} visibleFields - 可见字段列表
* @param {Object} options - 配置选项
* @param {string[]} options.alwaysInclude - 始终包含的字段(即使不可见)
* @returns {Object} 过滤后的表单数据
*
* @example
* const filteredData = filterHiddenFields(formData, visibleFields.value)
* await submitAPI(filteredData)
*/
export function filterHiddenFields(formData, visibleFields, options = {}) {
const { alwaysInclude = [] } = options
const filtered = {}
for (const [key, value] of Object.entries(formData)) {
// 可见字段始终包含
if (visibleFields.includes(key)) {
filtered[key] = value
continue
}
// 配置为始终包含的字段
if (alwaysInclude.includes(key)) {
filtered[key] = value
}
// 其他不可见字段被过滤掉
}
return filtered
}
/**
* 获取字段的条件依赖关系(用于调试和可视化)
*
* @description 返回字段的条件依赖图
* @param {Object} fieldDefinitions - 字段定义
* @returns {Map<string, Set<string>>} 字段到依赖字段的映射
*/
export function getFieldDependencyGraph(fieldDefinitions = PLAN_FIELD_DEFINITIONS) {
const graph = new Map()
for (const [key, definition] of Object.entries(fieldDefinitions)) {
const deps = new Set()
// 从 show_when 提取依赖
if (definition.show_when) {
const conditionDeps = getConditionDependencies(definition.show_when)
conditionDeps.forEach(d => deps.add(d))
}
// 从 affects 提取反向依赖
if (definition.affects) {
for (const affectedKey of definition.affects) {
if (!graph.has(affectedKey)) {
graph.set(affectedKey, new Set())
}
graph.get(affectedKey).add(key)
}
}
// 从 depends_on 提取
if (definition.depends_on) {
deps.add(definition.depends_on)
}
if (deps.size > 0) {
graph.set(key, deps)
}
}
return graph
}
......
/**
* 计划书字段条件引擎测试
*
* @description 测试条件操作符和评估逻辑
* @module config/__tests__/plan-conditions.test
*/
import { describe, it, expect } from 'vitest'
import {
CONDITION_OPERATORS,
normalizeOperator,
evaluateSingleCondition,
evaluateAnd,
evaluateOr,
evaluateNot,
evaluateCondition,
getConditionDependencies,
convertToNewFormat
} from '../plan-conditions'
describe('CONDITION_OPERATORS', () => {
describe('比较操作符', () => {
it('eq - 等于', () => {
expect(CONDITION_OPERATORS.eq('是', '是')).toBe(true)
expect(CONDITION_OPERATORS.eq('是', '否')).toBe(false)
expect(CONDITION_OPERATORS.eq(1, 1)).toBe(true)
expect(CONDITION_OPERATORS.eq(1, '1')).toBe(false) // 严格相等
})
it('ne - 不等于', () => {
expect(CONDITION_OPERATORS.ne('是', '否')).toBe(true)
expect(CONDITION_OPERATORS.ne('是', '是')).toBe(false)
})
it('gt - 大于', () => {
expect(CONDITION_OPERATORS.gt(30, 18)).toBe(true)
expect(CONDITION_OPERATORS.gt(18, 18)).toBe(false)
expect(CONDITION_OPERATORS.gt(10, 18)).toBe(false)
})
it('gte - 大于等于', () => {
expect(CONDITION_OPERATORS.gte(30, 18)).toBe(true)
expect(CONDITION_OPERATORS.gte(18, 18)).toBe(true)
expect(CONDITION_OPERATORS.gte(10, 18)).toBe(false)
})
it('lt - 小于', () => {
expect(CONDITION_OPERATORS.lt(10, 18)).toBe(true)
expect(CONDITION_OPERATORS.lt(18, 18)).toBe(false)
expect(CONDITION_OPERATORS.lt(30, 18)).toBe(false)
})
it('lte - 小于等于', () => {
expect(CONDITION_OPERATORS.lte(10, 18)).toBe(true)
expect(CONDITION_OPERATORS.lte(18, 18)).toBe(true)
expect(CONDITION_OPERATORS.lte(30, 18)).toBe(false)
})
})
describe('集合操作符', () => {
it('in - 包含于', () => {
expect(CONDITION_OPERATORS.in('A', ['A', 'B', 'C'])).toBe(true)
expect(CONDITION_OPERATORS.in('D', ['A', 'B', 'C'])).toBe(false)
expect(CONDITION_OPERATORS.in('A', 'not-array')).toBe(false)
})
it('nin - 不包含于', () => {
expect(CONDITION_OPERATORS.nin('D', ['A', 'B', 'C'])).toBe(true)
expect(CONDITION_OPERATORS.nin('A', ['A', 'B', 'C'])).toBe(false)
})
})
describe('字符串操作符', () => {
it('contains - 包含子串', () => {
expect(CONDITION_OPERATORS.contains('Hello World', 'World')).toBe(true)
expect(CONDITION_OPERATORS.contains('Hello World', 'world')).toBe(false) // 大小写敏感
expect(CONDITION_OPERATORS.contains(null, 'test')).toBe(false)
})
it('startsWith - 前缀匹配', () => {
expect(CONDITION_OPERATORS.startsWith('Hello', 'Hel')).toBe(true)
expect(CONDITION_OPERATORS.startsWith('Hello', 'hel')).toBe(false)
})
it('endsWith - 后缀匹配', () => {
expect(CONDITION_OPERATORS.endsWith('Hello', 'llo')).toBe(true)
expect(CONDITION_OPERATORS.endsWith('Hello', 'LLO')).toBe(false)
})
it('matches - 正则匹配', () => {
expect(CONDITION_OPERATORS.matches('ABC123', '^[A-Z]+\\d+$')).toBe(true)
expect(CONDITION_OPERATORS.matches('abc', '^[A-Z]+$')).toBe(false)
expect(CONDITION_OPERATORS.matches('test', '[invalid')).toBe(false) // 无效正则
})
})
describe('布尔/空值操作符', () => {
it('truthy - 真值', () => {
expect(CONDITION_OPERATORS.truthy(true)).toBe(true)
expect(CONDITION_OPERATORS.truthy(1)).toBe(true)
expect(CONDITION_OPERATORS.truthy('text')).toBe(true)
expect(CONDITION_OPERATORS.truthy(false)).toBe(false)
expect(CONDITION_OPERATORS.truthy(0)).toBe(false)
expect(CONDITION_OPERATORS.truthy('')).toBe(false)
expect(CONDITION_OPERATORS.truthy(null)).toBe(false)
})
it('falsy - 假值', () => {
expect(CONDITION_OPERATORS.falsy(false)).toBe(true)
expect(CONDITION_OPERATORS.falsy(0)).toBe(true)
expect(CONDITION_OPERATORS.falsy('')).toBe(true)
expect(CONDITION_OPERATORS.falsy(null)).toBe(true)
expect(CONDITION_OPERATORS.falsy(true)).toBe(false)
})
it('empty - 空值', () => {
expect(CONDITION_OPERATORS.empty('')).toBe(true)
expect(CONDITION_OPERATORS.empty(null)).toBe(true)
expect(CONDITION_OPERATORS.empty(undefined)).toBe(true)
expect(CONDITION_OPERATORS.empty(0)).toBe(false)
expect(CONDITION_OPERATORS.empty('text')).toBe(false)
})
it('notEmpty - 非空', () => {
expect(CONDITION_OPERATORS.notEmpty('text')).toBe(true)
expect(CONDITION_OPERATORS.notEmpty(0)).toBe(true)
expect(CONDITION_OPERATORS.notEmpty('')).toBe(false)
expect(CONDITION_OPERATORS.notEmpty(null)).toBe(false)
})
it('emptyArray - 空数组', () => {
expect(CONDITION_OPERATORS.emptyArray([])).toBe(true)
expect(CONDITION_OPERATORS.emptyArray(null)).toBe(true)
expect(CONDITION_OPERATORS.emptyArray([1])).toBe(false)
})
it('notEmptyArray - 非空数组', () => {
expect(CONDITION_OPERATORS.notEmptyArray([1])).toBe(true)
expect(CONDITION_OPERATORS.notEmptyArray([])).toBe(false)
})
})
})
describe('normalizeOperator', () => {
it('返回标准操作符', () => {
expect(normalizeOperator('eq')).toBe('eq')
expect(normalizeOperator('gt')).toBe('gt')
})
it('处理别名', () => {
expect(normalizeOperator('equals')).toBe('eq')
expect(normalizeOperator('==')).toBe('eq')
expect(normalizeOperator('===')).toBe('eq')
expect(normalizeOperator('!=')).toBe('ne')
expect(normalizeOperator('>')).toBe('gt')
expect(normalizeOperator('>=')).toBe('gte')
})
it('未知操作符返回 null', () => {
expect(normalizeOperator('unknown')).toBe(null)
})
})
describe('evaluateSingleCondition', () => {
const formData = { smoker: '是', age: 30, name: 'John' }
it('评估等于条件', () => {
expect(evaluateSingleCondition(
{ field: 'smoker', op: 'eq', value: '是' },
formData
)).toBe(true)
expect(evaluateSingleCondition(
{ field: 'smoker', op: 'eq', value: '否' },
formData
)).toBe(false)
})
it('评估大于条件', () => {
expect(evaluateSingleCondition(
{ field: 'age', op: 'gt', value: 18 },
formData
)).toBe(true)
})
it('评估包含条件', () => {
expect(evaluateSingleCondition(
{ field: 'name', op: 'contains', value: 'John' },
formData
)).toBe(true)
})
it('缺少字段或操作符返回 false', () => {
expect(evaluateSingleCondition({ op: 'eq', value: 'x' }, formData)).toBe(false)
expect(evaluateSingleCondition({ field: 'x', value: 'y' }, formData)).toBe(false)
})
it('未知操作符返回 false', () => {
expect(evaluateSingleCondition(
{ field: 'smoker', op: 'unknown', value: '是' },
formData
)).toBe(false)
})
})
describe('evaluateAnd', () => {
const formData = { smoker: '是', age: 30 }
it('所有条件满足返回 true', () => {
expect(evaluateAnd([
{ field: 'smoker', op: 'eq', value: '是' },
{ field: 'age', op: 'gte', value: 18 }
], formData)).toBe(true)
})
it('任一条件不满足返回 false', () => {
expect(evaluateAnd([
{ field: 'smoker', op: 'eq', value: '是' },
{ field: 'age', op: 'gte', value: 50 }
], formData)).toBe(false)
})
it('空数组返回 true', () => {
expect(evaluateAnd([], formData)).toBe(true)
})
})
describe('evaluateOr', () => {
const formData = { smoker: '是', age: 30 }
it('任一条件满足返回 true', () => {
expect(evaluateOr([
{ field: 'smoker', op: 'eq', value: '否' },
{ field: 'age', op: 'gte', value: 18 }
], formData)).toBe(true)
})
it('所有条件不满足返回 false', () => {
expect(evaluateOr([
{ field: 'smoker', op: 'eq', value: '否' },
{ field: 'age', op: 'gte', value: 50 }
], formData)).toBe(false)
})
it('空数组返回 false', () => {
expect(evaluateOr([], formData)).toBe(false)
})
})
describe('evaluateNot', () => {
const formData = { smoker: '是' }
it('条件不满足返回 true', () => {
expect(evaluateNot(
{ field: 'smoker', op: 'eq', value: '否' },
formData
)).toBe(true)
})
it('条件满足返回 false', () => {
expect(evaluateNot(
{ field: 'smoker', op: 'eq', value: '是' },
formData
)).toBe(false)
})
})
describe('evaluateCondition', () => {
const formData = {
smoker: '是',
age: 30,
product_type: 'A',
coverage: 100000,
payment_period: '20年'
}
it('空条件返回 true', () => {
expect(evaluateCondition(null, formData)).toBe(true)
expect(evaluateCondition(undefined, formData)).toBe(true)
})
it('简单条件', () => {
expect(evaluateCondition(
{ field: 'smoker', op: 'eq', value: '是' },
formData
)).toBe(true)
})
it('AND 逻辑', () => {
expect(evaluateCondition({
and: [
{ field: 'smoker', op: 'eq', value: '是' },
{ field: 'age', op: 'gte', value: 30 }
]
}, formData)).toBe(true)
expect(evaluateCondition({
and: [
{ field: 'smoker', op: 'eq', value: '是' },
{ field: 'age', op: 'gt', value: 50 }
]
}, formData)).toBe(false)
})
it('OR 逻辑', () => {
expect(evaluateCondition({
or: [
{ field: 'smoker', op: 'eq', value: '否' },
{ field: 'age', op: 'gte', value: 30 }
]
}, formData)).toBe(true)
expect(evaluateCondition({
or: [
{ field: 'smoker', op: 'eq', value: '否' },
{ field: 'age', op: 'gt', value: 50 }
]
}, formData)).toBe(false)
})
it('NOT 逻辑', () => {
expect(evaluateCondition({
not: { field: 'smoker', op: 'eq', value: '否' }
}, formData)).toBe(true)
})
it('嵌套条件', () => {
// (smoker == '是' OR age > 50) AND product_type in ['A', 'B']
expect(evaluateCondition({
and: [
{
or: [
{ field: 'smoker', op: 'eq', value: '是' },
{ field: 'age', op: 'gt', value: 50 }
]
},
{ field: 'product_type', op: 'in', value: ['A', 'B'] }
]
}, formData)).toBe(true)
})
it('数组默认为 AND', () => {
expect(evaluateCondition([
{ field: 'smoker', op: 'eq', value: '是' },
{ field: 'age', op: 'gte', value: 30 }
], formData)).toBe(true)
})
it('旧格式兼容 - equals', () => {
expect(evaluateCondition(
{ field: 'smoker', equals: '是' },
formData
)).toBe(true)
})
it('旧格式兼容 - 扁平对象', () => {
expect(evaluateCondition(
{ smoker: '是', age: 30 },
formData
)).toBe(true)
})
})
describe('getConditionDependencies', () => {
it('提取简单条件的依赖', () => {
const deps = getConditionDependencies({ field: 'smoker', op: 'eq', value: '是' })
expect(deps.has('smoker')).toBe(true)
expect(deps.size).toBe(1)
})
it('提取 AND 条件的依赖', () => {
const deps = getConditionDependencies({
and: [
{ field: 'smoker', op: 'eq', value: '是' },
{ field: 'age', op: 'gte', value: 30 }
]
})
expect(deps.has('smoker')).toBe(true)
expect(deps.has('age')).toBe(true)
expect(deps.size).toBe(2)
})
it('提取嵌套条件的依赖', () => {
const deps = getConditionDependencies({
and: [
{ field: 'a', op: 'eq', value: 1 },
{
or: [
{ field: 'b', op: 'eq', value: 2 },
{ field: 'c', op: 'eq', value: 3 }
]
}
]
})
expect(deps.has('a')).toBe(true)
expect(deps.has('b')).toBe(true)
expect(deps.has('c')).toBe(true)
expect(deps.size).toBe(3)
})
it('提取扁平对象的依赖', () => {
const deps = getConditionDependencies({ smoker: '是', age: 30 })
expect(deps.has('smoker')).toBe(true)
expect(deps.has('age')).toBe(true)
})
})
describe('convertToNewFormat', () => {
it('已经是新格式则直接返回', () => {
const condition = { field: 'smoker', op: 'eq', value: '是' }
expect(convertToNewFormat(condition)).toEqual(condition)
})
it('转换旧格式 equals', () => {
expect(convertToNewFormat(
{ field: 'smoker', equals: '是' }
)).toEqual({ field: 'smoker', op: 'eq', value: '是' })
})
it('转换旧格式数组为 AND', () => {
expect(convertToNewFormat([
{ field: 'a', equals: 'x' },
{ field: 'b', equals: 'y' }
])).toEqual({
and: [
{ field: 'a', op: 'eq', value: 'x' },
{ field: 'b', op: 'eq', value: 'y' }
]
})
})
it('单个条件的数组不包装 AND', () => {
expect(convertToNewFormat([
{ field: 'a', equals: 'x' }
])).toEqual({ field: 'a', op: 'eq', value: 'x' })
})
it('转换扁平对象', () => {
expect(convertToNewFormat({ smoker: '是' })).toEqual({
field: 'smoker',
op: 'eq',
value: '是'
})
})
it('转换多字段扁平对象为 AND', () => {
expect(convertToNewFormat({ smoker: '是', age: 30 })).toEqual({
and: [
{ field: 'smoker', op: 'eq', value: '是' },
{ field: 'age', op: 'eq', value: 30 }
]
})
})
})
/**
* 计划书字段条件规则引擎
*
* @description 定义条件操作符和评估逻辑,支持复杂的字段显示/隐藏条件
* @module config/plan-conditions
* @author Claude Code
* @created 2026-02-15
* @version 1.0.0
*/
/**
* 条件操作符定义
*
* @description 所有可用的条件比较操作符
* @type {Object<string, Function>}
*/
export const CONDITION_OPERATORS = {
// ========== 比较操作 ==========
/**
* 等于(严格相等)
* @param {*} actual - 实际值
* @param {*} expected - 期望值
* @returns {boolean}
*/
eq: (actual, expected) => actual === expected,
/**
* 不等于
* @param {*} actual - 实际值
* @param {*} expected - 期望值
* @returns {boolean}
*/
ne: (actual, expected) => actual !== expected,
/**
* 大于
* @param {number} actual - 实际值
* @param {number} expected - 期望值
* @returns {boolean}
*/
gt: (actual, expected) => {
const num = Number(actual)
const exp = Number(expected)
return !Number.isNaN(num) && !Number.isNaN(exp) && num > exp
},
/**
* 大于等于
* @param {number} actual - 实际值
* @param {number} expected - 期望值
* @returns {boolean}
*/
gte: (actual, expected) => {
const num = Number(actual)
const exp = Number(expected)
return !Number.isNaN(num) && !Number.isNaN(exp) && num >= exp
},
/**
* 小于
* @param {number} actual - 实际值
* @param {number} expected - 期望值
* @returns {boolean}
*/
lt: (actual, expected) => {
const num = Number(actual)
const exp = Number(expected)
return !Number.isNaN(num) && !Number.isNaN(exp) && num < exp
},
/**
* 小于等于
* @param {number} actual - 实际值
* @param {number} expected - 期望值
* @returns {boolean}
*/
lte: (actual, expected) => {
const num = Number(actual)
const exp = Number(expected)
return !Number.isNaN(num) && !Number.isNaN(exp) && num <= exp
},
// ========== 集合操作 ==========
/**
* 包含于(值在数组中)
* @param {*} actual - 实际值
* @param {Array} expected - 期望数组
* @returns {boolean}
*/
in: (actual, expected) => {
if (!Array.isArray(expected)) return false
return expected.includes(actual)
},
/**
* 不包含于(值不在数组中)
* @param {*} actual - 实际值
* @param {Array} expected - 期望数组
* @returns {boolean}
*/
nin: (actual, expected) => {
if (!Array.isArray(expected)) return true
return !expected.includes(actual)
},
// ========== 字符串操作 ==========
/**
* 字符串包含
* @param {string} actual - 实际值
* @param {string} expected - 期望子串
* @returns {boolean}
*/
contains: (actual, expected) => {
return String(actual ?? '').includes(String(expected ?? ''))
},
/**
* 字符串前缀匹配
* @param {string} actual - 实际值
* @param {string} expected - 期望前缀
* @returns {boolean}
*/
startsWith: (actual, expected) => {
return String(actual ?? '').startsWith(String(expected ?? ''))
},
/**
* 字符串后缀匹配
* @param {string} actual - 实际值
* @param {string} expected - 期望后缀
* @returns {boolean}
*/
endsWith: (actual, expected) => {
return String(actual ?? '').endsWith(String(expected ?? ''))
},
/**
* 正则表达式匹配
* @param {string} actual - 实际值
* @param {string|RegExp} expected - 正则表达式
* @returns {boolean}
*/
matches: (actual, expected) => {
try {
const regex = expected instanceof RegExp ? expected : new RegExp(expected)
return regex.test(String(actual ?? ''))
} catch {
return false
}
},
// ========== 布尔/空值操作 ==========
/**
* 真值检查
* @param {*} actual - 实际值
* @returns {boolean}
*/
truthy: (actual) => !!actual,
/**
* 假值检查
* @param {*} actual - 实际值
* @returns {boolean}
*/
falsy: (actual) => !actual,
/**
* 空值检查(null, undefined, 空字符串)
* @param {*} actual - 实际值
* @returns {boolean}
*/
empty: (actual) => actual === '' || actual === null || actual === undefined,
/**
* 非空检查
* @param {*} actual - 实际值
* @returns {boolean}
*/
notEmpty: (actual) => actual !== '' && actual !== null && actual !== undefined,
/**
* 空数组检查
* @param {*} actual - 实际值
* @returns {boolean}
*/
emptyArray: (actual) => !Array.isArray(actual) || actual.length === 0,
/**
* 非空数组检查
* @param {*} actual - 实际值
* @returns {boolean}
*/
notEmptyArray: (actual) => Array.isArray(actual) && actual.length > 0
}
/**
* 操作符别名映射(向后兼容)
*
* @description 旧格式操作符到新格式的映射
* @type {Object<string, string>}
*/
export const OPERATOR_ALIASES = {
equals: 'eq',
'==': 'eq',
'===': 'eq',
'!=': 'ne',
'!==': 'ne',
'>': 'gt',
'>=': 'gte',
'<': 'lt',
'<=': 'lte'
}
/**
* 解析操作符(支持别名)
*
* @param {string} op - 操作符名称或别名
* @returns {string|null} 标准操作符名称
*/
export function normalizeOperator(op) {
if (CONDITION_OPERATORS[op]) return op
if (OPERATOR_ALIASES[op]) return OPERATOR_ALIASES[op]
return null
}
/**
* 评估单个条件
*
* @description 评估一个简单的条件表达式
* @param {Object} condition - 条件对象
* @param {string} condition.field - 字段名
* @param {string} condition.op - 操作符
* @param {*} condition.value - 期望值
* @param {Object} formData - 表单数据
* @returns {boolean} 条件是否满足
*
* @example
* evaluateSingleCondition(
* { field: 'smoker', op: 'eq', value: '是' },
* { smoker: '是', age: 30 }
* ) // true
*/
export function evaluateSingleCondition(condition, formData) {
const { field, op, value } = condition
if (!field || !op) {
console.warn('[plan-conditions] 条件缺少 field 或 op:', condition)
return false
}
const normalizedOp = normalizeOperator(op)
if (!normalizedOp) {
console.warn(`[plan-conditions] 未知操作符: ${op}`)
return false
}
const operator = CONDITION_OPERATORS[normalizedOp]
const actualValue = formData[field]
try {
return operator(actualValue, value)
} catch (err) {
console.error(`[plan-conditions] 条件评估失败:`, err)
return false
}
}
/**
* 评估 AND 逻辑
*
* @param {Array} conditions - 条件数组
* @param {Object} formData - 表单数据
* @returns {boolean} 所有条件是否都满足
*/
export function evaluateAnd(conditions, formData) {
if (!Array.isArray(conditions) || conditions.length === 0) return true
return conditions.every(c => evaluateCondition(c, formData))
}
/**
* 评估 OR 逻辑
*
* @param {Array} conditions - 条件数组
* @param {Object} formData - 表单数据
* @returns {boolean} 是否有任一条件满足
*/
export function evaluateOr(conditions, formData) {
if (!Array.isArray(conditions) || conditions.length === 0) return false
return conditions.some(c => evaluateCondition(c, formData))
}
/**
* 评估 NOT 逻辑
*
* @param {Object} condition - 条件对象
* @param {Object} formData - 表单数据
* @returns {boolean} 条件是否不满足
*/
export function evaluateNot(condition, formData) {
if (!condition) return false
return !evaluateCondition(condition, formData)
}
/**
* 评估条件(主入口)
*
* @description 评估任意条件表达式,支持简单条件和逻辑组合
* @param {Object|Array} condition - 条件表达式
* @param {Object} formData - 表单数据
* @returns {boolean} 条件是否满足
*
* @example
* // 简单条件
* evaluateCondition({ field: 'smoker', op: 'eq', value: '是' }, formData)
*
* @example
* // AND 条件
* evaluateCondition({
* and: [
* { field: 'smoker', op: 'eq', value: '是' },
* { field: 'age', op: 'gte', value: 30 }
* ]
* }, formData)
*
* @example
* // OR 条件
* evaluateCondition({
* or: [
* { field: 'smoker', op: 'eq', value: '是' },
* { field: 'age', op: 'gt', value: 50 }
* ]
* }, formData)
*
* @example
* // 嵌套条件
* evaluateCondition({
* and: [
* { field: 'type', op: 'in', value: ['A', 'B'] },
* {
* or: [
* { field: 'coverage', op: 'gte', value: 100000 },
* { field: 'period', op: 'eq', value: '20年' }
* ]
* }
* ]
* }, formData)
*/
export function evaluateCondition(condition, formData) {
// 空条件默认为 true
if (!condition) return true
// 数组条件:默认为 AND 逻辑
if (Array.isArray(condition)) {
return evaluateAnd(condition, formData)
}
// 对象条件
if (typeof condition === 'object') {
// AND 逻辑
if (condition.and) {
return evaluateAnd(condition.and, formData)
}
// OR 逻辑
if (condition.or) {
return evaluateOr(condition.or, formData)
}
// NOT 逻辑
if (condition.not) {
return evaluateNot(condition.not, formData)
}
// 简单条件(包含 field 和 op)
if (condition.field && condition.op) {
return evaluateSingleCondition(condition, formData)
}
// 旧格式兼容:{ field: 'xxx', equals: 'yyy' }
if (condition.field && condition.equals !== undefined) {
return evaluateSingleCondition(
{ field: condition.field, op: 'eq', value: condition.equals },
formData
)
}
// 旧格式兼容:{ field: 'xxx', not_equals: 'yyy' }
if (condition.field && condition.not_equals !== undefined) {
return evaluateSingleCondition(
{ field: condition.field, op: 'ne', value: condition.not_equals },
formData
)
}
// 扁平条件对象:{ smoker: '是', age: 30 } → 所有条件 AND
const keys = Object.keys(condition)
if (keys.length > 0 && !keys.some(k => ['and', 'or', 'not', 'field', 'op'].includes(k))) {
return keys.every(field => {
const expectedValue = condition[field]
const actualValue = formData[field]
return actualValue === expectedValue
})
}
}
console.warn('[plan-conditions] 无法识别的条件格式:', condition)
return false
}
/**
* 获取条件依赖的字段列表
*
* @description 从条件表达式中提取所有依赖的字段名
* @param {Object|Array} condition - 条件表达式
* @returns {Set<string>} 依赖的字段名集合
*
* @example
* getConditionDependencies({ and: [{ field: 'a', op: 'eq', value: 1 }, { field: 'b', op: 'eq', value: 2 }] })
* // Set { 'a', 'b' }
*/
export function getConditionDependencies(condition) {
const deps = new Set()
if (!condition) return deps
// 数组条件
if (Array.isArray(condition)) {
condition.forEach(c => {
const subDeps = getConditionDependencies(c)
subDeps.forEach(d => deps.add(d))
})
return deps
}
if (typeof condition === 'object') {
// 简单条件
if (condition.field) {
deps.add(condition.field)
}
// 扁平条件对象
const keys = Object.keys(condition)
if (keys.length > 0 && !keys.some(k => ['and', 'or', 'not', 'field', 'op', 'equals'].includes(k))) {
keys.forEach(k => deps.add(k))
}
// 递归处理逻辑组合
if (condition.and) {
condition.and.forEach(c => {
const subDeps = getConditionDependencies(c)
subDeps.forEach(d => deps.add(d))
})
}
if (condition.or) {
condition.or.forEach(c => {
const subDeps = getConditionDependencies(c)
subDeps.forEach(d => deps.add(d))
})
}
if (condition.not) {
const subDeps = getConditionDependencies(condition.not)
subDeps.forEach(d => deps.add(d))
}
}
return deps
}
/**
* 将旧格式转换为新格式
*
* @description 将旧的 show_when 格式转换为新的条件格式
* @param {Object|Array|string} oldFormat - 旧格式条件
* @returns {Object|null} 新格式条件
*
* @example
* // 旧格式
* { field: 'withdrawal_mode', equals: '指定提取金额' }
* // 转换为
* { field: 'withdrawal_mode', op: 'eq', value: '指定提取金额' }
*
* @example
* // 旧格式数组
* [{ field: 'a', equals: 'x' }, { field: 'b', equals: 'y' }]
* // 转换为
* { and: [{ field: 'a', op: 'eq', value: 'x' }, { field: 'b', op: 'eq', value: 'y' }] }
*/
export function convertToNewFormat(oldFormat) {
if (!oldFormat) return null
// 已经是新格式
if (oldFormat.and || oldFormat.or || oldFormat.not || oldFormat.op) {
return oldFormat
}
// 旧格式单个条件
if (oldFormat.field && oldFormat.equals !== undefined) {
return {
field: oldFormat.field,
op: 'eq',
value: oldFormat.equals
}
}
// 旧格式数组
if (Array.isArray(oldFormat) && oldFormat.length > 0) {
const conditions = oldFormat.map(c => {
if (c.field && c.equals !== undefined) {
return { field: c.field, op: 'eq', value: c.equals }
}
return c
})
// 单个条件不需要包装 and
if (conditions.length === 1) {
return conditions[0]
}
return { and: conditions }
}
// 扁平对象格式 { smoker: '是' }
const keys = Object.keys(oldFormat)
if (keys.length > 0 && !keys.some(k => ['and', 'or', 'not', 'field', 'op', 'equals'].includes(k))) {
if (keys.length === 1) {
return { field: keys[0], op: 'eq', value: oldFormat[keys[0]] }
}
return {
and: keys.map(field => ({ field, op: 'eq', value: oldFormat[field] }))
}
}
return oldFormat
}
......@@ -72,6 +72,7 @@ const savingsSubmitMapping = {
}
// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
// @updated 2026-02-15 - 迁移到新的条件规则格式,使用 clear_when_hidden 替代 reset_map
const savingsFormSchema = {
// 基础字段:非提取计划部分
base_fields: [
......@@ -83,24 +84,25 @@ const savingsFormSchema = {
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
],
// 提取计划字段:由 withdrawal_plan 开关控制
// 新格式说明:
// - show_when: { field: 'xxx', op: 'eq', value: 'yyy' } 新格式条件
// - show_when: { field: 'xxx', equals: 'yyy' } 旧格式(向后兼容)
// - clear_when_hidden: true 隐藏时自动清空字段值
withdrawal_fields: [
{ id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
{ id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
{ id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_withdrawal_amount', key: 'annual_withdrawal_amount', type: 'amount', label: '每年提取金额', placeholder: '请输入每年提取金额', input_label: '请输入每年提取金额', required: true, currency_from: 'withdrawal_plan.default_currency', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_period_specified', key: 'withdrawal_period_specified', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
{ id: 'withdrawal_period_fixed', key: 'withdrawal_period_fixed', type: 'select', label: '按年岁:提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }
],
// 提取模式切换时的清空逻辑,避免脏字段影响提交
reset_map: {
withdrawal_mode: {
'最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
'指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
}
}
{ id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)', clear_when_hidden: true },
// 指定提取金额模式字段
{ id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: { field: 'withdrawal_mode', op: 'eq', value: '指定提取金额' }, clear_when_hidden: true },
{ id: 'annual_withdrawal_amount', key: 'annual_withdrawal_amount', type: 'amount', label: '每年提取金额', placeholder: '请输入每年提取金额', input_label: '请输入每年提取金额', required: true, currency_from: 'withdrawal_plan.default_currency', show_when: { field: 'withdrawal_mode', op: 'eq', value: '指定提取金额' }, clear_when_hidden: true },
{ id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: { field: 'withdrawal_mode', op: 'eq', value: '指定提取金额' }, clear_when_hidden: true },
{ id: 'withdrawal_period_specified', key: 'withdrawal_period_specified', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: { field: 'withdrawal_mode', op: 'eq', value: '指定提取金额' }, clear_when_hidden: true },
{ id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: { field: 'withdrawal_mode', op: 'eq', value: '指定提取金额' }, clear_when_hidden: true },
// 最高固定提取金额模式字段
{ id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: { field: 'withdrawal_mode', op: 'eq', value: '最高固定提取金额' }, clear_when_hidden: true },
{ id: 'withdrawal_period_fixed', key: 'withdrawal_period_fixed', type: 'select', label: '按年岁:提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: { field: 'withdrawal_mode', op: 'eq', value: '最高固定提取金额' }, clear_when_hidden: true }
]
// reset_map 已被 clear_when_hidden 替代
// 当 withdrawal_mode 切换时,不可见的字段会自动清空
}
export const PLAN_TEMPLATES = {
......@@ -366,6 +368,24 @@ export const PLAN_TEMPLATES = {
submit_mapping: savingsSubmitMapping
}
},
/**
* 长宁終身壽險計劃3
* @added 2026-02-15T06:30:03.691Z
* @source docs/to-parse/计划书模版4.docx
*/
'life-insurance-3-d8fde07d': {
name: '长宁終身壽險計劃3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: ["5年","12年","15年","20年"],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
}
}
/**
......
......@@ -415,9 +415,11 @@ export async function mockProductListAPI(params) {
const list = []
const startIndex = page * limit
// 🔧 测试商品:第一页第一位固定为储蓄产品(form_sn:savings-product-30b41aae)
// 🔧 测试商品:第一页前两位固定为测试产品
if (page === 0) {
const testCategory = PRODUCT_CATEGORIES.find(c => parseInt(c.id) === 1)
// 测试商品1: 储蓄产品
const testProduct1 = {
id: 'savings-2-148b3acd',
product_name: '测试计划书-智享未来2(form_sn:savings-2-148b3acd)',
......@@ -432,19 +434,38 @@ export async function mockProductListAPI(params) {
_test_note: 'form_sn:savings-2-148b3acd'
}
// 检查分类和关键词过滤
let shouldInclude = true
if (cid && !testProduct1.categories.some(c => parseInt(c.id) === parseInt(cid))) {
shouldInclude = false
}
if (keyword && !testProduct1.product_name.includes(keyword)) {
shouldInclude = false
// 测试商品2: 人寿保险产品
const testProduct2 = {
id: 'life-insurance-3-d8fde07d',
product_name: '测试计划书-人生无忧3(form_sn:life-insurance-3-d8fde07d)',
cover_image: 'https://picsum.photos/seed/life-insurance-3-d8fde07d/400/300',
recommend: 'hot',
form_sn: 'life-insurance-3-d8fde07d', // ✅ 关键字段:对应真实 API 的 form_sn
created_time: new Date().toISOString(),
categories: [testCategory], // ✅ 符合真实 API 结构:categories 是数组
tags: [{ id: '1', name: '热销', bg_color: '#FEE2E2', text_color: '#DC2626' }],
// 测试标识(不影响业务逻辑)
_test: true,
_test_note: 'form_sn:life-insurance-3-d8fde07d'
}
if (shouldInclude) {
list.push(testProduct1)
console.log('[Mock] listAPI - 测试商品已置顶: form_sn=savings-2-148b3acd')
}
// 检查分类和关键词过滤,依次添加测试商品
const testProducts = [testProduct1, testProduct2]
testProducts.forEach((testProduct, index) => {
let shouldInclude = true
if (cid && !testProduct.categories.some(c => parseInt(c.id) === parseInt(cid))) {
shouldInclude = false
}
if (keyword && !testProduct.product_name.includes(keyword)) {
shouldInclude = false
}
if (shouldInclude) {
list.push(testProduct)
console.log(`[Mock] listAPI - 测试商品${index + 1}已置顶: form_sn=${testProduct.form_sn}`)
}
})
}
for (let i = 0; i < limit; i++) {
......
......@@ -234,6 +234,16 @@ function resolveSchemaRefs(config) {
}
}
/**
* 构建表单 Schema 代码
*
* @description 将 schema 对象转换为代码字符串
* @param {Object|string} value - schema 值
* @param {string} fallbackRef - 回退引用名
* @returns {string} 代码字符串
*
* @updated 2026-02-15 - 支持新的条件格式 show_when: { field, op, value }
*/
function buildSchemaCode(value, fallbackRef) {
if (!value || isEmptyObject(value)) {
return fallbackRef
......@@ -241,13 +251,14 @@ function buildSchemaCode(value, fallbackRef) {
if (value && typeof value === 'object' && !Array.isArray(value)) {
const baseFields = value.base_fields
const withdrawalFields = value.withdrawal_fields
const resetMap = value.reset_map
const baseFieldsEmpty = Array.isArray(baseFields) && baseFields.length === 0
const withdrawalFieldsEmpty = !Array.isArray(withdrawalFields) || withdrawalFields.length === 0
const resetMapEmpty = !resetMap || (typeof resetMap === 'object' && !Array.isArray(resetMap) && Object.keys(resetMap).length === 0)
if (baseFieldsEmpty && withdrawalFieldsEmpty && resetMapEmpty) {
if (baseFieldsEmpty && withdrawalFieldsEmpty) {
return fallbackRef
}
// 处理字段中的 show_when 格式转换
const processedValue = processSchemaFields(value)
return JSON.stringify(processedValue, null, 2).replace(/\n/g, '\n ')
}
if (typeof value === 'string') {
return value
......@@ -255,6 +266,42 @@ function buildSchemaCode(value, fallbackRef) {
return JSON.stringify(value, null, 2).replace(/\n/g, '\n ')
}
/**
* 处理 Schema 字段,转换条件格式
*
* @private
*/
function processSchemaFields(schema) {
const result = {}
for (const [key, value] of Object.entries(schema)) {
if (key === 'base_fields' || key === 'withdrawal_fields') {
result[key] = value.map(field => processFieldCondition(field))
} else if (key !== 'reset_map') {
// 忽略 reset_map,不再生成
result[key] = value
}
}
return result
}
/**
* 处理字段条件,转换为新格式
*
* @private
*/
function processFieldCondition(field) {
const result = { ...field }
// 转换 show_when: { field, equals } -> { field, op: 'eq', value }
if (result.show_when && result.show_when.equals !== undefined) {
result.show_when = {
field: result.show_when.field,
op: 'eq',
value: result.show_when.equals
}
}
return result
}
function isEmptyObject(value) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return false
......