feat(plan): 合并选项控制功能到 develop
- 完善计划书字段选项控制功能 - 优化表单交互体验 - 提取字段配置模板
Showing
44 changed files
with
1142 additions
and
1551 deletions
| 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 | -``` |
docs/parsed-backup/backup-log.jsonl
deleted
100644 → 0
| 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"} |
docs/parsed-backup/parse-audit.jsonl
deleted
100644 → 0
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 | -} |
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
docs/tasks/plan/字段条件显示系统扩展.md
0 → 100644
| 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": [ | ... | ... |
scripts/README.md
0 → 100644
| 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) |
scripts/api-generator/README.md
0 → 100644
| 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 | +``` |
scripts/changelog/README.md
0 → 100644
| 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. **定期归档**:每月运行一次归档脚本 |
scripts/doc-parser/README.md
0 → 100644
| 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 | ... | ... |
This diff is collapsed. Click to expand it.
scripts/整理报告.md
0 → 100644
| 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 | } | ... | ... |
src/config/__tests__/plan-conditions.test.js
0 → 100644
This diff is collapsed. Click to expand it.
src/config/plan-conditions.js
0 → 100644
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: 人寿保险产品 |
| 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++) { | ... | ... |
| 1 | + | ... | ... |
| ... | @@ -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 | ... | ... |
-
Please register or login to post a comment