hookehuyr

docs(plan): 创建计划书模块完整文档和经验教训总结

- 创建计划书模块架构和经验教训总结文档
- 创建快速使用指南(5分钟快速集成)
- 更新架构文档,明确提取计划三层结构
- 添加 SavingsTemplate 组件(储蓄型产品模板)
- 修复 PlanFormContainer 中的模板导入路径

影响文件:
- docs/lessons-learned/plan-entry-module-summary.md (新增)
- docs/plan/plan-entry-quick-guide.md (新增)
- docs/CHANGELOG.md (更新)
- docs/plan/plan-entry-architecture.md (更新)
- src/components/PlanTemplates/SavingsTemplate.vue (新增)
- src/components/PlanFormContainer.vue (修复)
......@@ -5,6 +5,76 @@
---
## [2026-02-06] - 计划书生成模块架构与经验教训总结
### 文档
- 创建完整的计划书生成模块架构文档
- 总结所有历史问题和解决方案
- 整理最佳实践和常见陷阱
### 核心要点
- **架构模式**:配置驱动 + 动态组件加载
- **关键经验**:响应式表单、字段联动、事件处理
- **常见问题**:AmountInput 报错、提取计划字段结构、模板导入路径
---
## [2026-02-06] - 新增储蓄型保险计划书模板并修正提取计划逻辑
### 新增
- 创建 `SavingsTemplate.vue` 组件,支持 GS/GC/FA/LV2 等储蓄型产品
- 实现提取计划配置功能(完全符合产品需求)
### 修正
- 修正提取计划的字段结构,完全符合需求文档
- 修正提取计划的层级关系和字段联动逻辑
### 功能特性
- **基础字段**:性别、出生年月日、年龄、是否吸烟、年缴保费、缴费年期
- **提取计划**(完全符合小程序端需求):
1. **第一层确认**:是否希望生成一份容许减少名义金额的提取说明?(是/否)
2. **提取选项**(仅当选择"是"时显示):
- **指定提取金额**
- **按年岁**:由几岁开始、提取期(年)、每年递增提取之百分比(%)
- **按保单年度**:由几岁开始、提取期(年)
- **最高固定提取金额**
- **按年岁**:由几岁开始、提取期(年)
- **小程序端币种固定**:使用配置中的默认币种(HKD),不需要用户选择
### 字段说明
- `withdrawal_enabled`: 是否启用提取计划(是/否)
- `withdrawal_mode`: 提取选项(指定提取金额/最高固定提取金额)
- `specified_amount_type`: 指定提取金额的方式(按年岁/按保单年度)
- `withdrawal_start_age`: 开始提取年龄
- `withdrawal_period`: 提取年期
- `increase_rate`: 每年递增提取之百分比(%)
- `annual_amount`: 每年提取金额(小程序端不需要此字段)
### 技术实现
- 组件路径:`src/components/PlanTemplates/SavingsTemplate.vue`
- 使用 `computed` 动态读取提取计划配置
- 实现多层条件渲染:根据"是否启用"→"提取选项"→"提取方式"动态显示字段
- 使用 `watch` 监听字段变化,自动清理不相关数据
- 修复 `PlanFormContainer.vue` 中的 `SavingsTemplate` 导入路径
### 影响范围
- 新增文件:`src/components/PlanTemplates/SavingsTemplate.vue`
- 修改文件:`src/components/PlanFormContainer.vue`(修复导入路径)
### 修正原因
- 原实现缺少第一层确认("是否希望生成提取说明")
- 原实现缺少"按年岁/按保单年度"的子选项
- 原实现缺少"每年递增提取之百分比"字段
- 原实现将"提取币种"作为独立字段,实际小程序端不需要此字段(币种固定使用配置中的默认币种)
- 原实现字段分配错误:"按保单年度"在需求中只需要两个字段,不是四个字段
### 最终字段分配(小程序端)
- **按年岁**:由几岁开始、提取期(年)、每年递增提取之百分比(3个字段)
- **按保单年度**:由几岁开始、提取期(年)(2个字段)
- **最高固定提取金额**:按年岁:由几岁开始、提取期(年)(2个字段)
---
## [2026-02-06] - 修复保额输入组件输入报错及体验问题
### 修复
......
......@@ -4,10 +4,11 @@
**设计师**: Claude Code
**状态**: ✅ 已批准
**批准时间**: 2026-02-06
**版本**: v2.0
**版本**: v2.1
**最后更新**: 2026-02-06
**更新记录**
- v2.1 (2026-02-06): 更新储蓄型产品提取计划逻辑,明确三层结构和字段分布
- v2.0 (2026-02-06): 整合储蓄型产品(GS/GC/FA/LV2),新增提取计划功能
- v1.0 (2026-02-06): 初始版本,支持人寿保险和重疾保险产品
......@@ -97,19 +98,48 @@
- FA(宏浚傳承保障計劃)
- LV2(赤霞珠終身壽險計劃2)
- 核心信息:性别、年龄、出生年月日、是否吸烟
- 保额
- 保额(年缴保费)
- 缴费年期:各产品不同(详见配置文件)
- **提取计划功能**(所有储蓄产品通用):
- 方式 1:年龄指定金额(按年龄提取)
- 开始年龄(start_age)
- 提取年期(withdrawal_period)
- 每年提取金额(annual_amount)
- 币种(currency):支持 HKD、USD、CNY
- 增加率(increase_rate):每年提取金额的增长百分比
- 方式 2:最高固定金额(按年龄提取)
- 开始年龄(start_age)
- 提取年期(withdrawal_period)
- **多币种支持**:提取金额支持多币种(HKD、USD、CNY)
**三层结构**
**第一层**:是否希望生成一份容许减少名义金额的提取说明?(是/否)
**第二层**(选择"是"时显示):
- 提取选项(二选一):
1. 指定提取金额
2. 最高固定提取金额
**第三层**(根据第二层选择显示不同字段):
**A. 指定提取金额模式**
- 提取方式(二选一):
1. 按年岁
2. 按保单年度
- **按年岁**字段(3个):
- 由几岁开始(withdrawal_start_age)
- 提取期(年)(withdrawal_period)
- 每年递增提取之百分比(%)(increase_rate)
- **按保单年度**字段(2个):
- 由几岁开始(withdrawal_start_age)
- 提取期(年)(withdrawal_period)
**B. 最高固定提取金额模式**(2个字段):
- 按年岁:由几岁开始(withdrawal_start_age)
- 提取期(年)(withdrawal_period)
**⚠️ 小程序端特别说明**
- 币种固定(使用配置中的默认币种,如 HKD)
- 无需手动选择币种字段
- 无需"每年提取金额"字段(由后端计算)
**字段清理逻辑**
- 切换提取方式时,自动清除不相关字段
- 切换"按年岁"和"按保单年度"时,清除 annual_amount 和 increase_rate
- 选择"否"(不启用提取计划)时,清除所有提取计划相关字段
---
......@@ -357,100 +387,197 @@ src/
**业务场景**:储蓄型产品(GS/GC/FA/LV2)支持提取计划功能
**两种提取方式**
**三层结构**
#### 第一层:启用确认
**问题**:是否希望生成一份容许减少名义金额的提取说明?
**选项**:是 / 否(默认:否)
#### 第二层:提取选项(第一层选择"是"时显示)
**问题**:提取选项
**选项**
1. 指定提取金额
2. 最高固定提取金额
#### 第三层:具体字段(根据第二层选择显示不同字段)
#### 方式 1:年龄指定金额(按年龄提取)
##### A. 指定提取金额模式
**字段**
- `start_age`:开始提取年龄(数字)
- `withdrawal_period`:提取年期(数字,单位:年)
- `annual_amount`:每年提取金额(数字,单位:分)
- `currency`:币种(HKD/USD/CNY)
- `increase_rate`:增加率(百分比,如 5 表示 5%)
**子选项**:提取方式
**示例**
**选项**
1. 按年岁
2. 按保单年度
**按年岁字段**(3个):
```javascript
{
mode: 'specified_amount',
start_age: 60,
withdrawal_period: 10,
annual_amount: 5000000, // 50,000.00 HKD
currency: 'HKD',
increase_rate: 5 // 每年增长 5%
withdrawal_enabled: '是',
withdrawal_mode: '指定提取金额',
specified_amount_type: '按年岁',
withdrawal_start_age: 60, // 由几岁开始
withdrawal_period: '10年', // 提取期(年)
increase_rate: '5' // 每年递增提取之百分比(%)
}
```
#### 方式 2:最高固定金额(按年龄提取)
**按保单年度字段**(2个):
```javascript
{
withdrawal_enabled: '是',
withdrawal_mode: '指定提取金额',
specified_amount_type: '按保单年度',
withdrawal_start_age: 60, // 由几岁开始
withdrawal_period: '10年' // 提取期(年)
}
```
**字段**
- `start_age`:开始提取年龄(数字)
- `withdrawal_period`:提取年期(数字,单位:年)
##### B. 最高固定提取金额模式(2个字段)
**示例**
```javascript
{
mode: 'fixed_amount',
start_age: 60,
withdrawal_period: 10
withdrawal_enabled: '是',
withdrawal_mode: '最高固定提取金额',
withdrawal_start_age: 60, // 按年岁:由几岁开始
withdrawal_period: '10年' // 提取期(年)
}
```
**组件设计**
**⚠️ 小程序端特别说明**
- 币种固定(从配置文件读取,如 HKD),无需用户选择
- 无需"每年提取金额"字段(小程序端不需要)
- 字段清理逻辑:切换模式时自动清除不相关字段
**组件设计(三层结构)**
```vue
<template>
<div>
<!-- 提取方式选择 -->
<!-- 第一层:启用确认 -->
<PlanFieldRadio
v-model="withdrawalPlan.mode"
label="提取方式"
:options="['年龄指定金额', '最高固定金额']"
/>
<!-- 开始年龄 -->
<PlanFieldAgePicker
v-model="withdrawalPlan.start_age"
label="开始年龄"
placeholder="请选择开始提取年龄"
v-model="form.withdrawal_enabled"
label="是否希望生成一份容许减少名义金额的提取说明?"
:options="['是', '否']"
/>
<!-- 提取年期 -->
<PlanFieldSelect
v-model="withdrawalPlan.withdrawal_period"
label="提取年期"
placeholder="请选择提取年期"
:options="withdrawalPeriodOptions"
/>
<!-- 第二层 + 第三层:仅当选择"是"时显示 -->
<template v-if="form.withdrawal_enabled === '是'">
<h3>款项提取(容许减少名义金额)</h3>
<!-- 方式1:年龄指定金额 - 额外字段 -->
<template v-if="withdrawalPlan.mode === '年龄指定金额'">
<!-- 每年提取金额 -->
<PlanFieldAmount
v-model="withdrawalPlan.annual_amount"
label="每年提取金额"
placeholder="请输入金额"
:currency="withdrawalPlan.currency"
<!-- 第二层:提取选项 -->
<PlanFieldRadio
v-model="form.withdrawal_mode"
label="提取选项"
:options="['指定提取金额', '最高固定提取金额']"
@change="onWithdrawalModeChange"
/>
<!-- 币种 -->
<CurrencySelector
v-model="withdrawalPlan.currency"
label="币种"
:options="['HKD', 'USD', 'CNY']"
/>
<!-- 第三层 A:指定提取金额模式 -->
<template v-if="form.withdrawal_mode === '指定提取金额'">
<!-- 子选项:提取方式 -->
<PlanFieldRadio
v-model="form.specified_amount_type"
label="提取方式"
:options="['按年岁', '按保单年度']"
/>
<!-- 增加率 -->
<div>
<div class="text-sm text-gray-600 mb-2">增加率(%)</div>
<nut-input
v-model="withdrawalPlan.increase_rate"
type="digit"
placeholder="请输入增加率"
<!-- 按年岁字段 -->
<template v-if="form.specified_amount_type === '按年岁'">
<PlanFieldAgePicker
v-model="form.withdrawal_start_age"
label="由几岁开始"
placeholder="请输入开始提取年龄"
/>
<PlanFieldSelect
v-model="form.withdrawal_period"
label="提取期(年)"
placeholder="请选择提取期"
:options="withdrawalPeriods"
/>
<!-- 每年递增提取之百分比 -->
<div>
<div class="text-sm text-gray-700 mb-2">
每年递增提取之百分比(%)
</div>
<nut-input
v-model="form.increase_rate"
type="digit"
placeholder="请输入递增百分比"
/>
</div>
</template>
<!-- 按保单年度字段 -->
<template v-if="form.specified_amount_type === '按保单年度'">
<PlanFieldAgePicker
v-model="form.withdrawal_start_age"
label="由几岁开始"
placeholder="请输入开始提取年龄"
/>
<PlanFieldSelect
v-model="form.withdrawal_period"
label="提取期(年)"
placeholder="请选择提取期"
:options="withdrawalPeriods"
/>
</template>
</template>
<!-- 第三层 B:最高固定提取金额模式 -->
<template v-if="form.withdrawal_mode === '最高固定提取金额'">
<PlanFieldAgePicker
v-model="form.withdrawal_start_age"
label="按年岁:由几岁开始"
placeholder="请输入开始提取年龄"
/>
</div>
<PlanFieldSelect
v-model="form.withdrawal_period"
label="提取期(年)"
placeholder="请选择提取期"
:options="withdrawalPeriods"
/>
</template>
</template>
</div>
</template>
<script setup>
// 提取模式切换时的字段清理逻辑
const onWithdrawalModeChange = (mode) => {
if (mode === '最高固定提取金额') {
// 最高固定金额模式不需要指定金额的相关字段
delete form.specified_amount_type
delete form.increase_rate
}
}
// 监听提取方式变化
watch(() => form.specified_amount_type, (newType) => {
// 两种方式都不需要 annual_amount 和 increase_rate(小程序端不需要)
delete form.annual_amount
delete form.increase_rate
})
// 监听启用状态变化
watch(() => form.withdrawal_enabled, (newValue) => {
if (newValue === '否') {
// 清除所有提取计划相关字段
delete form.withdrawal_mode
delete form.specified_amount_type
delete form.withdrawal_start_age
delete form.withdrawal_period
delete form.increase_rate
}
})
</script>
```
---
......
# 计划书生成模块 - 快速使用指南
> **适用场景**:需要在页面中添加"计划书"功能
---
## 🚀 5分钟快速集成
### 1. 在页面中引入组件
```vue
<script setup>
import { ref } from 'vue'
import PlanFormContainer from '@/components/PlanFormContainer.vue'
const showPlanPopup = ref(false)
const selectedProduct = ref(null)
</script>
```
### 2. 绑定按钮点击事件
```vue
<template>
<!-- 在产品列表或产品详情页 -->
<nut-button
type="primary"
@click="openPlanPopup(product)"
>
计划书
</nut-button>
</template>
```
### 3. 打开计划书弹窗
```javascript
const openPlanPopup = (product) => {
// 确保产品有 form_sn 字段
if (!product.form_sn) {
console.error('产品缺少 form_sn 字段', product)
return
}
selectedProduct.value = product
showPlanPopup.value = true
}
```
### 4. 处理提交
```javascript
const handleClose = () => {
showPlanPopup.value = false
}
const handleSubmit = (formData) => {
console.log('提交计划书:', formData)
// 调用 API 提交
submitPlanAPI({
product_id: selectedProduct.value.id,
form_sn: selectedProduct.value.form_sn,
form_data: formData
})
handleClose()
}
```
### 5. 渲染弹窗组件
```vue
<template>
<PlanFormContainer
v-model:visible="showPlanPopup"
:product="selectedProduct"
@close="handleClose"
@submit="handleSubmit"
/>
</template>
```
---
## ✅ 完整示例
```vue
<template>
<div class="product-page">
<!-- 产品信息 -->
<nut-card>
<h2>{{ product.product_name }}</h2>
<nut-button
type="primary"
@click="openPlanPopup(product)"
>
生成计划书
</nut-button>
</nut-card>
<!-- 计划书弹窗 -->
<PlanFormContainer
v-model:visible="showPlanPopup"
:product="selectedProduct"
@close="handleClose"
@submit="handleSubmit"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import PlanFormContainer from '@/components/PlanFormContainer.vue'
import { submitPlanAPI } from '@/api/plan'
const product = ref({
id: 1,
product_name: "WIOP3E 盈传创富保障计划 3",
form_sn: "life-insurance-wiop3e"
})
const showPlanPopup = ref(false)
const selectedProduct = ref(null)
const openPlanPopup = (prod) => {
if (!prod.form_sn) {
Taro.showToast({
title: '产品配置错误',
icon: 'error'
})
return
}
selectedProduct.value = prod
showPlanPopup.value = true
}
const handleClose = () => {
showPlanPopup.value = false
}
const handleSubmit = async (formData) => {
try {
Taro.showLoading({ title: '提交中...' })
const res = await submitPlanAPI({
product_id: selectedProduct.value.id,
form_sn: selectedProduct.value.form_sn,
form_data: formData
})
if (res.code === 1) {
Taro.showToast({
title: '提交成功',
icon: 'success'
})
// 跳转到结果页
Taro.navigateTo({
url: `/pages/plan-submit-result/index?id=${res.data.plan_id}`
})
} else {
Taro.showToast({
title: res.msg || '提交失败',
icon: 'error'
})
}
} catch (err) {
console.error('提交计划书失败:', err)
Taro.showToast({
title: '网络错误',
icon: 'error'
})
} finally {
Taro.hideLoading()
}
handleClose()
}
</script>
```
---
## 🔧 添加新产品支持
### 步骤 1: 添加配置
`src/config/plan-templates.js` 中添加:
```javascript
export const PLAN_TEMPLATES = {
// ... 现有配置
'your-product-form-sn': {
name: '产品名称',
component: 'YourProductTemplate',
config: {
currency: 'USD',
payment_periods: ['5 年', '10 年'],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
}
}
```
### 步骤 2: 创建模板
`src/components/PlanTemplates/YourProductTemplate.vue`
```vue
<template>
<div v-if="config">
<!-- 使用通用字段组件 -->
<PlanFieldRadio v-model="form.gender" label="性别" :options="['男', '女']" />
<!-- ... 其他字段 -->
</div>
</template>
<script setup>
import { reactive, watch } from 'vue'
import PlanFieldRadio from '../PlanFields/RadioGroup.vue'
const props = defineProps({
modelValue: { type: Object, default: () => ({}) },
config: { type: Object, required: true }
})
const emit = defineEmits(['update:modelValue'])
const form = reactive({ ...props.modelValue })
watch(() => form, (newVal) => emit('update:modelValue', newVal), { deep: true })
</script>
```
### 步骤 3: 注册组件
`src/components/PlanFormContainer.vue` 中:
```javascript
import YourProductTemplate from './PlanTemplates/YourProductTemplate.vue'
const componentMap = {
// ... 现有组件
'YourProductTemplate': YourProductTemplate
}
```
---
## 🐛 常见问题
### Q1: 弹窗显示"未找到对应的计划书模版"
**原因**:产品缺少 `form_sn` 字段
**解决**
```javascript
// 检查产品数据
console.log(product.form_sn) // 应该有值,如 "life-insurance-wiop3e"
// 如果没有,联系后端添加
```
---
### Q2: 表单输入时出现 `value.replace is not a function`
**原因**:这是已修复的问题
**解决**:确保使用最新版本的 `AmountInput.vue`
---
### Q3: 年龄选择器显示为 018,提交需要 18
**说明**:这是正常行为
- **显示**:3 位数字格式(018)
- **提交**:普通数字(18)
组件会自动处理转换,无需手动处理。
---
### Q4: 提取计划字段显示不正确
**说明**:提取计划有复杂的条件渲染逻辑
- 第一层:是否启用(是/否)
- 第二层:提取选项(指定提取金额/最高固定提取金额)
- 第三层:具体方式(按年岁/按保单年度)
确保按顺序选择,相关字段会自动显示。
---
## 📚 更多信息
- [完整架构文档](./plan-entry-architecture.md)
- [经验教训总结](../lessons-learned/plan-entry-module-summary.md)
- [API 联调日志](../api-integration-log.md)
---
**最后更新**: 2026-02-06
# 计划书生成模块 - 完整总结与经验教训
**创建时间**: 2026-02-06
**模块**: 计划书生成(Plan Entry)
**状态**: ✅ 已完成
---
## 📐 模块架构
### 1. 整体架构图
```
PlanFormContainer.vue (表单容器)
↓ 根据 product.form_sn 动态加载
├─ LifeInsuranceTemplate.vue (人寿保险)
├─ CriticalIllnessTemplate.vue (重疾保险)
└─ SavingsTemplate.vue (储蓄型产品)
↓ 使用通用字段组件
└─ PlanFields/
├─ AgePicker.vue (年龄选择器)
├─ AmountInput.vue (保额输入)
├─ DatePicker.vue (日期选择器)
├─ RadioGroup.vue (单选组)
└─ SelectPicker.vue (下拉选择)
```
### 2. 核心文件
| 文件 | 用途 | 关键特性 |
|------|------|----------|
| `PlanFormContainer.vue` | 动态模板容器 | 根据 form_sn 加载模板 |
| `config/plan-templates.js` | 模板配置映射 | form_sn → 组件配置 |
| `PlanTemplates/*.vue` | 具体模板组件 | LifeInsurance/CriticalIllness/Savings |
| `PlanFields/*.vue` | 通用表单字段 | 可复用的表单组件 |
### 3. 配置驱动设计
**核心原则**:通过产品的 `form_sn` 字段自动识别并加载对应模板
```javascript
// 产品 API 返回
{
id: 1,
product_name: "WIOP3E 盈传创富保障计划 3",
form_sn: "life-insurance-wiop3e" // ← 关键字段
}
// 配置文件映射
export const PLAN_TEMPLATES = {
'life-insurance-wiop3e': {
name: 'WIOP3E...',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: ['整付(0-75 岁)', '5 年(0-70 岁)', ...]
}
}
}
```
---
## ⚠️ 关键问题与解决方案
### 问题 1: AmountInput 组件输入报错
**症状**
```
value.replace is not a function
TypeError: value.replace is not a function
```
**根本原因**
- NutUI 在小程序环境下,`@input` 事件返回的对象结构是 `{ detail: { value: "xxx" } }`
- 原代码直接对 `e` 调用 `.replace()`,没有提取实际的值
**✅ 正确做法**
```javascript
// src/components/PlanFields/AmountInput.vue
const onInput = (e) => {
// 防御性提取值(兼容 Web 和小程序)
const rawValue = e?.detail?.value || e?.target?.value || ''
// 转换为字符串再处理
const valueStr = String(rawValue)
// 处理输入逻辑
// ...
}
```
**关键要点**
- ✅ 始终从 `e.detail.value``e.target.value` 提取值
- ✅ 使用 `String()` 显式转换,避免类型错误
- ✅ 使用内部状态 `inputValue` 分离显示值和模型值
- ✅ 仅在 `@blur` 时进行严格格式化
---
### 问题 2: 提取计划字段结构错误(SavingsTemplate)
**历史问题**
经过多次修正,最初实现的字段结构与需求不符。
**最终正确的结构**(小程序端):
```javascript
// 第一层:是否启用提取计划
withdrawal_enabled: '是' | '否'
// 第二层:提取选项(仅当启用时显示)
withdrawal_mode: '指定提取金额' | '最高固定提取金额'
// 第三层:根据不同选项显示不同字段
if (withdrawal_mode === '指定提取金额') {
specified_amount_type: '按年岁' | '按保单年度'
if (specified_amount_type === '按年岁') {
withdrawal_start_age: number // 由几岁开始
withdrawal_period: string // 提取期(年)
increase_rate: string // 每年递增提取之百分比(%)
// ❌ 不需要:annual_amount(小程序端不需要此字段)
}
if (specified_amount_type === '按保单年度') {
withdrawal_start_age: number
withdrawal_period: string
// ❌ 不需要:annual_amount, increase_rate
}
}
if (withdrawal_mode === '最高固定提取金额') {
withdrawal_start_age: number // 按年岁:由几岁开始
withdrawal_period: string // 提取期(年)
}
```
**关键要点**
-**三层结构**:启用确认 → 提取选项 → 具体字段
-**小程序端币种固定**:使用配置中的 `default_currency`,不需要用户选择
-**字段按需显示**:根据用户选择动态显示相关字段
-**自动清理无关字段**:使用 `watch` 监听变化,删除不相关字段
**字段清理逻辑**
```javascript
// 当切换提取模式时
watch(
() => form.withdrawal_mode,
(mode) => {
if (mode === '最高固定提取金额') {
// 清除指定金额相关字段
delete form.specified_amount_type
delete form.annual_amount
delete form.increase_rate
}
}
)
// 当切换指定金额类型时
watch(
() => form.specified_amount_type,
() => {
// 小程序端不需要这些字段
delete form.annual_amount
delete form.increase_rate
}
)
// 当关闭提取计划时
watch(
() => form.withdrawal_enabled,
(enabled) => {
if (enabled === '否') {
// 清除所有提取计划字段
delete form.withdrawal_mode
delete form.specified_amount_type
delete form.withdrawal_start_age
delete form.withdrawal_period
delete form.annual_amount
delete form.increase_rate
}
}
)
```
---
### 问题 3: 模板组件导入路径错误
**症状**
```
Failed to resolve component: SavingsTemplate
```
**原因**
`PlanFormContainer.vue``SavingsTemplate` 的导入路径错误
**✅ 正确做法**
```javascript
// ❌ 错误
import SavingsTemplate from './PlanTemplates/SavingsTemplate.vue'
// ✅ 正确
import SavingsTemplate from './PlanTemplates/SavingsTemplate.vue'
// 或者使用路径别名
import SavingsTemplate from '@/components/PlanTemplates/SavingsTemplate.vue'
```
**关键要点**
- ✅ 检查组件导入路径是否正确
- ✅ 使用 VSCode 的"跳转到定义"功能验证路径
- ✅ 组件名称和文件名保持一致(PascalCase)
---
### 问题 4: 年龄计算和显示
**需求**
- 显示:3 位数字格式(018)
- 提交:普通数字(18)
**✅ 正确做法**
```javascript
// src/components/PlanFields/AgePicker.vue
// 显示格式化(转换为字符串,补齐3位)
const displayAge = computed(() => {
return String(props.modelValue || 0).padStart(3, '0')
})
// 提交时转换为数字
const onConfirm = ({ value }) => {
// value[0] 是 Picker 返回的数组,取出第一个值
const ageValue = value[0] || 0
// 发出数字值
emit('update:modelValue', Number(ageValue))
}
```
**关键要点**
- ✅ 显示和提交分开处理
- ✅ 使用 `padStart(3, '0')` 格式化显示
- ✅ 使用 `Number()` 转换提交值
- ✅ 兼容 iOS 日期格式(将 `-` 替换为 `/`
---
## 💡 核心设计模式
### 1. 响应式表单数据同步
**所有模板组件统一使用**
```javascript
import { reactive, watch } from 'vue'
const props = defineProps({
modelValue: { type: Object, default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
// 使用 reactive 创建响应式表单
const form = reactive({
...props.modelValue,
// 设置默认值
field1: props.modelValue.field1 || '默认值'
})
// 监听表单变化,同步到父组件
watch(
() => form,
(newVal) => emit('update:modelValue', newVal),
{ deep: true }
)
```
**关键要点**
- ✅ 使用 `reactive` 而非 `ref`(表单对象)
- ✅ 使用 `watch` + `deep: true` 监听对象变化
- ✅ 通过 `emit('update:modelValue')` 同步到父组件
- ✅ 父组件使用 `v-model` 绑定
---
### 2. 动态组件加载
**PlanFormContainer 中的实现**
```javascript
import { computed } from 'vue'
import LifeInsuranceTemplate from './PlanTemplates/LifeInsuranceTemplate.vue'
import CriticalIllnessTemplate from './PlanTemplates/CriticalIllnessTemplate.vue'
import SavingsTemplate from './PlanTemplates/SavingsTemplate.vue'
// 组件映射表
const componentMap = {
'LifeInsuranceTemplate': LifeInsuranceTemplate,
'CriticalIllnessTemplate': CriticalIllnessTemplate,
'SavingsTemplate': SavingsTemplate
}
// 根据配置动态选择组件
const currentTemplateComponent = computed(() => {
const componentName = templateConfig.value.component
return componentMap[componentName] || null
})
```
**关键要点**
- ✅ 使用 `computed` 动态计算组件
- ✅ 使用组件映射表(对象)而非 `if-else`
- ✅ 配置中的 `component` 字段与映射表 key 对应
- ✅ 使用 `<component :is="xxx">` 动态渲染
---
### 3. 配置驱动 + 后端覆盖
**设计原则**
- 前端配置文件提供默认值
- 后端 `plan_config` 可覆盖前端配置
```javascript
// PlanFormContainer.vue
const templateConfig = computed(() => {
// 从配置文件中查找
const config = PLAN_TEMPLATES[props.product.form_sn]
if (!config) return null
// 合并配置:后端优先
return {
...config,
config: {
...config.config, // 前端默认配置
...(props.product.plan_config || {}) // 后端覆盖配置
}
}
})
```
**关键要点**
- ✅ 前端配置作为后备(fallback)
- ✅ 后端配置优先(通过展开运算符覆盖)
- ✅ 支持部分覆盖(如只覆盖 `currency`,其他用默认值)
---
## 📋 完整使用流程
### 1. 在页面中使用
```vue
<script setup>
import { ref } from 'vue'
import PlanFormContainer from '@/components/PlanFormContainer.vue'
const showPlanPopup = ref(false)
const selectedProduct = ref(null)
const openPlanPopup = (product) => {
selectedProduct.value = product
showPlanPopup.value = true
}
const handleClose = () => {
showPlanPopup.value = false
}
const handleSubmit = (formData) => {
console.log('提交计划书:', formData)
// 调用提交 API
// submitPlanAPI({
// product_id: selectedProduct.value.id,
// form_sn: selectedProduct.value.form_sn,
// form_data: formData
// })
}
</script>
<template>
<button @click="openPlanPopup(product)">计划书</button>
<PlanFormContainer
v-model:visible="showPlanPopup"
:product="selectedProduct"
@close="handleClose"
@submit="handleSubmit"
/>
</template>
```
---
### 2. 添加新产品配置
**步骤**
1. **确认产品的 `form_sn`**
```javascript
// 从产品 API 中查看
{
id: 10,
product_name: "新产品",
form_sn: "new-product-form-sn" // ← 记住这个值
}
```
2. **在 `config/plan-templates.js` 中添加配置**
```javascript
export const PLAN_TEMPLATES = {
// ... 其他配置
'new-product-form-sn': {
name: '新产品名称',
component: 'NewProductTemplate',
config: {
currency: 'USD',
payment_periods: ['5 年', '10 年'],
age_range: { min: 0, max: 65 },
insurance_period: '终身'
}
}
}
```
3. **创建模板组件**
```vue
<!-- src/components/PlanTemplates/NewProductTemplate.vue -->
<template>
<div v-if="config">
<!-- 使用通用字段组件 -->
<PlanFieldRadio v-model="form.gender" label="性别" :options="['男', '女']" />
<!-- ... 其他字段 -->
</div>
</template>
<script setup>
import { reactive, watch } from 'vue'
import PlanFieldRadio from '../PlanFields/RadioGroup.vue'
// ... 导入其他字段组件
const props = defineProps({
modelValue: { type: Object, default: () => ({}) },
config: { type: Object, required: true }
})
const emit = defineEmits(['update:modelValue'])
const form = reactive({ ...props.modelValue })
watch(() => form, (newVal) => emit('update:modelValue', newVal), { deep: true })
</script>
```
4. **在 `PlanFormContainer.vue` 中导入**
```javascript
import NewProductTemplate from './PlanTemplates/NewProductTemplate.vue'
const componentMap = {
// ... 其他组件
'NewProductTemplate': NewProductTemplate
}
```
---
## 🎯 最佳实践总结
### 1. 表单字段组件开发
**必须遵循**:
- ✅ 使用 `v-model` 双向绑定
- ✅ 使用 `<script setup>` 语法
- ✅ Props 必须有类型和默认值
- ✅ Emits 必须定义事件名
- ✅ 添加详细的 JSDoc 注释
**示例**:
```vue
<script setup>
/**
* 年龄选择器
*
* @description 使用 NutUI Picker 选择年龄,显示 3 位数字格式,提交普通数字
* @author Claude Code
* @example
* <AgePicker
* v-model="age"
* label="年龄"
* placeholder="请选择年龄"
* />
*/
import { computed } from 'vue'
const props = defineProps({
/**
* 年龄值(数字)
* @type {number}
* @default 0
*/
modelValue: {
type: Number,
default: 0
},
/**
* 标签文本
* @type {string}
*/
label: {
type: String,
required: true
},
/**
* 占位符文本
* @type {string}
*/
placeholder: {
type: String,
default: ''
}
})
const emit = defineEmits([
/**
* 更新年龄值事件
* @event update:modelValue
* @param {number} value - 年龄值
*/
'update:modelValue'
])
// ... 组件逻辑
</script>
```
---
### 2. 模板组件开发
**必须遵循**:
- ✅ 只负责字段布局,不包含复杂逻辑
- ✅ 使用通用字段组件组合
- ✅ 表单数据统一管理在 `form` 对象中
- ✅ 使用 `watch` 同步数据变化
**❌ 避免**:
- ❌ 在模板组件中直接调用 API
- ❌ 在模板组件中处理提交逻辑
- ❌ 过度复杂的条件渲染(超过3层)
---
### 3. 配置文件管理
**必须遵循**:
- ✅ 每个产品必须有唯一的 `form_sn`
- ✅ 配置必须有默认值(后端不传时的后备)
- ✅ 币种使用标准代码(USD/CNY/HKD/EUR)
- ✅ 导出工具函数(`getTemplateConfig`、`getCurrencySymbol`)
---
### 4. 响应式数据管理
**必须遵循**:
- ✅ 表单对象使用 `reactive`
- ✅ 监听对象使用 `deep: true`
- ✅ 删除属性使用 `delete form.xxx`(不是 `form.xxx = undefined`)
- ✅ 条件渲染使用 `v-if` 而非 `v-show`(表单字段)
---
## 🚨 常见陷阱
### 陷阱 1: 直接修改 props
**❌ 错误**:
```javascript
const props = defineProps({ modelValue: Object })
props.modelValue.field = 'value' // 直接修改 props
```
**✅ 正确**:
```javascript
const form = reactive({ ...props.modelValue })
form.field = 'value' // 修改响应式对象
// 通过 watch 同步到父组件
watch(() => form, (newVal) => emit('update:modelValue', newVal), { deep: true })
```
---
### 陷阱 2: 忘记清理字段
**❌ 错误**:
```javascript
// 切换模式时,旧字段仍然存在
watch(() => form.mode, (mode) => {
if (mode === 'mode_a') {
// 添加新字段
form.field_a = 'value'
}
// 忘记删除 mode_b 的字段
})
```
**✅ 正确**:
```javascript
watch(() => form.mode, (mode) => {
if (mode === 'mode_a') {
form.field_a = 'value'
delete form.field_b // 清理无关字段
} else if (mode === 'mode_b') {
form.field_b = 'value'
delete form.field_a // 清理无关字段
}
})
```
---
### 陷阱 3: 事件对象处理不一致
**❌ 错误**:
```javascript
const onInput = (e) => {
// 直接使用 e,可能在 Web 和小程序中表现不同
const value = e.value
}
```
**✅ 正确**:
```javascript
const onInput = (e) => {
// 防御性提取值,兼容 Web 和小程序
const value = e?.detail?.value || e?.target?.value || ''
// 显式转换为字符串
const valueStr = String(value)
}
```
---
### 陷阱 4: 日期计算不兼容 iOS
**❌ 错误**:
```javascript
const birthDate = new Date('1990-01-01') // iOS 可能不支持
```
**✅ 正确**:
```javascript
const dateStr = birthday.replace(/-/g, '/') // 转换为 1990/01/01
const birthDate = new Date(dateStr)
if (!Number.isNaN(birthDate.getTime())) {
// 安全使用
}
```
---
## 📚 参考文档
### 项目文档
- [计划书架构设计](../plan/plan-entry-architecture.md)
- [API 联调日志](../api-integration-log.md)
- [变更日志](../CHANGELOG.md)
### 技术文档
- [Taro 官方文档](https://docs.taro.zone/)
- [NutUI Taro 文档](https://nutui.jd.com/4/taro/)
- [Vue 3 官方文档](https://cn.vuejs.org/)
### 经验教训
- [项目全局经验教训](../lessons-learned.md)
---
## ✅ 验收清单
### 功能完整性
- [ ] 所有产品类型都能正确加载对应模板
- [ ] 表单字段联动正常(出生日期 → 年龄)
- [ ] 提取计划多层条件渲染正确
- [ ] 表单数据同步正常(v-model)
- [ ] 提交数据格式正确
### 代码质量
- [ ] 所有组件都有 JSDoc 注释
- [ ] 所有函数都有类型定义
- [ ] 没有 `console.log``debugger`
- [ ] 命名清晰,符合规范
- [ ] 组件职责单一
### 测试覆盖
- [ ] 测试不同产品的模板加载
- [ ] 测试表单字段输入和验证
- [ ] 测试字段联动逻辑
- [ ] 测试提交流程
- [ ] 测试边界情况(空值、异常值)
---
**文档版本**: v1.0
**创建时间**: 2026-02-06
**最后更新**: 2026-02-06
......@@ -43,7 +43,7 @@ import { ref, computed, watch } from 'vue'
import PlanPopup from './PlanPopup/index.vue'
import LifeInsuranceTemplate from './PlanTemplates/LifeInsuranceTemplate.vue'
import CriticalIllnessTemplate from './PlanTemplates/CriticalIllnessTemplate.vue'
import SavingsTemplate from './SavingsTemplate.vue'
import SavingsTemplate from './PlanTemplates/SavingsTemplate.vue'
import { PLAN_TEMPLATES } from '@/config/plan-templates'
/**
......
<template>
<div v-if="config">
<!-- 性别 -->
<PlanFieldRadio
v-model="form.gender"
label="性别"
:options="['男', '女']"
class="mb-5"
/>
<!-- 出生年月日 -->
<PlanFieldDatePicker
v-model="form.birthday"
label="出生年月日"
placeholder="请选择日期"
@change="onBirthdayChange"
class="mb-5"
/>
<!-- 年龄(根据出生日期自动计算,可编辑) -->
<PlanFieldAgePicker
v-model="form.age"
label="年龄"
placeholder="请选择出生日期自动计算"
class="mb-5"
/>
<!-- 是否吸烟 -->
<PlanFieldRadio
v-model="form.smoker"
label="是否吸烟"
:options="['是', '否']"
class="mb-5"
/>
<!-- 保额(年缴保费) -->
<PlanFieldAmount
v-model="form.coverage"
label="年缴保费"
placeholder="请输入年缴保费"
:currency="config.currency"
class="mb-5"
/>
<!-- 缴费年期 -->
<PlanFieldSelect
v-model="form.payment_period"
label="缴费年期"
placeholder="请选择缴费年期"
:options="config.payment_periods"
class="mb-5"
/>
<!-- 分割线 -->
<div class="border-t border-gray-200 my-6"></div>
<!-- 提取计划配置 -->
<div v-if="config.withdrawal_plan?.enabled" class="withdrawal-plan-section">
<!-- 第一层:是否希望生成一份容许减少名义金额的提取说明? -->
<PlanFieldRadio
v-model="form.withdrawal_enabled"
label="是否希望生成一份容许减少名义金额的提取说明?"
:options="['是', '否']"
class="mb-5"
/>
<!-- 仅当选择"是"时才显示以下内容 -->
<template v-if="form.withdrawal_enabled === '是'">
<h3 class="text-base font-semibold text-gray-900 mb-4">款项提取(容许减少名义金额)</h3>
<!-- 提取选项:指定提取金额 / 最高固定提取金额 -->
<PlanFieldRadio
v-model="form.withdrawal_mode"
label="提取选项"
:options="['指定提取金额', '最高固定提取金额']"
@change="onWithdrawalModeChange"
class="mb-5"
/>
<!-- 指定提取金额模式 -->
<template v-if="form.withdrawal_mode === '指定提取金额'">
<!-- 按年岁/按保单年度选择 -->
<PlanFieldRadio
v-model="form.specified_amount_type"
label="提取方式"
:options="['按年岁', '按保单年度']"
class="mb-5"
/>
<!-- 按年岁 -->
<template v-if="form.specified_amount_type === '按年岁'">
<!-- 由几岁开始 -->
<PlanFieldAgePicker
v-model="form.withdrawal_start_age"
label="由几岁开始"
placeholder="请输入开始提取年龄"
class="mb-5"
/>
<!-- 提取期(年) -->
<PlanFieldSelect
v-model="form.withdrawal_period"
label="提取期(年)"
placeholder="请选择提取期"
:options="withdrawalPeriods"
class="mb-5"
/>
<!-- 每年递增提取之百分比 -->
<div class="mb-5">
<div class="text-sm text-gray-700 mb-2">每年递增提取之百分比(%</div>
<nut-input
v-model="form.increase_rate"
type="digit"
placeholder="请输入递增百分比"
class="w-full"
/>
</div>
</template>
<!-- 按保单年度 -->
<template v-if="form.specified_amount_type === '按保单年度'">
<!-- 由几岁开始 -->
<PlanFieldAgePicker
v-model="form.withdrawal_start_age"
label="由几岁开始"
placeholder="请输入开始提取年龄"
class="mb-5"
/>
<!-- 提取期(年) -->
<PlanFieldSelect
v-model="form.withdrawal_period"
label="提取期(年)"
placeholder="请选择提取期"
:options="withdrawalPeriods"
class="mb-5"
/>
</template>
</template>
<!-- 最高固定提取金额模式 -->
<template v-if="form.withdrawal_mode === '最高固定提取金额'">
<!-- 按年岁:由几岁开始 -->
<PlanFieldAgePicker
v-model="form.withdrawal_start_age"
label="按年岁:由几岁开始"
placeholder="请输入开始提取年龄"
class="mb-5"
/>
<!-- 提取期(年) -->
<PlanFieldSelect
v-model="form.withdrawal_period"
label="提取期(年)"
placeholder="请选择提取期"
:options="withdrawalPeriods"
class="mb-5"
/>
</template>
</template>
</div>
</div>
<!-- 配置缺失提示 -->
<div v-else class="text-center text-gray-500 py-10">
<p>⚠️ 模版配置未找到</p>
<p class="text-sm mt-2">请检查产品配置或联系开发人员</p>
</div>
</template>
<script setup>
/**
* 储蓄型保险计划书模版
*
* @description GS/GC/FA/LV2 等储蓄型保险产品的计划书录入表单
* - 支持出生日期自动计算年龄
* - 支持提取计划配置(多种提取模式和方式)
* - 表单字段:性别、年龄、出生年月日、是否吸烟、保额、缴费年期
* - 提取计划:指定提取金额(按年岁/按保单年度)、最高固定提取金额
* - 小程序端币种固定(使用配置中的默认币种)
* @author Claude Code
* @example
* <SavingsTemplate
* v-model="formData"
* :config="templateConfig"
* />
*/
import { reactive, watch, computed } from 'vue'
import PlanFieldAgePicker from '../PlanFields/AgePicker.vue'
import PlanFieldAmount from '../PlanFields/AmountInput.vue'
import PlanFieldDatePicker from '../PlanFields/DatePicker.vue'
import PlanFieldRadio from '../PlanFields/RadioGroup.vue'
import PlanFieldSelect from '../PlanFields/SelectPicker.vue'
/**
* 组件属性
*/
const props = defineProps({
/**
* 表单数据对象
* @type {Object}
*/
modelValue: {
type: Object,
default: () => ({})
},
/**
* 模版配置
* @type {Object}
* @property {string} currency - 币种代码
* @property {Array<string>} payment_periods - 缴费年期选项
* @property {Object} age_range - 年龄范围 { min, max }
* @property {string} insurance_period - 保险期间
* @property {Object} withdrawal_plan - 提取计划配置
* @property {boolean} withdrawal_plan.enabled - 是否启用提取计划
* @property {Array<string>} withdrawal_plan.currencies - 支持的币种
* @property {string} withdrawal_plan.default_currency - 默认币种
* @property {Array<string>} withdrawal_plan.withdrawal_modes - 提取模式
* @property {Array<string>} withdrawal_plan.withdrawal_periods - 提取年期
*/
config: {
type: Object,
required: true
}
})
/**
* 组件事件
*/
const emit = defineEmits([
/**
* 更新表单数据事件
* @event update:modelValue
* @param {Object} value - 表单数据
*/
'update:modelValue'
])
/**
* 表单数据
* @type {Object}
*/
const form = reactive({
...props.modelValue,
// 默认值
withdrawal_enabled: props.modelValue.withdrawal_enabled || '否',
withdrawal_mode: props.modelValue.withdrawal_mode || '指定提取金额',
specified_amount_type: props.modelValue.specified_amount_type || '按年岁'
})
/**
* 监听表单数据变化,同步到父组件
*/
watch(
() => form,
(newVal) => emit('update:modelValue', newVal),
{ deep: true }
)
/**
* 默认币种(从配置读取)
* @type {ComputedRef<string>}
*/
const defaultCurrency = computed(() => {
return props.config?.withdrawal_plan?.default_currency || 'HKD'
})
/**
* 提取年期选项(从配置读取)
* @type {ComputedRef<Array<string>>}
*/
const withdrawalPeriods = computed(() => {
return props.config?.withdrawal_plan?.withdrawal_periods || [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
})
/**
* 出生日期变化时自动计算年龄
* @param {string} birthday - 出生日期(格式:YYYY-MM-DD)
*
* @description 用户选择出生日期后,自动计算并填充年龄字段
* 计算公式:当前年份 - 出生年份
*/
const onBirthdayChange = (birthday) => {
if (birthday) {
// 兼容 iOS 的日期格式 (YYYY/MM/DD)
const dateStr = birthday.replace(/-/g, '/')
const birthDate = new Date(dateStr)
if (!Number.isNaN(birthDate.getTime())) {
const birthYear = birthDate.getFullYear()
const currentYear = new Date().getFullYear()
const calculatedAge = currentYear - birthYear
// 自动填充年龄字段(确保非负)
form.age = Math.max(0, calculatedAge)
}
}
}
/**
* 提取模式变化时的处理
* @param {string} mode - 新的提取模式
*
* @description 当用户切换提取模式时,清除不相关的字段
* - 切换到"指定提取金额":保留字段,等待用户选择子选项
* - 切换到"最高固定金额":清除指定金额相关字段
*/
const onWithdrawalModeChange = (mode) => {
if (mode === '最高固定提取金额') {
// 最高固定金额模式不需要指定金额的相关字段
delete form.specified_amount_type
delete form.annual_amount
delete form.increase_rate
} else if (mode === '指定提取金额') {
// 指定提取金额模式,等待用户选择按年岁或按保单年度
// 保留现有字段
}
}
/**
* 监听指定提取金额方式变化
* @description 当用户在"按年岁"和"按保单年度"之间切换时,清理不相关字段
*/
watch(
() => form.specified_amount_type,
(newType) => {
// 两种方式都不需要 annual_amount 和 increase_rate(小程序端不需要)
delete form.annual_amount
delete form.increase_rate
}
)
/**
* 监听提取计划启用状态变化
* @description 当用户选择"否"时,清除所有提取计划相关字段
*/
watch(
() => form.withdrawal_enabled,
(newValue) => {
if (newValue === '否') {
// 清除所有提取计划相关字段
delete form.withdrawal_mode
delete form.specified_amount_type
delete form.withdrawal_start_age
delete form.withdrawal_period
delete form.annual_amount
delete form.increase_rate
}
}
)
</script>
<style lang="less" scoped>
/* 提取计划区域样式 */
.withdrawal-plan-section {
/* 可以在这里添加特殊的样式 */
}
</style>
......@@ -103,17 +103,19 @@ export const PLAN_TEMPLATES = {
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏摯傳承保障計劃
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏摯傳承保障計劃',
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付(0-100 岁)',
'5 年(0-80 岁)',
'10 年(0-75 岁)'
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
......@@ -140,17 +142,17 @@ export const PLAN_TEMPLATES = {
}
},
// GC - 宏摯家傳承保險計劃
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏摯家傳承保險計劃',
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付(0-100 岁)',
'5 年(0-80 岁)',
'10 年(0-75 岁)'
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
......@@ -173,17 +175,17 @@ export const PLAN_TEMPLATES = {
}
},
// FA - 宏浚傳承保障計劃
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚傳承保障計劃',
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付(0-100 岁)',
'5 年(0-80 岁)',
'10 年(0-75 岁)'
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
......@@ -206,17 +208,18 @@ export const PLAN_TEMPLATES = {
}
},
// LV2 - 赤霞珠終身壽險計劃2(储蓄型终身寿险)
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠終身壽險計劃2',
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付(0-100 岁)',
'5 年(0-80 岁)',
'10 年(0-75 岁)'
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
......