hookehuyr

feat(parser): 完善文档解析与测试

- 更新 parse-docs.js 解析逻辑
- 添加完整的测试用例
- 更新 CHANGELOG.md
- 完善配置生成器

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
......@@ -24,8 +24,3 @@ CLAUDE.md
*.pptx
.swc/
# 文档解析工具临时文件
docs/parsed-backup/
docs/to-parse/*.md
!docs/to-parse/README.md
......
......@@ -101,6 +101,12 @@ pnpm lint
### 文档解析
-**解析验证** - 成功解析 测试计划书-智享未来.md 并生成配置
-**优化建议** - 解析结果需补齐 form_schema 与 submit_mapping 以便直接渲染
-**规则落地** - 解析生成按保险类型自动注入 form_schema 与 submit_mapping
-**空值回退** - 空 schema 自动回退默认 schema,避免生成空表单
-**文件过滤** - 解析批量扫描时自动跳过 docs/to-parse/README.md
### 测试与验证
-**测试修正** - 搜索页单测固定使用真实 API Mock,避免受全局 Mock 开关影响
## 🆕 最新更新(2026-02-13)
......
## [2026-02-14] - 空表单回退规则补齐
### 修复
- 空 schema 自动回退默认 schema,避免生成空表单
- 生成结果中空 schema 配置回填为默认 schema 引用
---
**详细信息**
- **影响文件**: scripts/parse-docs.js, scripts/parse-docs.test.js, src/utils/parsers/config-generator.js, src/config/plan-templates.js, README.md
- **技术栈**: Node.js, Vitest
- **测试状态**: pnpm test 通过;pnpm lint 30 warnings
- **备注**: form_schema 为空结构时将使用对应类型默认 schema
---
## [2026-02-14] - 解析文件过滤
### 修复
- 解析待处理目录时跳过 README.md
---
**详细信息**
- **影响文件**: scripts/parse-docs.js, README.md
- **技术栈**: Node.js
- **测试状态**: pnpm test 通过;pnpm lint 30 warnings
- **备注**: 仅过滤 docs/to-parse/README.md
---
## [2026-02-14] - 搜索测试 Mock 开关修正
### 修复
- 搜索页单测固定使用真实 API Mock,避免被全局 Mock 开关干扰
---
**详细信息**
- **影响文件**: src/pages/search/index.test.js
- **技术栈**: Vitest
- **测试状态**: pnpm test 通过;pnpm lint 30 warnings
- **备注**: 用例统一 mock USE_MOCK_DATA=false
---
## [2026-02-14] - 解析配置字段补齐
### 新增
- 解析配置生成按保险类型注入 form_schema 与 submit_mapping
- 补充解析生成默认 schema/mapping 输出测试
---
**详细信息**
- **影响文件**: scripts/parse-docs.js, scripts/parse-docs.test.js, src/utils/parsers/config-generator.js, README.md
- **技术栈**: Node.js, Vitest
- **测试状态**: pnpm test 失败(search 页面 2 项断言);pnpm lint 30 warnings
- **备注**: 解析生成不会再输出空的 form_schema/submit_mapping
---
## [2026-02-14] - Mock 数据结构优化
### 新增
......
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771074633927.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-product-30b41aae"],"at":"2026-02-14T13:10:33.928Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771077530778.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-2-148b3acd"],"at":"2026-02-14T13:58:50.779Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771077569110.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-readme-a4296d1f"],"at":"2026-02-14T13:59:29.110Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771077989896.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-readme-a4296d1f"],"at":"2026-02-14T14:06:29.896Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771078080604.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-readme-a4296d1f"],"at":"2026-02-14T14:08:00.605Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771078351660.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-2-148b3acd"],"at":"2026-02-14T14:12:31.660Z"}
{"at":"2026-02-14T13:10:33.928Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":3,"success_list":[{"form_sn":"savings-product-30b41aae","product_name":"测试计划书-智享未来","file":"测试计划书-智享未来.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-product-30b41aae"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T13:58:50.780Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-2-148b3acd"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T13:59:29.111Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-readme-a4296d1f","product_name":"README","file":"README.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-readme-a4296d1f"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T14:06:15.148Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-readme-a4296d1f","product_name":"README","file":"README.md"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":["savings-readme-a4296d1f"],"reason":"conflict"}}
{"at":"2026-02-14T14:06:29.897Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-readme-a4296d1f","product_name":"README","file":"README.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-readme-a4296d1f"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T14:08:00.605Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-readme-a4296d1f","product_name":"README","file":"README.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-readme-a4296d1f"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T14:12:31.661Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":1,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-2-148b3acd"],"conflicts":[],"reason":null}}
/**
* 计划书模版配置
*
* @description 定义产品 form_sn 到模版组件和配置的映射关系
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
* 计划书模版配置映射
* @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
*
* @example
* // 产品 API 返回
* {
* id: 1,
* product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
* form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
* }
*/
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD', // 币种:USD/CNY/HKD/EUR
payment_periods: [
// 缴费年期选项
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 }, // 年龄范围
insurance_period: '终身' // 保险期间
}
},
// 人寿保险产品 - WIOP3
'life-insurance-wiop3': {
name: 'WIOP3 - 盈传创富保障计划 3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: [
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 },
insurance_period: '终身'
}
},
// 重疾保险产品 - MPC
'critical-illness-mpc': {
name: 'MPC 守护无间重疾',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
},
// 重疾保险产品 - MBC PRO
'critical-illness-mbc-pro': {
name: 'MBC PRO 活跃人生重疾保 PRO',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
},
// 重疾保险产品 - MBC2
'critical-illness-mbc2': {
name: 'MBC2 活跃人生重疾保 2',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
},
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 提取计划配置
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
default_currency: 'USD', // 统一为美元
withdrawal_modes: [
'年龄指定金额', // 方式1
'最高固定金额' // 方式2
],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
},
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['年龄指定金额', '最高固定金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
},
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['年龄指定金额', '最高固定金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
},
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['年龄指定金额', '最高固定金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
}
}
/**
* 全局功能开关
* @description 用于控制实验性功能或未来扩展功能的开关
*
* @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
*/
export const FEATURE_FLAGS = {
/**
* 多币种切换功能
* @description false: 方案 1 - 固定币种(当前实现)
* true: 方案 2 - 支持多币种切换(未来扩展)
* @type {boolean}
*/
MULTI_CURRENCY_ENABLED: false
}
/**
* 币种符号映射
* @description 币种代码到符号的映射关系
*/
export const CURRENCY_SYMBOLS = {
CNY: '¥', // 人民币
USD: '$', // 美元
HKD: 'HK$', // 港币
EUR: '€' // 欧元
}
/**
* 币种完整信息映射
* @description 币种代码到完整信息的映射(用于多币种模式)
*/
export const CURRENCY_MAP = {
CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
USD: { label: '美元', symbol: '$', value: 'USD' },
HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
EUR: { label: '欧元', symbol: '€', value: 'EUR' }
}
/**
* 根据 form_sn 获取模版配置
* @param {string} formSn - 产品 API 返回的 form_sn 字段
* @returns {Object|null} 模版配置对象,未找到返回 null
*
* @example
* const config = getTemplateConfig('life-insurance-wiop3e')
* // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
*/
export function getTemplateConfig(formSn) {
if (!formSn) {
console.warn('[plan-templates] form_sn 为空')
return null
}
const config = PLAN_TEMPLATES[formSn]
if (!config) {
console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
return null
}
return config
}
/**
* 获取币种符号
* @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
* @returns {string} 币种符号
*
* @example
* const symbol = getCurrencySymbol('USD') // 返回: '$'
*/
export function getCurrencySymbol(currencyCode) {
return CURRENCY_SYMBOLS[currencyCode] || '¥'
}
/**
* 计划书模版配置
*
* @description 定义产品 form_sn 到模版组件和配置的映射关系
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
* 计划书模版配置映射
* @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
*
* @example
* // 产品 API 返回
* {
* id: 1,
* product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
* form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
* }
*/
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD', // 币种:USD/CNY/HKD/EUR
payment_periods: [
// 缴费年期选项
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 }, // 年龄范围
insurance_period: '终身' // 保险期间
}
},
// 人寿保险产品 - WIOP3
'life-insurance-wiop3': {
name: 'WIOP3 - 盈传创富保障计划 3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: [
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 },
insurance_period: '终身'
}
},
// 重疾保险产品 - MPC
'critical-illness-mpc': {
name: 'MPC 守护无间重疾',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
},
// 重疾保险产品 - MBC PRO
'critical-illness-mbc-pro': {
name: 'MBC PRO 活跃人生重疾保 PRO',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
},
// 重疾保险产品 - MBC2
'critical-illness-mbc2': {
name: 'MBC2 活跃人生重疾保 2',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
},
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 提取计划配置
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
default_currency: 'USD', // 统一为美元
withdrawal_modes: [
'年龄指定金额', // 方式1
'最高固定金额' // 方式2
],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
},
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['年龄指定金额', '最高固定金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
},
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['年龄指定金额', '最高固定金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
},
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['年龄指定金额', '最高固定金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
}
}
}
}
/**
* 全局功能开关
* @description 用于控制实验性功能或未来扩展功能的开关
*
* @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
*/
export const FEATURE_FLAGS = {
/**
* 多币种切换功能
* @description false: 方案 1 - 固定币种(当前实现)
* true: 方案 2 - 支持多币种切换(未来扩展)
* @type {boolean}
*/
MULTI_CURRENCY_ENABLED: false
}
/**
* 币种符号映射
* @description 币种代码到符号的映射关系
*/
export const CURRENCY_SYMBOLS = {
CNY: '¥', // 人民币
USD: '$', // 美元
HKD: 'HK$', // 港币
EUR: '€' // 欧元
}
/**
* 币种完整信息映射
* @description 币种代码到完整信息的映射(用于多币种模式)
*/
export const CURRENCY_MAP = {
CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
USD: { label: '美元', symbol: '$', value: 'USD' },
HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
EUR: { label: '欧元', symbol: '€', value: 'EUR' }
}
/**
* 根据 form_sn 获取模版配置
* @param {string} formSn - 产品 API 返回的 form_sn 字段
* @returns {Object|null} 模版配置对象,未找到返回 null
*
* @example
* const config = getTemplateConfig('life-insurance-wiop3e')
* // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
*/
export function getTemplateConfig(formSn) {
if (!formSn) {
console.warn('[plan-templates] form_sn 为空')
return null
}
const config = PLAN_TEMPLATES[formSn]
if (!config) {
console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
return null
}
return config
}
/**
* 获取币种符号
* @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
* @returns {string} 币种符号
*
* @example
* const symbol = getCurrencySymbol('USD') // 返回: '$'
*/
export function getCurrencySymbol(currencyCode) {
return CURRENCY_SYMBOLS[currencyCode] || '¥'
}
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.
# 保险知识库
> 本文档提供保险产品解析所需的行业知识,AI 解析器可以引用此内容来正确识别和提取产品信息。
## 📚 保险产品分类
### 1. 储蓄型保险(Savings Insurance)
**特点**
- 具有储蓄功能,保单有现金价值
- 通常支持灵活提取现金
- 保险期间较长(终身或至指定年龄如 100 岁)
**常见产品名关键词**
- 储蓄计划
- 传承计划
- 累积计划
- 增值计划
- 万能险
- 投连险
**关键提取字段**
| 字段 | 说明 | 示例 |
|------|------|------|
| 产品名称 | 保险产品全称 | 宏挚传承保障计划 |
| 币种 | 支持的货币 | HKD, USD, CNY |
| 缴费年期 | 可选择的缴费年限 | 整交、5年、10年 |
| 保险期间 | 保障期限 | 终身、至 100 岁 |
| 最低/最高投保年龄 | 年龄限制 | 15 日 - 75 岁 |
| 提取方式 | 现金提取方式 | 灵活提取、固定提取 |
| 提取周期 | 提取频率 | 每年、每半年、每季、每月 |
---
### 2. 人寿保险(Life Insurance)
**特点**
- 提供身故保障
- 保费相对较低
- 保障期间固定
**常见产品名关键词**
- 人寿保险
- 定期寿险
- 终身寿险
- 保障计划
**关键提取字段**
| 字段 | 说明 | 示例 |
|------|------|------|
| 身故保险金 | 被人身故赔付金额 | 基本保额 × 倍数 |
| 保费形式 | 缴费方式 | 年缴、季缴、月缴 |
---
### 3. 重疾保险(Critical Illness Insurance)
**特点**
- 保障重大疾病
- 确诊即赔付
- 可选保费豁免
**常见产品名关键词**
- 重疾险
- 危疾保障
- 重大疾病保险
- 健康保障
**关键提取字段**
| 字段 | 说明 | 示例 |
|------|------|------|
| 保障疾病数量 | 覆盖病种数量 | 100+ 种 |
| 赔付次数 | 可赔付次数 | 单次、多次 |
| 等待期 | 观察期 | 90 天、180 天 |
---
## 📋 保险专业术语对照表
| 中文术语 | 英文术语 | 说明 |
|---------|---------|------|
| 投保人 | Applicant | 购买保险的人 |
| 被保险人 | Insured | 受保障的人 |
| 受益人 | Beneficiary | 接受保险金的人 |
| 保额 | Sum Assured | 保险公司承担的最高赔偿额 |
| 保费 | Premium | 投保人支付的费用 |
| 缴费年期 | Payment Period | 保费缴纳的年限 |
| 保险期间 | Policy Term | 保险合同的有效期 |
| 现金价值 | Cash Value | 退保时可获得的价值 |
| 保单年度 | Policy Year | 保单生效的年数 |
| 宽限期 | Grace Period | 逾期未缴保费但保障仍有效的期间 |
| 观察期/等待期 | Waiting Period | 保险生效后需等待的一段时间 |
| 免责期 | Exclusion Period | 保险公司不承担责任的时间 |
---
## 🏦 币种识别
| 币种代码 | 币种名称 | 符号 |
|---------|---------|------|
| HKD | 港元 | $, HK$ |
| USD | 美元 | US$, $ |
| CNY | 人民币 | ¥, RMB |
| MOP | 澳门元 | MOP$ |
---
## 📊 缴费年期识别规则
| 文案表述 | 标准化值 |
|---------|---------|
| 整交、趸交 | lump_sum |
| 5年、5年缴 | 5 |
| 10年、10年缴 | 10 |
| 15年、15年缴 | 15 |
| 20年、20年缴 | 20 |
| 25年、25年缴 | 25 |
| 30年、30年缴 | 30 |
| 终身缴费、至终身 | to_age_100 |
---
## 📅 保险期间识别规则
| 文案表述 | 标准化值 |
|---------|---------|
| 终身、100岁 |终身 |
| 至 85 岁 | to_85 |
| 至 88 岁 | to_88 |
| 至 100 岁 | to_100 |
| 1 年、1 年期 | 1_year |
| 20 年、20 年期 | 20_years |
---
## 🎯 年龄范围识别
| 文案表述 | 最低年龄 | 最高年龄 |
|---------|---------|---------|
| 出生 15 天起 | 0 | - |
| 15 日 - 75 岁 | 0 | 75 |
| 18 岁 - 65 岁 | 18 | 65 |
| 30 日 - 70 岁 | 0 | 70 |
**注意**:年龄通常按周岁计算。
---
## 💰 提取功能识别
### 提取方式(Withdrawal Modes)
| 文案表述 | 标准化值 |
|---------|---------|
| 灵活提取 | flexible |
| 固定提取 | fixed |
| 定期提取 | regular |
| 终身年金 | lifetime_annuity |
### 提取周期(Withdrawal Periods)
| 文案表述 | 标准化值 |
|---------|---------|
| 每年 | yearly |
| 每半年 | half_yearly |
| 每季 | quarterly |
| 每月 | monthly |
| 终身 | lifetime |
---
## 🔍 产品名称关键词识别
### 储蓄型关键词
- 传承、累积、增值、储蓄、分红
- 宏挚、迈达、创富、丰誉
- 5G、6G、世代
### 人寿保险关键词
- 人寿、寿险、身故保障
- 定期、终身、保额
### 重疾保险关键词
- 重疾、危疾、重大疾病
- 疾病保障、健康保障
- 守护、守护
---
## 📐 配置生成规则
### form_sn 生成规则
```
{产品类型}-{产品代号}-{币种}
```
**示例**
- `savings-hc77-hkd` - 宏挚 77(储蓄型,港币)
- `life-term-20-usd` - 定期寿险 20 年期(人寿,美元)
- `ci-essential-cny` - 基础重疾险(重疾,人民币)
### 组件选择规则
| 产品类型 | 对应组件 |
|---------|----------|
| 储蓄型 | SavingsTemplate |
| 人寿保险 | LifeInsuranceTemplate |
| 重疾保险 | CriticalIllnessTemplate |
---
## ⚠️ 常见解析错误及修正
### 错误 1:币种识别错误
- **问题**:将 "USD$" 识别为港币
- **修正**:检查符号,$ 后面跟 USD 才是美元
### 错误 2:年龄范围解析错误
- **问题**:将 "15 日 - 75 岁" 的最低年龄识别为 15
- **修正**:15 日应转换为 0 岁
### 错误 3:缴费年期单位错误
- **问题**:将 "20 年期" 识别为 20 年缴费
- **修正**:"年期"通常指保险期间,"缴费"才是缴费年期
### 错误 4:提取方式缺失
- **问题**:储蓄型产品未识别到提取功能
- **修正**:查找"现金价值"、"提取"、"红利"等关键词
---
## 📖 参考规范
### 产品说明书标准结构
1. **产品概述** - 产品定位和特点
2. **投保条件** - 年龄、职业限制
3. **保障内容** - 具体保障项目
4. **缴费方式** - 缴费年期和币种
5. **红利分配**(如有)- 红利政策和领取方式
6. **提取功能**(如有)- 现金价值提取规则
7. **费用说明** - 各项费用标准
8. **案例演示** - 实际投保示例
### 文档解析优先级
1. **表格数据** > 文本描述
2. **明确的数字** > 模糊的表达(如"左右"、"约")
3. **章节标题** > 正文内容
4. **示例数据** > 说明文字
---
## 🔄 更新记录
| 日期 | 更新内容 | 更新人 |
|------|---------|--------|
| 2026-02-13 | 创建初始版本,添加保险产品分类和术语表 | Claude Code |
# 智享未来储蓄计划书
## 产品概述
**产品名称**: 智享未来储蓄计划
**产品类型**: 储蓄型保险
**保险公司**: 宏利人寿保险
**币种**: 美元 (USD)
---
## 产品特点
### 核心优势
- 保证现金价值,稳健增值
- 灵活提取方式,满足不同人生阶段需求
- 多种缴费年期选择,适应不同财务规划
- 美元/港币/人民币多币种选择
### 适合人群
- 希望长期稳健增值的客户
- 有子女教育金规划需求的父母
- 有养老规划需求的人士
- 希望灵活运用资金的高净值人士
---
## 投保条件
### 投保年龄
- 最低投保年龄:出生 15 天
- 最高投保年龄:75 岁
### 缴费年期
- 整付(0-75 岁)
- 3 年期缴
- 5 年期缴
- 10 年期缴
### 保险期间
- 终身(至 100 岁)
---
## 保障利益
### 身故保险金
若被保险人身故,我们将支付身故保险金予受益人。
### 现金价值
保单生效后具有现金价值,现金价值随保单年度增长而增加。
### 红利分配
本公司将根据分红保险业务的投资经营状况决定红利分配金额。
---
## 提取计划
### 提取方式
客户可选择以下提取方式:
**方式一:年龄指定金额提取**
- 客户可指定从特定年龄开始,每年提取固定金额
- 提取年龄可选:18/20/22/25/30/35/40/45/50/55/60 岁
- 每年提取金额:最低 1,000 美元
**方式二:最高固定金额提取**
- 客户可选择提取最高固定金额
- 适用于需要大额资金支出的场景
### 提取周期
- 每年提取
- 每 2 年提取
- 每 3 年提取
- 每 5 年提取
- 每 10 年提取
- 终身提取
### 支持币种
- 美元 (USD)
- 港币 (HKD)
- 人民币 (CNY)
---
## 缴费示例
### 案例:35 岁男性,整付,年缴保费 50,000 美元
| 保单年度 | 年缴保费 | 累计保费 | 保证现金价值 |
|---------|---------|---------|-------------|
| 第 1 年 | $50,000 | $50,000 | $45,000 |
| 第 5 年 | $50,000 | $250,000 | $235,000 |
| 第 10 年 | $50,000 | $500,000 | $490,000 |
| 第 20 年 | - | $500,000 | $1,050,000 |
| 第 30 年 | - | $500,000 | $1,650,000 |
*注:以上数字仅为示例,实际现金价值以保险公司公布为准。
---
## 退保规定
### 犹豫期
保单签收后 15 天内为犹豫期,投保人可无条件解除合同,本公司无息退还已收保费。
### 退保价值
保单生效后,投保人可申请解除合同,本公司将支付退保金(即当时的现金价值)予投保人。
---
## 保费表
### 整付保费表(美元)
| 年龄 | 男性 | 女性 |
|-----|------|------|
| 0 岁 | $45,200 | $44,800 |
| 10 岁 | $46,500 | $46,100 |
| 20 岁 | $48,000 | $47,600 |
| 30 岁 | $50,000 | $49,500 |
| 40 岁 | $53,500 | $52,800 |
| 50 岁 | $59,200 | $58,000 |
| 60 岁 | $68,500 | $66,800 |
| 70 岁 | $89,200 | $86,500 |
### 5 年期缴保费表(美元)
| 年龄 | 年缴保费 | 总保费 |
|-----|---------|-------|
| 0 岁 | $10,500 | $52,500 |
| 10 岁 | $10,800 | $54,000 |
| 20 岁 | $11,200 | $56,000 |
| 30 岁 | $11,800 | $59,000 |
| 40 岁 | $12,800 | $64,000 |
| 50 岁 | $14,500 | $72,500 |
| 60 岁 | $17,200 | $86,000 |
| 70 岁 | $23,500 | $117,500 |
---
## 责任免除
因下列情形之一导致被保险人身故的,本公司不承担保险责任:
1. 投保人故意造成被保险人死亡、故意伤害被保险人
2. 被保险人故意自伤、故意犯罪或抗拒依法采取的刑事强制措施
3. 被保险人主动吸食或注射毒品
4. 被保险人在本合同成立或复效之日起 2 年内自杀
5. 战争、军事冲突、暴乱或武装叛乱
6. 核爆炸、核辐射、辐射污染
---
## 特别约定
1. 本合同项下的保险责任自本公司同意承保并收取首期保险费后开始
2. 投保人应如实告知被保险人的健康状况,如故意不履行如实告知义务,本公司有权解除合同
3. 本合同项下的保险金请求权,自被保险人或受益人知道保险事故发生之日起 5 年内不行使而消灭
---
## 联系方式
**客服热线**: 400-888-8888
**公司网址**: www.manulife.com
**营业时间**: 周一至周五 9:00-17:30
---
*本计划书仅供参考,具体保险责任及责任免除以保险合同为准*
---
**文档版本**: V1.0
**生成日期**: 2026年2月13日
**用途**: 测试文档解析功能
# 智享未来储蓄计划书
## 产品概述
**产品名称**: 智享未来储蓄计划
**产品类型**: 储蓄型保险
**保险公司**: 宏利人寿保险
**币种**: 美元 (USD)
---
## 产品特点
### 核心优势
- 保证现金价值,稳健增值
- 灵活提取方式,满足不同人生阶段需求
- 多种缴费年期选择,适应不同财务规划
- 美元/港币/人民币多币种选择
### 适合人群
- 希望长期稳健增值的客户
- 有子女教育金规划需求的父母
- 有养老规划需求的人士
- 希望灵活运用资金的高净值人士
---
## 投保条件
### 投保年龄
- 最低投保年龄:出生 15 天
- 最高投保年龄:75 岁
### 缴费年期
- 整付(0-75 岁)
- 3 年期缴
- 5 年期缴
- 10 年期缴
### 保险期间
- 终身(至 100 岁)
---
## 保障利益
### 身故保险金
若被保险人身故,我们将支付身故保险金予受益人。
### 现金价值
保单生效后具有现金价值,现金价值随保单年度增长而增加。
### 红利分配
本公司将根据分红保险业务的投资经营状况决定红利分配金额。
---
## 提取计划
### 提取方式
客户可选择以下提取方式:
**方式一:年龄指定金额提取**
- 客户可指定从特定年龄开始,每年提取固定金额
- 提取年龄可选:18/20/22/25/30/35/40/45/50/55/60 岁
- 每年提取金额:最低 1,000 美元
**方式二:最高固定金额提取**
- 客户可选择提取最高固定金额
- 适用于需要大额资金支出的场景
### 提取周期
- 每年提取
- 每 2 年提取
- 每 3 年提取
- 每 5 年提取
- 每 10 年提取
- 终身提取
### 支持币种
- 美元 (USD)
- 港币 (HKD)
- 人民币 (CNY)
---
## 缴费示例
### 案例:35 岁男性,整付,年缴保费 50,000 美元
| 保单年度 | 年缴保费 | 累计保费 | 保证现金价值 |
|---------|---------|---------|-------------|
| 第 1 年 | $50,000 | $50,000 | $45,000 |
| 第 5 年 | $50,000 | $250,000 | $235,000 |
| 第 10 年 | $50,000 | $500,000 | $490,000 |
| 第 20 年 | - | $500,000 | $1,050,000 |
| 第 30 年 | - | $500,000 | $1,650,000 |
*注:以上数字仅为示例,实际现金价值以保险公司公布为准。
---
## 退保规定
### 犹豫期
保单签收后 15 天内为犹豫期,投保人可无条件解除合同,本公司无息退还已收保费。
### 退保价值
保单生效后,投保人可申请解除合同,本公司将支付退保金(即当时的现金价值)予投保人。
---
## 保费表
### 整付保费表(美元)
| 年龄 | 男性 | 女性 |
|-----|------|------|
| 0 岁 | $45,200 | $44,800 |
| 10 岁 | $46,500 | $46,100 |
| 20 岁 | $48,000 | $47,600 |
| 30 岁 | $50,000 | $49,500 |
| 40 岁 | $53,500 | $52,800 |
| 50 岁 | $59,200 | $58,000 |
| 60 岁 | $68,500 | $66,800 |
| 70 岁 | $89,200 | $86,500 |
### 5 年期缴保费表(美元)
| 年龄 | 年缴保费 | 总保费 |
|-----|---------|-------|
| 0 岁 | $10,500 | $52,500 |
| 10 岁 | $10,800 | $54,000 |
| 20 岁 | $11,200 | $56,000 |
| 30 岁 | $11,800 | $59,000 |
| 40 岁 | $12,800 | $64,000 |
| 50 岁 | $14,500 | $72,500 |
| 60 岁 | $17,200 | $86,000 |
| 70 岁 | $23,500 | $117,500 |
---
## 责任免除
因下列情形之一导致被保险人身故的,本公司不承担保险责任:
1. 投保人故意造成被保险人死亡、故意伤害被保险人
2. 被保险人故意自伤、故意犯罪或抗拒依法采取的刑事强制措施
3. 被保险人主动吸食或注射毒品
4. 被保险人在本合同成立或复效之日起 2 年内自杀
5. 战争、军事冲突、暴乱或武装叛乱
6. 核爆炸、核辐射、辐射污染
---
## 特别约定
1. 本合同项下的保险责任自本公司同意承保并收取首期保险费后开始
2. 投保人应如实告知被保险人的健康状况,如故意不履行如实告知义务,本公司有权解除合同
3. 本合同项下的保险金请求权,自被保险人或受益人知道保险事故发生之日起 5 年内不行使而消灭
---
## 联系方式
**客服热线**: 400-888-8888
**公司网址**: www.manulife.com
**营业时间**: 周一至周五 9:00-17:30
---
*本计划书仅供参考,具体保险责任及责任免除以保险合同为准*
---
**文档版本**: V1.0
**生成日期**: 2026年2月13日
**用途**: 测试文档解析功能
......@@ -36,7 +36,7 @@
"prepare": "husky",
"parse:docs": "node scripts/parse-docs.js",
"parse:docs:list": "node scripts/parse-docs.js --list",
"parse:docs:file": "node scripts/parse-docs.js --file=\"产品说明书.pdf\"",
"parse:docs:file": "node scripts/parse-docs.js --file=",
"release": "standard-version"
},
"browserslist": [
......
......@@ -187,6 +187,7 @@ function getDocsToParse() {
const files = fs.readdirSync(DOCS_DIR)
return files
.filter(file => SUPPORTED_EXTENSIONS.includes(path.extname(file).toLowerCase()))
.filter(file => file !== 'README.md')
.map(file => ({
name: file,
fullPath: path.join(DOCS_DIR, file),
......@@ -225,6 +226,9 @@ export function generateConfigCode(config) {
const componentName = isSavings
? 'SavingsTemplate'
: (productType === 'critical-illness' ? 'CriticalIllnessTemplate' : 'LifeInsuranceTemplate')
const { form_schema_ref, submit_mapping_ref } = resolveSchemaRefs(config)
const form_schema_code = buildSchemaCode(config.form_schema, form_schema_ref)
const submit_mapping_code = buildSchemaCode(config.submit_mapping, submit_mapping_ref)
let code = " /**\n"
code += " * " + config.product_name + "\n"
......@@ -251,27 +255,15 @@ export function generateConfigCode(config) {
code += " withdrawal_modes: " + JSON.stringify(config.withdrawal_modes || []) + ",\n"
code += " withdrawal_periods: " + JSON.stringify(config.withdrawal_periods || []) + "\n"
code += " },\n"
if (config.form_schema) {
const form_schema_code = JSON.stringify(config.form_schema, null, 2).replace(/\n/g, '\n ')
code += " form_schema: " + form_schema_code + ",\n"
}
if (config.submit_mapping) {
const submit_mapping_code = JSON.stringify(config.submit_mapping, null, 2).replace(/\n/g, '\n ')
code += " submit_mapping: " + submit_mapping_code + "\n"
}
code += " form_schema: " + form_schema_code + ",\n"
code += " submit_mapping: " + submit_mapping_code + "\n"
} else {
code += " currency: '" + config.currency + "',\n"
code += " payment_periods: " + JSON.stringify(config.payment_periods || []) + ",\n"
code += " age_range: { min: " + (config.age_range?.min || 0) + ", max: " + (config.age_range?.max || 75) + " },\n"
code += " insurance_period: '" + (config.insurance_period || '终身') + "',\n"
if (config.form_schema) {
const form_schema_code = JSON.stringify(config.form_schema, null, 2).replace(/\n/g, '\n ')
code += " form_schema: " + form_schema_code + ",\n"
}
if (config.submit_mapping) {
const submit_mapping_code = JSON.stringify(config.submit_mapping, null, 2).replace(/\n/g, '\n ')
code += " submit_mapping: " + submit_mapping_code + "\n"
}
code += " form_schema: " + form_schema_code + ",\n"
code += " submit_mapping: " + submit_mapping_code + "\n"
}
code += " }\n"
......@@ -280,6 +272,48 @@ export function generateConfigCode(config) {
return { formSn, code }
}
function resolveSchemaRefs(config) {
const isSavings = config?.is_savings || config?.product_type === 'savings'
if (isSavings) {
return {
form_schema_ref: 'savingsFormSchema',
submit_mapping_ref: 'savingsSubmitMapping'
}
}
return {
form_schema_ref: 'protectionFormSchema',
submit_mapping_ref: 'baseSubmitMapping'
}
}
function buildSchemaCode(value, fallbackRef) {
if (!value || isEmptyObject(value)) {
return fallbackRef
}
if (value && typeof value === 'object' && !Array.isArray(value)) {
const baseFields = value.base_fields
const withdrawalFields = value.withdrawal_fields
const resetMap = value.reset_map
const baseFieldsEmpty = Array.isArray(baseFields) && baseFields.length === 0
const withdrawalFieldsEmpty = !Array.isArray(withdrawalFields) || withdrawalFields.length === 0
const resetMapEmpty = !resetMap || (typeof resetMap === 'object' && !Array.isArray(resetMap) && Object.keys(resetMap).length === 0)
if (baseFieldsEmpty && withdrawalFieldsEmpty && resetMapEmpty) {
return fallbackRef
}
}
if (typeof value === 'string') {
return value
}
return JSON.stringify(value, null, 2).replace(/\n/g, '\n ')
}
function isEmptyObject(value) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return false
}
return Object.keys(value).length === 0
}
function formatSize(size) {
if (size < 1024) return `${size} B`
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`
......
......@@ -38,6 +38,37 @@ describe('parse-docs 生成逻辑', () => {
expect(result.code.includes("config: {\n category")).toBe(false)
})
it('generateConfigCode 按类型输出默认 schema 与 mapping', () => {
const savings_result = generateConfigCode({
product_name: '宏挚传承保障计划',
product_type: 'savings',
currency: 'USD',
payment_periods: ['整付'],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
is_savings: true,
withdrawal_modes: ['年龄指定金额'],
withdrawal_periods: ['1年'],
form_schema: { base_fields: [], withdrawal_fields: [], reset_map: {} },
submit_mapping: {}
})
const life_result = generateConfigCode({
product_name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
product_type: 'life-insurance',
currency: 'USD',
payment_periods: ['整付'],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
form_schema: { base_fields: [] },
submit_mapping: {}
})
expect(savings_result.code.includes('form_schema: savingsFormSchema')).toBe(true)
expect(savings_result.code.includes('submit_mapping: savingsSubmitMapping')).toBe(true)
expect(life_result.code.includes('form_schema: protectionFormSchema')).toBe(true)
expect(life_result.code.includes('submit_mapping: baseSubmitMapping')).toBe(true)
})
it('updateConfigContent 插入到 PLAN_TEMPLATES 末尾', () => {
const base_content = `export const PLAN_TEMPLATES = {
'a': {
......
......@@ -342,31 +342,30 @@ export const PLAN_TEMPLATES = {
},
/**
* 测试计划书-智享未来
* @added 2026-02-14T13:10:33.924Z
* @source docs/to-parse/测试计划书-智享未来.md
* 测试计划书-智享未来2
* @added 2026-02-14T14:12:31.658Z
* @source docs/to-parse/测试计划书-智享未来2.md
*/
'savings-product-30b41aae': {
name: '测试计划书-智享未来',
'savings-2-148b3acd': {
name: '测试计划书-智享未来2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: ["整付", "3年", "5年"],
payment_periods: ["整付","3年","5年"],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ["年龄指定金额", "最高固定金额"],
withdrawal_periods: ["1年", "3年", "5年", "10年"]
withdrawal_modes: ["年龄指定金额","最高固定金额"],
withdrawal_periods: ["1年","3年","5年","10年"]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
}
}
}}
/**
* 全局功能开关
......
......@@ -30,6 +30,10 @@ vi.mock('@/api/search', () => ({
searchAPI: vi.fn()
}))
vi.mock('@/config/app', () => ({
USE_MOCK_DATA: false
}))
const create_wrapper = () => {
const pinia = createPinia()
setActivePinia(pinia)
......
......@@ -81,6 +81,9 @@ function generateSavingsConfig(config, formSn, includeComment) {
const paymentPeriodsArray = JSON.stringify(config.payment_periods, null, 2)
const withdrawalModesArray = JSON.stringify(config.withdrawal_modes, null, 2)
const withdrawalPeriodsArray = JSON.stringify(config.withdrawal_periods, null, 2)
const { form_schema_ref, submit_mapping_ref } = resolveSchemaRefs(config)
const form_schema_code = buildSchemaCode(config.form_schema, form_schema_ref)
const submit_mapping_code = buildSchemaCode(config.submit_mapping, submit_mapping_ref)
return ` '${formSn}': {${comment}
name: '${config.product_name}',
......@@ -97,7 +100,9 @@ function generateSavingsConfig(config, formSn, includeComment) {
default_currency: '${config.currency}',
withdrawal_modes: ${withdrawalModesArray},
withdrawal_periods: ${withdrawalPeriodsArray}
}
},
form_schema: ${form_schema_code},
submit_mapping: ${submit_mapping_code}
}
}`
}
......@@ -113,6 +118,9 @@ function generateLifeInsuranceConfig(config, formSn, includeComment) {
// form_sn: ${formSn}` : ''
const paymentPeriodsArray = JSON.stringify(config.payment_periods, null, 2)
const { form_schema_ref, submit_mapping_ref } = resolveSchemaRefs(config)
const form_schema_code = buildSchemaCode(config.form_schema, form_schema_ref)
const submit_mapping_code = buildSchemaCode(config.submit_mapping, submit_mapping_ref)
return ` '${formSn}': {${comment}
name: '${config.product_name}',
......@@ -121,7 +129,9 @@ function generateLifeInsuranceConfig(config, formSn, includeComment) {
currency: '${config.currency}',
payment_periods: ${paymentPeriodsArray},
age_range: { min: ${config.age_range.min}, max: ${config.age_range.max} },
insurance_period: '${config.insurance_period}'
insurance_period: '${config.insurance_period}',
form_schema: ${form_schema_code},
submit_mapping: ${submit_mapping_code}
}
}`
}
......@@ -137,6 +147,9 @@ function generateCriticalIllnessConfig(config, formSn, includeComment) {
// form_sn: ${formSn}` : ''
const paymentPeriodsArray = JSON.stringify(config.payment_periods, null, 2)
const { form_schema_ref, submit_mapping_ref } = resolveSchemaRefs(config)
const form_schema_code = buildSchemaCode(config.form_schema, form_schema_ref)
const submit_mapping_code = buildSchemaCode(config.submit_mapping, submit_mapping_ref)
return ` '${formSn}': {${comment}
name: '${config.product_name}',
......@@ -145,7 +158,9 @@ function generateCriticalIllnessConfig(config, formSn, includeComment) {
currency: '${config.currency}',
payment_periods: ${paymentPeriodsArray},
age_range: { min: ${config.age_range.min}, max: ${config.age_range.max} },
insurance_period: '${config.insurance_period}'
insurance_period: '${config.insurance_period}',
form_schema: ${form_schema_code},
submit_mapping: ${submit_mapping_code}
}
}`
}
......@@ -205,6 +220,48 @@ function toCamelCase(str) {
.replace(/^(.)/, (c) => c.toLowerCase())
}
function resolveSchemaRefs(config) {
const isSavings = config?.is_savings || config?.product_type === 'savings'
if (isSavings) {
return {
form_schema_ref: 'savingsFormSchema',
submit_mapping_ref: 'savingsSubmitMapping'
}
}
return {
form_schema_ref: 'protectionFormSchema',
submit_mapping_ref: 'baseSubmitMapping'
}
}
function buildSchemaCode(value, fallbackRef) {
if (!value || isEmptyObject(value)) {
return fallbackRef
}
if (value && typeof value === 'object' && !Array.isArray(value)) {
const baseFields = value.base_fields
const withdrawalFields = value.withdrawal_fields
const resetMap = value.reset_map
const baseFieldsEmpty = Array.isArray(baseFields) && baseFields.length === 0
const withdrawalFieldsEmpty = !Array.isArray(withdrawalFields) || withdrawalFields.length === 0
const resetMapEmpty = !resetMap || (typeof resetMap === 'object' && !Array.isArray(resetMap) && Object.keys(resetMap).length === 0)
if (baseFieldsEmpty && withdrawalFieldsEmpty && resetMapEmpty) {
return fallbackRef
}
}
if (typeof value === 'string') {
return value
}
return JSON.stringify(value, null, 2).replace(/\n/g, '\n ')
}
function isEmptyObject(value) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return false
}
return Object.keys(value).length === 0
}
/**
* 批量生成配置代码
*
......