hookehuyr

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

- 完善计划书字段选项控制功能
- 优化表单交互体验
- 提取字段配置模板
Showing 44 changed files with 1135 additions and 1544 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"}
This diff is collapsed. Click to expand it.
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 +
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,14 +106,51 @@ export function findProductTitles(content) { ...@@ -99,14 +106,51 @@ 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 +
118 + products.push({
119 + index: match.index,
120 + code,
121 + name,
122 + fullTitle
123 + })
124 + }
125 + }
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)
102 products.push({ 144 products.push({
103 index: match.index, 145 index: match.index,
104 - code: match[1] || null, 146 + code: null,
105 - name: match[2] || fullTitle, 147 + name: fullTitle.split(/[-—::]/)[0].trim(), // 移除后缀说明
106 fullTitle 148 fullTitle
107 }) 149 })
108 } 150 }
109 } 151 }
152 + }
153 + }
110 154
111 // 按出现位置排序 155 // 按出现位置排序
112 products.sort((a, b) => a.index - b.index) 156 products.sort((a, b) => a.index - b.index)
......
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
105 - const currentValue = formData[condition.field]
106 - if (currentValue !== condition.equals) {
107 return false 111 return false
108 } 112 }
109 } 113 }
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 - }
118 - }
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
212 } 322 }
323 +
324 + // 其他不可见字段被过滤掉
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 }
......
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
...@@ -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: 人寿保险产品
438 + const testProduct2 = {
439 + id: 'life-insurance-3-d8fde07d',
440 + product_name: '测试计划书-人生无忧3(form_sn:life-insurance-3-d8fde07d)',
441 + cover_image: 'https://picsum.photos/seed/life-insurance-3-d8fde07d/400/300',
442 + recommend: 'hot',
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'
450 + }
451 +
452 + // 检查分类和关键词过滤,依次添加测试商品
453 + const testProducts = [testProduct1, testProduct2]
454 +
455 + testProducts.forEach((testProduct, index) => {
436 let shouldInclude = true 456 let shouldInclude = true
437 - if (cid && !testProduct1.categories.some(c => parseInt(c.id) === parseInt(cid))) { 457 + if (cid && !testProduct.categories.some(c => parseInt(c.id) === parseInt(cid))) {
438 shouldInclude = false 458 shouldInclude = false
439 } 459 }
440 - if (keyword && !testProduct1.product_name.includes(keyword)) { 460 + if (keyword && !testProduct.product_name.includes(keyword)) {
441 shouldInclude = false 461 shouldInclude = false
442 } 462 }
443 463
444 if (shouldInclude) { 464 if (shouldInclude) {
445 - list.push(testProduct1) 465 + list.push(testProduct)
446 - console.log('[Mock] listAPI - 测试商品已置顶: form_sn=savings-2-148b3acd') 466 + console.log(`[Mock] listAPI - 测试商品${index + 1}已置顶: form_sn=${testProduct.form_sn}`)
447 } 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
......