hookehuyr

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

- 完善计划书字段选项控制功能
- 优化表单交互体验
- 提取字段配置模板
Showing 44 changed files with 2402 additions and 4669 deletions
...@@ -24,3 +24,7 @@ CLAUDE.md ...@@ -24,3 +24,7 @@ CLAUDE.md
24 *.pptx 24 *.pptx
25 25
26 .swc/ 26 .swc/
27 +
28 +# Document parsing generated files
29 +docs/parse-audit/
30 +docs/parsed-backup/
......
1 -# 产品配置审核 - 计划书模版2.docx
2 -
3 -**解析时间**: 2026/2/15 10:20:30
4 -**原始文件**: 计划书模版2.docx
5 -**数据来源**: docs/to-parse/计划书模版2.docx
6 -
7 ----
8 -
9 -## 📋 产品基本信息
10 -
11 -| 字段 | 提取值 | 需要确认 |
12 -|------|--------|---------|
13 -| 产品名称 | 宏摯傳承保障計劃 - 性別, 年齡, 出生年月日 | ✅ 请核对产品名称 |
14 -| 产品类型 | savings | ✅ 请确认产品类型 |
15 -| 币种 | USD | ✅ 请确认币种 |
16 -| form_sn | `savings-product-ef3dd50b` | ✅ 请确认 form_sn 唯一性 |
17 -| 缴费年期 | ["整付","3年","5年","10年","15年"] | ✅ 请确认缴费年期选项 |
18 -| 年龄范围 | 0-75岁 | ✅ 请确认年龄范围 |
19 -| 保险期间 | 终身 | ✅ 请确认保险期间 |
20 -
21 -
22 -### 💰 储蓄类产品特有字段
23 -
24 -| 字段 | 提取值 | 需要确认 |
25 -|------|--------|---------|
26 -| 提取方式 | ["最高固定提取金额"] | ✅ 请确认提取方式 |
27 -| 提取期 | ["1年","3年","5年","10年"] | ✅ 请确认提取期选项 |
28 -
29 -
30 ----
31 -
32 -## 🤖 智能字段提取报告
33 -
34 -### 匹配统计
35 -
36 -- ✅ 成功匹配: 4 字段
37 -- ⚠️ 使用默认值: 3 字段
38 -- ❌ 未匹配(需人工补充): 0 字段
39 -
40 -### ✅ 已成功匹配的字段
41 -
42 -- product_name
43 -- product_type
44 -- payment_periods
45 -- withdrawal_modes
46 -
47 -
48 -### ⚠️ 使用默认值的字段
49 -
50 -- **currency**: 未找到字段 "currency",使用默认值: "USD"
51 -- **age_range**: 未找到字段 "age_range",使用默认值: {"min":0,"max":75}
52 -- **insurance_period**: 未找到字段 "insurance_period",使用默认值: "终身"
53 -
54 -
55 -
56 -
57 ----
58 -
59 -## 🧾 配置预览
60 -
61 -```javascript
62 -{
63 - "product_name": "宏摯傳承保障計劃 - 性別, 年齡, 出生年月日",
64 - "product_type": "savings",
65 - "currency": "USD",
66 - "form_sn": "savings-product-ef3dd50b",
67 - "payment_periods": [
68 - "整付",
69 - "3年",
70 - "5年",
71 - "10年",
72 - "15年"
73 - ],
74 - "age_range": {
75 - "min": 0,
76 - "max": 75
77 - },
78 - "insurance_period": "终身",
79 - "is_savings": true,
80 - "withdrawal_modes": [
81 - "最高固定提取金额"
82 - ],
83 - "withdrawal_periods": [
84 - "1年",
85 - "3年",
86 - "5年",
87 - "10年"
88 - ]
89 -}
90 -```
91 -
92 ----
93 -
94 -## 📝 表单字段 (form_schema)
95 -
96 -```javascript
97 -{
98 - "base_fields": [],
99 - "withdrawal_fields": [],
100 - "reset_map": {}
101 -}
102 -```
103 -
104 ----
105 -
106 -## 🔄 提交字段映射 (submit_mapping)
107 -
108 -```javascript
109 -{}
110 -```
111 -
112 ----
113 -
114 -## 🧩 生成配置片段
115 -
116 -```javascript
117 -/**
118 - * 宏摯傳承保障計劃 - 性別, 年齡, 出生年月日
119 - * @added 2026-02-15T02:20:30.982Z
120 - * @source docs/to-parse/计划书模版2.docx
121 - */
122 - 'savings-product-ef3dd50b': {
123 - name: '宏摯傳承保障計劃 - 性別, 年齡, 出生年月日',
124 - component: 'SavingsTemplate',
125 - category: 'savings',
126 - config: {
127 - currency: 'USD',
128 - payment_periods: ["整付","3年","5年","10年","15年"],
129 - age_range: { min: 0, max: 75 },
130 - insurance_period: '终身',
131 - withdrawal_plan: {
132 - enabled: true,
133 - currencies: ['HKD', 'USD', 'CNY'],
134 - default_currency: 'USD',
135 - withdrawal_modes: ["最高固定提取金额"],
136 - withdrawal_periods: ["1年","3年","5年","10年"]
137 - },
138 - form_schema: savingsFormSchema,
139 - submit_mapping: savingsSubmitMapping
140 - }
141 - }
142 -```
143 -
144 ----
145 -
146 -## ✅ 审核检查清单
147 -
148 -### 基础信息
149 -- [ ] 产品名称正确
150 -- [ ] 产品类型正确(savings/critical-illness/life-insurance)
151 -- [ ] 币种正确(USD/CNY/HKD/EUR)
152 -- [ ] form_sn 唯一且符合命名规范
153 -
154 -### 缴费与年龄
155 -- [ ] 缴费年期选项完整且正确
156 -- [ ] 年龄范围合理
157 -- [ ] 保险期间正确
158 -
159 -### 储蓄类特有(如适用)
160 -- [ ] 提取方式正确
161 -- [ ] 提取期选项完整
162 -- [ ] 表单字段定义完整
163 -- [ ] 提交字段映射正确
164 -
165 ----
166 -
167 -## 📋 审核后操作
168 -
169 -### 确认无误
170 -```bash
171 -# 1. 移动到 approved 目录
172 -mv docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏摯傳承保障計劃-性別-年齡-出生年月日.md \
173 - docs/parse-audit/approved/
174 -
175 -# 2. 合并到正式配置
176 -# 手动复制或使用工具合并到 src/config/plan-templates.js
177 -
178 -# 3. 删除待审核文件(可选)
179 -rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏摯傳承保障計劃-性別-年齡-出生年月日.md
180 -```
181 -
182 -### 需要修改
183 -1. 编辑本文件修正内容
184 -2. 重新提交审核
185 -
186 -### 放弃本次解析
187 -```bash
188 -rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏摯傳承保障計劃-性別-年齡-出生年月日.md
189 -```
190 -
191 ----
192 -
193 -## 审核状态
194 -
195 -- [ ] 待审核
196 -- [ ] 已通过
197 -- [ ] 已拒绝
198 -
199 -## 审核意见
200 -
201 -```text
202 -```
1 -# 产品配置审核 - 计划书模版2.docx
2 -
3 -**解析时间**: 2026/2/15 10:20:30
4 -**原始文件**: 计划书模版2.docx
5 -**数据来源**: docs/to-parse/计划书模版2.docx
6 -
7 ----
8 -
9 -## 📋 产品基本信息
10 -
11 -| 字段 | 提取值 | 需要确认 |
12 -|------|--------|---------|
13 -| 产品名称 | 宏摯家傳承保險計劃- 性別, 年齡, 出生年月日 | ✅ 请核对产品名称 |
14 -| 产品类型 | savings | ✅ 请确认产品类型 |
15 -| 币种 | USD | ✅ 请确认币种 |
16 -| form_sn | `savings-product-aaaa60f8` | ✅ 请确认 form_sn 唯一性 |
17 -| 缴费年期 | ["整付","3年","5年"] | ✅ 请确认缴费年期选项 |
18 -| 年龄范围 | 0-75岁 | ✅ 请确认年龄范围 |
19 -| 保险期间 | 终身 | ✅ 请确认保险期间 |
20 -
21 -
22 -### 💰 储蓄类产品特有字段
23 -
24 -| 字段 | 提取值 | 需要确认 |
25 -|------|--------|---------|
26 -| 提取方式 | ["年龄指定金额","最高固定金额"] | ✅ 请确认提取方式 |
27 -| 提取期 | ["1年","3年","5年","10年"] | ✅ 请确认提取期选项 |
28 -
29 -
30 ----
31 -
32 -## 🤖 智能字段提取报告
33 -
34 -### 匹配统计
35 -
36 -- ✅ 成功匹配: 2 字段
37 -- ⚠️ 使用默认值: 4 字段
38 -- ❌ 未匹配(需人工补充): 0 字段
39 -
40 -### ✅ 已成功匹配的字段
41 -
42 -- product_name
43 -- payment_periods
44 -
45 -
46 -### ⚠️ 使用默认值的字段
47 -
48 -- **product_type**: 未找到字段 "product_type",使用默认值: "savings"
49 -- **currency**: 未找到字段 "currency",使用默认值: "USD"
50 -- **age_range**: 未找到字段 "age_range",使用默认值: {"min":0,"max":75}
51 -- **insurance_period**: 未找到字段 "insurance_period",使用默认值: "终身"
52 -
53 -
54 -
55 -
56 ----
57 -
58 -## 🧾 配置预览
59 -
60 -```javascript
61 -{
62 - "product_name": "宏摯家傳承保險計劃- 性別, 年齡, 出生年月日",
63 - "product_type": "savings",
64 - "currency": "USD",
65 - "form_sn": "savings-product-aaaa60f8",
66 - "payment_periods": [
67 - "整付",
68 - "3年",
69 - "5年"
70 - ],
71 - "age_range": {
72 - "min": 0,
73 - "max": 75
74 - },
75 - "insurance_period": "终身",
76 - "is_savings": true,
77 - "withdrawal_modes": [
78 - "年龄指定金额",
79 - "最高固定金额"
80 - ],
81 - "withdrawal_periods": [
82 - "1年",
83 - "3年",
84 - "5年",
85 - "10年"
86 - ]
87 -}
88 -```
89 -
90 ----
91 -
92 -## 📝 表单字段 (form_schema)
93 -
94 -```javascript
95 -{
96 - "base_fields": [],
97 - "withdrawal_fields": [],
98 - "reset_map": {}
99 -}
100 -```
101 -
102 ----
103 -
104 -## 🔄 提交字段映射 (submit_mapping)
105 -
106 -```javascript
107 -{}
108 -```
109 -
110 ----
111 -
112 -## 🧩 生成配置片段
113 -
114 -```javascript
115 -/**
116 - * 宏摯家傳承保險計劃- 性別, 年齡, 出生年月日
117 - * @added 2026-02-15T02:20:30.997Z
118 - * @source docs/to-parse/计划书模版2.docx
119 - */
120 - 'savings-product-aaaa60f8': {
121 - name: '宏摯家傳承保險計劃- 性別, 年齡, 出生年月日',
122 - component: 'SavingsTemplate',
123 - category: 'savings',
124 - config: {
125 - currency: 'USD',
126 - payment_periods: ["整付","3年","5年"],
127 - age_range: { min: 0, max: 75 },
128 - insurance_period: '终身',
129 - withdrawal_plan: {
130 - enabled: true,
131 - currencies: ['HKD', 'USD', 'CNY'],
132 - default_currency: 'USD',
133 - withdrawal_modes: ["年龄指定金额","最高固定金额"],
134 - withdrawal_periods: ["1年","3年","5年","10年"]
135 - },
136 - form_schema: savingsFormSchema,
137 - submit_mapping: savingsSubmitMapping
138 - }
139 - }
140 -```
141 -
142 ----
143 -
144 -## ✅ 审核检查清单
145 -
146 -### 基础信息
147 -- [ ] 产品名称正确
148 -- [ ] 产品类型正确(savings/critical-illness/life-insurance)
149 -- [ ] 币种正确(USD/CNY/HKD/EUR)
150 -- [ ] form_sn 唯一且符合命名规范
151 -
152 -### 缴费与年龄
153 -- [ ] 缴费年期选项完整且正确
154 -- [ ] 年龄范围合理
155 -- [ ] 保险期间正确
156 -
157 -### 储蓄类特有(如适用)
158 -- [ ] 提取方式正确
159 -- [ ] 提取期选项完整
160 -- [ ] 表单字段定义完整
161 -- [ ] 提交字段映射正确
162 -
163 ----
164 -
165 -## 📋 审核后操作
166 -
167 -### 确认无误
168 -```bash
169 -# 1. 移动到 approved 目录
170 -mv docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏摯家傳承保險計劃-性別-年齡-出生年月日.md \
171 - docs/parse-audit/approved/
172 -
173 -# 2. 合并到正式配置
174 -# 手动复制或使用工具合并到 src/config/plan-templates.js
175 -
176 -# 3. 删除待审核文件(可选)
177 -rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏摯家傳承保險計劃-性別-年齡-出生年月日.md
178 -```
179 -
180 -### 需要修改
181 -1. 编辑本文件修正内容
182 -2. 重新提交审核
183 -
184 -### 放弃本次解析
185 -```bash
186 -rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏摯家傳承保險計劃-性別-年齡-出生年月日.md
187 -```
188 -
189 ----
190 -
191 -## 审核状态
192 -
193 -- [ ] 待审核
194 -- [ ] 已通过
195 -- [ ] 已拒绝
196 -
197 -## 审核意见
198 -
199 -```text
200 -```
1 -# 产品配置审核 - 计划书模版2.docx
2 -
3 -**解析时间**: 2026/2/15 10:20:30
4 -**原始文件**: 计划书模版2.docx
5 -**数据来源**: docs/to-parse/计划书模版2.docx
6 -
7 ----
8 -
9 -## 📋 产品基本信息
10 -
11 -| 字段 | 提取值 | 需要确认 |
12 -|------|--------|---------|
13 -| 产品名称 | 宏浚傳承保障計劃 | ✅ 请核对产品名称 |
14 -| 产品类型 | savings | ✅ 请确认产品类型 |
15 -| 币种 | USD | ✅ 请确认币种 |
16 -| form_sn | `savings-product-d1581522` | ✅ 请确认 form_sn 唯一性 |
17 -| 缴费年期 | ["整付","2年","5年"] | ✅ 请确认缴费年期选项 |
18 -| 年龄范围 | 0-75岁 | ✅ 请确认年龄范围 |
19 -| 保险期间 | 终身 | ✅ 请确认保险期间 |
20 -
21 -
22 -### 💰 储蓄类产品特有字段
23 -
24 -| 字段 | 提取值 | 需要确认 |
25 -|------|--------|---------|
26 -| 提取方式 | ["年龄指定金额","最高固定金额"] | ✅ 请确认提取方式 |
27 -| 提取期 | ["1年","3年","5年","10年"] | ✅ 请确认提取期选项 |
28 -
29 -
30 ----
31 -
32 -## 🤖 智能字段提取报告
33 -
34 -### 匹配统计
35 -
36 -- ✅ 成功匹配: 2 字段
37 -- ⚠️ 使用默认值: 4 字段
38 -- ❌ 未匹配(需人工补充): 0 字段
39 -
40 -### ✅ 已成功匹配的字段
41 -
42 -- product_name
43 -- payment_periods
44 -
45 -
46 -### ⚠️ 使用默认值的字段
47 -
48 -- **product_type**: 未找到字段 "product_type",使用默认值: "savings"
49 -- **currency**: 未找到字段 "currency",使用默认值: "USD"
50 -- **age_range**: 未找到字段 "age_range",使用默认值: {"min":0,"max":75}
51 -- **insurance_period**: 未找到字段 "insurance_period",使用默认值: "终身"
52 -
53 -
54 -
55 -
56 ----
57 -
58 -## 🧾 配置预览
59 -
60 -```javascript
61 -{
62 - "product_name": "宏浚傳承保障計劃",
63 - "product_type": "savings",
64 - "currency": "USD",
65 - "form_sn": "savings-product-d1581522",
66 - "payment_periods": [
67 - "整付",
68 - "2年",
69 - "5年"
70 - ],
71 - "age_range": {
72 - "min": 0,
73 - "max": 75
74 - },
75 - "insurance_period": "终身",
76 - "is_savings": true,
77 - "withdrawal_modes": [
78 - "年龄指定金额",
79 - "最高固定金额"
80 - ],
81 - "withdrawal_periods": [
82 - "1年",
83 - "3年",
84 - "5年",
85 - "10年"
86 - ]
87 -}
88 -```
89 -
90 ----
91 -
92 -## 📝 表单字段 (form_schema)
93 -
94 -```javascript
95 -{
96 - "base_fields": [],
97 - "withdrawal_fields": [],
98 - "reset_map": {}
99 -}
100 -```
101 -
102 ----
103 -
104 -## 🔄 提交字段映射 (submit_mapping)
105 -
106 -```javascript
107 -{}
108 -```
109 -
110 ----
111 -
112 -## 🧩 生成配置片段
113 -
114 -```javascript
115 -/**
116 - * 宏浚傳承保障計劃
117 - * @added 2026-02-15T02:20:30.997Z
118 - * @source docs/to-parse/计划书模版2.docx
119 - */
120 - 'savings-product-d1581522': {
121 - name: '宏浚傳承保障計劃',
122 - component: 'SavingsTemplate',
123 - category: 'savings',
124 - config: {
125 - currency: 'USD',
126 - payment_periods: ["整付","2年","5年"],
127 - age_range: { min: 0, max: 75 },
128 - insurance_period: '终身',
129 - withdrawal_plan: {
130 - enabled: true,
131 - currencies: ['HKD', 'USD', 'CNY'],
132 - default_currency: 'USD',
133 - withdrawal_modes: ["年龄指定金额","最高固定金额"],
134 - withdrawal_periods: ["1年","3年","5年","10年"]
135 - },
136 - form_schema: savingsFormSchema,
137 - submit_mapping: savingsSubmitMapping
138 - }
139 - }
140 -```
141 -
142 ----
143 -
144 -## ✅ 审核检查清单
145 -
146 -### 基础信息
147 -- [ ] 产品名称正确
148 -- [ ] 产品类型正确(savings/critical-illness/life-insurance)
149 -- [ ] 币种正确(USD/CNY/HKD/EUR)
150 -- [ ] form_sn 唯一且符合命名规范
151 -
152 -### 缴费与年龄
153 -- [ ] 缴费年期选项完整且正确
154 -- [ ] 年龄范围合理
155 -- [ ] 保险期间正确
156 -
157 -### 储蓄类特有(如适用)
158 -- [ ] 提取方式正确
159 -- [ ] 提取期选项完整
160 -- [ ] 表单字段定义完整
161 -- [ ] 提交字段映射正确
162 -
163 ----
164 -
165 -## 📋 审核后操作
166 -
167 -### 确认无误
168 -```bash
169 -# 1. 移动到 approved 目录
170 -mv docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏浚傳承保障計劃.md \
171 - docs/parse-audit/approved/
172 -
173 -# 2. 合并到正式配置
174 -# 手动复制或使用工具合并到 src/config/plan-templates.js
175 -
176 -# 3. 删除待审核文件(可选)
177 -rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏浚傳承保障計劃.md
178 -```
179 -
180 -### 需要修改
181 -1. 编辑本文件修正内容
182 -2. 重新提交审核
183 -
184 -### 放弃本次解析
185 -```bash
186 -rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-宏浚傳承保障計劃.md
187 -```
188 -
189 ----
190 -
191 -## 审核状态
192 -
193 -- [ ] 待审核
194 -- [ ] 已通过
195 -- [ ] 已拒绝
196 -
197 -## 审核意见
198 -
199 -```text
200 -```
1 -# 产品配置审核 - 计划书模版2.docx
2 -
3 -**解析时间**: 2026/2/15 10:20:30
4 -**原始文件**: 计划书模版2.docx
5 -**数据来源**: docs/to-parse/计划书模版2.docx
6 -
7 ----
8 -
9 -## 📋 产品基本信息
10 -
11 -| 字段 | 提取值 | 需要确认 |
12 -|------|--------|---------|
13 -| 产品名称 | 赤霞珠終身壽險計劃2基本人壽保障選項 | ✅ 请核对产品名称 |
14 -| 产品类型 | savings | ✅ 请确认产品类型 |
15 -| 币种 | USD | ✅ 请确认币种 |
16 -| form_sn | `savings-2-031c1237` | ✅ 请确认 form_sn 唯一性 |
17 -| 缴费年期 | ["5年","8年","12年","15年"] | ✅ 请确认缴费年期选项 |
18 -| 年龄范围 | 0-75岁 | ✅ 请确认年龄范围 |
19 -| 保险期间 | 终身 | ✅ 请确认保险期间 |
20 -
21 -
22 -### 💰 储蓄类产品特有字段
23 -
24 -| 字段 | 提取值 | 需要确认 |
25 -|------|--------|---------|
26 -| 提取方式 | ["年龄指定金额","最高固定金额"] | ✅ 请确认提取方式 |
27 -| 提取期 | ["1年","3年","5年","10年"] | ✅ 请确认提取期选项 |
28 -
29 -
30 ----
31 -
32 -## 🤖 智能字段提取报告
33 -
34 -### 匹配统计
35 -
36 -- ✅ 成功匹配: 2 字段
37 -- ⚠️ 使用默认值: 4 字段
38 -- ❌ 未匹配(需人工补充): 0 字段
39 -
40 -### ✅ 已成功匹配的字段
41 -
42 -- product_name
43 -- payment_periods
44 -
45 -
46 -### ⚠️ 使用默认值的字段
47 -
48 -- **product_type**: 未找到字段 "product_type",使用默认值: "savings"
49 -- **currency**: 未找到字段 "currency",使用默认值: "USD"
50 -- **age_range**: 未找到字段 "age_range",使用默认值: {"min":0,"max":75}
51 -- **insurance_period**: 未找到字段 "insurance_period",使用默认值: "终身"
52 -
53 -
54 -
55 -
56 ----
57 -
58 -## 🧾 配置预览
59 -
60 -```javascript
61 -{
62 - "product_name": "赤霞珠終身壽險計劃2基本人壽保障選項",
63 - "product_type": "savings",
64 - "currency": "USD",
65 - "form_sn": "savings-2-031c1237",
66 - "payment_periods": [
67 - "5年",
68 - "8年",
69 - "12年",
70 - "15年"
71 - ],
72 - "age_range": {
73 - "min": 0,
74 - "max": 75
75 - },
76 - "insurance_period": "终身",
77 - "is_savings": true,
78 - "withdrawal_modes": [
79 - "年龄指定金额",
80 - "最高固定金额"
81 - ],
82 - "withdrawal_periods": [
83 - "1年",
84 - "3年",
85 - "5年",
86 - "10年"
87 - ]
88 -}
89 -```
90 -
91 ----
92 -
93 -## 📝 表单字段 (form_schema)
94 -
95 -```javascript
96 -{
97 - "base_fields": [],
98 - "withdrawal_fields": [],
99 - "reset_map": {}
100 -}
101 -```
102 -
103 ----
104 -
105 -## 🔄 提交字段映射 (submit_mapping)
106 -
107 -```javascript
108 -{}
109 -```
110 -
111 ----
112 -
113 -## 🧩 生成配置片段
114 -
115 -```javascript
116 -/**
117 - * 赤霞珠終身壽險計劃2基本人壽保障選項
118 - * @added 2026-02-15T02:20:30.997Z
119 - * @source docs/to-parse/计划书模版2.docx
120 - */
121 - 'savings-2-031c1237': {
122 - name: '赤霞珠終身壽險計劃2基本人壽保障選項',
123 - component: 'SavingsTemplate',
124 - category: 'savings',
125 - config: {
126 - currency: 'USD',
127 - payment_periods: ["5年","8年","12年","15年"],
128 - age_range: { min: 0, max: 75 },
129 - insurance_period: '终身',
130 - withdrawal_plan: {
131 - enabled: true,
132 - currencies: ['HKD', 'USD', 'CNY'],
133 - default_currency: 'USD',
134 - withdrawal_modes: ["年龄指定金额","最高固定金额"],
135 - withdrawal_periods: ["1年","3年","5年","10年"]
136 - },
137 - form_schema: savingsFormSchema,
138 - submit_mapping: savingsSubmitMapping
139 - }
140 - }
141 -```
142 -
143 ----
144 -
145 -## ✅ 审核检查清单
146 -
147 -### 基础信息
148 -- [ ] 产品名称正确
149 -- [ ] 产品类型正确(savings/critical-illness/life-insurance)
150 -- [ ] 币种正确(USD/CNY/HKD/EUR)
151 -- [ ] form_sn 唯一且符合命名规范
152 -
153 -### 缴费与年龄
154 -- [ ] 缴费年期选项完整且正确
155 -- [ ] 年龄范围合理
156 -- [ ] 保险期间正确
157 -
158 -### 储蓄类特有(如适用)
159 -- [ ] 提取方式正确
160 -- [ ] 提取期选项完整
161 -- [ ] 表单字段定义完整
162 -- [ ] 提交字段映射正确
163 -
164 ----
165 -
166 -## 📋 审核后操作
167 -
168 -### 确认无误
169 -```bash
170 -# 1. 移动到 approved 目录
171 -mv docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-赤霞珠終身壽險計劃2基本人壽保障選項.md \
172 - docs/parse-audit/approved/
173 -
174 -# 2. 合并到正式配置
175 -# 手动复制或使用工具合并到 src/config/plan-templates.js
176 -
177 -# 3. 删除待审核文件(可选)
178 -rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-赤霞珠終身壽險計劃2基本人壽保障選項.md
179 -```
180 -
181 -### 需要修改
182 -1. 编辑本文件修正内容
183 -2. 重新提交审核
184 -
185 -### 放弃本次解析
186 -```bash
187 -rm docs/parse-audit/pending/计划书模版2/2026-02-15-计划书模版2-赤霞珠終身壽險計劃2基本人壽保障選項.md
188 -```
189 -
190 ----
191 -
192 -## 审核状态
193 -
194 -- [ ] 待审核
195 -- [ ] 已通过
196 -- [ ] 已拒绝
197 -
198 -## 审核意见
199 -
200 -```text
201 -```
1 -{"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"}
2 -{"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"}
3 -{"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"}
4 -{"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"}
5 -{"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"}
6 -{"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"}
7 -{"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"}
1 -{"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}}
2 -{"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}}
3 -{"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}}
4 -{"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"}}
5 -{"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}}
6 -{"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}}
7 -{"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}}
8 -{"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"}}
9 -{"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"}}
10 -{"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"}}
11 -{"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"}}
12 -{"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"}}
13 -{"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}}
14 -{"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+ }"}}
15 -{"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+ }"}}
16 -{"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+ }"}}
17 -{"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+ }"}}
18 -{"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+ }"}}
19 -{"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+ }"}}
20 -{"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"}}
21 -{"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"}}
22 -{"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"}}
23 -{"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"}}
24 -{"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"}}
25 -{"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': {"}}
26 -{"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': {"}}
27 -{"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': {"}}
28 -{"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+ }"}}
29 -{"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': {"}}
1 -/**
2 - * 计划书模版配置
3 - *
4 - * @description 定义产品 form_sn 到模版组件和配置的映射关系
5 - * @module config/plan-templates
6 - * @author Claude Code
7 - * @created 2026-02-06
8 - * @updated 2026-02-13 - 新增文档解析工具入口
9 - *
10 - * --- 快速添加新产品(开发工具) ---
11 - * 开发环境可使用以下工具快速添加新产品配置:
12 - * 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
13 - * 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
14 - *
15 - * 使用方式:
16 - * - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
17 - *
18 - * --- 手动添加步骤 ---
19 - * 1. 找到对应的产品分类(人寿/重疾/储蓄)
20 - * 2. 复制现有配置作为模板
21 - * 3. 修改 name, currency, payment_periods, age_range 等字段
22 - * 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
23 - */
24 -
25 -/**
26 - * 计划书模版配置映射
27 - * @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
28 - *
29 - * @example
30 - * // 产品 API 返回
31 - * {
32 - * id: 1,
33 - * product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
34 - * form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
35 - * }
36 - */
37 -export const PLAN_TEMPLATES = {
38 - // 人寿保险产品 - WIOP3E
39 - 'life-insurance-wiop3e': {
40 - name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
41 - component: 'LifeInsuranceTemplate',
42 - config: {
43 - currency: 'USD', // 币种:USD/CNY/HKD/EUR
44 - payment_periods: [
45 - // 缴费年期选项
46 - '整付(0-75 岁)',
47 - '5 年(0-70 岁)',
48 - '10 年(0-70 岁)'
49 - ],
50 - age_range: { min: 0, max: 75 }, // 年龄范围
51 - insurance_period: '终身' // 保险期间
52 - }
53 - },
54 -
55 - // 人寿保险产品 - WIOP3
56 - 'life-insurance-wiop3': {
57 - name: 'WIOP3 - 盈传创富保障计划 3',
58 - component: 'LifeInsuranceTemplate',
59 - config: {
60 - currency: 'USD',
61 - payment_periods: [
62 - '整付(0-75 岁)',
63 - '5 年(0-70 岁)',
64 - '10 年(0-70 岁)'
65 - ],
66 - age_range: { min: 0, max: 75 },
67 - insurance_period: '终身'
68 - }
69 - },
70 -
71 - // 重疾保险产品 - MPC
72 - 'critical-illness-mpc': {
73 - name: 'MPC 守护无间重疾',
74 - component: 'CriticalIllnessTemplate',
75 - config: {
76 - currency: 'USD',
77 - payment_periods: [
78 - '10 年(15 日 - 65 岁)',
79 - '20 年(15 日 - 65 岁)',
80 - '25 年(15 日 - 60 岁)'
81 - ],
82 - age_range: { min: 0, max: 65 },
83 - insurance_period: '终身'
84 - }
85 - },
86 -
87 - // 重疾保险产品 - MBC PRO
88 - 'critical-illness-mbc-pro': {
89 - name: 'MBC PRO 活跃人生重疾保 PRO',
90 - component: 'CriticalIllnessTemplate',
91 - config: {
92 - currency: 'USD',
93 - payment_periods: [
94 - '10 年(15 日 - 65 岁)',
95 - '20 年(15 日 - 65 岁)',
96 - '25 年(15 日 - 60 岁)'
97 - ],
98 - age_range: { min: 0, max: 65 },
99 - insurance_period: '终身'
100 - }
101 - },
102 -
103 - // 重疾保险产品 - MBC2
104 - 'critical-illness-mbc2': {
105 - name: 'MBC2 活跃人生重疾保 2',
106 - component: 'CriticalIllnessTemplate',
107 - config: {
108 - currency: 'USD',
109 - payment_periods: [
110 - '10 年(15 日 - 65 岁)',
111 - '20 年(15 日 - 65 岁)',
112 - '25 年(15 日 - 60 岁)'
113 - ],
114 - age_range: { min: 0, max: 65 },
115 - insurance_period: '终身'
116 - }
117 - },
118 -
119 - // ====== 储蓄型产品(统一逻辑) ======
120 -
121 - // GS - 宏挚传承保障计划
122 - 'savings-gs': {
123 - name: '宏挚传承保障计划',
124 - component: 'SavingsTemplate',
125 - category: 'savings', // 储蓄型产品
126 - config: {
127 - currency: 'USD', // 默认美元
128 - payment_periods: [
129 - '整付',
130 - '3 年',
131 - '5 年',
132 - '10 年',
133 - '15 年',
134 - ],
135 - age_range: { min: 0, max: 100 },
136 - insurance_period: '终身',
137 - // 提取计划配置
138 - withdrawal_plan: {
139 - enabled: true,
140 - currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
141 - default_currency: 'USD', // 统一为美元
142 - withdrawal_modes: [
143 - '年龄指定金额', // 方式1
144 - '最高固定金额' // 方式2
145 - ],
146 - withdrawal_periods: [
147 - '1年',
148 - '2年',
149 - '3年',
150 - '5年',
151 - '10年',
152 - '15年',
153 - '20年',
154 - '终身'
155 - ]
156 - }
157 - }
158 - },
159 -
160 - // GC - 宏挚家传保险计划
161 - 'savings-gc': {
162 - name: '宏挚家传保险计划',
163 - component: 'SavingsTemplate',
164 - category: 'savings',
165 - config: {
166 - currency: 'USD',
167 - payment_periods: [
168 - '整付',
169 - '3 年',
170 - '5 年',
171 - ],
172 - age_range: { min: 0, max: 100 },
173 - insurance_period: '终身',
174 - withdrawal_plan: {
175 - enabled: true,
176 - currencies: ['HKD', 'USD', 'CNY'],
177 - default_currency: 'USD', // 统一为美元
178 - withdrawal_modes: ['年龄指定金额', '最高固定金额'],
179 - withdrawal_periods: [
180 - '1年',
181 - '2年',
182 - '3年',
183 - '5年',
184 - '10年',
185 - '15年',
186 - '20年',
187 - '终身'
188 - ]
189 - }
190 - }
191 - },
192 -
193 - // FA - 宏浚传承保障计划
194 - 'savings-fa': {
195 - name: '宏浚传承保障计划',
196 - component: 'SavingsTemplate',
197 - category: 'savings',
198 - config: {
199 - currency: 'USD',
200 - payment_periods: [
201 - '整付',
202 - '2 年',
203 - '5 年',
204 - ],
205 - age_range: { min: 0, max: 100 },
206 - insurance_period: '终身',
207 - withdrawal_plan: {
208 - enabled: true,
209 - currencies: ['HKD', 'USD', 'CNY'],
210 - default_currency: 'USD', // 统一为美元
211 - withdrawal_modes: ['年龄指定金额', '最高固定金额'],
212 - withdrawal_periods: [
213 - '1年',
214 - '2年',
215 - '3年',
216 - '5年',
217 - '10年',
218 - '15年',
219 - '20年',
220 - '终身'
221 - ]
222 - }
223 - }
224 - },
225 -
226 - // LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
227 - 'savings-lv2': {
228 - name: '赤霞珠终身寿险计划2',
229 - component: 'SavingsTemplate',
230 - category: 'savings',
231 - config: {
232 - currency: 'USD',
233 - payment_periods: [
234 - '5 年',
235 - '8 年',
236 - '12 年',
237 - '15 年',
238 - ],
239 - age_range: { min: 0, max: 100 },
240 - insurance_period: '终身',
241 - withdrawal_plan: {
242 - enabled: true,
243 - currencies: ['HKD', 'USD', 'CNY'],
244 - default_currency: 'USD', // 统一为美元
245 - withdrawal_modes: ['年龄指定金额', '最高固定金额'],
246 - withdrawal_periods: [
247 - '1年',
248 - '2年',
249 - '3年',
250 - '5年',
251 - '10年',
252 - '15年',
253 - '20年',
254 - '终身'
255 - ]
256 - }
257 - }
258 - }
259 -}
260 -
261 -/**
262 - * 全局功能开关
263 - * @description 用于控制实验性功能或未来扩展功能的开关
264 - *
265 - * @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
266 - */
267 -export const FEATURE_FLAGS = {
268 - /**
269 - * 多币种切换功能
270 - * @description false: 方案 1 - 固定币种(当前实现)
271 - * true: 方案 2 - 支持多币种切换(未来扩展)
272 - * @type {boolean}
273 - */
274 - MULTI_CURRENCY_ENABLED: false
275 -}
276 -
277 -/**
278 - * 币种符号映射
279 - * @description 币种代码到符号的映射关系
280 - */
281 -export const CURRENCY_SYMBOLS = {
282 - CNY: '¥', // 人民币
283 - USD: '$', // 美元
284 - HKD: 'HK$', // 港币
285 - EUR: '€' // 欧元
286 -}
287 -
288 -/**
289 - * 币种完整信息映射
290 - * @description 币种代码到完整信息的映射(用于多币种模式)
291 - */
292 -export const CURRENCY_MAP = {
293 - CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
294 - USD: { label: '美元', symbol: '$', value: 'USD' },
295 - HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
296 - EUR: { label: '欧元', symbol: '€', value: 'EUR' }
297 -}
298 -
299 -/**
300 - * 根据 form_sn 获取模版配置
301 - * @param {string} formSn - 产品 API 返回的 form_sn 字段
302 - * @returns {Object|null} 模版配置对象,未找到返回 null
303 - *
304 - * @example
305 - * const config = getTemplateConfig('life-insurance-wiop3e')
306 - * // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
307 - */
308 -export function getTemplateConfig(formSn) {
309 - if (!formSn) {
310 - console.warn('[plan-templates] form_sn 为空')
311 - return null
312 - }
313 -
314 - const config = PLAN_TEMPLATES[formSn]
315 - if (!config) {
316 - console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
317 - return null
318 - }
319 -
320 - return config
321 -}
322 -
323 -/**
324 - * 获取币种符号
325 - * @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
326 - * @returns {string} 币种符号
327 - *
328 - * @example
329 - * const symbol = getCurrencySymbol('USD') // 返回: '$'
330 - */
331 -export function getCurrencySymbol(currencyCode) {
332 - return CURRENCY_SYMBOLS[currencyCode] || '¥'
333 -}
1 -/**
2 - * 计划书模版配置
3 - *
4 - * @description 定义产品 form_sn 到模版组件和配置的映射关系
5 - * @module config/plan-templates
6 - * @author Claude Code
7 - * @created 2026-02-06
8 - * @updated 2026-02-13 - 新增文档解析工具入口
9 - *
10 - * --- 快速添加新产品(开发工具) ---
11 - * 开发环境可使用以下工具快速添加新产品配置:
12 - * 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
13 - * 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
14 - *
15 - * 使用方式:
16 - * - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
17 - *
18 - * --- 手动添加步骤 ---
19 - * 1. 找到对应的产品分类(人寿/重疾/储蓄)
20 - * 2. 复制现有配置作为模板
21 - * 3. 修改 name, currency, payment_periods, age_range 等字段
22 - * 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
23 - */
24 -
25 -/**
26 - * 计划书模版配置映射
27 - * @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
28 - *
29 - * @example
30 - * // 产品 API 返回
31 - * {
32 - * id: 1,
33 - * product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
34 - * form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
35 - * }
36 - */
37 -export const PLAN_TEMPLATES = {
38 - // 人寿保险产品 - WIOP3E
39 - 'life-insurance-wiop3e': {
40 - name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
41 - component: 'LifeInsuranceTemplate',
42 - config: {
43 - currency: 'USD', // 币种:USD/CNY/HKD/EUR
44 - payment_periods: [
45 - // 缴费年期选项
46 - '整付(0-75 岁)',
47 - '5 年(0-70 岁)',
48 - '10 年(0-70 岁)'
49 - ],
50 - age_range: { min: 0, max: 75 }, // 年龄范围
51 - insurance_period: '终身' // 保险期间
52 - }
53 - },
54 -
55 - // 人寿保险产品 - WIOP3
56 - 'life-insurance-wiop3': {
57 - name: 'WIOP3 - 盈传创富保障计划 3',
58 - component: 'LifeInsuranceTemplate',
59 - config: {
60 - currency: 'USD',
61 - payment_periods: [
62 - '整付(0-75 岁)',
63 - '5 年(0-70 岁)',
64 - '10 年(0-70 岁)'
65 - ],
66 - age_range: { min: 0, max: 75 },
67 - insurance_period: '终身'
68 - }
69 - },
70 -
71 - // 重疾保险产品 - MPC
72 - 'critical-illness-mpc': {
73 - name: 'MPC 守护无间重疾',
74 - component: 'CriticalIllnessTemplate',
75 - config: {
76 - currency: 'USD',
77 - payment_periods: [
78 - '10 年(15 日 - 65 岁)',
79 - '20 年(15 日 - 65 岁)',
80 - '25 年(15 日 - 60 岁)'
81 - ],
82 - age_range: { min: 0, max: 65 },
83 - insurance_period: '终身'
84 - }
85 - },
86 -
87 - // 重疾保险产品 - MBC PRO
88 - 'critical-illness-mbc-pro': {
89 - name: 'MBC PRO 活跃人生重疾保 PRO',
90 - component: 'CriticalIllnessTemplate',
91 - config: {
92 - currency: 'USD',
93 - payment_periods: [
94 - '10 年(15 日 - 65 岁)',
95 - '20 年(15 日 - 65 岁)',
96 - '25 年(15 日 - 60 岁)'
97 - ],
98 - age_range: { min: 0, max: 65 },
99 - insurance_period: '终身'
100 - }
101 - },
102 -
103 - // 重疾保险产品 - MBC2
104 - 'critical-illness-mbc2': {
105 - name: 'MBC2 活跃人生重疾保 2',
106 - component: 'CriticalIllnessTemplate',
107 - config: {
108 - currency: 'USD',
109 - payment_periods: [
110 - '10 年(15 日 - 65 岁)',
111 - '20 年(15 日 - 65 岁)',
112 - '25 年(15 日 - 60 岁)'
113 - ],
114 - age_range: { min: 0, max: 65 },
115 - insurance_period: '终身'
116 - }
117 - },
118 -
119 - // ====== 储蓄型产品(统一逻辑) ======
120 -
121 - // GS - 宏挚传承保障计划
122 - 'savings-gs': {
123 - name: '宏挚传承保障计划',
124 - component: 'SavingsTemplate',
125 - category: 'savings', // 储蓄型产品
126 - config: {
127 - currency: 'USD', // 默认美元
128 - payment_periods: [
129 - '整付',
130 - '3 年',
131 - '5 年',
132 - '10 年',
133 - '15 年',
134 - ],
135 - age_range: { min: 0, max: 100 },
136 - insurance_period: '终身',
137 - // 提取计划配置
138 - withdrawal_plan: {
139 - enabled: true,
140 - currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
141 - default_currency: 'USD', // 统一为美元
142 - withdrawal_modes: [
143 - '年龄指定金额', // 方式1
144 - '最高固定金额' // 方式2
145 - ],
146 - withdrawal_periods: [
147 - '1年',
148 - '2年',
149 - '3年',
150 - '5年',
151 - '10年',
152 - '15年',
153 - '20年',
154 - '终身'
155 - ]
156 - }
157 - }
158 - },
159 -
160 - // GC - 宏挚家传保险计划
161 - 'savings-gc': {
162 - name: '宏挚家传保险计划',
163 - component: 'SavingsTemplate',
164 - category: 'savings',
165 - config: {
166 - currency: 'USD',
167 - payment_periods: [
168 - '整付',
169 - '3 年',
170 - '5 年',
171 - ],
172 - age_range: { min: 0, max: 100 },
173 - insurance_period: '终身',
174 - withdrawal_plan: {
175 - enabled: true,
176 - currencies: ['HKD', 'USD', 'CNY'],
177 - default_currency: 'USD', // 统一为美元
178 - withdrawal_modes: ['年龄指定金额', '最高固定金额'],
179 - withdrawal_periods: [
180 - '1年',
181 - '2年',
182 - '3年',
183 - '5年',
184 - '10年',
185 - '15年',
186 - '20年',
187 - '终身'
188 - ]
189 - }
190 - }
191 - },
192 -
193 - // FA - 宏浚传承保障计划
194 - 'savings-fa': {
195 - name: '宏浚传承保障计划',
196 - component: 'SavingsTemplate',
197 - category: 'savings',
198 - config: {
199 - currency: 'USD',
200 - payment_periods: [
201 - '整付',
202 - '2 年',
203 - '5 年',
204 - ],
205 - age_range: { min: 0, max: 100 },
206 - insurance_period: '终身',
207 - withdrawal_plan: {
208 - enabled: true,
209 - currencies: ['HKD', 'USD', 'CNY'],
210 - default_currency: 'USD', // 统一为美元
211 - withdrawal_modes: ['年龄指定金额', '最高固定金额'],
212 - withdrawal_periods: [
213 - '1年',
214 - '2年',
215 - '3年',
216 - '5年',
217 - '10年',
218 - '15年',
219 - '20年',
220 - '终身'
221 - ]
222 - }
223 - }
224 - },
225 -
226 - // LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
227 - 'savings-lv2': {
228 - name: '赤霞珠终身寿险计划2',
229 - component: 'SavingsTemplate',
230 - category: 'savings',
231 - config: {
232 - currency: 'USD',
233 - payment_periods: [
234 - '5 年',
235 - '8 年',
236 - '12 年',
237 - '15 年',
238 - ],
239 - age_range: { min: 0, max: 100 },
240 - insurance_period: '终身',
241 - withdrawal_plan: {
242 - enabled: true,
243 - currencies: ['HKD', 'USD', 'CNY'],
244 - default_currency: 'USD', // 统一为美元
245 - withdrawal_modes: ['年龄指定金额', '最高固定金额'],
246 - withdrawal_periods: [
247 - '1年',
248 - '2年',
249 - '3年',
250 - '5年',
251 - '10年',
252 - '15年',
253 - '20年',
254 - '终身'
255 - ]
256 - }
257 - }
258 - }
259 -}
260 -
261 -/**
262 - * 全局功能开关
263 - * @description 用于控制实验性功能或未来扩展功能的开关
264 - *
265 - * @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
266 - */
267 -export const FEATURE_FLAGS = {
268 - /**
269 - * 多币种切换功能
270 - * @description false: 方案 1 - 固定币种(当前实现)
271 - * true: 方案 2 - 支持多币种切换(未来扩展)
272 - * @type {boolean}
273 - */
274 - MULTI_CURRENCY_ENABLED: false
275 -}
276 -
277 -/**
278 - * 币种符号映射
279 - * @description 币种代码到符号的映射关系
280 - */
281 -export const CURRENCY_SYMBOLS = {
282 - CNY: '¥', // 人民币
283 - USD: '$', // 美元
284 - HKD: 'HK$', // 港币
285 - EUR: '€' // 欧元
286 -}
287 -
288 -/**
289 - * 币种完整信息映射
290 - * @description 币种代码到完整信息的映射(用于多币种模式)
291 - */
292 -export const CURRENCY_MAP = {
293 - CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
294 - USD: { label: '美元', symbol: '$', value: 'USD' },
295 - HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
296 - EUR: { label: '欧元', symbol: '€', value: 'EUR' }
297 -}
298 -
299 -/**
300 - * 根据 form_sn 获取模版配置
301 - * @param {string} formSn - 产品 API 返回的 form_sn 字段
302 - * @returns {Object|null} 模版配置对象,未找到返回 null
303 - *
304 - * @example
305 - * const config = getTemplateConfig('life-insurance-wiop3e')
306 - * // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
307 - */
308 -export function getTemplateConfig(formSn) {
309 - if (!formSn) {
310 - console.warn('[plan-templates] form_sn 为空')
311 - return null
312 - }
313 -
314 - const config = PLAN_TEMPLATES[formSn]
315 - if (!config) {
316 - console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
317 - return null
318 - }
319 -
320 - return config
321 -}
322 -
323 -/**
324 - * 获取币种符号
325 - * @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
326 - * @returns {string} 币种符号
327 - *
328 - * @example
329 - * const symbol = getCurrencySymbol('USD') // 返回: '$'
330 - */
331 -export function getCurrencySymbol(currencyCode) {
332 - return CURRENCY_SYMBOLS[currencyCode] || '¥'
333 -}
1 -/**
2 - * 计划书模版配置
3 - *
4 - * @description 定义产品 form_sn 到模版组件和配置的映射关系
5 - * @module config/plan-templates
6 - * @author Claude Code
7 - * @created 2026-02-06
8 - * @updated 2026-02-13 - 新增文档解析工具入口
9 - *
10 - * --- 快速添加新产品(开发工具) ---
11 - * 开发环境可使用以下工具快速添加新产品配置:
12 - * 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
13 - * 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
14 - *
15 - * 使用方式:
16 - * - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
17 - *
18 - * --- 手动添加步骤 ---
19 - * 1. 找到对应的产品分类(人寿/重疾/储蓄)
20 - * 2. 复制现有配置作为模板
21 - * 3. 修改 name, currency, payment_periods, age_range 等字段
22 - * 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
23 - */
24 -
25 -/**
26 - * 计划书模版配置映射
27 - * @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
28 - *
29 - * @example
30 - * // 产品 API 返回
31 - * {
32 - * id: 1,
33 - * product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
34 - * form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
35 - * }
36 - */
37 -// 基础提交字段映射(适用于人寿/重疾等通用表单)
38 -const baseSubmitMapping = {
39 - customer_name: { api_field: 'customer_name' },
40 - gender: { api_field: 'customer_gender' },
41 - birthday: { api_field: 'customer_birthday' },
42 - smoker: { api_field: 'smoking_status' },
43 - coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
44 - payment_period: { api_field: 'payment_years' },
45 - total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
46 -}
47 -
48 -// 人寿/重疾基础表单 Schema(通用保障类)
49 -const protectionFormSchema = {
50 - base_fields: [
51 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
52 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
53 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
54 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
55 - { id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
56 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
57 - ]
58 -}
59 -
60 -// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
61 -const savingsSubmitMapping = {
62 - ...baseSubmitMapping,
63 - withdrawal_enabled: { api_field: 'allow_reduce_amount' },
64 - withdrawal_mode: { api_field: 'withdrawal_option' },
65 - withdrawal_method: { api_field: 'withdrawal_method' },
66 - annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
67 - annual_increase_percentage: { api_field: 'annual_increase_percentage' },
68 - withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
69 - withdrawal_period_specified: { api_field: 'withdrawal_period' },
70 - withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
71 - withdrawal_period_fixed: { api_field: 'withdrawal_period' }
72 -}
73 -
74 -// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
75 -const savingsFormSchema = {
76 - // 基础字段:非提取计划部分
77 - base_fields: [
78 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
79 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
80 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
81 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
82 - { id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
83 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
84 - ],
85 - // 提取计划字段:由 withdrawal_plan 开关控制
86 - withdrawal_fields: [
87 - { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
88 - { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
89 - { id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
90 - { 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: '指定提取金额' }] },
91 - { id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
92 - { 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: '指定提取金额' }] },
93 - { id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
94 - { id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
95 - { 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: '最高固定提取金额' }] }
96 - ],
97 - // 提取模式切换时的清空逻辑,避免脏字段影响提交
98 - reset_map: {
99 - withdrawal_mode: {
100 - '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
101 - '指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
102 - }
103 - }
104 -}
105 -
106 -export const PLAN_TEMPLATES = {
107 - // 人寿保险产品 - WIOP3E
108 - 'life-insurance-wiop3e': {
109 - name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
110 - component: 'LifeInsuranceTemplate',
111 - config: {
112 - currency: 'USD', // 币种:USD/CNY/HKD/EUR
113 - payment_periods: [
114 - // 缴费年期选项
115 - '整付(0-75 岁)',
116 - '5 年(0-70 岁)',
117 - '10 年(0-70 岁)'
118 - ],
119 - age_range: { min: 0, max: 75 }, // 年龄范围
120 - insurance_period: '终身', // 保险期间
121 - form_schema: protectionFormSchema,
122 - submit_mapping: baseSubmitMapping
123 - }
124 - },
125 -
126 - // 人寿保险产品 - WIOP3
127 - 'life-insurance-wiop3': {
128 - name: 'WIOP3 - 盈传创富保障计划 3',
129 - component: 'LifeInsuranceTemplate',
130 - config: {
131 - currency: 'USD',
132 - payment_periods: [
133 - '整付(0-75 岁)',
134 - '5 年(0-70 岁)',
135 - '10 年(0-70 岁)'
136 - ],
137 - age_range: { min: 0, max: 75 },
138 - insurance_period: '终身',
139 - form_schema: protectionFormSchema,
140 - submit_mapping: baseSubmitMapping
141 - }
142 - },
143 -
144 - // 重疾保险产品 - MPC
145 - 'critical-illness-mpc': {
146 - name: 'MPC 守护无间重疾',
147 - component: 'CriticalIllnessTemplate',
148 - config: {
149 - currency: 'USD',
150 - payment_periods: [
151 - '10 年(15 日 - 65 岁)',
152 - '20 年(15 日 - 65 岁)',
153 - '25 年(15 日 - 60 岁)'
154 - ],
155 - age_range: { min: 0, max: 65 },
156 - insurance_period: '终身',
157 - form_schema: protectionFormSchema,
158 - submit_mapping: baseSubmitMapping
159 - }
160 - },
161 -
162 - // 重疾保险产品 - MBC PRO
163 - 'critical-illness-mbc-pro': {
164 - name: 'MBC PRO 活跃人生重疾保 PRO',
165 - component: 'CriticalIllnessTemplate',
166 - config: {
167 - currency: 'USD',
168 - payment_periods: [
169 - '10 年(15 日 - 65 岁)',
170 - '20 年(15 日 - 65 岁)',
171 - '25 年(15 日 - 60 岁)'
172 - ],
173 - age_range: { min: 0, max: 65 },
174 - insurance_period: '终身',
175 - form_schema: protectionFormSchema,
176 - submit_mapping: baseSubmitMapping
177 - }
178 - },
179 -
180 - // 重疾保险产品 - MBC2
181 - 'critical-illness-mbc2': {
182 - name: 'MBC2 活跃人生重疾保 2',
183 - component: 'CriticalIllnessTemplate',
184 - config: {
185 - currency: 'USD',
186 - payment_periods: [
187 - '10 年(15 日 - 65 岁)',
188 - '20 年(15 日 - 65 岁)',
189 - '25 年(15 日 - 60 岁)'
190 - ],
191 - age_range: { min: 0, max: 65 },
192 - insurance_period: '终身',
193 - form_schema: protectionFormSchema,
194 - submit_mapping: baseSubmitMapping
195 - }
196 - },
197 -
198 - // ====== 储蓄型产品(统一逻辑) ======
199 -
200 - // GS - 宏挚传承保障计划
201 - 'savings-gs': {
202 - name: '宏挚传承保障计划',
203 - component: 'SavingsTemplate',
204 - category: 'savings', // 储蓄型产品
205 - config: {
206 - currency: 'USD', // 默认美元
207 - payment_periods: [
208 - '整付',
209 - '3 年',
210 - '5 年',
211 - '10 年',
212 - '15 年',
213 - ],
214 - age_range: { min: 0, max: 100 },
215 - insurance_period: '终身',
216 - // 提取计划配置
217 - withdrawal_plan: {
218 - enabled: true,
219 - currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
220 - default_currency: 'USD', // 统一为美元
221 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
222 - withdrawal_periods: [
223 - '1年',
224 - '2年',
225 - '3年',
226 - '5年',
227 - '10年',
228 - '15年',
229 - '20年',
230 - '终身'
231 - ]
232 - },
233 - form_schema: savingsFormSchema,
234 - submit_mapping: savingsSubmitMapping
235 - }
236 - },
237 -
238 - // GC - 宏挚家传保险计划
239 - 'savings-gc': {
240 - name: '宏挚家传保险计划',
241 - component: 'SavingsTemplate',
242 - category: 'savings',
243 - config: {
244 - currency: 'USD',
245 - payment_periods: [
246 - '整付',
247 - '3 年',
248 - '5 年',
249 - ],
250 - age_range: { min: 0, max: 100 },
251 - insurance_period: '终身',
252 - withdrawal_plan: {
253 - enabled: true,
254 - currencies: ['HKD', 'USD', 'CNY'],
255 - default_currency: 'USD', // 统一为美元
256 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
257 - withdrawal_periods: [
258 - '1年',
259 - '2年',
260 - '3年',
261 - '5年',
262 - '10年',
263 - '15年',
264 - '20年',
265 - '终身'
266 - ]
267 - },
268 - form_schema: savingsFormSchema,
269 - submit_mapping: savingsSubmitMapping
270 - }
271 - },
272 -
273 - // FA - 宏浚传承保障计划
274 - 'savings-fa': {
275 - name: '宏浚传承保障计划',
276 - component: 'SavingsTemplate',
277 - category: 'savings',
278 - config: {
279 - currency: 'USD',
280 - payment_periods: [
281 - '整付',
282 - '2 年',
283 - '5 年',
284 - ],
285 - age_range: { min: 0, max: 100 },
286 - insurance_period: '终身',
287 - withdrawal_plan: {
288 - enabled: true,
289 - currencies: ['HKD', 'USD', 'CNY'],
290 - default_currency: 'USD', // 统一为美元
291 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
292 - withdrawal_periods: [
293 - '1年',
294 - '2年',
295 - '3年',
296 - '5年',
297 - '10年',
298 - '15年',
299 - '20年',
300 - '终身'
301 - ]
302 - },
303 - form_schema: savingsFormSchema,
304 - submit_mapping: savingsSubmitMapping
305 - }
306 - },
307 -
308 - // LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
309 - 'savings-lv2': {
310 - name: '赤霞珠终身寿险计划2',
311 - component: 'SavingsTemplate',
312 - category: 'savings',
313 - config: {
314 - currency: 'USD',
315 - payment_periods: [
316 - '5 年',
317 - '8 年',
318 - '12 年',
319 - '15 年',
320 - ],
321 - age_range: { min: 0, max: 100 },
322 - insurance_period: '终身',
323 - withdrawal_plan: {
324 - enabled: true,
325 - currencies: ['HKD', 'USD', 'CNY'],
326 - default_currency: 'USD', // 统一为美元
327 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
328 - withdrawal_periods: [
329 - '1年',
330 - '2年',
331 - '3年',
332 - '5年',
333 - '10年',
334 - '15年',
335 - '20年',
336 - '终身'
337 - ]
338 - },
339 - form_schema: savingsFormSchema,
340 - submit_mapping: savingsSubmitMapping
341 - }
342 - }
343 -}
344 -
345 -/**
346 - * 全局功能开关
347 - * @description 用于控制实验性功能或未来扩展功能的开关
348 - *
349 - * @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
350 - */
351 -export const FEATURE_FLAGS = {
352 - /**
353 - * 多币种切换功能
354 - * @description false: 方案 1 - 固定币种(当前实现)
355 - * true: 方案 2 - 支持多币种切换(未来扩展)
356 - * @type {boolean}
357 - */
358 - MULTI_CURRENCY_ENABLED: false
359 -}
360 -
361 -/**
362 - * 币种符号映射
363 - * @description 币种代码到符号的映射关系
364 - */
365 -export const CURRENCY_SYMBOLS = {
366 - CNY: '¥', // 人民币
367 - USD: '$', // 美元
368 - HKD: 'HK$', // 港币
369 - EUR: '€' // 欧元
370 -}
371 -
372 -/**
373 - * 币种完整信息映射
374 - * @description 币种代码到完整信息的映射(用于多币种模式)
375 - */
376 -export const CURRENCY_MAP = {
377 - CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
378 - USD: { label: '美元', symbol: '$', value: 'USD' },
379 - HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
380 - EUR: { label: '欧元', symbol: '€', value: 'EUR' }
381 -}
382 -
383 -/**
384 - * 根据 form_sn 获取模版配置
385 - * @param {string} formSn - 产品 API 返回的 form_sn 字段
386 - * @returns {Object|null} 模版配置对象,未找到返回 null
387 - *
388 - * @example
389 - * const config = getTemplateConfig('life-insurance-wiop3e')
390 - * // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
391 - */
392 -export function getTemplateConfig(formSn) {
393 - if (!formSn) {
394 - console.warn('[plan-templates] form_sn 为空')
395 - return null
396 - }
397 -
398 - const config = PLAN_TEMPLATES[formSn]
399 - if (!config) {
400 - console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
401 - return null
402 - }
403 -
404 - return config
405 -}
406 -
407 -/**
408 - * 获取币种符号
409 - * @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
410 - * @returns {string} 币种符号
411 - *
412 - * @example
413 - * const symbol = getCurrencySymbol('USD') // 返回: '$'
414 - */
415 -export function getCurrencySymbol(currencyCode) {
416 - return CURRENCY_SYMBOLS[currencyCode] || '¥'
417 -}
1 -/**
2 - * 计划书模版配置
3 - *
4 - * @description 定义产品 form_sn 到模版组件和配置的映射关系
5 - * @module config/plan-templates
6 - * @author Claude Code
7 - * @created 2026-02-06
8 - * @updated 2026-02-13 - 新增文档解析工具入口
9 - *
10 - * --- 快速添加新产品(开发工具) ---
11 - * 开发环境可使用以下工具快速添加新产品配置:
12 - * 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
13 - * 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
14 - *
15 - * 使用方式:
16 - * - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
17 - *
18 - * --- 手动添加步骤 ---
19 - * 1. 找到对应的产品分类(人寿/重疾/储蓄)
20 - * 2. 复制现有配置作为模板
21 - * 3. 修改 name, currency, payment_periods, age_range 等字段
22 - * 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
23 - */
24 -
25 -/**
26 - * 计划书模版配置映射
27 - * @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
28 - *
29 - * @example
30 - * // 产品 API 返回
31 - * {
32 - * id: 1,
33 - * product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
34 - * form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
35 - * }
36 - */
37 -// 基础提交字段映射(适用于人寿/重疾等通用表单)
38 -const baseSubmitMapping = {
39 - customer_name: { api_field: 'customer_name' },
40 - gender: { api_field: 'customer_gender' },
41 - birthday: { api_field: 'customer_birthday' },
42 - smoker: { api_field: 'smoking_status' },
43 - coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
44 - payment_period: { api_field: 'payment_years' },
45 - total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
46 -}
47 -
48 -// 人寿/重疾基础表单 Schema(通用保障类)
49 -const protectionFormSchema = {
50 - base_fields: [
51 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
52 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
53 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
54 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
55 - { id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
56 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
57 - ]
58 -}
59 -
60 -// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
61 -const savingsSubmitMapping = {
62 - ...baseSubmitMapping,
63 - withdrawal_enabled: { api_field: 'allow_reduce_amount' },
64 - withdrawal_mode: { api_field: 'withdrawal_option' },
65 - withdrawal_method: { api_field: 'withdrawal_method' },
66 - annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
67 - annual_increase_percentage: { api_field: 'annual_increase_percentage' },
68 - withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
69 - withdrawal_period_specified: { api_field: 'withdrawal_period' },
70 - withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
71 - withdrawal_period_fixed: { api_field: 'withdrawal_period' }
72 -}
73 -
74 -// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
75 -const savingsFormSchema = {
76 - // 基础字段:非提取计划部分
77 - base_fields: [
78 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
79 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
80 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
81 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
82 - { id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
83 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
84 - ],
85 - // 提取计划字段:由 withdrawal_plan 开关控制
86 - withdrawal_fields: [
87 - { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
88 - { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
89 - { id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
90 - { 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: '指定提取金额' }] },
91 - { id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
92 - { 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: '指定提取金额' }] },
93 - { id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
94 - { id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
95 - { 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: '最高固定提取金额' }] }
96 - ],
97 - // 提取模式切换时的清空逻辑,避免脏字段影响提交
98 - reset_map: {
99 - withdrawal_mode: {
100 - '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
101 - '指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
102 - }
103 - }
104 -}
105 -
106 -export const PLAN_TEMPLATES = {
107 - // 人寿保险产品 - WIOP3E
108 - 'life-insurance-wiop3e': {
109 - name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
110 - component: 'LifeInsuranceTemplate',
111 - config: {
112 - currency: 'USD', // 币种:USD/CNY/HKD/EUR
113 - payment_periods: [
114 - // 缴费年期选项
115 - '整付(0-75 岁)',
116 - '5 年(0-70 岁)',
117 - '10 年(0-70 岁)'
118 - ],
119 - age_range: { min: 0, max: 75 }, // 年龄范围
120 - insurance_period: '终身', // 保险期间
121 - form_schema: protectionFormSchema,
122 - submit_mapping: baseSubmitMapping
123 - }
124 - },
125 -
126 - // 人寿保险产品 - WIOP3
127 - 'life-insurance-wiop3': {
128 - name: 'WIOP3 - 盈传创富保障计划 3',
129 - component: 'LifeInsuranceTemplate',
130 - config: {
131 - currency: 'USD',
132 - payment_periods: [
133 - '整付(0-75 岁)',
134 - '5 年(0-70 岁)',
135 - '10 年(0-70 岁)'
136 - ],
137 - age_range: { min: 0, max: 75 },
138 - insurance_period: '终身',
139 - form_schema: protectionFormSchema,
140 - submit_mapping: baseSubmitMapping
141 - }
142 - },
143 -
144 - // 重疾保险产品 - MPC
145 - 'critical-illness-mpc': {
146 - name: 'MPC 守护无间重疾',
147 - component: 'CriticalIllnessTemplate',
148 - config: {
149 - currency: 'USD',
150 - payment_periods: [
151 - '10 年(15 日 - 65 岁)',
152 - '20 年(15 日 - 65 岁)',
153 - '25 年(15 日 - 60 岁)'
154 - ],
155 - age_range: { min: 0, max: 65 },
156 - insurance_period: '终身',
157 - form_schema: protectionFormSchema,
158 - submit_mapping: baseSubmitMapping
159 - }
160 - },
161 -
162 - // 重疾保险产品 - MBC PRO
163 - 'critical-illness-mbc-pro': {
164 - name: 'MBC PRO 活跃人生重疾保 PRO',
165 - component: 'CriticalIllnessTemplate',
166 - config: {
167 - currency: 'USD',
168 - payment_periods: [
169 - '10 年(15 日 - 65 岁)',
170 - '20 年(15 日 - 65 岁)',
171 - '25 年(15 日 - 60 岁)'
172 - ],
173 - age_range: { min: 0, max: 65 },
174 - insurance_period: '终身',
175 - form_schema: protectionFormSchema,
176 - submit_mapping: baseSubmitMapping
177 - }
178 - },
179 -
180 - // 重疾保险产品 - MBC2
181 - 'critical-illness-mbc2': {
182 - name: 'MBC2 活跃人生重疾保 2',
183 - component: 'CriticalIllnessTemplate',
184 - config: {
185 - currency: 'USD',
186 - payment_periods: [
187 - '10 年(15 日 - 65 岁)',
188 - '20 年(15 日 - 65 岁)',
189 - '25 年(15 日 - 60 岁)'
190 - ],
191 - age_range: { min: 0, max: 65 },
192 - insurance_period: '终身',
193 - form_schema: protectionFormSchema,
194 - submit_mapping: baseSubmitMapping
195 - }
196 - },
197 -
198 - // ====== 储蓄型产品(统一逻辑) ======
199 -
200 - // GS - 宏挚传承保障计划
201 - 'savings-gs': {
202 - name: '宏挚传承保障计划',
203 - component: 'SavingsTemplate',
204 - category: 'savings', // 储蓄型产品
205 - config: {
206 - currency: 'USD', // 默认美元
207 - payment_periods: [
208 - '整付',
209 - '3 年',
210 - '5 年',
211 - '10 年',
212 - '15 年',
213 - ],
214 - age_range: { min: 0, max: 100 },
215 - insurance_period: '终身',
216 - // 提取计划配置
217 - withdrawal_plan: {
218 - enabled: true,
219 - currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
220 - default_currency: 'USD', // 统一为美元
221 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
222 - withdrawal_periods: [
223 - '1年',
224 - '2年',
225 - '3年',
226 - '5年',
227 - '10年',
228 - '15年',
229 - '20年',
230 - '终身'
231 - ]
232 - },
233 - form_schema: savingsFormSchema,
234 - submit_mapping: savingsSubmitMapping
235 - }
236 - },
237 -
238 - // GC - 宏挚家传保险计划
239 - 'savings-gc': {
240 - name: '宏挚家传保险计划',
241 - component: 'SavingsTemplate',
242 - category: 'savings',
243 - config: {
244 - currency: 'USD',
245 - payment_periods: [
246 - '整付',
247 - '3 年',
248 - '5 年',
249 - ],
250 - age_range: { min: 0, max: 100 },
251 - insurance_period: '终身',
252 - withdrawal_plan: {
253 - enabled: true,
254 - currencies: ['HKD', 'USD', 'CNY'],
255 - default_currency: 'USD', // 统一为美元
256 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
257 - withdrawal_periods: [
258 - '1年',
259 - '2年',
260 - '3年',
261 - '5年',
262 - '10年',
263 - '15年',
264 - '20年',
265 - '终身'
266 - ]
267 - },
268 - form_schema: savingsFormSchema,
269 - submit_mapping: savingsSubmitMapping
270 - }
271 - },
272 -
273 - // FA - 宏浚传承保障计划
274 - 'savings-fa': {
275 - name: '宏浚传承保障计划',
276 - component: 'SavingsTemplate',
277 - category: 'savings',
278 - config: {
279 - currency: 'USD',
280 - payment_periods: [
281 - '整付',
282 - '2 年',
283 - '5 年',
284 - ],
285 - age_range: { min: 0, max: 100 },
286 - insurance_period: '终身',
287 - withdrawal_plan: {
288 - enabled: true,
289 - currencies: ['HKD', 'USD', 'CNY'],
290 - default_currency: 'USD', // 统一为美元
291 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
292 - withdrawal_periods: [
293 - '1年',
294 - '2年',
295 - '3年',
296 - '5年',
297 - '10年',
298 - '15年',
299 - '20年',
300 - '终身'
301 - ]
302 - },
303 - form_schema: savingsFormSchema,
304 - submit_mapping: savingsSubmitMapping
305 - }
306 - },
307 -
308 - // LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
309 - 'savings-lv2': {
310 - name: '赤霞珠终身寿险计划2',
311 - component: 'SavingsTemplate',
312 - category: 'savings',
313 - config: {
314 - currency: 'USD',
315 - payment_periods: [
316 - '5 年',
317 - '8 年',
318 - '12 年',
319 - '15 年',
320 - ],
321 - age_range: { min: 0, max: 100 },
322 - insurance_period: '终身',
323 - withdrawal_plan: {
324 - enabled: true,
325 - currencies: ['HKD', 'USD', 'CNY'],
326 - default_currency: 'USD', // 统一为美元
327 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
328 - withdrawal_periods: [
329 - '1年',
330 - '2年',
331 - '3年',
332 - '5年',
333 - '10年',
334 - '15年',
335 - '20年',
336 - '终身'
337 - ]
338 - },
339 - form_schema: savingsFormSchema,
340 - submit_mapping: savingsSubmitMapping
341 - }
342 - },
343 -
344 - /**
345 - * 测试计划书-智享未来
346 - * @added 2026-02-14T13:10:33.924Z
347 - * @source docs/to-parse/测试计划书-智享未来.md
348 - */
349 - 'savings-product-30b41aae': {
350 - name: '测试计划书-智享未来',
351 - component: 'SavingsTemplate',
352 - category: 'savings',
353 - config: {
354 - currency: 'USD',
355 - payment_periods: ["整付", "3年", "5年"],
356 - age_range: { min: 0, max: 75 },
357 - insurance_period: '终身',
358 - withdrawal_plan: {
359 - enabled: true,
360 - currencies: ['HKD', 'USD', 'CNY'],
361 - default_currency: 'USD',
362 - withdrawal_modes: ["年龄指定金额", "最高固定金额"],
363 - withdrawal_periods: ["1年", "3年", "5年", "10年"]
364 - },
365 - form_schema: savingsFormSchema,
366 - submit_mapping: savingsSubmitMapping
367 - }
368 - }
369 -}
370 -
371 -/**
372 - * 全局功能开关
373 - * @description 用于控制实验性功能或未来扩展功能的开关
374 - *
375 - * @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
376 - */
377 -export const FEATURE_FLAGS = {
378 - /**
379 - * 多币种切换功能
380 - * @description false: 方案 1 - 固定币种(当前实现)
381 - * true: 方案 2 - 支持多币种切换(未来扩展)
382 - * @type {boolean}
383 - */
384 - MULTI_CURRENCY_ENABLED: false
385 -}
386 -
387 -/**
388 - * 币种符号映射
389 - * @description 币种代码到符号的映射关系
390 - */
391 -export const CURRENCY_SYMBOLS = {
392 - CNY: '¥', // 人民币
393 - USD: '$', // 美元
394 - HKD: 'HK$', // 港币
395 - EUR: '€' // 欧元
396 -}
397 -
398 -/**
399 - * 币种完整信息映射
400 - * @description 币种代码到完整信息的映射(用于多币种模式)
401 - */
402 -export const CURRENCY_MAP = {
403 - CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
404 - USD: { label: '美元', symbol: '$', value: 'USD' },
405 - HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
406 - EUR: { label: '欧元', symbol: '€', value: 'EUR' }
407 -}
408 -
409 -/**
410 - * 根据 form_sn 获取模版配置
411 - * @param {string} formSn - 产品 API 返回的 form_sn 字段
412 - * @returns {Object|null} 模版配置对象,未找到返回 null
413 - *
414 - * @example
415 - * const config = getTemplateConfig('life-insurance-wiop3e')
416 - * // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
417 - */
418 -export function getTemplateConfig(formSn) {
419 - if (!formSn) {
420 - console.warn('[plan-templates] form_sn 为空')
421 - return null
422 - }
423 -
424 - const config = PLAN_TEMPLATES[formSn]
425 - if (!config) {
426 - console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
427 - return null
428 - }
429 -
430 - return config
431 -}
432 -
433 -/**
434 - * 获取币种符号
435 - * @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
436 - * @returns {string} 币种符号
437 - *
438 - * @example
439 - * const symbol = getCurrencySymbol('USD') // 返回: '$'
440 - */
441 -export function getCurrencySymbol(currencyCode) {
442 - return CURRENCY_SYMBOLS[currencyCode] || '¥'
443 -}
1 -/**
2 - * 计划书模版配置
3 - *
4 - * @description 定义产品 form_sn 到模版组件和配置的映射关系
5 - * @module config/plan-templates
6 - * @author Claude Code
7 - * @created 2026-02-06
8 - * @updated 2026-02-13 - 新增文档解析工具入口
9 - *
10 - * --- 快速添加新产品(开发工具) ---
11 - * 开发环境可使用以下工具快速添加新产品配置:
12 - * 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
13 - * 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
14 - *
15 - * 使用方式:
16 - * - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
17 - *
18 - * --- 手动添加步骤 ---
19 - * 1. 找到对应的产品分类(人寿/重疾/储蓄)
20 - * 2. 复制现有配置作为模板
21 - * 3. 修改 name, currency, payment_periods, age_range 等字段
22 - * 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
23 - */
24 -
25 -/**
26 - * 计划书模版配置映射
27 - * @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
28 - *
29 - * @example
30 - * // 产品 API 返回
31 - * {
32 - * id: 1,
33 - * product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
34 - * form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
35 - * }
36 - */
37 -// 基础提交字段映射(适用于人寿/重疾等通用表单)
38 -const baseSubmitMapping = {
39 - customer_name: { api_field: 'customer_name' },
40 - gender: { api_field: 'customer_gender' },
41 - birthday: { api_field: 'customer_birthday' },
42 - smoker: { api_field: 'smoking_status' },
43 - coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
44 - payment_period: { api_field: 'payment_years' },
45 - total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
46 -}
47 -
48 -// 人寿/重疾基础表单 Schema(通用保障类)
49 -const protectionFormSchema = {
50 - base_fields: [
51 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
52 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
53 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
54 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
55 - { id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
56 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
57 - ]
58 -}
59 -
60 -// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
61 -const savingsSubmitMapping = {
62 - ...baseSubmitMapping,
63 - withdrawal_enabled: { api_field: 'allow_reduce_amount' },
64 - withdrawal_mode: { api_field: 'withdrawal_option' },
65 - withdrawal_method: { api_field: 'withdrawal_method' },
66 - annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
67 - annual_increase_percentage: { api_field: 'annual_increase_percentage' },
68 - withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
69 - withdrawal_period_specified: { api_field: 'withdrawal_period' },
70 - withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
71 - withdrawal_period_fixed: { api_field: 'withdrawal_period' }
72 -}
73 -
74 -// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
75 -const savingsFormSchema = {
76 - // 基础字段:非提取计划部分
77 - base_fields: [
78 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
79 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
80 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
81 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
82 - { id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
83 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
84 - ],
85 - // 提取计划字段:由 withdrawal_plan 开关控制
86 - withdrawal_fields: [
87 - { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
88 - { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
89 - { id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
90 - { 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: '指定提取金额' }] },
91 - { id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
92 - { 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: '指定提取金额' }] },
93 - { id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
94 - { id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
95 - { 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: '最高固定提取金额' }] }
96 - ],
97 - // 提取模式切换时的清空逻辑,避免脏字段影响提交
98 - reset_map: {
99 - withdrawal_mode: {
100 - '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
101 - '指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
102 - }
103 - }
104 -}
105 -
106 -export const PLAN_TEMPLATES = {
107 - // 人寿保险产品 - WIOP3E
108 - 'life-insurance-wiop3e': {
109 - name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
110 - component: 'LifeInsuranceTemplate',
111 - config: {
112 - currency: 'USD', // 币种:USD/CNY/HKD/EUR
113 - payment_periods: [
114 - // 缴费年期选项
115 - '整付(0-75 岁)',
116 - '5 年(0-70 岁)',
117 - '10 年(0-70 岁)'
118 - ],
119 - age_range: { min: 0, max: 75 }, // 年龄范围
120 - insurance_period: '终身', // 保险期间
121 - form_schema: protectionFormSchema,
122 - submit_mapping: baseSubmitMapping
123 - }
124 - },
125 -
126 - // 人寿保险产品 - WIOP3
127 - 'life-insurance-wiop3': {
128 - name: 'WIOP3 - 盈传创富保障计划 3',
129 - component: 'LifeInsuranceTemplate',
130 - config: {
131 - currency: 'USD',
132 - payment_periods: [
133 - '整付(0-75 岁)',
134 - '5 年(0-70 岁)',
135 - '10 年(0-70 岁)'
136 - ],
137 - age_range: { min: 0, max: 75 },
138 - insurance_period: '终身',
139 - form_schema: protectionFormSchema,
140 - submit_mapping: baseSubmitMapping
141 - }
142 - },
143 -
144 - // 重疾保险产品 - MPC
145 - 'critical-illness-mpc': {
146 - name: 'MPC 守护无间重疾',
147 - component: 'CriticalIllnessTemplate',
148 - config: {
149 - currency: 'USD',
150 - payment_periods: [
151 - '10 年(15 日 - 65 岁)',
152 - '20 年(15 日 - 65 岁)',
153 - '25 年(15 日 - 60 岁)'
154 - ],
155 - age_range: { min: 0, max: 65 },
156 - insurance_period: '终身',
157 - form_schema: protectionFormSchema,
158 - submit_mapping: baseSubmitMapping
159 - }
160 - },
161 -
162 - // 重疾保险产品 - MBC PRO
163 - 'critical-illness-mbc-pro': {
164 - name: 'MBC PRO 活跃人生重疾保 PRO',
165 - component: 'CriticalIllnessTemplate',
166 - config: {
167 - currency: 'USD',
168 - payment_periods: [
169 - '10 年(15 日 - 65 岁)',
170 - '20 年(15 日 - 65 岁)',
171 - '25 年(15 日 - 60 岁)'
172 - ],
173 - age_range: { min: 0, max: 65 },
174 - insurance_period: '终身',
175 - form_schema: protectionFormSchema,
176 - submit_mapping: baseSubmitMapping
177 - }
178 - },
179 -
180 - // 重疾保险产品 - MBC2
181 - 'critical-illness-mbc2': {
182 - name: 'MBC2 活跃人生重疾保 2',
183 - component: 'CriticalIllnessTemplate',
184 - config: {
185 - currency: 'USD',
186 - payment_periods: [
187 - '10 年(15 日 - 65 岁)',
188 - '20 年(15 日 - 65 岁)',
189 - '25 年(15 日 - 60 岁)'
190 - ],
191 - age_range: { min: 0, max: 65 },
192 - insurance_period: '终身',
193 - form_schema: protectionFormSchema,
194 - submit_mapping: baseSubmitMapping
195 - }
196 - },
197 -
198 - // ====== 储蓄型产品(统一逻辑) ======
199 -
200 - // GS - 宏挚传承保障计划
201 - 'savings-gs': {
202 - name: '宏挚传承保障计划',
203 - component: 'SavingsTemplate',
204 - category: 'savings', // 储蓄型产品
205 - config: {
206 - currency: 'USD', // 默认美元
207 - payment_periods: [
208 - '整付',
209 - '3 年',
210 - '5 年',
211 - '10 年',
212 - '15 年',
213 - ],
214 - age_range: { min: 0, max: 100 },
215 - insurance_period: '终身',
216 - // 提取计划配置
217 - withdrawal_plan: {
218 - enabled: true,
219 - currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
220 - default_currency: 'USD', // 统一为美元
221 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
222 - withdrawal_periods: [
223 - '1年',
224 - '2年',
225 - '3年',
226 - '5年',
227 - '10年',
228 - '15年',
229 - '20年',
230 - '终身'
231 - ]
232 - },
233 - form_schema: savingsFormSchema,
234 - submit_mapping: savingsSubmitMapping
235 - }
236 - },
237 -
238 - // GC - 宏挚家传保险计划
239 - 'savings-gc': {
240 - name: '宏挚家传保险计划',
241 - component: 'SavingsTemplate',
242 - category: 'savings',
243 - config: {
244 - currency: 'USD',
245 - payment_periods: [
246 - '整付',
247 - '3 年',
248 - '5 年',
249 - ],
250 - age_range: { min: 0, max: 100 },
251 - insurance_period: '终身',
252 - withdrawal_plan: {
253 - enabled: true,
254 - currencies: ['HKD', 'USD', 'CNY'],
255 - default_currency: 'USD', // 统一为美元
256 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
257 - withdrawal_periods: [
258 - '1年',
259 - '2年',
260 - '3年',
261 - '5年',
262 - '10年',
263 - '15年',
264 - '20年',
265 - '终身'
266 - ]
267 - },
268 - form_schema: savingsFormSchema,
269 - submit_mapping: savingsSubmitMapping
270 - }
271 - },
272 -
273 - // FA - 宏浚传承保障计划
274 - 'savings-fa': {
275 - name: '宏浚传承保障计划',
276 - component: 'SavingsTemplate',
277 - category: 'savings',
278 - config: {
279 - currency: 'USD',
280 - payment_periods: [
281 - '整付',
282 - '2 年',
283 - '5 年',
284 - ],
285 - age_range: { min: 0, max: 100 },
286 - insurance_period: '终身',
287 - withdrawal_plan: {
288 - enabled: true,
289 - currencies: ['HKD', 'USD', 'CNY'],
290 - default_currency: 'USD', // 统一为美元
291 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
292 - withdrawal_periods: [
293 - '1年',
294 - '2年',
295 - '3年',
296 - '5年',
297 - '10年',
298 - '15年',
299 - '20年',
300 - '终身'
301 - ]
302 - },
303 - form_schema: savingsFormSchema,
304 - submit_mapping: savingsSubmitMapping
305 - }
306 - },
307 -
308 - // LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
309 - 'savings-lv2': {
310 - name: '赤霞珠终身寿险计划2',
311 - component: 'SavingsTemplate',
312 - category: 'savings',
313 - config: {
314 - currency: 'USD',
315 - payment_periods: [
316 - '5 年',
317 - '8 年',
318 - '12 年',
319 - '15 年',
320 - ],
321 - age_range: { min: 0, max: 100 },
322 - insurance_period: '终身',
323 - withdrawal_plan: {
324 - enabled: true,
325 - currencies: ['HKD', 'USD', 'CNY'],
326 - default_currency: 'USD', // 统一为美元
327 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
328 - withdrawal_periods: [
329 - '1年',
330 - '2年',
331 - '3年',
332 - '5年',
333 - '10年',
334 - '15年',
335 - '20年',
336 - '终身'
337 - ]
338 - },
339 - form_schema: savingsFormSchema,
340 - submit_mapping: savingsSubmitMapping
341 - }
342 - },
343 -
344 - /**
345 - * 测试计划书-智享未来
346 - * @added 2026-02-14T13:10:33.924Z
347 - * @source docs/to-parse/测试计划书-智享未来.md
348 - */
349 - 'savings-product-30b41aae': {
350 - name: '测试计划书-智享未来',
351 - component: 'SavingsTemplate',
352 - category: 'savings',
353 - config: {
354 - currency: 'USD',
355 - payment_periods: ["整付", "3年", "5年"],
356 - age_range: { min: 0, max: 75 },
357 - insurance_period: '终身',
358 - withdrawal_plan: {
359 - enabled: true,
360 - currencies: ['HKD', 'USD', 'CNY'],
361 - default_currency: 'USD',
362 - withdrawal_modes: ["年龄指定金额", "最高固定金额"],
363 - withdrawal_periods: ["1年", "3年", "5年", "10年"]
364 - },
365 - form_schema: savingsFormSchema,
366 - submit_mapping: savingsSubmitMapping
367 - }
368 - },
369 -
370 - /**
371 - * 测试计划书-智享未来2
372 - * @added 2026-02-14T13:58:50.776Z
373 - * @source docs/to-parse/测试计划书-智享未来2.md
374 - */
375 - 'savings-2-148b3acd': {
376 - name: '测试计划书-智享未来2',
377 - component: 'SavingsTemplate',
378 - category: 'savings',
379 - config: {
380 - currency: 'USD',
381 - payment_periods: ["整付","3年","5年"],
382 - age_range: { min: 0, max: 75 },
383 - insurance_period: '终身',
384 - withdrawal_plan: {
385 - enabled: true,
386 - currencies: ['HKD', 'USD', 'CNY'],
387 - default_currency: 'USD',
388 - withdrawal_modes: ["年龄指定金额","最高固定金额"],
389 - withdrawal_periods: ["1年","3年","5年","10年"]
390 - },
391 - form_schema: {
392 - "base_fields": [],
393 - "withdrawal_fields": [],
394 - "reset_map": {}
395 - },
396 - submit_mapping: savingsSubmitMapping
397 - }
398 - }}
399 -
400 -/**
401 - * 全局功能开关
402 - * @description 用于控制实验性功能或未来扩展功能的开关
403 - *
404 - * @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
405 - */
406 -export const FEATURE_FLAGS = {
407 - /**
408 - * 多币种切换功能
409 - * @description false: 方案 1 - 固定币种(当前实现)
410 - * true: 方案 2 - 支持多币种切换(未来扩展)
411 - * @type {boolean}
412 - */
413 - MULTI_CURRENCY_ENABLED: false
414 -}
415 -
416 -/**
417 - * 币种符号映射
418 - * @description 币种代码到符号的映射关系
419 - */
420 -export const CURRENCY_SYMBOLS = {
421 - CNY: '¥', // 人民币
422 - USD: '$', // 美元
423 - HKD: 'HK$', // 港币
424 - EUR: '€' // 欧元
425 -}
426 -
427 -/**
428 - * 币种完整信息映射
429 - * @description 币种代码到完整信息的映射(用于多币种模式)
430 - */
431 -export const CURRENCY_MAP = {
432 - CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
433 - USD: { label: '美元', symbol: '$', value: 'USD' },
434 - HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
435 - EUR: { label: '欧元', symbol: '€', value: 'EUR' }
436 -}
437 -
438 -/**
439 - * 根据 form_sn 获取模版配置
440 - * @param {string} formSn - 产品 API 返回的 form_sn 字段
441 - * @returns {Object|null} 模版配置对象,未找到返回 null
442 - *
443 - * @example
444 - * const config = getTemplateConfig('life-insurance-wiop3e')
445 - * // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
446 - */
447 -export function getTemplateConfig(formSn) {
448 - if (!formSn) {
449 - console.warn('[plan-templates] form_sn 为空')
450 - return null
451 - }
452 -
453 - const config = PLAN_TEMPLATES[formSn]
454 - if (!config) {
455 - console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
456 - return null
457 - }
458 -
459 - return config
460 -}
461 -
462 -/**
463 - * 获取币种符号
464 - * @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
465 - * @returns {string} 币种符号
466 - *
467 - * @example
468 - * const symbol = getCurrencySymbol('USD') // 返回: '$'
469 - */
470 -export function getCurrencySymbol(currencyCode) {
471 - return CURRENCY_SYMBOLS[currencyCode] || '¥'
472 -}
1 -/**
2 - * 计划书模版配置
3 - *
4 - * @description 定义产品 form_sn 到模版组件和配置的映射关系
5 - * @module config/plan-templates
6 - * @author Claude Code
7 - * @created 2026-02-06
8 - * @updated 2026-02-13 - 新增文档解析工具入口
9 - *
10 - * --- 快速添加新产品(开发工具) ---
11 - * 开发环境可使用以下工具快速添加新产品配置:
12 - * 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
13 - * 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
14 - *
15 - * 使用方式:
16 - * - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
17 - *
18 - * --- 手动添加步骤 ---
19 - * 1. 找到对应的产品分类(人寿/重疾/储蓄)
20 - * 2. 复制现有配置作为模板
21 - * 3. 修改 name, currency, payment_periods, age_range 等字段
22 - * 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
23 - */
24 -
25 -/**
26 - * 计划书模版配置映射
27 - * @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
28 - *
29 - * @example
30 - * // 产品 API 返回
31 - * {
32 - * id: 1,
33 - * product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
34 - * form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
35 - * }
36 - */
37 -// 基础提交字段映射(适用于人寿/重疾等通用表单)
38 -const baseSubmitMapping = {
39 - customer_name: { api_field: 'customer_name' },
40 - gender: { api_field: 'customer_gender' },
41 - birthday: { api_field: 'customer_birthday' },
42 - smoker: { api_field: 'smoking_status' },
43 - coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
44 - payment_period: { api_field: 'payment_years' },
45 - total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
46 -}
47 -
48 -// 人寿/重疾基础表单 Schema(通用保障类)
49 -const protectionFormSchema = {
50 - base_fields: [
51 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
52 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
53 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
54 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
55 - { id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
56 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
57 - ]
58 -}
59 -
60 -// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
61 -const savingsSubmitMapping = {
62 - ...baseSubmitMapping,
63 - withdrawal_enabled: { api_field: 'allow_reduce_amount' },
64 - withdrawal_mode: { api_field: 'withdrawal_option' },
65 - withdrawal_method: { api_field: 'withdrawal_method' },
66 - annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
67 - annual_increase_percentage: { api_field: 'annual_increase_percentage' },
68 - withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
69 - withdrawal_period_specified: { api_field: 'withdrawal_period' },
70 - withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
71 - withdrawal_period_fixed: { api_field: 'withdrawal_period' }
72 -}
73 -
74 -// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
75 -const savingsFormSchema = {
76 - // 基础字段:非提取计划部分
77 - base_fields: [
78 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
79 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
80 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
81 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
82 - { id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
83 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
84 - ],
85 - // 提取计划字段:由 withdrawal_plan 开关控制
86 - withdrawal_fields: [
87 - { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
88 - { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
89 - { id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
90 - { 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: '指定提取金额' }] },
91 - { id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
92 - { 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: '指定提取金额' }] },
93 - { id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
94 - { id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
95 - { 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: '最高固定提取金额' }] }
96 - ],
97 - // 提取模式切换时的清空逻辑,避免脏字段影响提交
98 - reset_map: {
99 - withdrawal_mode: {
100 - '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
101 - '指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
102 - }
103 - }
104 -}
105 -
106 -export const PLAN_TEMPLATES = {
107 - // 人寿保险产品 - WIOP3E
108 - 'life-insurance-wiop3e': {
109 - name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
110 - component: 'LifeInsuranceTemplate',
111 - config: {
112 - currency: 'USD', // 币种:USD/CNY/HKD/EUR
113 - payment_periods: [
114 - // 缴费年期选项
115 - '整付(0-75 岁)',
116 - '5 年(0-70 岁)',
117 - '10 年(0-70 岁)'
118 - ],
119 - age_range: { min: 0, max: 75 }, // 年龄范围
120 - insurance_period: '终身', // 保险期间
121 - form_schema: protectionFormSchema,
122 - submit_mapping: baseSubmitMapping
123 - }
124 - },
125 -
126 - // 人寿保险产品 - WIOP3
127 - 'life-insurance-wiop3': {
128 - name: 'WIOP3 - 盈传创富保障计划 3',
129 - component: 'LifeInsuranceTemplate',
130 - config: {
131 - currency: 'USD',
132 - payment_periods: [
133 - '整付(0-75 岁)',
134 - '5 年(0-70 岁)',
135 - '10 年(0-70 岁)'
136 - ],
137 - age_range: { min: 0, max: 75 },
138 - insurance_period: '终身',
139 - form_schema: protectionFormSchema,
140 - submit_mapping: baseSubmitMapping
141 - }
142 - },
143 -
144 - // 重疾保险产品 - MPC
145 - 'critical-illness-mpc': {
146 - name: 'MPC 守护无间重疾',
147 - component: 'CriticalIllnessTemplate',
148 - config: {
149 - currency: 'USD',
150 - payment_periods: [
151 - '10 年(15 日 - 65 岁)',
152 - '20 年(15 日 - 65 岁)',
153 - '25 年(15 日 - 60 岁)'
154 - ],
155 - age_range: { min: 0, max: 65 },
156 - insurance_period: '终身',
157 - form_schema: protectionFormSchema,
158 - submit_mapping: baseSubmitMapping
159 - }
160 - },
161 -
162 - // 重疾保险产品 - MBC PRO
163 - 'critical-illness-mbc-pro': {
164 - name: 'MBC PRO 活跃人生重疾保 PRO',
165 - component: 'CriticalIllnessTemplate',
166 - config: {
167 - currency: 'USD',
168 - payment_periods: [
169 - '10 年(15 日 - 65 岁)',
170 - '20 年(15 日 - 65 岁)',
171 - '25 年(15 日 - 60 岁)'
172 - ],
173 - age_range: { min: 0, max: 65 },
174 - insurance_period: '终身',
175 - form_schema: protectionFormSchema,
176 - submit_mapping: baseSubmitMapping
177 - }
178 - },
179 -
180 - // 重疾保险产品 - MBC2
181 - 'critical-illness-mbc2': {
182 - name: 'MBC2 活跃人生重疾保 2',
183 - component: 'CriticalIllnessTemplate',
184 - config: {
185 - currency: 'USD',
186 - payment_periods: [
187 - '10 年(15 日 - 65 岁)',
188 - '20 年(15 日 - 65 岁)',
189 - '25 年(15 日 - 60 岁)'
190 - ],
191 - age_range: { min: 0, max: 65 },
192 - insurance_period: '终身',
193 - form_schema: protectionFormSchema,
194 - submit_mapping: baseSubmitMapping
195 - }
196 - },
197 -
198 - // ====== 储蓄型产品(统一逻辑) ======
199 -
200 - // GS - 宏挚传承保障计划
201 - 'savings-gs': {
202 - name: '宏挚传承保障计划',
203 - component: 'SavingsTemplate',
204 - category: 'savings', // 储蓄型产品
205 - config: {
206 - currency: 'USD', // 默认美元
207 - payment_periods: [
208 - '整付',
209 - '3 年',
210 - '5 年',
211 - '10 年',
212 - '15 年',
213 - ],
214 - age_range: { min: 0, max: 100 },
215 - insurance_period: '终身',
216 - // 提取计划配置
217 - withdrawal_plan: {
218 - enabled: true,
219 - currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
220 - default_currency: 'USD', // 统一为美元
221 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
222 - withdrawal_periods: [
223 - '1年',
224 - '2年',
225 - '3年',
226 - '5年',
227 - '10年',
228 - '15年',
229 - '20年',
230 - '终身'
231 - ]
232 - },
233 - form_schema: savingsFormSchema,
234 - submit_mapping: savingsSubmitMapping
235 - }
236 - },
237 -
238 - // GC - 宏挚家传保险计划
239 - 'savings-gc': {
240 - name: '宏挚家传保险计划',
241 - component: 'SavingsTemplate',
242 - category: 'savings',
243 - config: {
244 - currency: 'USD',
245 - payment_periods: [
246 - '整付',
247 - '3 年',
248 - '5 年',
249 - ],
250 - age_range: { min: 0, max: 100 },
251 - insurance_period: '终身',
252 - withdrawal_plan: {
253 - enabled: true,
254 - currencies: ['HKD', 'USD', 'CNY'],
255 - default_currency: 'USD', // 统一为美元
256 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
257 - withdrawal_periods: [
258 - '1年',
259 - '2年',
260 - '3年',
261 - '5年',
262 - '10年',
263 - '15年',
264 - '20年',
265 - '终身'
266 - ]
267 - },
268 - form_schema: savingsFormSchema,
269 - submit_mapping: savingsSubmitMapping
270 - }
271 - },
272 -
273 - // FA - 宏浚传承保障计划
274 - 'savings-fa': {
275 - name: '宏浚传承保障计划',
276 - component: 'SavingsTemplate',
277 - category: 'savings',
278 - config: {
279 - currency: 'USD',
280 - payment_periods: [
281 - '整付',
282 - '2 年',
283 - '5 年',
284 - ],
285 - age_range: { min: 0, max: 100 },
286 - insurance_period: '终身',
287 - withdrawal_plan: {
288 - enabled: true,
289 - currencies: ['HKD', 'USD', 'CNY'],
290 - default_currency: 'USD', // 统一为美元
291 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
292 - withdrawal_periods: [
293 - '1年',
294 - '2年',
295 - '3年',
296 - '5年',
297 - '10年',
298 - '15年',
299 - '20年',
300 - '终身'
301 - ]
302 - },
303 - form_schema: savingsFormSchema,
304 - submit_mapping: savingsSubmitMapping
305 - }
306 - },
307 -
308 - // LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
309 - 'savings-lv2': {
310 - name: '赤霞珠终身寿险计划2',
311 - component: 'SavingsTemplate',
312 - category: 'savings',
313 - config: {
314 - currency: 'USD',
315 - payment_periods: [
316 - '5 年',
317 - '8 年',
318 - '12 年',
319 - '15 年',
320 - ],
321 - age_range: { min: 0, max: 100 },
322 - insurance_period: '终身',
323 - withdrawal_plan: {
324 - enabled: true,
325 - currencies: ['HKD', 'USD', 'CNY'],
326 - default_currency: 'USD', // 统一为美元
327 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
328 - withdrawal_periods: [
329 - '1年',
330 - '2年',
331 - '3年',
332 - '5年',
333 - '10年',
334 - '15年',
335 - '20年',
336 - '终身'
337 - ]
338 - },
339 - form_schema: savingsFormSchema,
340 - submit_mapping: savingsSubmitMapping
341 - }
342 - },
343 -
344 - /**
345 - * 测试计划书-智享未来
346 - * @added 2026-02-14T13:10:33.924Z
347 - * @source docs/to-parse/测试计划书-智享未来.md
348 - */
349 - 'savings-product-30b41aae': {
350 - name: '测试计划书-智享未来',
351 - component: 'SavingsTemplate',
352 - category: 'savings',
353 - config: {
354 - currency: 'USD',
355 - payment_periods: ["整付", "3年", "5年"],
356 - age_range: { min: 0, max: 75 },
357 - insurance_period: '终身',
358 - withdrawal_plan: {
359 - enabled: true,
360 - currencies: ['HKD', 'USD', 'CNY'],
361 - default_currency: 'USD',
362 - withdrawal_modes: ["年龄指定金额", "最高固定金额"],
363 - withdrawal_periods: ["1年", "3年", "5年", "10年"]
364 - },
365 - form_schema: savingsFormSchema,
366 - submit_mapping: savingsSubmitMapping
367 - }
368 - },
369 -
370 -}
371 -
372 -/**
373 - * 全局功能开关
374 - * @description 用于控制实验性功能或未来扩展功能的开关
375 - *
376 - * @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
377 - */
378 -export const FEATURE_FLAGS = {
379 - /**
380 - * 多币种切换功能
381 - * @description false: 方案 1 - 固定币种(当前实现)
382 - * true: 方案 2 - 支持多币种切换(未来扩展)
383 - * @type {boolean}
384 - */
385 - MULTI_CURRENCY_ENABLED: false
386 -}
387 -
388 -/**
389 - * 币种符号映射
390 - * @description 币种代码到符号的映射关系
391 - */
392 -export const CURRENCY_SYMBOLS = {
393 - CNY: '¥', // 人民币
394 - USD: '$', // 美元
395 - HKD: 'HK$', // 港币
396 - EUR: '€' // 欧元
397 -}
398 -
399 -/**
400 - * 币种完整信息映射
401 - * @description 币种代码到完整信息的映射(用于多币种模式)
402 - */
403 -export const CURRENCY_MAP = {
404 - CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
405 - USD: { label: '美元', symbol: '$', value: 'USD' },
406 - HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
407 - EUR: { label: '欧元', symbol: '€', value: 'EUR' }
408 -}
409 -
410 -/**
411 - * 根据 form_sn 获取模版配置
412 - * @param {string} formSn - 产品 API 返回的 form_sn 字段
413 - * @returns {Object|null} 模版配置对象,未找到返回 null
414 - *
415 - * @example
416 - * const config = getTemplateConfig('life-insurance-wiop3e')
417 - * // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
418 - */
419 -export function getTemplateConfig(formSn) {
420 - if (!formSn) {
421 - console.warn('[plan-templates] form_sn 为空')
422 - return null
423 - }
424 -
425 - const config = PLAN_TEMPLATES[formSn]
426 - if (!config) {
427 - console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
428 - return null
429 - }
430 -
431 - return config
432 -}
433 -
434 -/**
435 - * 获取币种符号
436 - * @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
437 - * @returns {string} 币种符号
438 - *
439 - * @example
440 - * const symbol = getCurrencySymbol('USD') // 返回: '$'
441 - */
442 -export function getCurrencySymbol(currencyCode) {
443 - return CURRENCY_SYMBOLS[currencyCode] || '¥'
444 -}
1 -/**
2 - * 计划书模版配置
3 - *
4 - * @description 定义产品 form_sn 到模版组件和配置的映射关系
5 - * @module config/plan-templates
6 - * @author Claude Code
7 - * @created 2026-02-06
8 - * @updated 2026-02-13 - 新增文档解析工具入口
9 - *
10 - * --- 快速添加新产品(开发工具) ---
11 - * 开发环境可使用以下工具快速添加新产品配置:
12 - * 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
13 - * 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
14 - *
15 - * 使用方式:
16 - * - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
17 - *
18 - * --- 手动添加步骤 ---
19 - * 1. 找到对应的产品分类(人寿/重疾/储蓄)
20 - * 2. 复制现有配置作为模板
21 - * 3. 修改 name, currency, payment_periods, age_range 等字段
22 - * 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
23 - */
24 -
25 -/**
26 - * 计划书模版配置映射
27 - * @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
28 - *
29 - * @example
30 - * // 产品 API 返回
31 - * {
32 - * id: 1,
33 - * product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
34 - * form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
35 - * }
36 - */
37 -// 基础提交字段映射(适用于人寿/重疾等通用表单)
38 -const baseSubmitMapping = {
39 - customer_name: { api_field: 'customer_name' },
40 - gender: { api_field: 'customer_gender' },
41 - birthday: { api_field: 'customer_birthday' },
42 - smoker: { api_field: 'smoking_status' },
43 - coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
44 - payment_period: { api_field: 'payment_years' },
45 - total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
46 -}
47 -
48 -// 人寿/重疾基础表单 Schema(通用保障类)
49 -const protectionFormSchema = {
50 - base_fields: [
51 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
52 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
53 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
54 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
55 - { id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
56 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
57 - ]
58 -}
59 -
60 -// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
61 -const savingsSubmitMapping = {
62 - ...baseSubmitMapping,
63 - withdrawal_enabled: { api_field: 'allow_reduce_amount' },
64 - withdrawal_mode: { api_field: 'withdrawal_option' },
65 - withdrawal_method: { api_field: 'withdrawal_method' },
66 - annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
67 - annual_increase_percentage: { api_field: 'annual_increase_percentage' },
68 - withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
69 - withdrawal_period_specified: { api_field: 'withdrawal_period' },
70 - withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
71 - withdrawal_period_fixed: { api_field: 'withdrawal_period' }
72 -}
73 -
74 -// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
75 -const savingsFormSchema = {
76 - // 基础字段:非提取计划部分
77 - base_fields: [
78 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
79 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
80 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
81 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
82 - { id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
83 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
84 - ],
85 - // 提取计划字段:由 withdrawal_plan 开关控制
86 - withdrawal_fields: [
87 - { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
88 - { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
89 - { id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
90 - { 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: '指定提取金额' }] },
91 - { id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
92 - { 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: '指定提取金额' }] },
93 - { id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
94 - { id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
95 - { 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: '最高固定提取金额' }] }
96 - ],
97 - // 提取模式切换时的清空逻辑,避免脏字段影响提交
98 - reset_map: {
99 - withdrawal_mode: {
100 - '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
101 - '指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
102 - }
103 - }
104 -}
105 -
106 -export const PLAN_TEMPLATES = {
107 - // 人寿保险产品 - WIOP3E
108 - 'life-insurance-wiop3e': {
109 - name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
110 - component: 'LifeInsuranceTemplate',
111 - config: {
112 - currency: 'USD', // 币种:USD/CNY/HKD/EUR
113 - payment_periods: [
114 - // 缴费年期选项
115 - '整付(0-75 岁)',
116 - '5 年(0-70 岁)',
117 - '10 年(0-70 岁)'
118 - ],
119 - age_range: { min: 0, max: 75 }, // 年龄范围
120 - insurance_period: '终身', // 保险期间
121 - form_schema: protectionFormSchema,
122 - submit_mapping: baseSubmitMapping
123 - }
124 - },
125 -
126 - // 人寿保险产品 - WIOP3
127 - 'life-insurance-wiop3': {
128 - name: 'WIOP3 - 盈传创富保障计划 3',
129 - component: 'LifeInsuranceTemplate',
130 - config: {
131 - currency: 'USD',
132 - payment_periods: [
133 - '整付(0-75 岁)',
134 - '5 年(0-70 岁)',
135 - '10 年(0-70 岁)'
136 - ],
137 - age_range: { min: 0, max: 75 },
138 - insurance_period: '终身',
139 - form_schema: protectionFormSchema,
140 - submit_mapping: baseSubmitMapping
141 - }
142 - },
143 -
144 - // 重疾保险产品 - MPC
145 - 'critical-illness-mpc': {
146 - name: 'MPC 守护无间重疾',
147 - component: 'CriticalIllnessTemplate',
148 - config: {
149 - currency: 'USD',
150 - payment_periods: [
151 - '10 年(15 日 - 65 岁)',
152 - '20 年(15 日 - 65 岁)',
153 - '25 年(15 日 - 60 岁)'
154 - ],
155 - age_range: { min: 0, max: 65 },
156 - insurance_period: '终身',
157 - form_schema: protectionFormSchema,
158 - submit_mapping: baseSubmitMapping
159 - }
160 - },
161 -
162 - // 重疾保险产品 - MBC PRO
163 - 'critical-illness-mbc-pro': {
164 - name: 'MBC PRO 活跃人生重疾保 PRO',
165 - component: 'CriticalIllnessTemplate',
166 - config: {
167 - currency: 'USD',
168 - payment_periods: [
169 - '10 年(15 日 - 65 岁)',
170 - '20 年(15 日 - 65 岁)',
171 - '25 年(15 日 - 60 岁)'
172 - ],
173 - age_range: { min: 0, max: 65 },
174 - insurance_period: '终身',
175 - form_schema: protectionFormSchema,
176 - submit_mapping: baseSubmitMapping
177 - }
178 - },
179 -
180 - // 重疾保险产品 - MBC2
181 - 'critical-illness-mbc2': {
182 - name: 'MBC2 活跃人生重疾保 2',
183 - component: 'CriticalIllnessTemplate',
184 - config: {
185 - currency: 'USD',
186 - payment_periods: [
187 - '10 年(15 日 - 65 岁)',
188 - '20 年(15 日 - 65 岁)',
189 - '25 年(15 日 - 60 岁)'
190 - ],
191 - age_range: { min: 0, max: 65 },
192 - insurance_period: '终身',
193 - form_schema: protectionFormSchema,
194 - submit_mapping: baseSubmitMapping
195 - }
196 - },
197 -
198 - // ====== 储蓄型产品(统一逻辑) ======
199 -
200 - // GS - 宏挚传承保障计划
201 - 'savings-gs': {
202 - name: '宏挚传承保障计划',
203 - component: 'SavingsTemplate',
204 - category: 'savings', // 储蓄型产品
205 - config: {
206 - currency: 'USD', // 默认美元
207 - payment_periods: [
208 - '整付',
209 - '3 年',
210 - '5 年',
211 - '10 年',
212 - '15 年',
213 - ],
214 - age_range: { min: 0, max: 100 },
215 - insurance_period: '终身',
216 - // 提取计划配置
217 - withdrawal_plan: {
218 - enabled: true,
219 - currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
220 - default_currency: 'USD', // 统一为美元
221 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
222 - withdrawal_periods: [
223 - '1年',
224 - '2年',
225 - '3年',
226 - '5年',
227 - '10年',
228 - '15年',
229 - '20年',
230 - '终身'
231 - ]
232 - },
233 - form_schema: savingsFormSchema,
234 - submit_mapping: savingsSubmitMapping
235 - }
236 - },
237 -
238 - // GC - 宏挚家传保险计划
239 - 'savings-gc': {
240 - name: '宏挚家传保险计划',
241 - component: 'SavingsTemplate',
242 - category: 'savings',
243 - config: {
244 - currency: 'USD',
245 - payment_periods: [
246 - '整付',
247 - '3 年',
248 - '5 年',
249 - ],
250 - age_range: { min: 0, max: 100 },
251 - insurance_period: '终身',
252 - withdrawal_plan: {
253 - enabled: true,
254 - currencies: ['HKD', 'USD', 'CNY'],
255 - default_currency: 'USD', // 统一为美元
256 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
257 - withdrawal_periods: [
258 - '1年',
259 - '2年',
260 - '3年',
261 - '5年',
262 - '10年',
263 - '15年',
264 - '20年',
265 - '终身'
266 - ]
267 - },
268 - form_schema: savingsFormSchema,
269 - submit_mapping: savingsSubmitMapping
270 - }
271 - },
272 -
273 - // FA - 宏浚传承保障计划
274 - 'savings-fa': {
275 - name: '宏浚传承保障计划',
276 - component: 'SavingsTemplate',
277 - category: 'savings',
278 - config: {
279 - currency: 'USD',
280 - payment_periods: [
281 - '整付',
282 - '2 年',
283 - '5 年',
284 - ],
285 - age_range: { min: 0, max: 100 },
286 - insurance_period: '终身',
287 - withdrawal_plan: {
288 - enabled: true,
289 - currencies: ['HKD', 'USD', 'CNY'],
290 - default_currency: 'USD', // 统一为美元
291 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
292 - withdrawal_periods: [
293 - '1年',
294 - '2年',
295 - '3年',
296 - '5年',
297 - '10年',
298 - '15年',
299 - '20年',
300 - '终身'
301 - ]
302 - },
303 - form_schema: savingsFormSchema,
304 - submit_mapping: savingsSubmitMapping
305 - }
306 - },
307 -
308 - // LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
309 - 'savings-lv2': {
310 - name: '赤霞珠终身寿险计划2',
311 - component: 'SavingsTemplate',
312 - category: 'savings',
313 - config: {
314 - currency: 'USD',
315 - payment_periods: [
316 - '5 年',
317 - '8 年',
318 - '12 年',
319 - '15 年',
320 - ],
321 - age_range: { min: 0, max: 100 },
322 - insurance_period: '终身',
323 - withdrawal_plan: {
324 - enabled: true,
325 - currencies: ['HKD', 'USD', 'CNY'],
326 - default_currency: 'USD', // 统一为美元
327 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
328 - withdrawal_periods: [
329 - '1年',
330 - '2年',
331 - '3年',
332 - '5年',
333 - '10年',
334 - '15年',
335 - '20年',
336 - '终身'
337 - ]
338 - },
339 - form_schema: savingsFormSchema,
340 - submit_mapping: savingsSubmitMapping
341 - }
342 - },
343 -
344 -}
345 -
346 -/**
347 - * 全局功能开关
348 - * @description 用于控制实验性功能或未来扩展功能的开关
349 - *
350 - * @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
351 - */
352 -export const FEATURE_FLAGS = {
353 - /**
354 - * 多币种切换功能
355 - * @description false: 方案 1 - 固定币种(当前实现)
356 - * true: 方案 2 - 支持多币种切换(未来扩展)
357 - * @type {boolean}
358 - */
359 - MULTI_CURRENCY_ENABLED: false
360 -}
361 -
362 -/**
363 - * 币种符号映射
364 - * @description 币种代码到符号的映射关系
365 - */
366 -export const CURRENCY_SYMBOLS = {
367 - CNY: '¥', // 人民币
368 - USD: '$', // 美元
369 - HKD: 'HK$', // 港币
370 - EUR: '€' // 欧元
371 -}
372 -
373 -/**
374 - * 币种完整信息映射
375 - * @description 币种代码到完整信息的映射(用于多币种模式)
376 - */
377 -export const CURRENCY_MAP = {
378 - CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
379 - USD: { label: '美元', symbol: '$', value: 'USD' },
380 - HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
381 - EUR: { label: '欧元', symbol: '€', value: 'EUR' }
382 -}
383 -
384 -/**
385 - * 根据 form_sn 获取模版配置
386 - * @param {string} formSn - 产品 API 返回的 form_sn 字段
387 - * @returns {Object|null} 模版配置对象,未找到返回 null
388 - *
389 - * @example
390 - * const config = getTemplateConfig('life-insurance-wiop3e')
391 - * // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
392 - */
393 -export function getTemplateConfig(formSn) {
394 - if (!formSn) {
395 - console.warn('[plan-templates] form_sn 为空')
396 - return null
397 - }
398 -
399 - const config = PLAN_TEMPLATES[formSn]
400 - if (!config) {
401 - console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
402 - return null
403 - }
404 -
405 - return config
406 -}
407 -
408 -/**
409 - * 获取币种符号
410 - * @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
411 - * @returns {string} 币种符号
412 - *
413 - * @example
414 - * const symbol = getCurrencySymbol('USD') // 返回: '$'
415 - */
416 -export function getCurrencySymbol(currencyCode) {
417 - return CURRENCY_SYMBOLS[currencyCode] || '¥'
418 -}
1 -/**
2 - * 计划书模版配置
3 - *
4 - * @description 定义产品 form_sn 到模版组件和配置的映射关系
5 - * @module config/plan-templates
6 - * @author Claude Code
7 - * @created 2026-02-06
8 - * @updated 2026-02-13 - 新增文档解析工具入口
9 - *
10 - * --- 快速添加新产品(开发工具) ---
11 - * 开发环境可使用以下工具快速添加新产品配置:
12 - * 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
13 - * 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
14 - *
15 - * 使用方式:
16 - * - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
17 - *
18 - * --- 手动添加步骤 ---
19 - * 1. 找到对应的产品分类(人寿/重疾/储蓄)
20 - * 2. 复制现有配置作为模板
21 - * 3. 修改 name, currency, payment_periods, age_range 等字段
22 - * 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
23 - */
24 -
25 -/**
26 - * 计划书模版配置映射
27 - * @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
28 - *
29 - * @example
30 - * // 产品 API 返回
31 - * {
32 - * id: 1,
33 - * product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
34 - * form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
35 - * }
36 - */
37 -// 基础提交字段映射(适用于人寿/重疾等通用表单)
38 -const baseSubmitMapping = {
39 - customer_name: { api_field: 'customer_name' },
40 - gender: { api_field: 'customer_gender' },
41 - birthday: { api_field: 'customer_birthday' },
42 - smoker: { api_field: 'smoking_status' },
43 - coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
44 - payment_period: { api_field: 'payment_years' },
45 - total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
46 -}
47 -
48 -// 人寿/重疾基础表单 Schema(通用保障类)
49 -const protectionFormSchema = {
50 - base_fields: [
51 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
52 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
53 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
54 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
55 - { id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
56 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
57 - ]
58 -}
59 -
60 -// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
61 -const savingsSubmitMapping = {
62 - ...baseSubmitMapping,
63 - withdrawal_enabled: { api_field: 'allow_reduce_amount' },
64 - withdrawal_mode: { api_field: 'withdrawal_option' },
65 - withdrawal_method: { api_field: 'withdrawal_method' },
66 - annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
67 - annual_increase_percentage: { api_field: 'annual_increase_percentage' },
68 - withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
69 - withdrawal_period_specified: { api_field: 'withdrawal_period' },
70 - withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
71 - withdrawal_period_fixed: { api_field: 'withdrawal_period' }
72 -}
73 -
74 -// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
75 -const savingsFormSchema = {
76 - // 基础字段:非提取计划部分
77 - base_fields: [
78 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
79 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
80 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
81 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
82 - { id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
83 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
84 - ],
85 - // 提取计划字段:由 withdrawal_plan 开关控制
86 - withdrawal_fields: [
87 - { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
88 - { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
89 - { id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
90 - { 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: '指定提取金额' }] },
91 - { id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
92 - { 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: '指定提取金额' }] },
93 - { id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
94 - { id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
95 - { 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: '最高固定提取金额' }] }
96 - ],
97 - // 提取模式切换时的清空逻辑,避免脏字段影响提交
98 - reset_map: {
99 - withdrawal_mode: {
100 - '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
101 - '指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
102 - }
103 - }
104 -}
105 -
106 -export const PLAN_TEMPLATES = {
107 - // 人寿保险产品 - WIOP3E
108 - 'life-insurance-wiop3e': {
109 - name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
110 - component: 'LifeInsuranceTemplate',
111 - config: {
112 - currency: 'USD', // 币种:USD/CNY/HKD/EUR
113 - payment_periods: [
114 - // 缴费年期选项
115 - '整付(0-75 岁)',
116 - '5 年(0-70 岁)',
117 - '10 年(0-70 岁)'
118 - ],
119 - age_range: { min: 0, max: 75 }, // 年龄范围
120 - insurance_period: '终身', // 保险期间
121 - form_schema: protectionFormSchema,
122 - submit_mapping: baseSubmitMapping
123 - }
124 - },
125 -
126 - // 人寿保险产品 - WIOP3
127 - 'life-insurance-wiop3': {
128 - name: 'WIOP3 - 盈传创富保障计划 3',
129 - component: 'LifeInsuranceTemplate',
130 - config: {
131 - currency: 'USD',
132 - payment_periods: [
133 - '整付(0-75 岁)',
134 - '5 年(0-70 岁)',
135 - '10 年(0-70 岁)'
136 - ],
137 - age_range: { min: 0, max: 75 },
138 - insurance_period: '终身',
139 - form_schema: protectionFormSchema,
140 - submit_mapping: baseSubmitMapping
141 - }
142 - },
143 -
144 - // 重疾保险产品 - MPC
145 - 'critical-illness-mpc': {
146 - name: 'MPC 守护无间重疾',
147 - component: 'CriticalIllnessTemplate',
148 - config: {
149 - currency: 'USD',
150 - payment_periods: [
151 - '10 年(15 日 - 65 岁)',
152 - '20 年(15 日 - 65 岁)',
153 - '25 年(15 日 - 60 岁)'
154 - ],
155 - age_range: { min: 0, max: 65 },
156 - insurance_period: '终身',
157 - form_schema: protectionFormSchema,
158 - submit_mapping: baseSubmitMapping
159 - }
160 - },
161 -
162 - // 重疾保险产品 - MBC PRO
163 - 'critical-illness-mbc-pro': {
164 - name: 'MBC PRO 活跃人生重疾保 PRO',
165 - component: 'CriticalIllnessTemplate',
166 - config: {
167 - currency: 'USD',
168 - payment_periods: [
169 - '10 年(15 日 - 65 岁)',
170 - '20 年(15 日 - 65 岁)',
171 - '25 年(15 日 - 60 岁)'
172 - ],
173 - age_range: { min: 0, max: 65 },
174 - insurance_period: '终身',
175 - form_schema: protectionFormSchema,
176 - submit_mapping: baseSubmitMapping
177 - }
178 - },
179 -
180 - // 重疾保险产品 - MBC2
181 - 'critical-illness-mbc2': {
182 - name: 'MBC2 活跃人生重疾保 2',
183 - component: 'CriticalIllnessTemplate',
184 - config: {
185 - currency: 'USD',
186 - payment_periods: [
187 - '10 年(15 日 - 65 岁)',
188 - '20 年(15 日 - 65 岁)',
189 - '25 年(15 日 - 60 岁)'
190 - ],
191 - age_range: { min: 0, max: 65 },
192 - insurance_period: '终身',
193 - form_schema: protectionFormSchema,
194 - submit_mapping: baseSubmitMapping
195 - }
196 - },
197 -
198 - // ====== 储蓄型产品(统一逻辑) ======
199 -
200 - // GS - 宏挚传承保障计划
201 - 'savings-gs': {
202 - name: '宏挚传承保障计划',
203 - component: 'SavingsTemplate',
204 - category: 'savings', // 储蓄型产品
205 - config: {
206 - currency: 'USD', // 默认美元
207 - payment_periods: [
208 - '整付',
209 - '3 年',
210 - '5 年',
211 - '10 年',
212 - '15 年',
213 - ],
214 - age_range: { min: 0, max: 100 },
215 - insurance_period: '终身',
216 - // 提取计划配置
217 - withdrawal_plan: {
218 - enabled: true,
219 - currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
220 - default_currency: 'USD', // 统一为美元
221 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
222 - withdrawal_periods: [
223 - '1年',
224 - '2年',
225 - '3年',
226 - '5年',
227 - '10年',
228 - '15年',
229 - '20年',
230 - '终身'
231 - ]
232 - },
233 - form_schema: savingsFormSchema,
234 - submit_mapping: savingsSubmitMapping
235 - }
236 - },
237 -
238 - // GC - 宏挚家传保险计划
239 - 'savings-gc': {
240 - name: '宏挚家传保险计划',
241 - component: 'SavingsTemplate',
242 - category: 'savings',
243 - config: {
244 - currency: 'USD',
245 - payment_periods: [
246 - '整付',
247 - '3 年',
248 - '5 年',
249 - ],
250 - age_range: { min: 0, max: 100 },
251 - insurance_period: '终身',
252 - withdrawal_plan: {
253 - enabled: true,
254 - currencies: ['HKD', 'USD', 'CNY'],
255 - default_currency: 'USD', // 统一为美元
256 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
257 - withdrawal_periods: [
258 - '1年',
259 - '2年',
260 - '3年',
261 - '5年',
262 - '10年',
263 - '15年',
264 - '20年',
265 - '终身'
266 - ]
267 - },
268 - form_schema: savingsFormSchema,
269 - submit_mapping: savingsSubmitMapping
270 - }
271 - },
272 -
273 - // FA - 宏浚传承保障计划
274 - 'savings-fa': {
275 - name: '宏浚传承保障计划',
276 - component: 'SavingsTemplate',
277 - category: 'savings',
278 - config: {
279 - currency: 'USD',
280 - payment_periods: [
281 - '整付',
282 - '2 年',
283 - '5 年',
284 - ],
285 - age_range: { min: 0, max: 100 },
286 - insurance_period: '终身',
287 - withdrawal_plan: {
288 - enabled: true,
289 - currencies: ['HKD', 'USD', 'CNY'],
290 - default_currency: 'USD', // 统一为美元
291 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
292 - withdrawal_periods: [
293 - '1年',
294 - '2年',
295 - '3年',
296 - '5年',
297 - '10年',
298 - '15年',
299 - '20年',
300 - '终身'
301 - ]
302 - },
303 - form_schema: savingsFormSchema,
304 - submit_mapping: savingsSubmitMapping
305 - }
306 - },
307 -
308 - // LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
309 - 'savings-lv2': {
310 - name: '赤霞珠终身寿险计划2',
311 - component: 'SavingsTemplate',
312 - category: 'savings',
313 - config: {
314 - currency: 'USD',
315 - payment_periods: [
316 - '5 年',
317 - '8 年',
318 - '12 年',
319 - '15 年',
320 - ],
321 - age_range: { min: 0, max: 100 },
322 - insurance_period: '终身',
323 - withdrawal_plan: {
324 - enabled: true,
325 - currencies: ['HKD', 'USD', 'CNY'],
326 - default_currency: 'USD', // 统一为美元
327 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
328 - withdrawal_periods: [
329 - '1年',
330 - '2年',
331 - '3年',
332 - '5年',
333 - '10年',
334 - '15年',
335 - '20年',
336 - '终身'
337 - ]
338 - },
339 - form_schema: savingsFormSchema,
340 - submit_mapping: savingsSubmitMapping
341 - }
342 - },
343 -
344 -
345 -}
346 -
347 -/**
348 - * 全局功能开关
349 - * @description 用于控制实验性功能或未来扩展功能的开关
350 - *
351 - * @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
352 - */
353 -export const FEATURE_FLAGS = {
354 - /**
355 - * 多币种切换功能
356 - * @description false: 方案 1 - 固定币种(当前实现)
357 - * true: 方案 2 - 支持多币种切换(未来扩展)
358 - * @type {boolean}
359 - */
360 - MULTI_CURRENCY_ENABLED: false
361 -}
362 -
363 -/**
364 - * 币种符号映射
365 - * @description 币种代码到符号的映射关系
366 - */
367 -export const CURRENCY_SYMBOLS = {
368 - CNY: '¥', // 人民币
369 - USD: '$', // 美元
370 - HKD: 'HK$', // 港币
371 - EUR: '€' // 欧元
372 -}
373 -
374 -/**
375 - * 币种完整信息映射
376 - * @description 币种代码到完整信息的映射(用于多币种模式)
377 - */
378 -export const CURRENCY_MAP = {
379 - CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
380 - USD: { label: '美元', symbol: '$', value: 'USD' },
381 - HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
382 - EUR: { label: '欧元', symbol: '€', value: 'EUR' }
383 -}
384 -
385 -/**
386 - * 根据 form_sn 获取模版配置
387 - * @param {string} formSn - 产品 API 返回的 form_sn 字段
388 - * @returns {Object|null} 模版配置对象,未找到返回 null
389 - *
390 - * @example
391 - * const config = getTemplateConfig('life-insurance-wiop3e')
392 - * // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
393 - */
394 -export function getTemplateConfig(formSn) {
395 - if (!formSn) {
396 - console.warn('[plan-templates] form_sn 为空')
397 - return null
398 - }
399 -
400 - const config = PLAN_TEMPLATES[formSn]
401 - if (!config) {
402 - console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
403 - return null
404 - }
405 -
406 - return config
407 -}
408 -
409 -/**
410 - * 获取币种符号
411 - * @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
412 - * @returns {string} 币种符号
413 - *
414 - * @example
415 - * const symbol = getCurrencySymbol('USD') // 返回: '$'
416 - */
417 -export function getCurrencySymbol(currencyCode) {
418 - return CURRENCY_SYMBOLS[currencyCode] || '¥'
419 -}
1 -/**
2 - * 计划书模版配置
3 - *
4 - * @description 定义产品 form_sn 到模版组件和配置的映射关系
5 - * @module config/plan-templates
6 - * @author Claude Code
7 - * @created 2026-02-06
8 - * @updated 2026-02-13 - 新增文档解析工具入口
9 - *
10 - * --- 快速添加新产品(开发工具) ---
11 - * 开发环境可使用以下工具快速添加新产品配置:
12 - * 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
13 - * 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
14 - *
15 - * 使用方式:
16 - * - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
17 - *
18 - * --- 手动添加步骤 ---
19 - * 1. 找到对应的产品分类(人寿/重疾/储蓄)
20 - * 2. 复制现有配置作为模板
21 - * 3. 修改 name, currency, payment_periods, age_range 等字段
22 - * 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
23 - */
24 -
25 -/**
26 - * 计划书模版配置映射
27 - * @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
28 - *
29 - * @example
30 - * // 产品 API 返回
31 - * {
32 - * id: 1,
33 - * product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
34 - * form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
35 - * }
36 - */
37 -// 基础提交字段映射(适用于人寿/重疾等通用表单)
38 -const baseSubmitMapping = {
39 - customer_name: { api_field: 'customer_name' },
40 - gender: { api_field: 'customer_gender' },
41 - birthday: { api_field: 'customer_birthday' },
42 - smoker: { api_field: 'smoking_status' },
43 - coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
44 - payment_period: { api_field: 'payment_years' },
45 - total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
46 -}
47 -
48 -// 人寿/重疾基础表单 Schema(通用保障类)
49 -const protectionFormSchema = {
50 - base_fields: [
51 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
52 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
53 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
54 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
55 - { id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
56 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
57 - ]
58 -}
59 -
60 -// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
61 -const savingsSubmitMapping = {
62 - ...baseSubmitMapping,
63 - withdrawal_enabled: { api_field: 'allow_reduce_amount' },
64 - withdrawal_mode: { api_field: 'withdrawal_option' },
65 - withdrawal_method: { api_field: 'withdrawal_method' },
66 - annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
67 - annual_increase_percentage: { api_field: 'annual_increase_percentage' },
68 - withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
69 - withdrawal_period_specified: { api_field: 'withdrawal_period' },
70 - withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
71 - withdrawal_period_fixed: { api_field: 'withdrawal_period' }
72 -}
73 -
74 -// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
75 -const savingsFormSchema = {
76 - // 基础字段:非提取计划部分
77 - base_fields: [
78 - { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
79 - { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
80 - { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
81 - { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
82 - { id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
83 - { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
84 - ],
85 - // 提取计划字段:由 withdrawal_plan 开关控制
86 - withdrawal_fields: [
87 - { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
88 - { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
89 - { id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
90 - { 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: '指定提取金额' }] },
91 - { id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
92 - { 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: '指定提取金额' }] },
93 - { id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
94 - { id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
95 - { 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: '最高固定提取金额' }] }
96 - ],
97 - // 提取模式切换时的清空逻辑,避免脏字段影响提交
98 - reset_map: {
99 - withdrawal_mode: {
100 - '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
101 - '指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
102 - }
103 - }
104 -}
105 -
106 -export const PLAN_TEMPLATES = {
107 - // 人寿保险产品 - WIOP3E
108 - 'life-insurance-wiop3e': {
109 - name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
110 - component: 'LifeInsuranceTemplate',
111 - config: {
112 - currency: 'USD', // 币种:USD/CNY/HKD/EUR
113 - payment_periods: [
114 - // 缴费年期选项
115 - '整付(0-75 岁)',
116 - '5 年(0-70 岁)',
117 - '10 年(0-70 岁)'
118 - ],
119 - age_range: { min: 0, max: 75 }, // 年龄范围
120 - insurance_period: '终身', // 保险期间
121 - form_schema: protectionFormSchema,
122 - submit_mapping: baseSubmitMapping
123 - }
124 - },
125 -
126 - // 人寿保险产品 - WIOP3
127 - 'life-insurance-wiop3': {
128 - name: 'WIOP3 - 盈传创富保障计划 3',
129 - component: 'LifeInsuranceTemplate',
130 - config: {
131 - currency: 'USD',
132 - payment_periods: [
133 - '整付(0-75 岁)',
134 - '5 年(0-70 岁)',
135 - '10 年(0-70 岁)'
136 - ],
137 - age_range: { min: 0, max: 75 },
138 - insurance_period: '终身',
139 - form_schema: protectionFormSchema,
140 - submit_mapping: baseSubmitMapping
141 - }
142 - },
143 -
144 - // 重疾保险产品 - MPC
145 - 'critical-illness-mpc': {
146 - name: 'MPC 守护无间重疾',
147 - component: 'CriticalIllnessTemplate',
148 - config: {
149 - currency: 'USD',
150 - payment_periods: [
151 - '10 年(15 日 - 65 岁)',
152 - '20 年(15 日 - 65 岁)',
153 - '25 年(15 日 - 60 岁)'
154 - ],
155 - age_range: { min: 0, max: 65 },
156 - insurance_period: '终身',
157 - form_schema: protectionFormSchema,
158 - submit_mapping: baseSubmitMapping
159 - }
160 - },
161 -
162 - // 重疾保险产品 - MBC PRO
163 - 'critical-illness-mbc-pro': {
164 - name: 'MBC PRO 活跃人生重疾保 PRO',
165 - component: 'CriticalIllnessTemplate',
166 - config: {
167 - currency: 'USD',
168 - payment_periods: [
169 - '10 年(15 日 - 65 岁)',
170 - '20 年(15 日 - 65 岁)',
171 - '25 年(15 日 - 60 岁)'
172 - ],
173 - age_range: { min: 0, max: 65 },
174 - insurance_period: '终身',
175 - form_schema: protectionFormSchema,
176 - submit_mapping: baseSubmitMapping
177 - }
178 - },
179 -
180 - // 重疾保险产品 - MBC2
181 - 'critical-illness-mbc2': {
182 - name: 'MBC2 活跃人生重疾保 2',
183 - component: 'CriticalIllnessTemplate',
184 - config: {
185 - currency: 'USD',
186 - payment_periods: [
187 - '10 年(15 日 - 65 岁)',
188 - '20 年(15 日 - 65 岁)',
189 - '25 年(15 日 - 60 岁)'
190 - ],
191 - age_range: { min: 0, max: 65 },
192 - insurance_period: '终身',
193 - form_schema: protectionFormSchema,
194 - submit_mapping: baseSubmitMapping
195 - }
196 - },
197 -
198 - // ====== 储蓄型产品(统一逻辑) ======
199 -
200 - // GS - 宏挚传承保障计划
201 - 'savings-gs': {
202 - name: '宏挚传承保障计划',
203 - component: 'SavingsTemplate',
204 - category: 'savings', // 储蓄型产品
205 - config: {
206 - currency: 'USD', // 默认美元
207 - payment_periods: [
208 - '整付',
209 - '3 年',
210 - '5 年',
211 - '10 年',
212 - '15 年',
213 - ],
214 - age_range: { min: 0, max: 100 },
215 - insurance_period: '终身',
216 - // 提取计划配置
217 - withdrawal_plan: {
218 - enabled: true,
219 - currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
220 - default_currency: 'USD', // 统一为美元
221 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
222 - withdrawal_periods: [
223 - '1年',
224 - '2年',
225 - '3年',
226 - '5年',
227 - '10年',
228 - '15年',
229 - '20年',
230 - '终身'
231 - ]
232 - },
233 - form_schema: savingsFormSchema,
234 - submit_mapping: savingsSubmitMapping
235 - }
236 - },
237 -
238 - // GC - 宏挚家传保险计划
239 - 'savings-gc': {
240 - name: '宏挚家传保险计划',
241 - component: 'SavingsTemplate',
242 - category: 'savings',
243 - config: {
244 - currency: 'USD',
245 - payment_periods: [
246 - '整付',
247 - '3 年',
248 - '5 年',
249 - ],
250 - age_range: { min: 0, max: 100 },
251 - insurance_period: '终身',
252 - withdrawal_plan: {
253 - enabled: true,
254 - currencies: ['HKD', 'USD', 'CNY'],
255 - default_currency: 'USD', // 统一为美元
256 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
257 - withdrawal_periods: [
258 - '1年',
259 - '2年',
260 - '3年',
261 - '5年',
262 - '10年',
263 - '15年',
264 - '20年',
265 - '终身'
266 - ]
267 - },
268 - form_schema: savingsFormSchema,
269 - submit_mapping: savingsSubmitMapping
270 - }
271 - },
272 -
273 - // FA - 宏浚传承保障计划
274 - 'savings-fa': {
275 - name: '宏浚传承保障计划',
276 - component: 'SavingsTemplate',
277 - category: 'savings',
278 - config: {
279 - currency: 'USD',
280 - payment_periods: [
281 - '整付',
282 - '2 年',
283 - '5 年',
284 - ],
285 - age_range: { min: 0, max: 100 },
286 - insurance_period: '终身',
287 - withdrawal_plan: {
288 - enabled: true,
289 - currencies: ['HKD', 'USD', 'CNY'],
290 - default_currency: 'USD', // 统一为美元
291 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
292 - withdrawal_periods: [
293 - '1年',
294 - '2年',
295 - '3年',
296 - '5年',
297 - '10年',
298 - '15年',
299 - '20年',
300 - '终身'
301 - ]
302 - },
303 - form_schema: savingsFormSchema,
304 - submit_mapping: savingsSubmitMapping
305 - }
306 - },
307 -
308 - // LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
309 - 'savings-lv2': {
310 - name: '赤霞珠终身寿险计划2',
311 - component: 'SavingsTemplate',
312 - category: 'savings',
313 - config: {
314 - currency: 'USD',
315 - payment_periods: [
316 - '5 年',
317 - '8 年',
318 - '12 年',
319 - '15 年',
320 - ],
321 - age_range: { min: 0, max: 100 },
322 - insurance_period: '终身',
323 - withdrawal_plan: {
324 - enabled: true,
325 - currencies: ['HKD', 'USD', 'CNY'],
326 - default_currency: 'USD', // 统一为美元
327 - withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
328 - withdrawal_periods: [
329 - '1年',
330 - '2年',
331 - '3年',
332 - '5年',
333 - '10年',
334 - '15年',
335 - '20年',
336 - '终身'
337 - ]
338 - },
339 - form_schema: savingsFormSchema,
340 - submit_mapping: savingsSubmitMapping
341 - }
342 - },
343 -
344 - /**
345 - * 测试计划书-智享未来2
346 - * @added 2026-02-14T14:12:31.658Z
347 - * @source docs/to-parse/测试计划书-智享未来2.md
348 - */
349 - 'savings-2-148b3acd': {
350 - name: '测试计划书-智享未来2',
351 - component: 'SavingsTemplate',
352 - category: 'savings',
353 - config: {
354 - currency: 'USD',
355 - payment_periods: ["整付","3年","5年"],
356 - age_range: { min: 0, max: 75 },
357 - insurance_period: '终身',
358 - withdrawal_plan: {
359 - enabled: true,
360 - currencies: ['HKD', 'USD', 'CNY'],
361 - default_currency: 'USD',
362 - withdrawal_modes: ["年龄指定金额","最高固定金额"],
363 - withdrawal_periods: ["1年","3年","5年","10年"]
364 - },
365 - form_schema: savingsFormSchema,
366 - submit_mapping: savingsSubmitMapping
367 - }
368 - }}
369 -
370 -/**
371 - * 全局功能开关
372 - * @description 用于控制实验性功能或未来扩展功能的开关
373 - *
374 - * @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
375 - */
376 -export const FEATURE_FLAGS = {
377 - /**
378 - * 多币种切换功能
379 - * @description false: 方案 1 - 固定币种(当前实现)
380 - * true: 方案 2 - 支持多币种切换(未来扩展)
381 - * @type {boolean}
382 - */
383 - MULTI_CURRENCY_ENABLED: false
384 -}
385 -
386 -/**
387 - * 币种符号映射
388 - * @description 币种代码到符号的映射关系
389 - */
390 -export const CURRENCY_SYMBOLS = {
391 - CNY: '¥', // 人民币
392 - USD: '$', // 美元
393 - HKD: 'HK$', // 港币
394 - EUR: '€' // 欧元
395 -}
396 -
397 -/**
398 - * 币种完整信息映射
399 - * @description 币种代码到完整信息的映射(用于多币种模式)
400 - */
401 -export const CURRENCY_MAP = {
402 - CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
403 - USD: { label: '美元', symbol: '$', value: 'USD' },
404 - HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
405 - EUR: { label: '欧元', symbol: '€', value: 'EUR' }
406 -}
407 -
408 -/**
409 - * 根据 form_sn 获取模版配置
410 - * @param {string} formSn - 产品 API 返回的 form_sn 字段
411 - * @returns {Object|null} 模版配置对象,未找到返回 null
412 - *
413 - * @example
414 - * const config = getTemplateConfig('life-insurance-wiop3e')
415 - * // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
416 - */
417 -export function getTemplateConfig(formSn) {
418 - if (!formSn) {
419 - console.warn('[plan-templates] form_sn 为空')
420 - return null
421 - }
422 -
423 - const config = PLAN_TEMPLATES[formSn]
424 - if (!config) {
425 - console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
426 - return null
427 - }
428 -
429 - return config
430 -}
431 -
432 -/**
433 - * 获取币种符号
434 - * @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
435 - * @returns {string} 币种符号
436 - *
437 - * @example
438 - * const symbol = getCurrencySymbol('USD') // 返回: '$'
439 - */
440 -export function getCurrencySymbol(currencyCode) {
441 - return CURRENCY_SYMBOLS[currencyCode] || '¥'
442 -}
1 +# 字段条件显示系统扩展计划
2 +
3 +## 背景与目标
4 +
5 +### 当前问题
6 +- `show_when` 只支持简单的等于比较 (`equals`)
7 +- 不支持 OR 条件、嵌套条件
8 +- 不支持不等于、大于、小于等操作符
9 +- 隐藏字段的清理逻辑分散在 `reset_map`
10 +- 提交时可能包含隐藏字段的脏数据
11 +
12 +### 目标
13 +构建一个声明式的条件规则系统,支持复杂逻辑同时保持配置可读性。
14 +
15 +## 技术方案:方案 B - 条件规则引擎
16 +
17 +### 核心文件变更
18 +
19 +| 文件 | 操作 | 说明 |
20 +|------|------|------|
21 +| `src/config/plan-conditions.js` | 新建 | 条件操作符和评估引擎 |
22 +| `src/composables/useFieldDependencies.js` | 修改 | 集成新的条件评估器 |
23 +| `src/composables/usePlanSubmit.js` | 新建 | 提交时字段过滤逻辑 |
24 +| `src/config/plan-templates.js` | 修改 | 迁移现有配置到新格式 |
25 +| `src/composables/__tests__/plan-conditions.test.js` | 新建 | 条件引擎单元测试 |
26 +
27 +---
28 +
29 +## 阶段 1:核心条件引擎
30 +
31 +### 任务 1.1 创建条件操作符定义
32 +- [x] 创建 `src/config/plan-conditions.js`
33 +- [x] 定义比较操作符:`eq`, `ne`, `gt`, `gte`, `lt`, `lte`
34 +- [x] 定义集合操作符:`in`, `nin`
35 +- [x] 定义字符串操作符:`contains`, `startsWith`, `matches`
36 +- [x] 定义布尔操作符:`truthy`, `falsy`, `empty`, `notEmpty`
37 +- [x] 编写单元测试验证操作符正确性
38 +
39 +### 任务 1.2 实现条件评估函数
40 +- [x] 实现 `evaluateCondition(condition, formData)` 函数
41 +- [x] 支持简单条件:`{ field, op, value }`
42 +- [x] 支持 AND 逻辑:`{ and: [...] }`
43 +- [x] 支持 OR 逻辑:`{ or: [...] }`
44 +- [x] 支持 NOT 逻辑:`{ not: {...} }`
45 +- [x] 支持嵌套条件组合
46 +- [x] 编写单元测试覆盖各种条件场景
47 +
48 +### 任务 1.3 向后兼容处理
49 +- [x] 支持旧格式 `show_when: { field: 'x', equals: 'y' }`
50 +- [x] 支持旧格式数组 `show_when: [{ field: 'x', equals: 'y' }]`
51 +- [x] 自动转换为新格式
52 +
53 +---
54 +
55 +## 阶段 2:清理机制
56 +
57 +### 任务 2.1 字段清理规则
58 +- [x] 在字段定义中添加 `clear_when_hidden` 属性
59 +- [x] 实现 `clear_when_hidden: true` 自动清空
60 +- [x] 实现 `clear_when_hidden: false` 保留值
61 +- [x] 实现级联清理 `clear_when_hidden: { clear_dependents: [...] }`
62 +
63 +### 任务 2.2 提交时字段过滤
64 +- [x] 实现 `filterHiddenFields(formData, visibleFields)` 函数
65 +- [x] 只提交当前可见的字段
66 +- [x] 保持 API 兼容性
67 +
68 +### 任务 2.3 更新 useFieldDependencies
69 +- [x] 集成新的条件评估引擎
70 +- [x] 实现字段隐藏时的自动清理
71 +- [x] 更新 `isFieldVisible` 使用新引擎
72 +- [x] 保持 API 兼容性
73 +
74 +---
75 +
76 +## 阶段 3:配置迁移
77 +
78 +### 任务 3.1 迁移储蓄类模板
79 +- [x] 迁移 `savingsFormSchema.withdrawal_fields` 到新格式
80 +- [x] 删除 `reset_map`,使用 `clear_when_hidden` 替代
81 +- [x] 验证功能正常(测试通过 160/160)
82 +
83 +### 任务 3.2 迁移保障类模板
84 +- [x] 检查 `protectionFormSchema` 是否需要条件
85 +- [x] 按需添加条件规则(保障类暂无条件需求,无需迁移)
86 +
87 +### 任务 3.3 更新文档解析工具 ⚠️ 重要
88 +- [x] 更新 `src/utils/parsers/config-generator.js` 生成新格式配置
89 +- [x] 修改 `show_when` 生成逻辑,使用 `{ field, op, value }` 格式
90 +- [x] 停止生成 `reset_map`(已忽略)
91 +- [x] 测试通过(160/160)
92 +
93 +**说明**:MCP 文档解析服务保持不变,仅更新配置生成器输出格式。
94 +
95 +### 任务 3.4 更新文档
96 +- [x] 更新 `plan-templates.js` 顶部的使用说明
97 +- [x] 添加条件规则配置示例(已在 plan-templates.js 注释中说明)
98 +- [x] 添加常见场景示例(已在 savingsFormSchema 中实现)
99 +
100 +---
101 +
102 +## 阶段 4:测试与验证
103 +
104 +### 任务 4.1 单元测试
105 +- [ ] 条件操作符测试覆盖率 > 90%
106 +- [ ] 条件评估引擎测试覆盖率 > 90%
107 +- [ ] useFieldDependencies 测试更新
108 +
109 +### 任务 4.2 集成测试
110 +- [ ] 测试储蓄类产品完整流程
111 +- [ ] 测试字段显示/隐藏切换
112 +- [ ] 测试提交时字段过滤
113 +- [ ] 测试向后兼容性
114 +
115 +### 任务 4.3 真机验证
116 +- [ ] 微信开发者工具验证
117 +- [ ] 检查性能影响
118 +- [ ] 检查内存占用
119 +
120 +---
121 +
122 +## 配置格式示例
123 +
124 +### 新格式示例
125 +
126 +```javascript
127 +// 简单条件
128 +show_when: { field: 'smoker', op: 'eq', value: '是' }
129 +
130 +// 多条件 AND
131 +show_when: {
132 + and: [
133 + { field: 'smoker', op: 'eq', value: '是' },
134 + { field: 'age', op: 'gte', value: 30 }
135 + ]
136 +}
137 +
138 +// OR 条件
139 +show_when: {
140 + or: [
141 + { field: 'smoker', op: 'eq', value: '是' },
142 + { field: 'age', op: 'gt', value: 50 }
143 + ]
144 +}
145 +
146 +// 嵌套条件
147 +show_when: {
148 + and: [
149 + { field: 'product_type', op: 'in', value: ['A', 'B'] },
150 + {
151 + or: [
152 + { field: 'coverage', op: 'gte', value: 100000 },
153 + { field: 'payment_period', op: 'eq', value: '20年' }
154 + ]
155 + }
156 + ]
157 +}
158 +
159 +// 清理规则
160 +{
161 + id: 'withdrawal_amount',
162 + show_when: { field: 'withdrawal_mode', op: 'eq', value: '指定提取金额' },
163 + clear_when_hidden: true // 隐藏时清空
164 +}
165 +```
166 +
167 +### 旧格式(保持兼容)
168 +
169 +```javascript
170 +// 旧格式仍然支持
171 +show_when: { field: 'withdrawal_mode', equals: '指定提取金额' }
172 +show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }]
173 +```
174 +
175 +---
176 +
177 +## 风险与注意事项
178 +
179 +1. **向后兼容**:必须保持现有配置格式可用
180 +2. **性能**:条件评估不能影响表单响应速度
181 +3. **可读性**:配置文件需要保持可理解性
182 +4. **测试覆盖**:每个操作符都需要充分测试
183 +
184 +---
185 +
186 +## 进度追踪
187 +
188 +| 阶段 | 状态 | 完成时间 |
189 +|------|------|---------|
190 +| 阶段 1:核心引擎 | ✅ 完成 | 2026-02-15 |
191 +| 阶段 2:清理机制 | ✅ 完成 | 2026-02-15 |
192 +| 阶段 3:配置迁移 | ✅ 完成 | 2026-02-15 |
193 +| 阶段 4:测试验证 | 🔄 待真机验证 | - |
194 +
195 +---
196 +
197 +**创建时间**: 2026-02-15
198 +**预计工期**: 3-4 天
199 +**维护者**: Claude Code
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
9 ## 📊 总体进度 9 ## 📊 总体进度
10 10
11 - [x] **第 1 步**: 目标与输出定义 11 - [x] **第 1 步**: 目标与输出定义
12 -- [ ] **第 2 步**: 文本抽取管线 12 +- [x] **第 2 步**: 文本抽取管线
13 -- [ ] **第 3 步**: 结构化解析与校验 13 +- [x] **第 3 步**: 结构化解析与校验
14 - [x] **第 4 步**: 生成与写入稳态化 14 - [x] **第 4 步**: 生成与写入稳态化
15 - [x] **第 5 步**: 测试与验证 15 - [x] **第 5 步**: 测试与验证
16 - [x] **第 6 步**: 运营与审计 16 - [x] **第 6 步**: 运营与审计
......
...@@ -29,15 +29,15 @@ ...@@ -29,15 +29,15 @@
29 "postinstall": "weapp-tw patch", 29 "postinstall": "weapp-tw patch",
30 "lint": "eslint --ext .js,.vue src", 30 "lint": "eslint --ext .js,.vue src",
31 "test": "vitest run", 31 "test": "vitest run",
32 - "api:generate": "node scripts/generateApiFromOpenAPI.js", 32 + "api:generate": "node scripts/api-generator/generateApiFromOpenAPI.js",
33 - "changelog:check": "bash scripts/check-changelog.sh 7", 33 + "changelog:check": "bash scripts/changelog/check-changelog.sh 7",
34 - "changelog:check:30": "bash scripts/check-changelog.sh 30", 34 + "changelog:check:30": "bash scripts/changelog/check-changelog.sh 30",
35 - "changelog:check:all": "bash scripts/check-changelog.sh 0", 35 + "changelog:check:all": "bash scripts/changelog/check-changelog.sh 0",
36 "prepare": "husky", 36 "prepare": "husky",
37 - "parse:docs": "node scripts/parse-docs.js", 37 + "parse:docs": "node scripts/doc-parser/parse-docs.js",
38 - "parse:docs:list": "node scripts/parse-docs.js --list", 38 + "parse:docs:list": "node scripts/doc-parser/parse-docs.js --list",
39 - "parse:docs:status": "node scripts/parse-docs.js --status", 39 + "parse:docs:status": "node scripts/doc-parser/parse-docs.js --status",
40 - "parse:docs:file": "node scripts/parse-docs.js --file=", 40 + "parse:docs:file": "node scripts/doc-parser/parse-docs.js --file=",
41 "release": "standard-version" 41 "release": "standard-version"
42 }, 42 },
43 "browserslist": [ 43 "browserslist": [
......
1 +# Scripts 目录说明
2 +
3 +> 最后更新: 2026-02-15
4 +
5 +本目录包含项目的各类开发工具脚本,按功能分组存放在子目录中。
6 +
7 +## 📁 目录结构
8 +
9 +```
10 +scripts/
11 +├── api-generator/ # API 代码生成工具
12 +├── doc-parser/ # 文档解析工具
13 +├── changelog/ # CHANGELOG 管理工具
14 +└── README.md # 本说明文件
15 +```
16 +
17 +---
18 +
19 +## 🚀 快速使用
20 +
21 +### API 代码生成
22 +```bash
23 +pnpm api:generate # 从 OpenAPI 生成 API 接口
24 +```
25 +
26 +### 文档解析
27 +```bash
28 +pnpm parse:docs # 解析待处理文档
29 +pnpm parse:docs:list # 查看待处理文档
30 +pnpm parse:docs:status # 查看解析状态
31 +```
32 +
33 +### CHANGELOG 检查
34 +```bash
35 +pnpm changelog:check # 检查最近 7 天的漏记
36 +pnpm changelog:check:30 # 检查最近 30 天的漏记
37 +```
38 +
39 +---
40 +
41 +## 📦 子目录详情
42 +
43 +| 目录 | 用途 | 相关 npm scripts |
44 +|------|------|-----------------|
45 +| `api-generator/` | 从 OpenAPI 规范生成 JavaScript API 代码 | `api:generate` |
46 +| `doc-parser/` | 解析 PDF/DOCX 等文档,智能提取配置字段 | `parse:docs:*` |
47 +| `changelog/` | 检查和归档 CHANGELOG 记录 | `changelog:check:*` |
48 +
49 +---
50 +
51 +## 🔗 相关文档
52 +
53 +- [API 生成指南](./api-generator/GUIDE.md)
54 +- [文档解析快速开始](./doc-parser/QUICKSTART.md)
55 +- [CHANGELOG 规范](../docs/CHANGELOG.md)
1 +# API 代码生成工具
2 +
3 +从 OpenAPI 规范自动生成 JavaScript API 接口代码。
4 +
5 +## 📁 文件说明
6 +
7 +| 文件 | 说明 | 使用频率 |
8 +|------|------|---------|
9 +| `generateApiFromOpenAPI.js` | 主脚本 - 从 OpenAPI 生成 API 代码 | ⭐⭐⭐ 高频 |
10 +| `apiDiff.js` | API 对比工具 - 检测 API 变更和破坏性改动 | ⭐⭐ 中频 |
11 +| `test-generate.js` | 测试脚本 - 验证生成的 API 文件是否正确 | ⭐ 低频 |
12 +| `API_GUIDE.md` | 使用指南 - 详细的 API 维护工作流文档 | 📖 文档 |
13 +
14 +## 🚀 使用方式
15 +
16 +```bash
17 +# 生成所有 API 接口
18 +pnpm api:generate
19 +
20 +# 生成指定模块
21 +pnpm api:generate -- --module=user
22 +```
23 +
24 +## 📖 详细文档
25 +
26 +参见 [API_GUIDE.md](./API_GUIDE.md) 了解完整的 API 维护工作流。
27 +
28 +## 🔧 工作原理
29 +
30 +1. 扫描 `docs/api-specs/` 目录下的 OpenAPI 文档
31 +2. 解析 Markdown 文件中的 YAML 规范
32 +3. 检测 API 变更(新增、修改、删除)
33 +4. 生成符合项目规范的 JavaScript API 代码
34 +5. 保存到 `src/api/` 目录
35 +
36 +## 📝 生成代码示例
37 +
38 +输入 (OpenAPI YAML):
39 +```yaml
40 +paths:
41 + /user/info:
42 + get:
43 + summary: 获取用户信息
44 + parameters:
45 + - name: user_id
46 + in: query
47 +```
48 +
49 +输出 (JavaScript):
50 +```javascript
51 +/**
52 + * 获取用户信息
53 + * @param {Object} params
54 + * @param {string} params.user_id - 用户ID
55 + */
56 +export const getUserInfoAPI = (params) => {
57 + return buildApiUrl('user_info', params)
58 +}
59 +```
1 +# CHANGELOG 管理工具
2 +
3 +检查和归档项目的 CHANGELOG 记录。
4 +
5 +## 📁 文件说明
6 +
7 +| 文件 | 说明 | 使用频率 |
8 +|------|------|---------|
9 +| `check-changelog.sh` | 漏记检查 - 扫描 git 提交,检查 CHANGELOG 漏记 | ⭐⭐⭐ 高频 |
10 +| `archive-changelog.sh` | 归档脚本 - 当记录超过 20 条时自动归档 | ⭐ 低频 |
11 +
12 +## 🚀 使用方式
13 +
14 +### 检查漏记
15 +
16 +```bash
17 +# 检查最近 7 天(默认)
18 +pnpm changelog:check
19 +
20 +# 检查最近 30 天
21 +pnpm changelog:check:30
22 +
23 +# 检查所有提交
24 +pnpm changelog:check:all
25 +
26 +# 直接运行
27 +./scripts/changelog/check-changelog.sh 7
28 +```
29 +
30 +### 归档旧记录
31 +
32 +```bash
33 +# 直接运行归档脚本
34 +./scripts/changelog/archive-changelog.sh
35 +```
36 +
37 +## 📋 检查输出示例
38 +
39 +```
40 +======================================
41 + CHANGELOG 漏记检查工具
42 +======================================
43 +
44 +检查范围: 最近 7 天
45 +
46 +[1/4] 正在获取 git 提交记录...
47 + 找到 5 个提交
48 +
49 +[2/4] 正在读取 CHANGELOG 记录...
50 + 找到 3 条记录
51 +
52 +[3/4] 正在对比...
53 +
54 +[4/4] 生成报告...
55 +
56 +⚠️ 发现 2 处漏记:
57 +
58 +1. feat(plan): 添加计划书表单验证
59 + 提交: abc1234 (2026-02-14)
60 + 建议: 在 CHANGELOG 中添加此功能记录
61 +
62 +2. fix(login): 修复登录跳转问题
63 + 提交: def5678 (2026-02-13)
64 + 建议: 在 CHANGELOG 中添加此修复记录
65 +```
66 +
67 +## 🔧 归档规则
68 +
69 +`docs/CHANGELOG.md` 记录数超过 20 条时:
70 +1. 自动将旧记录移动到 `docs/changelog-archive/`
71 +2. 主文件只保留最近 20 条记录
72 +3. 归档文件以日期命名:`CHANGELOG-archive-YYYYMMDD.md`
73 +
74 +## 📝 最佳实践
75 +
76 +1. **每次提交后检查**:运行 `pnpm changelog:check` 确保没有漏记
77 +2. **及时记录**:在提交代码时同步更新 CHANGELOG
78 +3. **定期归档**:每月运行一次归档脚本
1 +# 文档解析工具
2 +
3 +从 PDF、DOCX 等文档中智能提取配置字段,自动生成计划书模板配置。
4 +
5 +## 📁 文件说明
6 +
7 +| 文件 | 说明 | 使用频率 |
8 +|------|------|---------|
9 +| `parse-docs.js` | 主脚本 - 文档解析和配置生成 | ⭐⭐⭐ 高频 |
10 +| `smart-field-extractor.js` | 智能字段提取器 - 从文档中提取表单字段 | ⭐⭐ 中频 |
11 +| `product-splitter.js` | 产品分割器 - 识别和分割多产品文档 | ⭐⭐ 中频 |
12 +| `parse-config.js` | 配置文件 - markitdown 和 AI 服务配置 | 📋 配置 |
13 +| `parse-docs.test.js` | 测试文件 - 单元测试 | 🧪 测试 |
14 +| `QUICKSTART.md` | 快速开始指南 | 📖 文档 |
15 +
16 +## 🚀 使用方式
17 +
18 +```bash
19 +# 解析所有待处理文档
20 +pnpm parse:docs
21 +
22 +# 查看待处理文档列表
23 +pnpm parse:docs:list
24 +
25 +# 查看解析状态
26 +pnpm parse:docs:status
27 +
28 +# 解析指定文件
29 +pnpm parse:docs -- --file=产品说明书.pdf
30 +
31 +# 应用审核通过的配置
32 +pnpm parse:docs -- --apply=计划书模版4
33 +
34 +# 预览应用配置(不实际修改)
35 +pnpm parse:docs -- --apply=计划书模版4 --dry-run
36 +```
37 +
38 +## 📖 详细文档
39 +
40 +参见 [QUICKSTART.md](./QUICKSTART.md) 了解完整的快速开始指南。
41 +
42 +## 🔧 工作原理
43 +
44 +1. 扫描 `docs/to-parse/` 目录下的待处理文档
45 +2. 使用 markitdown 将文档转换为 Markdown
46 +3. 调用 AI 服务提取配置字段
47 +4. 生成可审核的配置文件
48 +5. 审核通过后应用到 `src/config/plan-templates.js`
49 +
50 +## 📝 支持的文档格式
51 +
52 +- PDF (`.pdf`)
53 +- Word (`.doc`, `.docx`)
54 +- 文本 (`.txt`, `.md`)
55 +
56 +## 🤖 AI 配置
57 +
58 +`.env` 文件中配置 AI 服务:
59 +
60 +```env
61 +# markitdown 服务 URL(可选)
62 +MARKITDOWN_URL=http://localhost:8000/convert
63 +
64 +# AI 服务配置(用于智能字段提取)
65 +AI_SERVICE_URL=your_ai_service_url
66 +AI_API_KEY=your_api_key
67 +```
68 +
69 +## 📋 输出示例
70 +
71 +解析后会生成:
72 +- `docs/parsed/产品名称.json` - 解析结果
73 +- `docs/parsed/产品名称.audit.md` - 审核报告
...@@ -15,6 +15,12 @@ ...@@ -15,6 +15,12 @@
15 * 15 *
16 * # 查看待处理文档 16 * # 查看待处理文档
17 * npm run parse:docs -- --list 17 * npm run parse:docs -- --list
18 + *
19 + * # 应用审核通过的配置
20 + * npm run parse:docs -- --apply=计划书模版4
21 + *
22 + * # 预览应用配置(不实际修改)
23 + * npm run parse:docs -- --apply=计划书模版4 --dry-run
18 */ 24 */
19 import crypto from 'crypto' 25 import crypto from 'crypto'
20 import fs from 'fs' 26 import fs from 'fs'
...@@ -976,17 +982,27 @@ ${code.trim()} ...@@ -976,17 +982,27 @@ ${code.trim()}
976 982
977 ## 📋 审核后操作 983 ## 📋 审核后操作
978 984
979 -### 确认无误 985 +### 方法 1:自动应用(推荐)
986 +\`\`\`bash
987 +# 预览变更(不实际修改)
988 +pnpm parse:docs -- --apply=${baseFileName} --dry-run
989 +
990 +# 确认无误后,正式应用
991 +pnpm parse:docs -- --apply=${baseFileName}
992 +
993 +# 说明:
994 +# 1. 自动提取配置代码并插入到 src/config/plan-templates.js
995 +# 2. 自动创建备份文件(docs/parsed-backup/)
996 +# 3. 自动将审核文件移动到 docs/parse-audit/approved/
997 +\`\`\`
998 +
999 +### 方法 2:手动操作
980 \`\`\`bash 1000 \`\`\`bash
981 # 1. 移动到 approved 目录 1001 # 1. 移动到 approved 目录
982 mv docs/parse-audit/pending/${baseFileName}/${auditFileName} \\ 1002 mv docs/parse-audit/pending/${baseFileName}/${auditFileName} \\
983 docs/parse-audit/approved/ 1003 docs/parse-audit/approved/
984 1004
985 -# 2. 合并到正式配置 1005 +# 2. 手动复制"生成配置片段"到 src/config/plan-templates.js
986 -# 手动复制或使用工具合并到 src/config/plan-templates.js
987 -
988 -# 3. 删除待审核文件(可选)
989 -rm docs/parse-audit/pending/${baseFileName}/${auditFileName}
990 \`\`\` 1006 \`\`\`
991 1007
992 ### 需要修改 1008 ### 需要修改
...@@ -1402,6 +1418,189 @@ function rollbackConfigFile(backupFile) { ...@@ -1402,6 +1418,189 @@ function rollbackConfigFile(backupFile) {
1402 return true 1418 return true
1403 } 1419 }
1404 1420
1421 +/**
1422 + * 从审核文件应用配置到 plan-templates.js
1423 + *
1424 + * @description 读取审核 markdown 文件,提取配置代码,插入到配置文件中
1425 + * @param {string} auditFileName - 审核文件名(不含路径,如 "计划书模版4")
1426 + * @param {Object} options - 选项
1427 + * @param {boolean} options.dry_run - 是否仅预览
1428 + * @returns {Object} 应用结果
1429 + */
1430 +function applyAuditFile(auditFileName, options = {}) {
1431 + const PENDING_DIR = path.resolve(process.cwd(), 'docs/parse-audit/pending')
1432 + const APPROVED_DIR = path.resolve(process.cwd(), 'docs/parse-audit/approved')
1433 +
1434 + // 1. 查找审核文件
1435 + let auditFile = null
1436 + let sourceDir = null
1437 +
1438 + // 先在 pending 目录查找
1439 + const pendingDirs = fs.existsSync(PENDING_DIR) ? fs.readdirSync(PENDING_DIR) : []
1440 + for (const dir of pendingDirs) {
1441 + const dirPath = path.join(PENDING_DIR, dir)
1442 + if (fs.statSync(dirPath).isDirectory()) {
1443 + const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.md'))
1444 + for (const file of files) {
1445 + // 匹配文件名或目录名
1446 + const normalizedName = dir.replace(/\s+/g, '').toLowerCase()
1447 + const normalizedInput = auditFileName.replace(/\s+/g, '').toLowerCase()
1448 + if (normalizedName.includes(normalizedInput) || normalizedInput.includes(normalizedName)) {
1449 + auditFile = path.join(dirPath, file)
1450 + sourceDir = PENDING_DIR
1451 + break
1452 + }
1453 + }
1454 + }
1455 + if (auditFile) break
1456 + }
1457 +
1458 + // 如果 pending 没找到,在 approved 目录查找
1459 + if (!auditFile && fs.existsSync(APPROVED_DIR)) {
1460 + const approvedFiles = fs.readdirSync(APPROVED_DIR).filter(f => f.endsWith('.md'))
1461 + for (const file of approvedFiles) {
1462 + // 从文件名提取产品名(格式:YYYY-MM-DD-产品名.md)
1463 + const match = file.match(/^\d{4}-\d{2}-\d{2}-(.+)\.md$/)
1464 + if (match) {
1465 + const normalizedName = match[1].replace(/\s+/g, '').toLowerCase()
1466 + const normalizedInput = auditFileName.replace(/\s+/g, '').toLowerCase()
1467 + if (normalizedName.includes(normalizedInput) || normalizedInput.includes(normalizedName)) {
1468 + auditFile = path.join(APPROVED_DIR, file)
1469 + sourceDir = APPROVED_DIR
1470 + break
1471 + }
1472 + }
1473 + }
1474 + }
1475 +
1476 + if (!auditFile) {
1477 + console.error("❌ 找不到审核文件: " + auditFileName)
1478 + console.log(" 搜索目录:")
1479 + console.log(" - docs/parse-audit/pending/")
1480 + console.log(" - docs/parse-audit/approved/")
1481 + return { ok: false, reason: 'file_not_found' }
1482 + }
1483 +
1484 + console.log("\n📄 找到审核文件: " + auditFile)
1485 +
1486 + // 2. 读取审核文件内容
1487 + const content = fs.readFileSync(auditFile, 'utf-8')
1488 +
1489 + // 3. 提取配置代码片段
1490 + const configMatch = content.match(/## 🧩 生成配置片段\s*\n+```javascript\s*\n([\s\S]*?)```/)
1491 + if (!configMatch) {
1492 + console.error("❌ 无法从审核文件中提取配置代码")
1493 + return { ok: false, reason: 'config_not_found' }
1494 + }
1495 +
1496 + const configCode = configMatch[1].trim()
1497 + console.log("\n📝 提取的配置代码:")
1498 + console.log("-".repeat(40))
1499 + console.log(configCode)
1500 + console.log("-".repeat(40))
1501 +
1502 + // 4. 提取 form_sn 用于去重检查
1503 + const formSnMatch = configCode.match(/'([^']+)':\s*\{/)
1504 + const formSn = formSnMatch ? formSnMatch[1] : null
1505 +
1506 + if (!formSn) {
1507 + console.error("❌ 无法从配置代码中提取 form_sn")
1508 + return { ok: false, reason: 'form_sn_not_found' }
1509 + }
1510 +
1511 + console.log("\n🔑 form_sn: " + formSn)
1512 +
1513 + // 5. 读取现有配置文件
1514 + const existingContent = fs.readFileSync(CONFIG_FILE, 'utf-8')
1515 +
1516 + // 检查是否已存在
1517 + if (existingContent.includes(`'${formSn}':`)) {
1518 + console.error("❌ 配置文件中已存在 form_sn: " + formSn)
1519 + console.log(" 如需更新,请先手动删除旧配置")
1520 + return { ok: false, reason: 'duplicate', formSn }
1521 + }
1522 +
1523 + // 6. 找到插入位置(PLAN_TEMPLATES 对象的结束位置)
1524 + // 查找最后一个产品配置的结束位置
1525 + const insertPattern = /(\n\s*'\w+[^']+':\s*\{[\s\S]*?\n\s*\}\s*,?\s*)(\n\})/
1526 + const match = existingContent.match(insertPattern)
1527 +
1528 + if (!match) {
1529 + console.error("❌ 无法定位插入位置")
1530 + return { ok: false, reason: 'insert_not_found' }
1531 + }
1532 +
1533 + // 7. 构建新配置(确保有逗号)
1534 + let newConfigEntry = configCode
1535 + // 确保配置以逗号结尾
1536 + if (!newConfigEntry.trimEnd().endsWith(',')) {
1537 + newConfigEntry = newConfigEntry.trimEnd() + ','
1538 + }
1539 +
1540 + // 8. 插入配置
1541 + const insertPosition = match.index + match[1].length
1542 + const updatedContent =
1543 + existingContent.slice(0, insertPosition) +
1544 + '\n\n' +
1545 + newConfigEntry +
1546 + existingContent.slice(insertPosition)
1547 +
1548 + if (options.dry_run) {
1549 + console.log("\n🧪 dry-run 模式,变更预览:")
1550 + console.log("-".repeat(40))
1551 + console.log("将插入以下配置:")
1552 + console.log(newConfigEntry)
1553 + console.log("-".repeat(40))
1554 + return { ok: true, dry_run: true, formSn }
1555 + }
1556 +
1557 + // 9. 备份并写入
1558 + let backupFile = null
1559 + if (fs.existsSync(CONFIG_FILE)) {
1560 + ensureDir(BACKUP_DIR)
1561 + backupFile = path.join(BACKUP_DIR, `plan-templates.backup.${Date.now()}.js`)
1562 + fs.copyFileSync(CONFIG_FILE, backupFile)
1563 + console.log("\n💾 已备份到: " + backupFile)
1564 + }
1565 +
1566 + writeFile(CONFIG_FILE, updatedContent)
1567 + console.log("\n✅ 配置已更新: " + CONFIG_FILE)
1568 +
1569 + writeBackupLog({
1570 + action: 'apply_audit',
1571 + backup_file: backupFile,
1572 + target_file: CONFIG_FILE,
1573 + audit_file: auditFile,
1574 + form_sn: formSn,
1575 + at: new Date().toISOString()
1576 + })
1577 +
1578 + // 10. 移动审核文件到 approved 目录(如果是从 pending 来的)
1579 + if (sourceDir === PENDING_DIR) {
1580 + ensureDir(APPROVED_DIR)
1581 + const fileName = path.basename(auditFile)
1582 + const approvedPath = path.join(APPROVED_DIR, fileName)
1583 +
1584 + // 检查目标是否已存在
1585 + if (fs.existsSync(approvedPath)) {
1586 + console.log("⚠️ approved 目录已存在同名文件,跳过移动")
1587 + } else {
1588 + fs.renameSync(auditFile, approvedPath)
1589 + console.log("📁 审核文件已移动到: " + approvedPath)
1590 +
1591 + // 删除空的 pending 子目录
1592 + const pendingSubDir = path.dirname(auditFile)
1593 + const remainingFiles = fs.readdirSync(pendingSubDir).filter(f => !f.startsWith('.'))
1594 + if (remainingFiles.length === 0) {
1595 + fs.rmdirSync(pendingSubDir)
1596 + console.log("🗑️ 已删除空目录: " + pendingSubDir)
1597 + }
1598 + }
1599 + }
1600 +
1601 + return { ok: true, formSn, backupFile }
1602 +}
1603 +
1405 function updateConfigFile(newConfigs, options = {}) { 1604 function updateConfigFile(newConfigs, options = {}) {
1406 console.log("\n" + "=".repeat(60)) 1605 console.log("\n" + "=".repeat(60))
1407 console.log("📝 更新配置文件: " + CONFIG_FILE) 1606 console.log("📝 更新配置文件: " + CONFIG_FILE)
...@@ -1563,9 +1762,16 @@ async function main() { ...@@ -1563,9 +1762,16 @@ async function main() {
1563 const listMode = args.includes('--list') 1762 const listMode = args.includes('--list')
1564 const fileMode = args.find(arg => arg.startsWith('--file=')) 1763 const fileMode = args.find(arg => arg.startsWith('--file='))
1565 const writeMode = args.includes('--write-config') 1764 const writeMode = args.includes('--write-config')
1566 - const dryRunMode = args.includes('--dry-run') || !writeMode
1567 const rollbackMode = args.find(arg => arg.startsWith('--rollback=')) 1765 const rollbackMode = args.find(arg => arg.startsWith('--rollback='))
1568 const statusMode = args.includes('--status') 1766 const statusMode = args.includes('--status')
1767 + const applyMode = args.find(arg => arg.startsWith('--apply='))
1768 +
1769 + // dry-run 逻辑:
1770 + // 1. 如果显式指定 --dry-run,则 dry-run
1771 + // 2. 如果是 apply 模式,默认不 dry-run(除非显式指定)
1772 + // 3. 如果是解析模式,默认 dry-run(除非显式指定 --write-config)
1773 + const explicitDryRun = args.includes('--dry-run')
1774 + const dryRunMode = applyMode ? explicitDryRun : (!writeMode && !explicitDryRun || explicitDryRun)
1569 1775
1570 // 检查解析器选择 1776 // 检查解析器选择
1571 const parserModeArg = args.find(arg => arg.startsWith('--parser=')) 1777 const parserModeArg = args.find(arg => arg.startsWith('--parser='))
...@@ -1586,6 +1792,11 @@ async function main() { ...@@ -1586,6 +1792,11 @@ async function main() {
1586 if (rollbackMode) { 1792 if (rollbackMode) {
1587 const backupFile = rollbackMode.split('=')[1] 1793 const backupFile = rollbackMode.split('=')[1]
1588 rollbackConfigFile(backupFile) 1794 rollbackConfigFile(backupFile)
1795 + } else if (applyMode) {
1796 + // 从审核文件应用配置
1797 + const auditFileName = applyMode.split('=')[1]
1798 + const applyOptions = { dry_run: dryRunMode }
1799 + applyAuditFile(auditFileName, applyOptions)
1589 } else if (listMode) { 1800 } else if (listMode) {
1590 // 列出模式 1801 // 列出模式
1591 const docs = getDocsToParse() 1802 const docs = getDocsToParse()
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
16 * - GC宏摯家傳承保險計劃- 性別, 年齡, 出生年月日 16 * - GC宏摯家傳承保險計劃- 性別, 年齡, 出生年月日
17 * - FA 宏浚傳承保障計劃 17 * - FA 宏浚傳承保障計劃
18 * - LV2 赤霞珠終身壽險計劃2基本人壽保障選項 18 * - LV2 赤霞珠終身壽險計劃2基本人壽保障選項
19 + * - LV3 长宁終身壽險計劃3
19 */ 20 */
20 const PRODUCT_TITLE_PATTERNS = [ 21 const PRODUCT_TITLE_PATTERNS = [
21 // 产品代码 + 产品名称 + 可选后缀 22 // 产品代码 + 产品名称 + 可选后缀
...@@ -31,14 +32,17 @@ const PRODUCT_TITLE_PATTERNS = [ ...@@ -31,14 +32,17 @@ const PRODUCT_TITLE_PATTERNS = [
31 /^([^\n]{2,30}?(?:計劃|计划|保障|保险|壽險|壽险)[^\n]*)/gm, 32 /^([^\n]{2,30}?(?:計劃|计划|保障|保险|壽險|壽险)[^\n]*)/gm,
32 33
33 // 产品代码开头的行 34 // 产品代码开头的行
34 - /^([A-Z]{2,4}\d?)\s*[-:]\s*([^\n]+)/gm 35 + /^([A-Z]{2,4}\d?)\s*[-:]\s*([^\n]+)/gm,
36 +
37 + // 新增:产品代码 + 产品名称 + 数字后缀(如 "LV3 长宁終身壽險計劃3")
38 + /^([A-Z]{2,3}\d?)\s+([^\n]{2,25}?(?:計劃|计划|壽險|壽险)\d?)/gm
35 ] 39 ]
36 40
37 /** 41 /**
38 * 产品代码前缀列表(用于优先匹配) 42 * 产品代码前缀列表(用于优先匹配)
39 */ 43 */
40 const PRODUCT_CODE_PREFIXES = [ 44 const PRODUCT_CODE_PREFIXES = [
41 - 'GS', 'GC', 'FA', 'LV2', 'LV', 'CR', 'HR', 'PR', 'SR', 45 + 'GS', 'GC', 'FA', 'LV2', 'LV3', 'LV', 'CR', 'HR', 'PR', 'SR',
42 'TR', 'UR', 'WR', 'XR', 'YR', 'ZR' 46 'TR', 'UR', 'WR', 'XR', 'YR', 'ZR'
43 ] 47 ]
44 48
...@@ -62,10 +66,11 @@ export function detectProductCount(content) { ...@@ -62,10 +66,11 @@ export function detectProductCount(content) {
62 export function findProductTitles(content) { 66 export function findProductTitles(content) {
63 const products = [] 67 const products = []
64 const seenCodes = new Set() 68 const seenCodes = new Set()
69 + const seenNames = new Set()
65 70
66 // 策略1: 优先匹配产品代码前缀 71 // 策略1: 优先匹配产品代码前缀
67 for (const prefix of PRODUCT_CODE_PREFIXES) { 72 for (const prefix of PRODUCT_CODE_PREFIXES) {
68 - // 匹配 "GS宏摯傳承保障計劃" 或 "GS 宏摯傳承保障計劃" 73 + // 匹配 "GS宏摯傳承保障計劃" 或 "GS 宏摯傳承保障計劃" 或 "LV3 长宁終身壽險計劃3"
69 const regex = new RegExp( 74 const regex = new RegExp(
70 `^(${prefix}\\d?)\\s*([\\u4e00-\\u9fa5]+(?:計劃|计划|保障|保险|壽險|壽险)[^\\n]*)`, 75 `^(${prefix}\\d?)\\s*([\\u4e00-\\u9fa5]+(?:計劃|计划|保障|保险|壽險|壽险)[^\\n]*)`,
71 'gm' 76 'gm'
...@@ -76,9 +81,11 @@ export function findProductTitles(content) { ...@@ -76,9 +81,11 @@ export function findProductTitles(content) {
76 const code = match[1] 81 const code = match[1]
77 const name = match[2].trim() 82 const name = match[2].trim()
78 83
79 - // 去重 84 + // 去重(基于代码或名称)
80 - if (seenCodes.has(code)) continue 85 + const nameKey = name.replace(/\s+/g, '').toLowerCase()
86 + if (seenCodes.has(code) || seenNames.has(nameKey)) continue
81 seenCodes.add(code) 87 seenCodes.add(code)
88 + seenNames.add(nameKey)
82 89
83 products.push({ 90 products.push({
84 index: match.index, 91 index: match.index,
...@@ -99,15 +106,52 @@ export function findProductTitles(content) { ...@@ -99,15 +106,52 @@ export function findProductTitles(content) {
99 const fullTitle = match[0].trim() 106 const fullTitle = match[0].trim()
100 if (fullTitle.length < 5) continue // 过滤太短的匹配 107 if (fullTitle.length < 5) continue // 过滤太短的匹配
101 108
109 + const code = match[1] || null
110 + const name = match[2] || fullTitle
111 +
112 + // 去重
113 + const nameKey = name.replace(/\s+/g, '').toLowerCase()
114 + if (seenNames.has(nameKey)) continue
115 + if (code) seenCodes.add(code)
116 + seenNames.add(nameKey)
117 +
102 products.push({ 118 products.push({
103 index: match.index, 119 index: match.index,
104 - code: match[1] || null, 120 + code,
105 - name: match[2] || fullTitle, 121 + name,
106 fullTitle 122 fullTitle
107 }) 123 })
108 } 124 }
109 } 125 }
110 126
127 + // 策略3: 新增 - 识别包含"计划"但不包含产品代码的行(纯计划书名称)
128 + // 适用于标题如 "宏挚传承保障计划" 或 "长宁终身寿险计划3"
129 + if (products.length === 0) {
130 + const planNameRegex = /^([^\n]{2,30}?(?:計劃|计划)[^\n]*)/gm
131 + let match
132 +
133 + while ((match = planNameRegex.exec(content)) !== null) {
134 + const fullTitle = match[1].trim()
135 +
136 + // 排除太短或包含其他关键词的行
137 + if (fullTitle.length < 5 || fullTitle.includes('選項') || fullTitle.includes('选项')) continue
138 +
139 + // 检查是否是产品名称(通常包含"保障"、"保险"、"寿险"等关键词)
140 + if (/(?:保障|保险|壽險|壽险|传承|家传)/.test(fullTitle)) {
141 + const nameKey = fullTitle.replace(/\s+/g, '').toLowerCase()
142 + if (!seenNames.has(nameKey)) {
143 + seenNames.add(nameKey)
144 + products.push({
145 + index: match.index,
146 + code: null,
147 + name: fullTitle.split(/[-—::]/)[0].trim(), // 移除后缀说明
148 + fullTitle
149 + })
150 + }
151 + }
152 + }
153 + }
154 +
111 // 按出现位置排序 155 // 按出现位置排序
112 products.sort((a, b) => a.index - b.index) 156 products.sort((a, b) => a.index - b.index)
113 157
......
...@@ -8,6 +8,107 @@ ...@@ -8,6 +8,107 @@
8 */ 8 */
9 9
10 /** 10 /**
11 + * 保险缴费年期匹配模式
12 + *
13 + * @description 用于识别各种格式的缴费年期选项
14 + * @constant {Object}
15 + */
16 +const PAYMENT_PERIOD_PATTERNS = {
17 + // 一次性缴费关键词
18 + singlePayment: ['整付', '趸交', '躉繳', 'lump sum', 'single'],
19 + // 数字+年格式(3年、5年、10年等)
20 + yearlyPattern: /^(\d+)\s*年$/,
21 + // 至X岁格式
22 + untilAgePattern: /^至?\s*(\d+)\s*岁$/,
23 + // 列表项格式(- 3年、• 5年等)
24 + listItemPattern: /^[-•·]\s*(.+)$/
25 +}
26 +
27 +/**
28 + * 检查是否为有效的缴费年期选项
29 + *
30 + * @param {string} text - 待检查文本
31 + * @returns {boolean} 是否为有效缴费年期
32 + */
33 +const isValidPaymentPeriod = (text) => {
34 + const trimmed = text.trim()
35 +
36 + // 排除无效关键字
37 + if (trimmed.includes('投保') || trimmed.includes('年龄') || trimmed.includes('年齡')) {
38 + return false
39 + }
40 +
41 + // 1. 匹配 "X年" 格式
42 + if (PAYMENT_PERIOD_PATTERNS.yearlyPattern.test(trimmed)) {
43 + return true
44 + }
45 +
46 + // 2. 匹配 "至X岁" 格式
47 + if (PAYMENT_PERIOD_PATTERNS.untilAgePattern.test(trimmed)) {
48 + return true
49 + }
50 +
51 + // 3. 匹配一次性缴费关键词
52 + if (PAYMENT_PERIOD_PATTERNS.singlePayment.some(kw =>
53 + trimmed.toLowerCase() === kw.toLowerCase()
54 + )) {
55 + return true
56 + }
57 +
58 + // 4. 匹配列表项格式(提取内容后递归检查)
59 + const listItemMatch = trimmed.match(PAYMENT_PERIOD_PATTERNS.listItemPattern)
60 + if (listItemMatch) {
61 + return isValidPaymentPeriod(listItemMatch[1])
62 + }
63 +
64 + // 5. 通用匹配:包含"年"或"岁"或"交"的短文本(2-10字符)
65 + if (trimmed.length >= 2 && trimmed.length <= 10) {
66 + if (/年|岁|交|付/.test(trimmed)) {
67 + return true
68 + }
69 + }
70 +
71 + return false
72 +}
73 +
74 +/**
75 + * 标准化缴费年期选项
76 + *
77 + * @param {string} text - 原始文本
78 + * @returns {string} 标准化后的文本
79 + */
80 +const normalizePaymentPeriod = (text) => {
81 + const trimmed = text.trim()
82 +
83 + // 标准化一次性缴费
84 + if (PAYMENT_PERIOD_PATTERNS.singlePayment.some(kw =>
85 + trimmed.toLowerCase() === kw.toLowerCase()
86 + )) {
87 + return '整付'
88 + }
89 +
90 + // 标准化 "X年" 格式
91 + const yearlyMatch = trimmed.match(PAYMENT_PERIOD_PATTERNS.yearlyPattern)
92 + if (yearlyMatch) {
93 + return `${yearlyMatch[1]}年`
94 + }
95 +
96 + // 标准化 "至X岁" 格式
97 + const ageMatch = trimmed.match(PAYMENT_PERIOD_PATTERNS.untilAgePattern)
98 + if (ageMatch) {
99 + return `至${ageMatch[1]}岁`
100 + }
101 +
102 + // 处理列表项格式
103 + const listItemMatch = trimmed.match(PAYMENT_PERIOD_PATTERNS.listItemPattern)
104 + if (listItemMatch) {
105 + return normalizePaymentPeriod(listItemMatch[1])
106 + }
107 +
108 + return trimmed
109 +}
110 +
111 +/**
11 * 字段提取规则配置 112 * 字段提取规则配置
12 * 113 *
13 * @description 定义每个字段的匹配规则、优先级和默认值 114 * @description 定义每个字段的匹配规则、优先级和默认值
...@@ -20,13 +121,30 @@ const FIELD_RULES = { ...@@ -20,13 +121,30 @@ const FIELD_RULES = {
20 /产品名称[::]\s*([^\n]+)/, 121 /产品名称[::]\s*([^\n]+)/,
21 /计划书名称[::]\s*([^\n]+)/, 122 /计划书名称[::]\s*([^\n]+)/,
22 /Product\s+Name[::]\s*([^\n]+)/i, 123 /Product\s+Name[::]\s*([^\n]+)/i,
23 - /^#\s+(.+)$/m // Markdown 标题 124 + /^#\s+(.+)$/m, // Markdown 标题
125 + // 新增:识别包含"计划"的标题行(如 "LV3 长宁終身壽險計劃3")
126 + // 格式:产品代码 + 中文产品名(包含"計劃/计划")
127 + /^[A-Z]{2,4}\d?\s*[-::]?\s*([^\n]*(?:計劃|计划)[^\n]*)/m,
128 + // 纯产品名称(包含"計劃/计划")- 通常为计划书名称
129 + /^([^\n]{2,30}?(?:計劃|计划)[^\n]*)/m
24 ], 130 ],
25 fallback: null, // 必填,无默认值 131 fallback: null, // 必填,无默认值
26 - required: true 132 + required: true,
133 + postProcess: (value) => {
134 + // 处理正则匹配结果(数组)
135 + if (Array.isArray(value) && value.length > 1) {
136 + let name = value[1] || value[0] || ''
137 + // 移除产品代码前缀
138 + name = name.replace(/^[A-Z]{2,4}\d?\s*[-::]?\s*/, '')
139 + // 移除后缀说明(如 "- 性別, 年齡, 出生年月日")
140 + name = name.split(/[-—::]/)[0].trim()
141 + return name || null
142 + }
143 + return value
144 + }
27 }, 145 },
28 146
29 - // 产品类型 147 + // 产品类型(保险类别)
30 product_type: { 148 product_type: {
31 priority: 2, 149 priority: 2,
32 patterns: [ 150 patterns: [
...@@ -34,9 +152,21 @@ const FIELD_RULES = { ...@@ -34,9 +152,21 @@ const FIELD_RULES = {
34 { 152 {
35 type: 'content_match', 153 type: 'content_match',
36 rules: [ 154 rules: [
37 - { keywords: ['储蓄', 'saving', '传承', '家传', '红利', '提取'], value: 'savings' }, 155 + // 储蓄型产品(新增"储蓄产品"关键字)
156 + { keywords: ['储蓄产品', '储蓄型', '储蓄', 'saving', '传承', '家传', '红利', '提取'], value: 'savings' },
157 + // 重疾型产品
38 { keywords: ['重疾', 'critical', '守护', '严重疾病'], value: 'critical-illness' }, 158 { keywords: ['重疾', 'critical', '守护', '严重疾病'], value: 'critical-illness' },
39 - { keywords: ['人寿', 'life', '创富', '身故保障'], value: 'life-insurance' } 159 + // 人寿型产品
160 + { keywords: ['人寿', 'life', '创富', '身故保障', '壽險', '壽险'], value: 'life-insurance' }
161 + ]
162 + },
163 + // 新增:从标题行推断(如 "LV3 长宁終身壽險計劃3" 中的"壽險")
164 + {
165 + type: 'title_match',
166 + rules: [
167 + { pattern: /終身壽險|終身寿险|壽險計劃|寿险计划/, value: 'life-insurance' },
168 + { pattern: /儲蓄計劃|储蓄计划|儲蓄產品|储蓄产品/, value: 'savings' },
169 + { pattern: /重疾|嚴重疾病/, value: 'critical-illness' }
40 ] 170 ]
41 } 171 }
42 ], 172 ],
...@@ -70,25 +200,12 @@ const FIELD_RULES = { ...@@ -70,25 +200,12 @@ const FIELD_RULES = {
70 priority: 4, 200 priority: 4,
71 patterns: [ 201 patterns: [
72 // 匹配 "年繳保費繳費年期" 或 "缴费年期" 后面的列表 202 // 匹配 "年繳保費繳費年期" 或 "缴费年期" 后面的列表
73 - // 策略:匹配到包含 "年" 或 "整付" 的所有行,直到遇到其他关键字 203 + // 策略:使用通用匹配函数识别各种格式的缴费年期选项
74 { 204 {
75 type: 'smart_list_extract', 205 type: 'smart_list_extract',
76 startPattern: /(?:年繳保費)?繳費年期[::\s]*\n/, 206 startPattern: /(?:年繳保費)?繳費年期[::\s]*\n/,
77 endKeywords: ['提取', '保險期間', '保险期间', '投保年龄', '投保年齡', '選是', '選項', 'GC宏', 'FA宏', 'LV2'], 207 endKeywords: ['提取', '保險期間', '保险期间', '投保年龄', '投保年齡', '選是', '選項', 'GC宏', 'FA宏', 'LV2'],
78 - itemFilter: (line) => { 208 + itemFilter: (line) => isValidPaymentPeriod(line)
79 - const trimmed = line.trim()
80 - // 排除包含"投保年龄"等关键字的行
81 - if (trimmed.includes('投保') || trimmed.includes('年龄') || trimmed.includes('年齡')) {
82 - return false
83 - }
84 - // 精确匹配 "整付" 或 "X年" 格式
85 - return trimmed && (
86 - /^\d+\s*年$/.test(trimmed) ||
87 - trimmed === '整付' ||
88 - /^\d+年$/.test(trimmed) ||
89 - /^[-•·]\s*\d+\s*年$/.test(trimmed) // 支持列表格式 "- 3年"
90 - )
91 - }
92 } 209 }
93 ], 210 ],
94 fallback: ['整付', '3年', '5年'], 211 fallback: ['整付', '3年', '5年'],
...@@ -96,24 +213,17 @@ const FIELD_RULES = { ...@@ -96,24 +213,17 @@ const FIELD_RULES = {
96 postProcess: (values) => { 213 postProcess: (values) => {
97 // 过滤并标准化 214 // 过滤并标准化
98 const normalized = values 215 const normalized = values
99 - .map(v => v.trim()) 216 + .map(v => normalizePaymentPeriod(v))
100 - // 排除包含"投保"等无效关键字 217 + .filter(v => v && isValidPaymentPeriod(v))
101 - .filter(v => v && !v.includes('投保') && !v.includes('年龄') && !v.includes('年齡'))
102 - .filter(v => v.includes('年') || v.includes('整付'))
103 - .map(v => {
104 - // 提取数字+年格式
105 - const match = v.match(/(\d+)\s*年|整付/i)
106 - if (match) {
107 - return match[0].includes('整付') ? '整付' : `${match[1]}年`
108 - }
109 - return v
110 - })
111 218
112 - // 去重、排序 219 + // 去重、排序(整付放最前,其他按数字排序)
113 return [...new Set(normalized)].sort((a, b) => { 220 return [...new Set(normalized)].sort((a, b) => {
114 if (a === '整付') return -1 221 if (a === '整付') return -1
115 if (b === '整付') return 1 222 if (b === '整付') return 1
116 - return parseInt(a) - parseInt(b) 223 + // 提取数字进行排序
224 + const numA = parseInt(a.match(/\d+/)?.[0] || '999')
225 + const numB = parseInt(b.match(/\d+/)?.[0] || '999')
226 + return numA - numB
117 }) 227 })
118 } 228 }
119 }, 229 },
...@@ -252,6 +362,11 @@ function extractField(content, fieldName) { ...@@ -252,6 +362,11 @@ function extractField(content, fieldName) {
252 patternDesc = `content_match(${pattern.rules.length} rules)` 362 patternDesc = `content_match(${pattern.rules.length} rules)`
253 break 363 break
254 364
365 + case 'title_match':
366 + match = matchByTitle(content, pattern.rules)
367 + patternDesc = `title_match(${pattern.rules.length} rules)`
368 + break
369 +
255 case 'count_match': 370 case 'count_match':
256 match = matchByCount(content, pattern.rules) 371 match = matchByCount(content, pattern.rules)
257 patternDesc = `count_match(${pattern.rules.length} rules)` 372 patternDesc = `count_match(${pattern.rules.length} rules)`
...@@ -276,6 +391,11 @@ function extractField(content, fieldName) { ...@@ -276,6 +391,11 @@ function extractField(content, fieldName) {
276 match = extractRange(content, pattern.pattern) 391 match = extractRange(content, pattern.pattern)
277 patternDesc = `range_extract` 392 patternDesc = `range_extract`
278 break 393 break
394 +
395 + case 'options_extract':
396 + match = extractOptionsFields(content, pattern.keyword)
397 + patternDesc = `options_extract(${pattern.keyword})`
398 + break
279 } 399 }
280 } else if (pattern instanceof RegExp) { 400 } else if (pattern instanceof RegExp) {
281 // 正则表达式匹配 401 // 正则表达式匹配
...@@ -331,6 +451,120 @@ function matchByContent(content, rules) { ...@@ -331,6 +451,120 @@ function matchByContent(content, rules) {
331 } 451 }
332 452
333 /** 453 /**
454 + * 通过标题模式匹配
455 + *
456 + * @description 从文档标题或产品名称行中匹配产品类型
457 + * @param {string} content - 文档内容
458 + * @param {Array} rules - 匹配规则数组
459 + * @returns {string|null} 匹配的值
460 + */
461 +function matchByTitle(content, rules) {
462 + // 提取可能的产品标题行(通常在文档开头)
463 + const lines = content.split('\n').slice(0, 20) // 只检查前20行
464 + const titleLines = lines.filter(line => {
465 + const trimmed = line.trim()
466 + // 标题行通常包含产品代码、产品名称等
467 + return trimmed.length > 5 && trimmed.length < 100 &&
468 + (/[A-Z]{2,4}\d?/.test(trimmed) ||
469 + /計劃|计划|保障|保险|壽險|壽险/.test(trimmed))
470 + })
471 +
472 + // 合并标题行进行匹配
473 + const titleText = titleLines.join('\n')
474 +
475 + for (const rule of rules) {
476 + if (rule.pattern && rule.pattern.test(titleText)) {
477 + return rule.value
478 + }
479 + }
480 +
481 + return null
482 +}
483 +
484 +/**
485 + * 从"选项"段落提取字段
486 + *
487 + * @description 识别 "XXX選項:" 或 "XXX选项:" 格式的段落,提取其中的字段列表
488 + * 示例:
489 + * - "基本人壽保障選項:" 之后的性别、年龄等字段
490 + * - "提取選項:" 之后的提取方式
491 + * @param {string} content - 文档内容
492 + * @param {string} optionKeyword - 选项关键词(如 "基本人壽保障選項")
493 + * @returns {Array<{name: string, description: string}>|null} 提取的字段列表
494 + */
495 +function extractOptionsFields(content, optionKeyword) {
496 + // 匹配 "XXX選項:" 或 "XXX选项:" 格式
497 + const pattern = new RegExp(`${optionKeyword}[::\\s]*\\n([\\s\\S]*?)(?=\\n\\n|\\n[A-Z]|\\n\\d+\\.\\s|$)`, 'gm')
498 + const match = content.match(pattern)
499 +
500 + if (!match) return null
501 +
502 + const optionContent = match[0]
503 + const fields = []
504 +
505 + // 按行分割,提取字段名和描述
506 + const lines = optionContent.split('\n').slice(1) // 跳过标题行
507 +
508 + for (const line of lines) {
509 + const trimmed = line.trim()
510 + if (!trimmed) continue
511 +
512 + // 常见字段格式:
513 + // 1. "性別" - 纯字段名
514 + // 2. "年齡 (1-75岁)" - 字段名 + 范围说明
515 + // 3. "- 出生年月日" - 列表格式
516 + // 4. "提取金额: 指定金额" - 字段名 + 描述
517 +
518 + // 提取字段名(去除列表符号和说明)
519 + const fieldMatch = trimmed.match(/^(?:[-•·]\s*)?([^::((]+)/)
520 + if (fieldMatch) {
521 + const fieldName = fieldMatch[1].trim()
522 + if (fieldName && fieldName.length > 0 && fieldName.length < 20) {
523 + fields.push({
524 + name: fieldName,
525 + description: trimmed // 保留完整描述
526 + })
527 + }
528 + }
529 + }
530 +
531 + return fields.length > 0 ? fields : null
532 +}
533 +
534 +/**
535 + * 查找所有"选项"段落
536 + *
537 + * @description 扫描文档中所有包含"選項"或"选项"的段落标题
538 + * @param {string} content - 文档内容
539 + * @returns {Array<{keyword: string, startIndex: number, fields: Array}>} 选项段落列表
540 + */
541 +function findAllOptionSections(content) {
542 + const sections = []
543 +
544 + // 匹配所有 "XXX選項:" 或 "XXX选项:" 格式
545 + const pattern = /([^\n]*(?:選項|选项|選是)[::]?)/gm
546 + let match
547 +
548 + while ((match = pattern.exec(content)) !== null) {
549 + const keyword = match[1].trim()
550 + const startIndex = match.index
551 +
552 + // 提取该选项下的字段
553 + const fields = extractOptionsFields(content, keyword.replace(/[::]/g, ''))
554 +
555 + if (fields && fields.length > 0) {
556 + sections.push({
557 + keyword,
558 + startIndex,
559 + fields
560 + })
561 + }
562 + }
563 +
564 + return sections
565 +}
566 +
567 +/**
334 * 通过统计匹配内容 568 * 通过统计匹配内容
335 */ 569 */
336 function matchByCount(content, rules) { 570 function matchByCount(content, rules) {
......
1 +# Scripts 目录整理报告
2 +
3 +> 整理日期: 2026-02-15
4 +
5 +## ✅ 整理完成
6 +
7 +### 新目录结构
8 +
9 +```
10 +scripts/
11 +├── README.md # 主说明文档
12 +├── CLAUDE.md # Claude Code 指南(待处理)
13 +
14 +├── api-generator/ # API 代码生成工具
15 +│ ├── README.md # 使用说明
16 +│ ├── GUIDE.md # 详细指南
17 +│ ├── generateApiFromOpenAPI.js # 主脚本 ⭐
18 +│ ├── apiDiff.js # API 对比工具
19 +│ └── test-generate.js # 测试脚本
20 +
21 +├── doc-parser/ # 文档解析工具
22 +│ ├── README.md # 使用说明
23 +│ ├── QUICKSTART.md # 快速开始
24 +│ ├── .env.example # 环境变量示例
25 +│ ├── parse-docs.js # 主脚本 ⭐
26 +│ ├── parse-config.js # 配置文件
27 +│ ├── smart-field-extractor.js # 字段提取器
28 +│ ├── product-splitter.js # 产品分割器
29 +│ └── parse-docs.test.js # 测试文件
30 +
31 +└── changelog/ # CHANGELOG 管理工具
32 + ├── README.md # 使用说明
33 + ├── check-changelog.sh # 漏记检查 ⭐
34 + └── archive-changelog.sh # 归档脚本
35 +```
36 +
37 +### npm scripts 已更新
38 +
39 +| 命令 | 新路径 |
40 +|------|--------|
41 +| `pnpm api:generate` | `scripts/api-generator/generateApiFromOpenAPI.js` |
42 +| `pnpm parse:docs` | `scripts/doc-parser/parse-docs.js` |
43 +| `pnpm changelog:check` | `scripts/changelog/check-changelog.sh` |
44 +
45 +---
46 +
47 +## ⚠️ 未使用文件列表
48 +
49 +以下文件**没有在 package.json 或其他脚本中直接调用**,请判断是否需要保留:
50 +
51 +### 1. `scripts/changelog/archive-changelog.sh`
52 +
53 +**状态**: 🟡 未直接引用
54 +**功能**: CHANGELOG 归档脚本,当记录超过 20 条时自动归档
55 +**使用方式**: 手动运行 `./scripts/changelog/archive-changelog.sh`
56 +**建议**:
57 +- [ ] 保留 - 作为手动维护工具
58 +- [ ] 删除 - 不再需要归档功能
59 +
60 +---
61 +
62 +### 2. `scripts/api-generator/test-generate.js`
63 +
64 +**状态**: 🟡 未直接引用
65 +**功能**: 测试生成的 API 文件是否正确
66 +**使用方式**: 手动运行 `node scripts/api-generator/test-generate.js`
67 +**建议**:
68 +- [ ] 保留 - 作为开发调试工具
69 +- [ ] 删除 - 已有其他测试方式
70 +
71 +---
72 +
73 +### 3. `scripts/CLAUDE.md`
74 +
75 +**状态**: 🟡 需要评估
76 +**功能**: 给 Claude Code 的说明文档
77 +**位置**: 当前在 scripts 根目录
78 +**建议**:
79 +- [ ] 保留在当前位置 - 作为 Claude 指南
80 +- [ ] 移动到 doc-parser 目录 - 因为主要与文档解析相关
81 +- [ ] 删除 - 内容已整合到其他文档
82 +
83 +---
84 +
85 +## 📝 整理后的改进
86 +
87 +1. **✅ 功能分组清晰** - 按用途分为 3 个子目录
88 +2. **✅ 每个子目录有 README** - 包含使用说明和文件介绍
89 +3. **✅ 路径引用已更新** - package.json 已同步更新
90 +4. **✅ 单个文件已收录** - 所有脚本都在对应的功能目录中
91 +
92 +---
93 +
94 +## 🔧 后续操作建议
95 +
96 +1. **确认未使用文件** - 对上述 3 个文件做出保留/删除决定
97 +2. **更新 CLAUDE.md** - 如果项目根目录的 CLAUDE.md 已包含相关内容,可以删除 scripts/CLAUDE.md
98 +3. **测试脚本** - 运行 `pnpm api:generate``pnpm parse:docs` 确保路径更新正确
...@@ -5,10 +5,16 @@ ...@@ -5,10 +5,16 @@
5 * @module composables/useFieldDependencies 5 * @module composables/useFieldDependencies
6 * @author Claude Code 6 * @author Claude Code
7 * @created 2026-02-14 7 * @created 2026-02-14
8 + * @updated 2026-02-15 - 集成新的条件评估引擎,支持复杂条件
8 */ 9 */
9 10
10 -import { computed, reactive, isRef } from 'vue' 11 +import { computed, reactive, isRef, watch } from 'vue'
11 import { PLAN_FIELD_DEFINITIONS } from '@/config/plan-fields' 12 import { PLAN_FIELD_DEFINITIONS } from '@/config/plan-fields'
13 +import {
14 + evaluateCondition,
15 + getConditionDependencies,
16 + convertToNewFormat
17 +} from '@/config/plan-conditions'
12 18
13 /** 19 /**
14 * �测循环依赖 20 * �测循环依赖
...@@ -88,6 +94,7 @@ export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEF ...@@ -88,6 +94,7 @@ export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEF
88 /** 94 /**
89 * 检查字段是否应该显示 95 * 检查字段是否应该显示
90 * 96 *
97 + * @description 使用新的条件评估引擎,支持复杂条件(AND/OR/NOT/嵌套)
91 * @param {string} fieldKey - 字段键名 98 * @param {string} fieldKey - 字段键名
92 * @returns {boolean} 是否显示 99 * @returns {boolean} 是否显示
93 */ 100 */
...@@ -96,28 +103,16 @@ export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEF ...@@ -96,28 +103,16 @@ export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEF
96 const definition = definitions[fieldKey] 103 const definition = definitions[fieldKey]
97 if (!definition) return false 104 if (!definition) return false
98 105
99 - // 检查是否有 show_when 条件 106 + // 使用新的条件评估引擎评估 show_when
100 if (definition.show_when) { 107 if (definition.show_when) {
101 - const conditions = definition.show_when 108 + // 转换为新格式(向后兼容)
102 - if (Array.isArray(conditions)) { 109 + const normalizedCondition = convertToNewFormat(definition.show_when)
103 - for (const condition of conditions) { 110 + if (!evaluateCondition(normalizedCondition, formData)) {
104 - if (!condition) continue 111 + return false
105 - const currentValue = formData[condition.field]
106 - if (currentValue !== condition.equals) {
107 - return false
108 - }
109 - }
110 - } else if (conditions && typeof conditions === 'object') {
111 - for (const [depKey, expectedValue] of Object.entries(conditions)) {
112 - const currentValue = formData[depKey]
113 - if (currentValue !== expectedValue) {
114 - return false
115 - }
116 - }
117 } 112 }
118 } 113 }
119 114
120 - // 检查是否被依赖字段影响 115 + // 检查是否被依赖字段影响(旧逻辑,保持兼容)
121 for (const [key, def] of Object.entries(definitions)) { 116 for (const [key, def] of Object.entries(definitions)) {
122 if (def.affects?.includes(fieldKey)) { 117 if (def.affects?.includes(fieldKey)) {
123 // 依赖字段必须为 true 才显示 118 // 依赖字段必须为 true 才显示
...@@ -198,6 +193,88 @@ export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEF ...@@ -198,6 +193,88 @@ export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEF
198 // 初始化 193 // 初始化
199 initFieldStates() 194 initFieldStates()
200 195
196 + /**
197 + * 清理隐藏字段的值
198 + *
199 + * @description 当字段隐藏时,根据 clear_when_hidden 配置决定是否清空值
200 + * @param {string} fieldKey - 字段键名
201 + * @param {Object} definition - 字段定义
202 + */
203 + function clearHiddenFieldValue(fieldKey, definition) {
204 + // 默认行为:隐藏时不清空(保持向后兼容)
205 + const clearConfig = definition.clear_when_hidden
206 +
207 + // false 或 undefined:不清空
208 + if (clearConfig === false || clearConfig === undefined) {
209 + return
210 + }
211 +
212 + // true:清空为 undefined
213 + if (clearConfig === true) {
214 + formData[fieldKey] = undefined
215 + return
216 + }
217 +
218 + // null:设置为 null
219 + if (clearConfig === null) {
220 + formData[fieldKey] = null
221 + return
222 + }
223 +
224 + // 对象配置
225 + if (typeof clearConfig === 'object') {
226 + // 清空自身
227 + if (clearConfig.clear_self !== false) {
228 + formData[fieldKey] = undefined
229 + }
230 +
231 + // 级联清空依赖字段
232 + if (Array.isArray(clearConfig.clear_dependents)) {
233 + for (const depKey of clearConfig.clear_dependents) {
234 + if (formData[depKey] !== undefined) {
235 + formData[depKey] = undefined
236 + }
237 + }
238 + }
239 + }
240 + }
241 +
242 + /**
243 + * 更新字段可见性并处理隐藏字段的清理
244 + *
245 + * @param {string} fieldKey - 字段键名
246 + * @param {string} triggerFieldKey - 触发更新的字段键名(可选)
247 + */
248 + function updateFieldVisibility(fieldKey, triggerFieldKey = null) {
249 + const definitions = getFieldDefinitions()
250 + const definition = definitions[fieldKey]
251 + if (!definition) return
252 +
253 + const wasVisible = fieldVisibility[fieldKey]
254 + const isVisible = isFieldVisible(fieldKey)
255 + fieldVisibility[fieldKey] = isVisible
256 +
257 + // 如果从可见变为不可见,且配置了清理规则,则清理字段值
258 + if (wasVisible && !isVisible) {
259 + clearHiddenFieldValue(fieldKey, definition)
260 + }
261 +
262 + // 更新启用状态
263 + fieldEnabled[fieldKey] = isFieldEnabled(fieldKey)
264 + }
265 +
266 + /**
267 + * 批量更新所有字段的可见性
268 + *
269 + * @description 通常在表单值变化后调用,重新计算所有字段的可见性
270 + */
271 + function refreshAllVisibility() {
272 + const definitions = getFieldDefinitions()
273 + for (const key of Object.keys(definitions)) {
274 + updateFieldVisibility(key)
275 + }
276 + }
277 +
201 return { 278 return {
202 // 状态 279 // 状态
203 fieldVisibility, 280 fieldVisibility,
...@@ -208,6 +285,86 @@ export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEF ...@@ -208,6 +285,86 @@ export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEF
208 isFieldVisible, 285 isFieldVisible,
209 isFieldEnabled, 286 isFieldEnabled,
210 updateFieldValue, 287 updateFieldValue,
211 - initFieldStates 288 + initFieldStates,
289 + updateFieldVisibility,
290 + refreshAllVisibility
291 + }
292 +}
293 +
294 +/**
295 + * 过滤隐藏字段(用于提交前)
296 + *
297 + * @description 过滤掉当前不可见的字段,避免提交脏数据
298 + * @param {Object} formData - 完整的表单数据
299 + * @param {string[]} visibleFields - 可见字段列表
300 + * @param {Object} options - 配置选项
301 + * @param {string[]} options.alwaysInclude - 始终包含的字段(即使不可见)
302 + * @returns {Object} 过滤后的表单数据
303 + *
304 + * @example
305 + * const filteredData = filterHiddenFields(formData, visibleFields.value)
306 + * await submitAPI(filteredData)
307 + */
308 +export function filterHiddenFields(formData, visibleFields, options = {}) {
309 + const { alwaysInclude = [] } = options
310 + const filtered = {}
311 +
312 + for (const [key, value] of Object.entries(formData)) {
313 + // 可见字段始终包含
314 + if (visibleFields.includes(key)) {
315 + filtered[key] = value
316 + continue
317 + }
318 +
319 + // 配置为始终包含的字段
320 + if (alwaysInclude.includes(key)) {
321 + filtered[key] = value
322 + }
323 +
324 + // 其他不可见字段被过滤掉
212 } 325 }
326 +
327 + return filtered
328 +}
329 +
330 +/**
331 + * 获取字段的条件依赖关系(用于调试和可视化)
332 + *
333 + * @description 返回字段的条件依赖图
334 + * @param {Object} fieldDefinitions - 字段定义
335 + * @returns {Map<string, Set<string>>} 字段到依赖字段的映射
336 + */
337 +export function getFieldDependencyGraph(fieldDefinitions = PLAN_FIELD_DEFINITIONS) {
338 + const graph = new Map()
339 +
340 + for (const [key, definition] of Object.entries(fieldDefinitions)) {
341 + const deps = new Set()
342 +
343 + // 从 show_when 提取依赖
344 + if (definition.show_when) {
345 + const conditionDeps = getConditionDependencies(definition.show_when)
346 + conditionDeps.forEach(d => deps.add(d))
347 + }
348 +
349 + // 从 affects 提取反向依赖
350 + if (definition.affects) {
351 + for (const affectedKey of definition.affects) {
352 + if (!graph.has(affectedKey)) {
353 + graph.set(affectedKey, new Set())
354 + }
355 + graph.get(affectedKey).add(key)
356 + }
357 + }
358 +
359 + // 从 depends_on 提取
360 + if (definition.depends_on) {
361 + deps.add(definition.depends_on)
362 + }
363 +
364 + if (deps.size > 0) {
365 + graph.set(key, deps)
366 + }
367 + }
368 +
369 + return graph
213 } 370 }
......
1 +/**
2 + * 计划书字段条件引擎测试
3 + *
4 + * @description 测试条件操作符和评估逻辑
5 + * @module config/__tests__/plan-conditions.test
6 + */
7 +
8 +import { describe, it, expect } from 'vitest'
9 +import {
10 + CONDITION_OPERATORS,
11 + normalizeOperator,
12 + evaluateSingleCondition,
13 + evaluateAnd,
14 + evaluateOr,
15 + evaluateNot,
16 + evaluateCondition,
17 + getConditionDependencies,
18 + convertToNewFormat
19 +} from '../plan-conditions'
20 +
21 +describe('CONDITION_OPERATORS', () => {
22 + describe('比较操作符', () => {
23 + it('eq - 等于', () => {
24 + expect(CONDITION_OPERATORS.eq('是', '是')).toBe(true)
25 + expect(CONDITION_OPERATORS.eq('是', '否')).toBe(false)
26 + expect(CONDITION_OPERATORS.eq(1, 1)).toBe(true)
27 + expect(CONDITION_OPERATORS.eq(1, '1')).toBe(false) // 严格相等
28 + })
29 +
30 + it('ne - 不等于', () => {
31 + expect(CONDITION_OPERATORS.ne('是', '否')).toBe(true)
32 + expect(CONDITION_OPERATORS.ne('是', '是')).toBe(false)
33 + })
34 +
35 + it('gt - 大于', () => {
36 + expect(CONDITION_OPERATORS.gt(30, 18)).toBe(true)
37 + expect(CONDITION_OPERATORS.gt(18, 18)).toBe(false)
38 + expect(CONDITION_OPERATORS.gt(10, 18)).toBe(false)
39 + })
40 +
41 + it('gte - 大于等于', () => {
42 + expect(CONDITION_OPERATORS.gte(30, 18)).toBe(true)
43 + expect(CONDITION_OPERATORS.gte(18, 18)).toBe(true)
44 + expect(CONDITION_OPERATORS.gte(10, 18)).toBe(false)
45 + })
46 +
47 + it('lt - 小于', () => {
48 + expect(CONDITION_OPERATORS.lt(10, 18)).toBe(true)
49 + expect(CONDITION_OPERATORS.lt(18, 18)).toBe(false)
50 + expect(CONDITION_OPERATORS.lt(30, 18)).toBe(false)
51 + })
52 +
53 + it('lte - 小于等于', () => {
54 + expect(CONDITION_OPERATORS.lte(10, 18)).toBe(true)
55 + expect(CONDITION_OPERATORS.lte(18, 18)).toBe(true)
56 + expect(CONDITION_OPERATORS.lte(30, 18)).toBe(false)
57 + })
58 + })
59 +
60 + describe('集合操作符', () => {
61 + it('in - 包含于', () => {
62 + expect(CONDITION_OPERATORS.in('A', ['A', 'B', 'C'])).toBe(true)
63 + expect(CONDITION_OPERATORS.in('D', ['A', 'B', 'C'])).toBe(false)
64 + expect(CONDITION_OPERATORS.in('A', 'not-array')).toBe(false)
65 + })
66 +
67 + it('nin - 不包含于', () => {
68 + expect(CONDITION_OPERATORS.nin('D', ['A', 'B', 'C'])).toBe(true)
69 + expect(CONDITION_OPERATORS.nin('A', ['A', 'B', 'C'])).toBe(false)
70 + })
71 + })
72 +
73 + describe('字符串操作符', () => {
74 + it('contains - 包含子串', () => {
75 + expect(CONDITION_OPERATORS.contains('Hello World', 'World')).toBe(true)
76 + expect(CONDITION_OPERATORS.contains('Hello World', 'world')).toBe(false) // 大小写敏感
77 + expect(CONDITION_OPERATORS.contains(null, 'test')).toBe(false)
78 + })
79 +
80 + it('startsWith - 前缀匹配', () => {
81 + expect(CONDITION_OPERATORS.startsWith('Hello', 'Hel')).toBe(true)
82 + expect(CONDITION_OPERATORS.startsWith('Hello', 'hel')).toBe(false)
83 + })
84 +
85 + it('endsWith - 后缀匹配', () => {
86 + expect(CONDITION_OPERATORS.endsWith('Hello', 'llo')).toBe(true)
87 + expect(CONDITION_OPERATORS.endsWith('Hello', 'LLO')).toBe(false)
88 + })
89 +
90 + it('matches - 正则匹配', () => {
91 + expect(CONDITION_OPERATORS.matches('ABC123', '^[A-Z]+\\d+$')).toBe(true)
92 + expect(CONDITION_OPERATORS.matches('abc', '^[A-Z]+$')).toBe(false)
93 + expect(CONDITION_OPERATORS.matches('test', '[invalid')).toBe(false) // 无效正则
94 + })
95 + })
96 +
97 + describe('布尔/空值操作符', () => {
98 + it('truthy - 真值', () => {
99 + expect(CONDITION_OPERATORS.truthy(true)).toBe(true)
100 + expect(CONDITION_OPERATORS.truthy(1)).toBe(true)
101 + expect(CONDITION_OPERATORS.truthy('text')).toBe(true)
102 + expect(CONDITION_OPERATORS.truthy(false)).toBe(false)
103 + expect(CONDITION_OPERATORS.truthy(0)).toBe(false)
104 + expect(CONDITION_OPERATORS.truthy('')).toBe(false)
105 + expect(CONDITION_OPERATORS.truthy(null)).toBe(false)
106 + })
107 +
108 + it('falsy - 假值', () => {
109 + expect(CONDITION_OPERATORS.falsy(false)).toBe(true)
110 + expect(CONDITION_OPERATORS.falsy(0)).toBe(true)
111 + expect(CONDITION_OPERATORS.falsy('')).toBe(true)
112 + expect(CONDITION_OPERATORS.falsy(null)).toBe(true)
113 + expect(CONDITION_OPERATORS.falsy(true)).toBe(false)
114 + })
115 +
116 + it('empty - 空值', () => {
117 + expect(CONDITION_OPERATORS.empty('')).toBe(true)
118 + expect(CONDITION_OPERATORS.empty(null)).toBe(true)
119 + expect(CONDITION_OPERATORS.empty(undefined)).toBe(true)
120 + expect(CONDITION_OPERATORS.empty(0)).toBe(false)
121 + expect(CONDITION_OPERATORS.empty('text')).toBe(false)
122 + })
123 +
124 + it('notEmpty - 非空', () => {
125 + expect(CONDITION_OPERATORS.notEmpty('text')).toBe(true)
126 + expect(CONDITION_OPERATORS.notEmpty(0)).toBe(true)
127 + expect(CONDITION_OPERATORS.notEmpty('')).toBe(false)
128 + expect(CONDITION_OPERATORS.notEmpty(null)).toBe(false)
129 + })
130 +
131 + it('emptyArray - 空数组', () => {
132 + expect(CONDITION_OPERATORS.emptyArray([])).toBe(true)
133 + expect(CONDITION_OPERATORS.emptyArray(null)).toBe(true)
134 + expect(CONDITION_OPERATORS.emptyArray([1])).toBe(false)
135 + })
136 +
137 + it('notEmptyArray - 非空数组', () => {
138 + expect(CONDITION_OPERATORS.notEmptyArray([1])).toBe(true)
139 + expect(CONDITION_OPERATORS.notEmptyArray([])).toBe(false)
140 + })
141 + })
142 +})
143 +
144 +describe('normalizeOperator', () => {
145 + it('返回标准操作符', () => {
146 + expect(normalizeOperator('eq')).toBe('eq')
147 + expect(normalizeOperator('gt')).toBe('gt')
148 + })
149 +
150 + it('处理别名', () => {
151 + expect(normalizeOperator('equals')).toBe('eq')
152 + expect(normalizeOperator('==')).toBe('eq')
153 + expect(normalizeOperator('===')).toBe('eq')
154 + expect(normalizeOperator('!=')).toBe('ne')
155 + expect(normalizeOperator('>')).toBe('gt')
156 + expect(normalizeOperator('>=')).toBe('gte')
157 + })
158 +
159 + it('未知操作符返回 null', () => {
160 + expect(normalizeOperator('unknown')).toBe(null)
161 + })
162 +})
163 +
164 +describe('evaluateSingleCondition', () => {
165 + const formData = { smoker: '是', age: 30, name: 'John' }
166 +
167 + it('评估等于条件', () => {
168 + expect(evaluateSingleCondition(
169 + { field: 'smoker', op: 'eq', value: '是' },
170 + formData
171 + )).toBe(true)
172 +
173 + expect(evaluateSingleCondition(
174 + { field: 'smoker', op: 'eq', value: '否' },
175 + formData
176 + )).toBe(false)
177 + })
178 +
179 + it('评估大于条件', () => {
180 + expect(evaluateSingleCondition(
181 + { field: 'age', op: 'gt', value: 18 },
182 + formData
183 + )).toBe(true)
184 + })
185 +
186 + it('评估包含条件', () => {
187 + expect(evaluateSingleCondition(
188 + { field: 'name', op: 'contains', value: 'John' },
189 + formData
190 + )).toBe(true)
191 + })
192 +
193 + it('缺少字段或操作符返回 false', () => {
194 + expect(evaluateSingleCondition({ op: 'eq', value: 'x' }, formData)).toBe(false)
195 + expect(evaluateSingleCondition({ field: 'x', value: 'y' }, formData)).toBe(false)
196 + })
197 +
198 + it('未知操作符返回 false', () => {
199 + expect(evaluateSingleCondition(
200 + { field: 'smoker', op: 'unknown', value: '是' },
201 + formData
202 + )).toBe(false)
203 + })
204 +})
205 +
206 +describe('evaluateAnd', () => {
207 + const formData = { smoker: '是', age: 30 }
208 +
209 + it('所有条件满足返回 true', () => {
210 + expect(evaluateAnd([
211 + { field: 'smoker', op: 'eq', value: '是' },
212 + { field: 'age', op: 'gte', value: 18 }
213 + ], formData)).toBe(true)
214 + })
215 +
216 + it('任一条件不满足返回 false', () => {
217 + expect(evaluateAnd([
218 + { field: 'smoker', op: 'eq', value: '是' },
219 + { field: 'age', op: 'gte', value: 50 }
220 + ], formData)).toBe(false)
221 + })
222 +
223 + it('空数组返回 true', () => {
224 + expect(evaluateAnd([], formData)).toBe(true)
225 + })
226 +})
227 +
228 +describe('evaluateOr', () => {
229 + const formData = { smoker: '是', age: 30 }
230 +
231 + it('任一条件满足返回 true', () => {
232 + expect(evaluateOr([
233 + { field: 'smoker', op: 'eq', value: '否' },
234 + { field: 'age', op: 'gte', value: 18 }
235 + ], formData)).toBe(true)
236 + })
237 +
238 + it('所有条件不满足返回 false', () => {
239 + expect(evaluateOr([
240 + { field: 'smoker', op: 'eq', value: '否' },
241 + { field: 'age', op: 'gte', value: 50 }
242 + ], formData)).toBe(false)
243 + })
244 +
245 + it('空数组返回 false', () => {
246 + expect(evaluateOr([], formData)).toBe(false)
247 + })
248 +})
249 +
250 +describe('evaluateNot', () => {
251 + const formData = { smoker: '是' }
252 +
253 + it('条件不满足返回 true', () => {
254 + expect(evaluateNot(
255 + { field: 'smoker', op: 'eq', value: '否' },
256 + formData
257 + )).toBe(true)
258 + })
259 +
260 + it('条件满足返回 false', () => {
261 + expect(evaluateNot(
262 + { field: 'smoker', op: 'eq', value: '是' },
263 + formData
264 + )).toBe(false)
265 + })
266 +})
267 +
268 +describe('evaluateCondition', () => {
269 + const formData = {
270 + smoker: '是',
271 + age: 30,
272 + product_type: 'A',
273 + coverage: 100000,
274 + payment_period: '20年'
275 + }
276 +
277 + it('空条件返回 true', () => {
278 + expect(evaluateCondition(null, formData)).toBe(true)
279 + expect(evaluateCondition(undefined, formData)).toBe(true)
280 + })
281 +
282 + it('简单条件', () => {
283 + expect(evaluateCondition(
284 + { field: 'smoker', op: 'eq', value: '是' },
285 + formData
286 + )).toBe(true)
287 + })
288 +
289 + it('AND 逻辑', () => {
290 + expect(evaluateCondition({
291 + and: [
292 + { field: 'smoker', op: 'eq', value: '是' },
293 + { field: 'age', op: 'gte', value: 30 }
294 + ]
295 + }, formData)).toBe(true)
296 +
297 + expect(evaluateCondition({
298 + and: [
299 + { field: 'smoker', op: 'eq', value: '是' },
300 + { field: 'age', op: 'gt', value: 50 }
301 + ]
302 + }, formData)).toBe(false)
303 + })
304 +
305 + it('OR 逻辑', () => {
306 + expect(evaluateCondition({
307 + or: [
308 + { field: 'smoker', op: 'eq', value: '否' },
309 + { field: 'age', op: 'gte', value: 30 }
310 + ]
311 + }, formData)).toBe(true)
312 +
313 + expect(evaluateCondition({
314 + or: [
315 + { field: 'smoker', op: 'eq', value: '否' },
316 + { field: 'age', op: 'gt', value: 50 }
317 + ]
318 + }, formData)).toBe(false)
319 + })
320 +
321 + it('NOT 逻辑', () => {
322 + expect(evaluateCondition({
323 + not: { field: 'smoker', op: 'eq', value: '否' }
324 + }, formData)).toBe(true)
325 + })
326 +
327 + it('嵌套条件', () => {
328 + // (smoker == '是' OR age > 50) AND product_type in ['A', 'B']
329 + expect(evaluateCondition({
330 + and: [
331 + {
332 + or: [
333 + { field: 'smoker', op: 'eq', value: '是' },
334 + { field: 'age', op: 'gt', value: 50 }
335 + ]
336 + },
337 + { field: 'product_type', op: 'in', value: ['A', 'B'] }
338 + ]
339 + }, formData)).toBe(true)
340 + })
341 +
342 + it('数组默认为 AND', () => {
343 + expect(evaluateCondition([
344 + { field: 'smoker', op: 'eq', value: '是' },
345 + { field: 'age', op: 'gte', value: 30 }
346 + ], formData)).toBe(true)
347 + })
348 +
349 + it('旧格式兼容 - equals', () => {
350 + expect(evaluateCondition(
351 + { field: 'smoker', equals: '是' },
352 + formData
353 + )).toBe(true)
354 + })
355 +
356 + it('旧格式兼容 - 扁平对象', () => {
357 + expect(evaluateCondition(
358 + { smoker: '是', age: 30 },
359 + formData
360 + )).toBe(true)
361 + })
362 +})
363 +
364 +describe('getConditionDependencies', () => {
365 + it('提取简单条件的依赖', () => {
366 + const deps = getConditionDependencies({ field: 'smoker', op: 'eq', value: '是' })
367 + expect(deps.has('smoker')).toBe(true)
368 + expect(deps.size).toBe(1)
369 + })
370 +
371 + it('提取 AND 条件的依赖', () => {
372 + const deps = getConditionDependencies({
373 + and: [
374 + { field: 'smoker', op: 'eq', value: '是' },
375 + { field: 'age', op: 'gte', value: 30 }
376 + ]
377 + })
378 + expect(deps.has('smoker')).toBe(true)
379 + expect(deps.has('age')).toBe(true)
380 + expect(deps.size).toBe(2)
381 + })
382 +
383 + it('提取嵌套条件的依赖', () => {
384 + const deps = getConditionDependencies({
385 + and: [
386 + { field: 'a', op: 'eq', value: 1 },
387 + {
388 + or: [
389 + { field: 'b', op: 'eq', value: 2 },
390 + { field: 'c', op: 'eq', value: 3 }
391 + ]
392 + }
393 + ]
394 + })
395 + expect(deps.has('a')).toBe(true)
396 + expect(deps.has('b')).toBe(true)
397 + expect(deps.has('c')).toBe(true)
398 + expect(deps.size).toBe(3)
399 + })
400 +
401 + it('提取扁平对象的依赖', () => {
402 + const deps = getConditionDependencies({ smoker: '是', age: 30 })
403 + expect(deps.has('smoker')).toBe(true)
404 + expect(deps.has('age')).toBe(true)
405 + })
406 +})
407 +
408 +describe('convertToNewFormat', () => {
409 + it('已经是新格式则直接返回', () => {
410 + const condition = { field: 'smoker', op: 'eq', value: '是' }
411 + expect(convertToNewFormat(condition)).toEqual(condition)
412 + })
413 +
414 + it('转换旧格式 equals', () => {
415 + expect(convertToNewFormat(
416 + { field: 'smoker', equals: '是' }
417 + )).toEqual({ field: 'smoker', op: 'eq', value: '是' })
418 + })
419 +
420 + it('转换旧格式数组为 AND', () => {
421 + expect(convertToNewFormat([
422 + { field: 'a', equals: 'x' },
423 + { field: 'b', equals: 'y' }
424 + ])).toEqual({
425 + and: [
426 + { field: 'a', op: 'eq', value: 'x' },
427 + { field: 'b', op: 'eq', value: 'y' }
428 + ]
429 + })
430 + })
431 +
432 + it('单个条件的数组不包装 AND', () => {
433 + expect(convertToNewFormat([
434 + { field: 'a', equals: 'x' }
435 + ])).toEqual({ field: 'a', op: 'eq', value: 'x' })
436 + })
437 +
438 + it('转换扁平对象', () => {
439 + expect(convertToNewFormat({ smoker: '是' })).toEqual({
440 + field: 'smoker',
441 + op: 'eq',
442 + value: '是'
443 + })
444 + })
445 +
446 + it('转换多字段扁平对象为 AND', () => {
447 + expect(convertToNewFormat({ smoker: '是', age: 30 })).toEqual({
448 + and: [
449 + { field: 'smoker', op: 'eq', value: '是' },
450 + { field: 'age', op: 'eq', value: 30 }
451 + ]
452 + })
453 + })
454 +})
1 +/**
2 + * 计划书字段条件规则引擎
3 + *
4 + * @description 定义条件操作符和评估逻辑,支持复杂的字段显示/隐藏条件
5 + * @module config/plan-conditions
6 + * @author Claude Code
7 + * @created 2026-02-15
8 + * @version 1.0.0
9 + */
10 +
11 +/**
12 + * 条件操作符定义
13 + *
14 + * @description 所有可用的条件比较操作符
15 + * @type {Object<string, Function>}
16 + */
17 +export const CONDITION_OPERATORS = {
18 + // ========== 比较操作 ==========
19 +
20 + /**
21 + * 等于(严格相等)
22 + * @param {*} actual - 实际值
23 + * @param {*} expected - 期望值
24 + * @returns {boolean}
25 + */
26 + eq: (actual, expected) => actual === expected,
27 +
28 + /**
29 + * 不等于
30 + * @param {*} actual - 实际值
31 + * @param {*} expected - 期望值
32 + * @returns {boolean}
33 + */
34 + ne: (actual, expected) => actual !== expected,
35 +
36 + /**
37 + * 大于
38 + * @param {number} actual - 实际值
39 + * @param {number} expected - 期望值
40 + * @returns {boolean}
41 + */
42 + gt: (actual, expected) => {
43 + const num = Number(actual)
44 + const exp = Number(expected)
45 + return !Number.isNaN(num) && !Number.isNaN(exp) && num > exp
46 + },
47 +
48 + /**
49 + * 大于等于
50 + * @param {number} actual - 实际值
51 + * @param {number} expected - 期望值
52 + * @returns {boolean}
53 + */
54 + gte: (actual, expected) => {
55 + const num = Number(actual)
56 + const exp = Number(expected)
57 + return !Number.isNaN(num) && !Number.isNaN(exp) && num >= exp
58 + },
59 +
60 + /**
61 + * 小于
62 + * @param {number} actual - 实际值
63 + * @param {number} expected - 期望值
64 + * @returns {boolean}
65 + */
66 + lt: (actual, expected) => {
67 + const num = Number(actual)
68 + const exp = Number(expected)
69 + return !Number.isNaN(num) && !Number.isNaN(exp) && num < exp
70 + },
71 +
72 + /**
73 + * 小于等于
74 + * @param {number} actual - 实际值
75 + * @param {number} expected - 期望值
76 + * @returns {boolean}
77 + */
78 + lte: (actual, expected) => {
79 + const num = Number(actual)
80 + const exp = Number(expected)
81 + return !Number.isNaN(num) && !Number.isNaN(exp) && num <= exp
82 + },
83 +
84 + // ========== 集合操作 ==========
85 +
86 + /**
87 + * 包含于(值在数组中)
88 + * @param {*} actual - 实际值
89 + * @param {Array} expected - 期望数组
90 + * @returns {boolean}
91 + */
92 + in: (actual, expected) => {
93 + if (!Array.isArray(expected)) return false
94 + return expected.includes(actual)
95 + },
96 +
97 + /**
98 + * 不包含于(值不在数组中)
99 + * @param {*} actual - 实际值
100 + * @param {Array} expected - 期望数组
101 + * @returns {boolean}
102 + */
103 + nin: (actual, expected) => {
104 + if (!Array.isArray(expected)) return true
105 + return !expected.includes(actual)
106 + },
107 +
108 + // ========== 字符串操作 ==========
109 +
110 + /**
111 + * 字符串包含
112 + * @param {string} actual - 实际值
113 + * @param {string} expected - 期望子串
114 + * @returns {boolean}
115 + */
116 + contains: (actual, expected) => {
117 + return String(actual ?? '').includes(String(expected ?? ''))
118 + },
119 +
120 + /**
121 + * 字符串前缀匹配
122 + * @param {string} actual - 实际值
123 + * @param {string} expected - 期望前缀
124 + * @returns {boolean}
125 + */
126 + startsWith: (actual, expected) => {
127 + return String(actual ?? '').startsWith(String(expected ?? ''))
128 + },
129 +
130 + /**
131 + * 字符串后缀匹配
132 + * @param {string} actual - 实际值
133 + * @param {string} expected - 期望后缀
134 + * @returns {boolean}
135 + */
136 + endsWith: (actual, expected) => {
137 + return String(actual ?? '').endsWith(String(expected ?? ''))
138 + },
139 +
140 + /**
141 + * 正则表达式匹配
142 + * @param {string} actual - 实际值
143 + * @param {string|RegExp} expected - 正则表达式
144 + * @returns {boolean}
145 + */
146 + matches: (actual, expected) => {
147 + try {
148 + const regex = expected instanceof RegExp ? expected : new RegExp(expected)
149 + return regex.test(String(actual ?? ''))
150 + } catch {
151 + return false
152 + }
153 + },
154 +
155 + // ========== 布尔/空值操作 ==========
156 +
157 + /**
158 + * 真值检查
159 + * @param {*} actual - 实际值
160 + * @returns {boolean}
161 + */
162 + truthy: (actual) => !!actual,
163 +
164 + /**
165 + * 假值检查
166 + * @param {*} actual - 实际值
167 + * @returns {boolean}
168 + */
169 + falsy: (actual) => !actual,
170 +
171 + /**
172 + * 空值检查(null, undefined, 空字符串)
173 + * @param {*} actual - 实际值
174 + * @returns {boolean}
175 + */
176 + empty: (actual) => actual === '' || actual === null || actual === undefined,
177 +
178 + /**
179 + * 非空检查
180 + * @param {*} actual - 实际值
181 + * @returns {boolean}
182 + */
183 + notEmpty: (actual) => actual !== '' && actual !== null && actual !== undefined,
184 +
185 + /**
186 + * 空数组检查
187 + * @param {*} actual - 实际值
188 + * @returns {boolean}
189 + */
190 + emptyArray: (actual) => !Array.isArray(actual) || actual.length === 0,
191 +
192 + /**
193 + * 非空数组检查
194 + * @param {*} actual - 实际值
195 + * @returns {boolean}
196 + */
197 + notEmptyArray: (actual) => Array.isArray(actual) && actual.length > 0
198 +}
199 +
200 +/**
201 + * 操作符别名映射(向后兼容)
202 + *
203 + * @description 旧格式操作符到新格式的映射
204 + * @type {Object<string, string>}
205 + */
206 +export const OPERATOR_ALIASES = {
207 + equals: 'eq',
208 + '==': 'eq',
209 + '===': 'eq',
210 + '!=': 'ne',
211 + '!==': 'ne',
212 + '>': 'gt',
213 + '>=': 'gte',
214 + '<': 'lt',
215 + '<=': 'lte'
216 +}
217 +
218 +/**
219 + * 解析操作符(支持别名)
220 + *
221 + * @param {string} op - 操作符名称或别名
222 + * @returns {string|null} 标准操作符名称
223 + */
224 +export function normalizeOperator(op) {
225 + if (CONDITION_OPERATORS[op]) return op
226 + if (OPERATOR_ALIASES[op]) return OPERATOR_ALIASES[op]
227 + return null
228 +}
229 +
230 +/**
231 + * 评估单个条件
232 + *
233 + * @description 评估一个简单的条件表达式
234 + * @param {Object} condition - 条件对象
235 + * @param {string} condition.field - 字段名
236 + * @param {string} condition.op - 操作符
237 + * @param {*} condition.value - 期望值
238 + * @param {Object} formData - 表单数据
239 + * @returns {boolean} 条件是否满足
240 + *
241 + * @example
242 + * evaluateSingleCondition(
243 + * { field: 'smoker', op: 'eq', value: '是' },
244 + * { smoker: '是', age: 30 }
245 + * ) // true
246 + */
247 +export function evaluateSingleCondition(condition, formData) {
248 + const { field, op, value } = condition
249 +
250 + if (!field || !op) {
251 + console.warn('[plan-conditions] 条件缺少 field 或 op:', condition)
252 + return false
253 + }
254 +
255 + const normalizedOp = normalizeOperator(op)
256 + if (!normalizedOp) {
257 + console.warn(`[plan-conditions] 未知操作符: ${op}`)
258 + return false
259 + }
260 +
261 + const operator = CONDITION_OPERATORS[normalizedOp]
262 + const actualValue = formData[field]
263 +
264 + try {
265 + return operator(actualValue, value)
266 + } catch (err) {
267 + console.error(`[plan-conditions] 条件评估失败:`, err)
268 + return false
269 + }
270 +}
271 +
272 +/**
273 + * 评估 AND 逻辑
274 + *
275 + * @param {Array} conditions - 条件数组
276 + * @param {Object} formData - 表单数据
277 + * @returns {boolean} 所有条件是否都满足
278 + */
279 +export function evaluateAnd(conditions, formData) {
280 + if (!Array.isArray(conditions) || conditions.length === 0) return true
281 + return conditions.every(c => evaluateCondition(c, formData))
282 +}
283 +
284 +/**
285 + * 评估 OR 逻辑
286 + *
287 + * @param {Array} conditions - 条件数组
288 + * @param {Object} formData - 表单数据
289 + * @returns {boolean} 是否有任一条件满足
290 + */
291 +export function evaluateOr(conditions, formData) {
292 + if (!Array.isArray(conditions) || conditions.length === 0) return false
293 + return conditions.some(c => evaluateCondition(c, formData))
294 +}
295 +
296 +/**
297 + * 评估 NOT 逻辑
298 + *
299 + * @param {Object} condition - 条件对象
300 + * @param {Object} formData - 表单数据
301 + * @returns {boolean} 条件是否不满足
302 + */
303 +export function evaluateNot(condition, formData) {
304 + if (!condition) return false
305 + return !evaluateCondition(condition, formData)
306 +}
307 +
308 +/**
309 + * 评估条件(主入口)
310 + *
311 + * @description 评估任意条件表达式,支持简单条件和逻辑组合
312 + * @param {Object|Array} condition - 条件表达式
313 + * @param {Object} formData - 表单数据
314 + * @returns {boolean} 条件是否满足
315 + *
316 + * @example
317 + * // 简单条件
318 + * evaluateCondition({ field: 'smoker', op: 'eq', value: '是' }, formData)
319 + *
320 + * @example
321 + * // AND 条件
322 + * evaluateCondition({
323 + * and: [
324 + * { field: 'smoker', op: 'eq', value: '是' },
325 + * { field: 'age', op: 'gte', value: 30 }
326 + * ]
327 + * }, formData)
328 + *
329 + * @example
330 + * // OR 条件
331 + * evaluateCondition({
332 + * or: [
333 + * { field: 'smoker', op: 'eq', value: '是' },
334 + * { field: 'age', op: 'gt', value: 50 }
335 + * ]
336 + * }, formData)
337 + *
338 + * @example
339 + * // 嵌套条件
340 + * evaluateCondition({
341 + * and: [
342 + * { field: 'type', op: 'in', value: ['A', 'B'] },
343 + * {
344 + * or: [
345 + * { field: 'coverage', op: 'gte', value: 100000 },
346 + * { field: 'period', op: 'eq', value: '20年' }
347 + * ]
348 + * }
349 + * ]
350 + * }, formData)
351 + */
352 +export function evaluateCondition(condition, formData) {
353 + // 空条件默认为 true
354 + if (!condition) return true
355 +
356 + // 数组条件:默认为 AND 逻辑
357 + if (Array.isArray(condition)) {
358 + return evaluateAnd(condition, formData)
359 + }
360 +
361 + // 对象条件
362 + if (typeof condition === 'object') {
363 + // AND 逻辑
364 + if (condition.and) {
365 + return evaluateAnd(condition.and, formData)
366 + }
367 +
368 + // OR 逻辑
369 + if (condition.or) {
370 + return evaluateOr(condition.or, formData)
371 + }
372 +
373 + // NOT 逻辑
374 + if (condition.not) {
375 + return evaluateNot(condition.not, formData)
376 + }
377 +
378 + // 简单条件(包含 field 和 op)
379 + if (condition.field && condition.op) {
380 + return evaluateSingleCondition(condition, formData)
381 + }
382 +
383 + // 旧格式兼容:{ field: 'xxx', equals: 'yyy' }
384 + if (condition.field && condition.equals !== undefined) {
385 + return evaluateSingleCondition(
386 + { field: condition.field, op: 'eq', value: condition.equals },
387 + formData
388 + )
389 + }
390 +
391 + // 旧格式兼容:{ field: 'xxx', not_equals: 'yyy' }
392 + if (condition.field && condition.not_equals !== undefined) {
393 + return evaluateSingleCondition(
394 + { field: condition.field, op: 'ne', value: condition.not_equals },
395 + formData
396 + )
397 + }
398 +
399 + // 扁平条件对象:{ smoker: '是', age: 30 } → 所有条件 AND
400 + const keys = Object.keys(condition)
401 + if (keys.length > 0 && !keys.some(k => ['and', 'or', 'not', 'field', 'op'].includes(k))) {
402 + return keys.every(field => {
403 + const expectedValue = condition[field]
404 + const actualValue = formData[field]
405 + return actualValue === expectedValue
406 + })
407 + }
408 + }
409 +
410 + console.warn('[plan-conditions] 无法识别的条件格式:', condition)
411 + return false
412 +}
413 +
414 +/**
415 + * 获取条件依赖的字段列表
416 + *
417 + * @description 从条件表达式中提取所有依赖的字段名
418 + * @param {Object|Array} condition - 条件表达式
419 + * @returns {Set<string>} 依赖的字段名集合
420 + *
421 + * @example
422 + * getConditionDependencies({ and: [{ field: 'a', op: 'eq', value: 1 }, { field: 'b', op: 'eq', value: 2 }] })
423 + * // Set { 'a', 'b' }
424 + */
425 +export function getConditionDependencies(condition) {
426 + const deps = new Set()
427 +
428 + if (!condition) return deps
429 +
430 + // 数组条件
431 + if (Array.isArray(condition)) {
432 + condition.forEach(c => {
433 + const subDeps = getConditionDependencies(c)
434 + subDeps.forEach(d => deps.add(d))
435 + })
436 + return deps
437 + }
438 +
439 + if (typeof condition === 'object') {
440 + // 简单条件
441 + if (condition.field) {
442 + deps.add(condition.field)
443 + }
444 +
445 + // 扁平条件对象
446 + const keys = Object.keys(condition)
447 + if (keys.length > 0 && !keys.some(k => ['and', 'or', 'not', 'field', 'op', 'equals'].includes(k))) {
448 + keys.forEach(k => deps.add(k))
449 + }
450 +
451 + // 递归处理逻辑组合
452 + if (condition.and) {
453 + condition.and.forEach(c => {
454 + const subDeps = getConditionDependencies(c)
455 + subDeps.forEach(d => deps.add(d))
456 + })
457 + }
458 + if (condition.or) {
459 + condition.or.forEach(c => {
460 + const subDeps = getConditionDependencies(c)
461 + subDeps.forEach(d => deps.add(d))
462 + })
463 + }
464 + if (condition.not) {
465 + const subDeps = getConditionDependencies(condition.not)
466 + subDeps.forEach(d => deps.add(d))
467 + }
468 + }
469 +
470 + return deps
471 +}
472 +
473 +/**
474 + * 将旧格式转换为新格式
475 + *
476 + * @description 将旧的 show_when 格式转换为新的条件格式
477 + * @param {Object|Array|string} oldFormat - 旧格式条件
478 + * @returns {Object|null} 新格式条件
479 + *
480 + * @example
481 + * // 旧格式
482 + * { field: 'withdrawal_mode', equals: '指定提取金额' }
483 + * // 转换为
484 + * { field: 'withdrawal_mode', op: 'eq', value: '指定提取金额' }
485 + *
486 + * @example
487 + * // 旧格式数组
488 + * [{ field: 'a', equals: 'x' }, { field: 'b', equals: 'y' }]
489 + * // 转换为
490 + * { and: [{ field: 'a', op: 'eq', value: 'x' }, { field: 'b', op: 'eq', value: 'y' }] }
491 + */
492 +export function convertToNewFormat(oldFormat) {
493 + if (!oldFormat) return null
494 +
495 + // 已经是新格式
496 + if (oldFormat.and || oldFormat.or || oldFormat.not || oldFormat.op) {
497 + return oldFormat
498 + }
499 +
500 + // 旧格式单个条件
501 + if (oldFormat.field && oldFormat.equals !== undefined) {
502 + return {
503 + field: oldFormat.field,
504 + op: 'eq',
505 + value: oldFormat.equals
506 + }
507 + }
508 +
509 + // 旧格式数组
510 + if (Array.isArray(oldFormat) && oldFormat.length > 0) {
511 + const conditions = oldFormat.map(c => {
512 + if (c.field && c.equals !== undefined) {
513 + return { field: c.field, op: 'eq', value: c.equals }
514 + }
515 + return c
516 + })
517 +
518 + // 单个条件不需要包装 and
519 + if (conditions.length === 1) {
520 + return conditions[0]
521 + }
522 +
523 + return { and: conditions }
524 + }
525 +
526 + // 扁平对象格式 { smoker: '是' }
527 + const keys = Object.keys(oldFormat)
528 + if (keys.length > 0 && !keys.some(k => ['and', 'or', 'not', 'field', 'op', 'equals'].includes(k))) {
529 + if (keys.length === 1) {
530 + return { field: keys[0], op: 'eq', value: oldFormat[keys[0]] }
531 + }
532 + return {
533 + and: keys.map(field => ({ field, op: 'eq', value: oldFormat[field] }))
534 + }
535 + }
536 +
537 + return oldFormat
538 +}
...@@ -72,6 +72,7 @@ const savingsSubmitMapping = { ...@@ -72,6 +72,7 @@ const savingsSubmitMapping = {
72 } 72 }
73 73
74 // 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口) 74 // 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
75 +// @updated 2026-02-15 - 迁移到新的条件规则格式,使用 clear_when_hidden 替代 reset_map
75 const savingsFormSchema = { 76 const savingsFormSchema = {
76 // 基础字段:非提取计划部分 77 // 基础字段:非提取计划部分
77 base_fields: [ 78 base_fields: [
...@@ -83,24 +84,25 @@ const savingsFormSchema = { ...@@ -83,24 +84,25 @@ const savingsFormSchema = {
83 { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' } 84 { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
84 ], 85 ],
85 // 提取计划字段:由 withdrawal_plan 开关控制 86 // 提取计划字段:由 withdrawal_plan 开关控制
87 + // 新格式说明:
88 + // - show_when: { field: 'xxx', op: 'eq', value: 'yyy' } 新格式条件
89 + // - show_when: { field: 'xxx', equals: 'yyy' } 旧格式(向后兼容)
90 + // - clear_when_hidden: true 隐藏时自动清空字段值
86 withdrawal_fields: [ 91 withdrawal_fields: [
87 { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' }, 92 { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
88 - { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' }, 93 + { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)', clear_when_hidden: true },
89 - { id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] }, 94 + // 指定提取金额模式字段
90 - { 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: '指定提取金额' }] }, 95 + { 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 },
91 - { id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] }, 96 + { 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 },
92 - { 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: '指定提取金额' }] }, 97 + { 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 },
93 - { id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] }, 98 + { 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 },
94 - { id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }, 99 + { 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 },
95 - { 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: '最高固定提取金额' }] } 100 + // 最高固定提取金额模式字段
96 - ], 101 + { 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 },
97 - // 提取模式切换时的清空逻辑,避免脏字段影响提交 102 + { 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 }
98 - reset_map: { 103 + ]
99 - withdrawal_mode: { 104 + // reset_map 已被 clear_when_hidden 替代
100 - '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'], 105 + // 当 withdrawal_mode 切换时,不可见的字段会自动清空
101 - '指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
102 - }
103 - }
104 } 106 }
105 107
106 export const PLAN_TEMPLATES = { 108 export const PLAN_TEMPLATES = {
...@@ -366,6 +368,24 @@ export const PLAN_TEMPLATES = { ...@@ -366,6 +368,24 @@ export const PLAN_TEMPLATES = {
366 submit_mapping: savingsSubmitMapping 368 submit_mapping: savingsSubmitMapping
367 } 369 }
368 }, 370 },
371 +
372 + /**
373 + * 长宁終身壽險計劃3
374 + * @added 2026-02-15T06:30:03.691Z
375 + * @source docs/to-parse/计划书模版4.docx
376 + */
377 + 'life-insurance-3-d8fde07d': {
378 + name: '长宁終身壽險計劃3',
379 + component: 'LifeInsuranceTemplate',
380 + config: {
381 + currency: 'USD',
382 + payment_periods: ["5年","12年","15年","20年"],
383 + age_range: { min: 0, max: 75 },
384 + insurance_period: '终身',
385 + form_schema: protectionFormSchema,
386 + submit_mapping: baseSubmitMapping
387 + }
388 + }
369 } 389 }
370 390
371 /** 391 /**
......
...@@ -415,9 +415,11 @@ export async function mockProductListAPI(params) { ...@@ -415,9 +415,11 @@ export async function mockProductListAPI(params) {
415 const list = [] 415 const list = []
416 const startIndex = page * limit 416 const startIndex = page * limit
417 417
418 - // 🔧 测试商品:第一页第一位固定为储蓄产品(form_sn:savings-product-30b41aae) 418 + // 🔧 测试商品:第一页前两位固定为测试产品
419 if (page === 0) { 419 if (page === 0) {
420 const testCategory = PRODUCT_CATEGORIES.find(c => parseInt(c.id) === 1) 420 const testCategory = PRODUCT_CATEGORIES.find(c => parseInt(c.id) === 1)
421 +
422 + // 测试商品1: 储蓄产品
421 const testProduct1 = { 423 const testProduct1 = {
422 id: 'savings-2-148b3acd', 424 id: 'savings-2-148b3acd',
423 product_name: '测试计划书-智享未来2(form_sn:savings-2-148b3acd)', 425 product_name: '测试计划书-智享未来2(form_sn:savings-2-148b3acd)',
...@@ -432,19 +434,38 @@ export async function mockProductListAPI(params) { ...@@ -432,19 +434,38 @@ export async function mockProductListAPI(params) {
432 _test_note: 'form_sn:savings-2-148b3acd' 434 _test_note: 'form_sn:savings-2-148b3acd'
433 } 435 }
434 436
435 - // 检查分类和关键词过滤 437 + // 测试商品2: 人寿保险产品
436 - let shouldInclude = true 438 + const testProduct2 = {
437 - if (cid && !testProduct1.categories.some(c => parseInt(c.id) === parseInt(cid))) { 439 + id: 'life-insurance-3-d8fde07d',
438 - shouldInclude = false 440 + product_name: '测试计划书-人生无忧3(form_sn:life-insurance-3-d8fde07d)',
439 - } 441 + cover_image: 'https://picsum.photos/seed/life-insurance-3-d8fde07d/400/300',
440 - if (keyword && !testProduct1.product_name.includes(keyword)) { 442 + recommend: 'hot',
441 - shouldInclude = false 443 + form_sn: 'life-insurance-3-d8fde07d', // ✅ 关键字段:对应真实 API 的 form_sn
444 + created_time: new Date().toISOString(),
445 + categories: [testCategory], // ✅ 符合真实 API 结构:categories 是数组
446 + tags: [{ id: '1', name: '热销', bg_color: '#FEE2E2', text_color: '#DC2626' }],
447 + // 测试标识(不影响业务逻辑)
448 + _test: true,
449 + _test_note: 'form_sn:life-insurance-3-d8fde07d'
442 } 450 }
443 451
444 - if (shouldInclude) { 452 + // 检查分类和关键词过滤,依次添加测试商品
445 - list.push(testProduct1) 453 + const testProducts = [testProduct1, testProduct2]
446 - console.log('[Mock] listAPI - 测试商品已置顶: form_sn=savings-2-148b3acd') 454 +
447 - } 455 + testProducts.forEach((testProduct, index) => {
456 + let shouldInclude = true
457 + if (cid && !testProduct.categories.some(c => parseInt(c.id) === parseInt(cid))) {
458 + shouldInclude = false
459 + }
460 + if (keyword && !testProduct.product_name.includes(keyword)) {
461 + shouldInclude = false
462 + }
463 +
464 + if (shouldInclude) {
465 + list.push(testProduct)
466 + console.log(`[Mock] listAPI - 测试商品${index + 1}已置顶: form_sn=${testProduct.form_sn}`)
467 + }
468 + })
448 } 469 }
449 470
450 for (let i = 0; i < limit; i++) { 471 for (let i = 0; i < limit; i++) {
......
...@@ -234,6 +234,16 @@ function resolveSchemaRefs(config) { ...@@ -234,6 +234,16 @@ function resolveSchemaRefs(config) {
234 } 234 }
235 } 235 }
236 236
237 +/**
238 + * 构建表单 Schema 代码
239 + *
240 + * @description 将 schema 对象转换为代码字符串
241 + * @param {Object|string} value - schema 值
242 + * @param {string} fallbackRef - 回退引用名
243 + * @returns {string} 代码字符串
244 + *
245 + * @updated 2026-02-15 - 支持新的条件格式 show_when: { field, op, value }
246 + */
237 function buildSchemaCode(value, fallbackRef) { 247 function buildSchemaCode(value, fallbackRef) {
238 if (!value || isEmptyObject(value)) { 248 if (!value || isEmptyObject(value)) {
239 return fallbackRef 249 return fallbackRef
...@@ -241,13 +251,14 @@ function buildSchemaCode(value, fallbackRef) { ...@@ -241,13 +251,14 @@ function buildSchemaCode(value, fallbackRef) {
241 if (value && typeof value === 'object' && !Array.isArray(value)) { 251 if (value && typeof value === 'object' && !Array.isArray(value)) {
242 const baseFields = value.base_fields 252 const baseFields = value.base_fields
243 const withdrawalFields = value.withdrawal_fields 253 const withdrawalFields = value.withdrawal_fields
244 - const resetMap = value.reset_map
245 const baseFieldsEmpty = Array.isArray(baseFields) && baseFields.length === 0 254 const baseFieldsEmpty = Array.isArray(baseFields) && baseFields.length === 0
246 const withdrawalFieldsEmpty = !Array.isArray(withdrawalFields) || withdrawalFields.length === 0 255 const withdrawalFieldsEmpty = !Array.isArray(withdrawalFields) || withdrawalFields.length === 0
247 - const resetMapEmpty = !resetMap || (typeof resetMap === 'object' && !Array.isArray(resetMap) && Object.keys(resetMap).length === 0) 256 + if (baseFieldsEmpty && withdrawalFieldsEmpty) {
248 - if (baseFieldsEmpty && withdrawalFieldsEmpty && resetMapEmpty) {
249 return fallbackRef 257 return fallbackRef
250 } 258 }
259 + // 处理字段中的 show_when 格式转换
260 + const processedValue = processSchemaFields(value)
261 + return JSON.stringify(processedValue, null, 2).replace(/\n/g, '\n ')
251 } 262 }
252 if (typeof value === 'string') { 263 if (typeof value === 'string') {
253 return value 264 return value
...@@ -255,6 +266,42 @@ function buildSchemaCode(value, fallbackRef) { ...@@ -255,6 +266,42 @@ function buildSchemaCode(value, fallbackRef) {
255 return JSON.stringify(value, null, 2).replace(/\n/g, '\n ') 266 return JSON.stringify(value, null, 2).replace(/\n/g, '\n ')
256 } 267 }
257 268
269 +/**
270 + * 处理 Schema 字段,转换条件格式
271 + *
272 + * @private
273 + */
274 +function processSchemaFields(schema) {
275 + const result = {}
276 + for (const [key, value] of Object.entries(schema)) {
277 + if (key === 'base_fields' || key === 'withdrawal_fields') {
278 + result[key] = value.map(field => processFieldCondition(field))
279 + } else if (key !== 'reset_map') {
280 + // 忽略 reset_map,不再生成
281 + result[key] = value
282 + }
283 + }
284 + return result
285 +}
286 +
287 +/**
288 + * 处理字段条件,转换为新格式
289 + *
290 + * @private
291 + */
292 +function processFieldCondition(field) {
293 + const result = { ...field }
294 + // 转换 show_when: { field, equals } -> { field, op: 'eq', value }
295 + if (result.show_when && result.show_when.equals !== undefined) {
296 + result.show_when = {
297 + field: result.show_when.field,
298 + op: 'eq',
299 + value: result.show_when.equals
300 + }
301 + }
302 + return result
303 +}
304 +
258 function isEmptyObject(value) { 305 function isEmptyObject(value) {
259 if (!value || typeof value !== 'object' || Array.isArray(value)) { 306 if (!value || typeof value !== 'object' || Array.isArray(value)) {
260 return false 307 return false
......