hookehuyr

refactor(plan): 重构表单提交逻辑,使用模板字段映射

- 重构 PlanFormContainer.vue 的 submit 函数,使用模板配置的 submit_mapping
- 新增完整的储蓄计划书模板 SavingsTemplate.vue
- 在 plan-templates.js 中添加详细的字段映射配置
- 更新 README.md 和 CHANGELOG.md
- 新增 plan-form-schema-usage.md 使用说明文档

影响文件:
- src/components/plan/PlanFormContainer.vue
- src/components/plan/PlanTemplates/SavingsTemplate.vue
- src/config/plan-templates.js
- docs/plan/plan-form-schema-usage.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
7 - **[经验教训总结](docs/lessons-learned.md)** - Taro 项目开发经验、最佳实践和常见陷阱 7 - **[经验教训总结](docs/lessons-learned.md)** - Taro 项目开发经验、最佳实践和常见陷阱
8 - **[CLAUDE.md](CLAUDE.md)** - 项目开发指南(供 Claude Code 使用) 8 - **[CLAUDE.md](CLAUDE.md)** - 项目开发指南(供 Claude Code 使用)
9 - **[文档导航](docs/README.md)** - 项目文档索引与使用建议 9 - **[文档导航](docs/README.md)** - 项目文档索引与使用建议
10 +- **[计划书表单 Schema 使用文档](docs/plan/plan-form-schema-usage.md)** - 计划书表单配置与扩展指南
10 11
11 ## 🚀 快速开始 12 ## 🚀 快速开始
12 13
...@@ -50,6 +51,10 @@ pnpm lint ...@@ -50,6 +51,10 @@ pnpm lint
50 51
51 ## 🆕 最新更新(2026-02-14) 52 ## 🆕 最新更新(2026-02-14)
52 53
54 +### 计划书表单演进
55 +-**Schema 驱动** - 储蓄类模板字段由配置驱动渲染与校验
56 +-**提交映射下沉** - 提交字段映射从容器迁移到模板配置
57 +
53 ### 字段命名优化 58 ### 字段命名优化
54 -**提取方式字段** - 统一将 specified_amount_type 重命名为 withdrawal_method 59 -**提取方式字段** - 统一将 specified_amount_type 重命名为 withdrawal_method
55 -**文档同步** - 更新提取计划相关文档字段示例 60 -**文档同步** - 更新提取计划相关文档字段示例
......
1 +## [2026-02-14] - 计划书Schema注释与使用文档
2 +
3 +### 更新
4 +- 补充计划书Schema与提交映射的详细注释与JSDoc
5 +- 新增计划书Schema使用文档,便于新增保险类型
6 +- README 补充相关文档入口
7 +
8 +---
9 +
10 +**详细信息**
11 +- **影响文件**: src/config/plan-templates.js, src/components/plan/PlanTemplates/SavingsTemplate.vue, src/components/plan/PlanFormContainer.vue, docs/plan/plan-form-schema-usage.md, README.md
12 +- **技术栈**: Vue 3, Taro 4
13 +- **测试状态**: 待测试
14 +- **备注**: Schema 配置与提交映射均有详细说明
15 +
16 +---
17 +
18 +## [2026-02-14] - 计划书表单 Schema 化(方案2)
19 +
20 +### 更新
21 +- 储蓄类模板使用表单 Schema 驱动字段渲染与校验
22 +- 提交字段映射迁移到模板配置,统一处理金额转换
23 +- 提取模式切换清空逻辑改为配置驱动
24 +
25 +---
26 +
27 +**详细信息**
28 +- **影响文件**: src/config/plan-templates.js, src/components/plan/PlanTemplates/SavingsTemplate.vue, src/components/plan/PlanFormContainer.vue, README.md
29 +- **技术栈**: Vue 3, Taro 4
30 +- **测试状态**: 待测试
31 +- **备注**: 储蓄类产品字段新增仅需调整配置
32 +
33 +---
34 +
1 ## [2026-02-14] - 文档对齐与业务说明更新 35 ## [2026-02-14] - 文档对齐与业务说明更新
2 36
3 ### 更新 37 ### 更新
......
1 +# 计划书表单 Schema 使用文档
2 +
3 +## 1. 文档目标
4 +用于说明计划书表单的 Schema 配置规范、字段类型、联动规则与提交映射,便于后续新增或扩展不同保险类型时快速落地。
5 +
6 +## 2. 核心思路
7 +- 统一由 Schema 描述字段渲染、校验与联动
8 +- 统一由 submit_mapping 处理字段到 API 字段的映射与金额转换
9 +- 模板组件只负责“渲染与校验”,不再硬编码字段逻辑
10 +
11 +## 3. Schema 结构
12 +```javascript
13 +// Schema 基础结构
14 +const form_schema = {
15 + // 基础字段
16 + base_fields: [
17 + {
18 + id: 'customer_name',
19 + key: 'customer_name',
20 + type: 'name',
21 + label: '申请人',
22 + placeholder: '请输入申请人',
23 + required: true
24 + }
25 + ],
26 + // 提取计划字段(可选)
27 + withdrawal_fields: [],
28 + // 联动清空规则(可选)
29 + reset_map: {}
30 +}
31 +```
32 +
33 +## 4. 字段类型说明
34 +| type | 组件 | 说明 |
35 +| --- | --- | --- |
36 +| name | NameInput | 姓名输入 |
37 +| radio | RadioGroup | 单选 |
38 +| date | DatePickerGlobal | 日期选择 |
39 +| amount | AmountKeyboard | 金额键盘输入(内部存分) |
40 +| age | AgePickerGlobal | 年龄选择 |
41 +| select | SelectPickerGlobal | 下拉选择 |
42 +| payment_period | PaymentPeriodRadio | 缴费年期 |
43 +| percentage | NutInput | 百分比输入 |
44 +
45 +## 5. 字段属性说明
46 +```javascript
47 +// 字段属性示例
48 +{
49 + id: 'coverage',
50 + key: 'coverage',
51 + type: 'amount',
52 + label: '年缴保费',
53 + placeholder: '请输入年缴保费',
54 + input_label: '请输入年缴保费金额',
55 + required: true,
56 + // 可从配置读取币种
57 + currency_from: 'currency',
58 + // 控制显示条件
59 + show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }],
60 + // 默认值
61 + default: '否',
62 + // 标题分组
63 + section_title: '款项提取(允许减少名义金额)'
64 +}
65 +```
66 +
67 +## 6. 联动规则与清空逻辑
68 +```javascript
69 +// 提取模式切换后,按规则清空脏字段
70 +const reset_map = {
71 + withdrawal_mode: {
72 + '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age', 'withdrawal_period'],
73 + '指定提取金额': ['withdrawal_start_age', 'withdrawal_period']
74 + }
75 +}
76 +```
77 +
78 +## 7. 提交字段映射
79 +```javascript
80 +// submit_mapping 示例(金额字段统一从分转元)
81 +const submit_mapping = {
82 + coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
83 + annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
84 + withdrawal_mode: { api_field: 'withdrawal_option' }
85 +}
86 +```
87 +
88 +## 8. 使用示例
89 +```vue
90 +<!-- 储蓄型模板使用示例 -->
91 +<template>
92 + <SavingsTemplate v-model="form_data" :config="template_config" />
93 +</template>
94 +
95 +<script setup>
96 +// 表单数据
97 +const form_data = ref({})
98 +
99 +// 模板配置(通常来自 plan-templates.js)
100 +const template_config = {
101 + currency: 'USD',
102 + payment_periods: ['整付', '5 年'],
103 + withdrawal_plan: {
104 + enabled: true,
105 + default_currency: 'USD',
106 + withdrawal_periods: ['1年', '2年', '终身']
107 + },
108 + form_schema: {},
109 + submit_mapping: {}
110 +}
111 +</script>
112 +```
113 +
114 +## 9. 新增保险类型流程
115 +1.`src/config/plan-templates.js` 新增产品项(配置 form_sn)
116 +2. 为该产品选择已有模板组件或新增模板组件
117 +3. 定义 `form_schema``submit_mapping`
118 +4. 在模板组件内使用 Schema 渲染(仅需接入通用逻辑)
119 +5. 验证校验与提交映射
120 +
121 +## 10. 新增产品配置示例
122 +```javascript
123 +// 示例:新增储蓄类产品配置
124 +'savings-new': {
125 + name: '示例储蓄产品',
126 + component: 'SavingsTemplate',
127 + category: 'savings',
128 + config: {
129 + currency: 'USD',
130 + payment_periods: ['整付', '5 年'],
131 + withdrawal_plan: {
132 + enabled: true,
133 + default_currency: 'USD',
134 + withdrawal_periods: ['1年', '2年', '终身']
135 + },
136 + form_schema: savingsFormSchema,
137 + submit_mapping: savingsSubmitMapping
138 + }
139 +}
140 +```
141 +
142 +## 11. 常见扩展点
143 +- 新字段:仅在 form_schema 增加字段并补充 submit_mapping
144 +- 新联动:在 show_when 与 reset_map 中定义条件
145 +- 新模板:复用现有字段组件,保持 schema 结构一致
...@@ -237,7 +237,11 @@ const close = async () => { ...@@ -237,7 +237,11 @@ const close = async () => {
237 console.log('[PlanFormContainer] 弹窗已关闭,表单已重置') 237 console.log('[PlanFormContainer] 弹窗已关闭,表单已重置')
238 } 238 }
239 239
240 -// 提交表单 - 将表单数据和产品信息提交到后端 API 240 +/**
241 + * 提交表单
242 + * @description 将表单数据与产品信息组装后提交到后端
243 + * @returns {Promise<boolean>} 是否提交成功
244 + */
241 const submit = async () => { 245 const submit = async () => {
242 if (!props.product) { 246 if (!props.product) {
243 console.error('[PlanFormContainer] 无法提交: 产品数据为空') 247 console.error('[PlanFormContainer] 无法提交: 产品数据为空')
...@@ -264,24 +268,22 @@ const submit = async () => { ...@@ -264,24 +268,22 @@ const submit = async () => {
264 }) 268 })
265 269
266 try { 270 try {
267 - // 字段名映射:将表单字段名映射为 API 期望的字段名 271 + // 默认字段映射:模板未提供 submit_mapping 时使用
268 - // 根据 API 文档 (docs/api-specs/plan/add.md) 定义 272 + const defaultMapping = {
269 - const fieldMapping = { 273 + customer_name: { api_field: 'customer_name' },
270 - customer_name: 'customer_name', // 申请人(已直接使用) 274 + gender: { api_field: 'customer_gender' },
271 - gender: 'customer_gender', // 性别 → customer_gender 275 + birthday: { api_field: 'customer_birthday' },
272 - birthday: 'customer_birthday', // 出生年月日 → customer_birthday 276 + smoker: { api_field: 'smoking_status' },
273 - smoker: 'smoking_status', // 是否吸烟 → smoking_status 277 + coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
274 - coverage: 'annual_premium', // 保额/年缴保费 → annual_premium 278 + payment_period: { api_field: 'payment_years' },
275 - payment_period: 'payment_years', // 缴费年期 → payment_years 279 + withdrawal_enabled: { api_field: 'allow_reduce_amount' },
276 - withdrawal_enabled: 'allow_reduce_amount', // 是否容许减少名义金额 280 + withdrawal_mode: { api_field: 'withdrawal_option' },
277 - withdrawal_mode: 'withdrawal_option', // 提取选项 281 + withdrawal_start_age: { api_field: 'withdrawal_start_age' },
278 - withdrawal_start_age: 'withdrawal_start_age', // 提取开始年龄 282 + withdrawal_period: { api_field: 'withdrawal_period' },
279 - withdrawal_period: 'withdrawal_period', // 提取期 283 + withdrawal_method: { api_field: 'withdrawal_method' },
280 - currency_type: 'currency_type', // 币种类型 284 + annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
281 - // 新增字段映射 285 + annual_increase_percentage: { api_field: 'annual_increase_percentage' },
282 - withdrawal_method: 'withdrawal_method', // 提取方式 286 + total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
283 - annual_withdrawal_amount: 'annual_withdrawal_amount', // 每年提取金额
284 - annual_increase_percentage: 'annual_increase_percentage', // 每年递增提取百分比
285 } 287 }
286 288
287 // 构建请求数据 289 // 构建请求数据
...@@ -290,35 +292,19 @@ const submit = async () => { ...@@ -290,35 +292,19 @@ const submit = async () => {
290 } 292 }
291 293
292 // 映射表单字段到 API 字段 294 // 映射表单字段到 API 字段
295 + const submitMapping = templateConfig.value?.config?.submit_mapping || defaultMapping
296 +
293 Object.keys(formData.value).forEach(key => { 297 Object.keys(formData.value).forEach(key => {
294 - const apiField = fieldMapping[key] 298 + const mapping = submitMapping[key]
295 - 299 + if (mapping) {
296 - if (apiField) { 300 + const apiField = typeof mapping === 'string' ? mapping : mapping.api_field
297 - // 有映射:使用映射后的字段名 301 + let value = formData.value[key]
298 - // 特殊处理:coverage(分)需要转换为元 302 + // 金额字段从分转换为元
299 - if (key === 'coverage') { 303 + if (typeof mapping === 'object' && mapping.transform === 'fen_to_yuan' && value !== null && value !== undefined && value !== '') {
300 - const coverageInYuan = (formData.value[key] / 100).toFixed(2) 304 + value = (value / 100).toFixed(2)
301 - console.log(`[PlanFormContainer] coverage 转换: ${key} (${formData.value[key]} ) ${apiField} (${coverageInYuan} )`)
302 - requestData[apiField] = coverageInYuan
303 - }
304 - // 特殊处理:annual_withdrawal_amount(分)需要转换为元
305 - else if (key === 'annual_withdrawal_amount') {
306 - const amountInYuan = (formData.value[key] / 100).toFixed(2)
307 - console.log(`[PlanFormContainer] annual_withdrawal_amount 转换: ${key} (${formData.value[key]} ) ${apiField} (${amountInYuan} )`)
308 - requestData[apiField] = amountInYuan
309 - }
310 - // 特殊处理:annual_increase_percentage(直接传递,已是字符串)
311 - else if (key === 'annual_increase_percentage') {
312 - requestData[apiField] = formData.value[key]
313 - }
314 - else {
315 - requestData[apiField] = formData.value[key]
316 } 305 }
317 - } else if (key === 'total_amount') { 306 + requestData[apiField] = value
318 - // 特殊处理:总保费(分 → 元)
319 - requestData.total_premium = (formData.value[key] / 100).toFixed(2)
320 } else { 307 } else {
321 - // 无映射:保持原字段名
322 requestData[key] = formData.value[key] 308 requestData[key] = formData.value[key]
323 } 309 }
324 }) 310 })
...@@ -329,7 +315,7 @@ const submit = async () => { ...@@ -329,7 +315,7 @@ const submit = async () => {
329 } 315 }
330 316
331 console.log('[PlanFormContainer] 提交计划书请求数据:', requestData) 317 console.log('[PlanFormContainer] 提交计划书请求数据:', requestData)
332 - console.log('[PlanFormContainer] 字段映射:', fieldMapping) 318 + console.log('[PlanFormContainer] 字段映射:', submitMapping)
333 319
334 // 调用 API 320 // 调用 API
335 const res = await addAPI(requestData) 321 const res = await addAPI(requestData)
......
1 <template> 1 <template>
2 <div v-if="config"> 2 <div v-if="config">
3 - <!-- 申请人 --> 3 + <template v-for="field in baseFields" :key="field.id || field.key">
4 - <PlanFieldName 4 + <component
5 - v-model="form.customer_name" 5 + v-if="isFieldVisible(field) && field.type !== 'percentage'"
6 - label="申请人" 6 + :is="getFieldComponent(field)"
7 - placeholder="请输入申请人" 7 + v-model="form[field.key]"
8 - :required="true" 8 + v-bind="getFieldProps(field)"
9 - class="mb-5"
10 - />
11 -
12 - <!-- 性别 -->
13 - <PlanFieldRadio
14 - v-model="form.gender"
15 - label="性别"
16 - :options="['男', '女']"
17 - :required="true"
18 - class="mb-5"
19 - />
20 -
21 - <!-- 出生日期 -->
22 - <PlanFieldDatePicker
23 - v-model="form.birthday"
24 - label="出生年月日"
25 - placeholder="请选择年月日"
26 - :required="true"
27 - class="mb-5"
28 - />
29 -
30 - <!-- 是否吸烟 -->
31 - <PlanFieldRadio
32 - v-model="form.smoker"
33 - label="是否吸烟"
34 - :options="['是', '否']"
35 - :required="true"
36 - class="mb-5"
37 - />
38 -
39 - <!-- 保额(年缴保费) -->
40 - <PlanFieldAmount
41 - v-model="form.coverage"
42 - label="年缴保费"
43 - placeholder="请输入年缴保费"
44 - :input-label="'请输入年缴保费金额'"
45 - :currency="config.currency"
46 - :required="true"
47 - class="mb-5"
48 - />
49 -
50 - <!-- 缴费年期 - 单选形式 -->
51 - <PaymentPeriodRadio
52 - v-model="form.payment_period"
53 - label="缴费年期"
54 - :options="config.payment_periods"
55 - :required="true"
56 - class="mb-5"
57 - />
58 -
59 - <!-- 分割线 -->
60 - <div class="border-t border-gray-200 my-6"></div>
61 -
62 - <!-- 提取计划配置 -->
63 - <div v-if="config.withdrawal_plan?.enabled" class="withdrawal-plan-section">
64 - <!-- 第一层:是否希望生成一份允许减少名义金额的提取说明? -->
65 - <PlanFieldRadio
66 - v-model="form.withdrawal_enabled"
67 - label="是否希望生成一份允许减少名义金额的提取说明?"
68 - :options="['是', '否']"
69 - :required="true"
70 class="mb-5" 9 class="mb-5"
71 /> 10 />
11 + <div v-else-if="isFieldVisible(field) && field.type === 'percentage'" class="mb-5">
12 + <div class="text-sm text-gray-700 mb-2 flex items-center">
13 + <span v-if="field.required" class="text-red-500 mr-1">*</span>
14 + <span>{{ field.label }}</span>
15 + </div>
16 + <nut-input
17 + v-model="form[field.key]"
18 + type="digit"
19 + :placeholder="field.placeholder"
20 + @input="(value) => onPercentageInput(value, field.key)"
21 + class="w-full"
22 + />
23 + </div>
24 + </template>
72 25
73 - <!-- 款项提取配置(始终显示,不受上面字段影响) --> 26 + <div class="border-t border-gray-200 my-6"></div>
74 - <h3 class="text-base font-semibold text-gray-900 mb-4">款项提取(允许减少名义金额)</h3>
75 27
76 - <!-- 提取选项:指定提取金额 / 最高固定提取金额 --> 28 + <div v-if="config.withdrawal_plan?.enabled" class="withdrawal-plan-section">
77 - <PlanFieldRadio 29 + <template v-for="field in withdrawalFields" :key="field.id || field.key">
78 - v-model="form.withdrawal_mode" 30 + <h3 v-if="field.section_title" class="text-base font-semibold text-gray-900 mb-4">
79 - label="提取选项" 31 + {{ field.section_title }}
80 - :options="['指定提取金额', '最高固定提取金额']" 32 + </h3>
81 - :required="true" 33 + <component
34 + v-if="isFieldVisible(field) && field.type !== 'percentage'"
35 + :is="getFieldComponent(field)"
36 + v-model="form[field.key]"
37 + v-bind="getFieldProps(field)"
82 class="mb-5" 38 class="mb-5"
83 /> 39 />
84 - 40 + <div v-else-if="isFieldVisible(field) && field.type === 'percentage'" class="mb-5">
85 - <!-- 指定提取金额模式 --> 41 + <div class="text-sm text-gray-700 mb-2 flex items-center">
86 - <template v-if="form.withdrawal_mode === '指定提取金额'"> 42 + <span v-if="field.required" class="text-red-500 mr-1">*</span>
87 - <!-- 提取方式:只有按年岁 --> 43 + <span>{{ field.label }}</span>
88 - <PlanFieldRadio 44 + </div>
89 - v-model="form.withdrawal_method" 45 + <nut-input
90 - label="提取方式" 46 + v-model="form[field.key]"
91 - :options="['按年岁']" 47 + type="digit"
92 - :required="true" 48 + :placeholder="field.placeholder"
93 - class="mb-5" 49 + @input="(value) => onPercentageInput(value, field.key)"
94 - /> 50 + class="w-full"
95 -
96 - <!-- 按年岁 -->
97 - <template v-if="form.withdrawal_method === '按年岁'">
98 - <!-- 每年提取金额 -->
99 - <PlanFieldAmount
100 - v-model="form.annual_withdrawal_amount"
101 - label="每年提取金额"
102 - placeholder="请输入每年提取金额"
103 - :input-label="'请输入每年提取金额'"
104 - :currency="config.withdrawal_plan.default_currency"
105 - :required="true"
106 - class="mb-5"
107 - />
108 -
109 - <!-- 由几岁开始 -->
110 - <PlanFieldAgePicker
111 - v-model="form.withdrawal_start_age"
112 - label="由几岁开始"
113 - placeholder="请输入开始提取年龄"
114 - :required="true"
115 - class="mb-5"
116 - />
117 -
118 - <!-- 提取期(年) -->
119 - <PlanFieldSelect
120 - v-model="form.withdrawal_period"
121 - label="提取期(年)"
122 - placeholder="请选择提取期"
123 - :options="withdrawalPeriods"
124 - :required="true"
125 - class="mb-5"
126 - />
127 -
128 - <!-- 每年递增提取之百分比 -->
129 - <div class="mb-5">
130 - <div class="text-sm text-gray-700 mb-2 flex items-center">
131 - <span class="text-red-500 mr-1">*</span>
132 - <span>每年递增提取之百分比(%</span>
133 - </div>
134 - <nut-input
135 - v-model="form.annual_increase_percentage"
136 - type="digit"
137 - placeholder="请输入递增百分比"
138 - @input="onPercentageInput"
139 - class="w-full"
140 - />
141 - </div>
142 - </template>
143 - </template>
144 -
145 - <!-- 最高固定提取金额模式 -->
146 - <template v-if="form.withdrawal_mode === '最高固定提取金额'">
147 - <!-- 按年岁:由几岁开始 -->
148 - <PlanFieldAgePicker
149 - v-model="form.withdrawal_start_age"
150 - label="按年岁:由几岁开始"
151 - placeholder="请输入开始提取年龄"
152 - :required="true"
153 - class="mb-5"
154 - />
155 -
156 - <!-- 提取期(年) -->
157 - <PlanFieldSelect
158 - v-model="form.withdrawal_period"
159 - label="提取期(年)"
160 - placeholder="请选择提取期"
161 - :options="withdrawalPeriods"
162 - :required="true"
163 - class="mb-5"
164 /> 51 />
165 - </template> 52 + </div>
53 + </template>
166 </div> 54 </div>
167 </div> 55 </div>
168 56
...@@ -187,7 +75,7 @@ ...@@ -187,7 +75,7 @@
187 * :config="templateConfig" 75 * :config="templateConfig"
188 * /> 76 * />
189 */ 77 */
190 -import { reactive, watch, computed, nextTick } from 'vue' 78 +import { reactive, watch, computed } from 'vue'
191 import Taro from '@tarojs/taro' 79 import Taro from '@tarojs/taro'
192 import PlanFieldName from '../PlanFields/NameInput.vue' 80 import PlanFieldName from '../PlanFields/NameInput.vue'
193 import PlanFieldAgePicker from '../PlanFields/AgePickerGlobal.vue' 81 import PlanFieldAgePicker from '../PlanFields/AgePickerGlobal.vue'
...@@ -257,20 +145,121 @@ const form = reactive({}) ...@@ -257,20 +145,121 @@ const form = reactive({})
257 145
258 let previousModelValue = null 146 let previousModelValue = null
259 147
260 -// 初始化默认值 148 +// 字段类型与组件的对应关系
149 +const fieldComponentMap = {
150 + name: PlanFieldName,
151 + radio: PlanFieldRadio,
152 + date: PlanFieldDatePicker,
153 + amount: PlanFieldAmount,
154 + age: PlanFieldAgePicker,
155 + select: PlanFieldSelect,
156 + payment_period: PaymentPeriodRadio
157 +}
158 +
159 +// Schema 配置入口
160 +const baseFields = computed(() => props.config?.form_schema?.base_fields || [])
161 +const withdrawalFields = computed(() => props.config?.form_schema?.withdrawal_fields || [])
162 +const resetMap = computed(() => props.config?.form_schema?.reset_map || {})
163 +
164 +/**
165 + * 获取字段对应的渲染组件
166 + * @param {Object} field - 字段配置
167 + * @returns {Object|null} Vue 组件
168 + */
169 +const getFieldComponent = (field) => {
170 + return fieldComponentMap[field.type] || null
171 +}
172 +
173 +/**
174 + * 组装字段渲染所需的 props
175 + * @param {Object} field - 字段配置
176 + * @returns {Object} 传入字段组件的 props
177 + */
178 +const getFieldProps = (field) => {
179 + const fieldProps = {
180 + label: field.label,
181 + placeholder: field.placeholder,
182 + required: !!field.required
183 + }
184 +
185 + if (field.options) {
186 + fieldProps.options = field.options
187 + }
188 +
189 + // 缴费年期选项由模板配置提供
190 + if (field.options_from === 'payment_periods') {
191 + fieldProps.options = fieldProps.options || props.config?.payment_periods
192 + }
193 +
194 + // 提取期选项由提取计划配置提供
195 + if (field.options_from === 'withdrawal_plan.withdrawal_periods') {
196 + fieldProps.options = fieldProps.options || props.config?.withdrawal_plan?.withdrawal_periods
197 + }
198 +
199 + // 基础币种来自模板配置
200 + if (field.currency_from === 'currency') {
201 + fieldProps.currency = props.config?.currency
202 + }
203 +
204 + // 提取计划币种来自提取计划配置
205 + if (field.currency_from === 'withdrawal_plan.default_currency') {
206 + fieldProps.currency = props.config?.withdrawal_plan?.default_currency
207 + }
208 +
209 + // 金额键盘的弹窗提示文本
210 + if (field.input_label) {
211 + fieldProps.inputLabel = field.input_label
212 + }
213 +
214 + return fieldProps
215 +}
216 +
217 +/**
218 + * 判断字段是否可见
219 + * @param {Object} field - 字段配置
220 + * @returns {boolean} 是否显示
221 + */
222 +const isFieldVisible = (field) => {
223 + if (!field.show_when || field.show_when.length === 0) {
224 + return true
225 + }
226 +
227 + return field.show_when.every(condition => {
228 + return form[condition.field] === condition.equals
229 + })
230 +}
231 +
232 +/**
233 + * 获取 Schema 默认值
234 + * @param {Object} value - 当前表单数据
235 + * @returns {Object} 默认值集合
236 + */
237 +const getSchemaDefaults = (value) => {
238 + const defaults = {}
239 + const fields = [...baseFields.value, ...withdrawalFields.value]
240 + fields.forEach(field => {
241 + if (field.default !== undefined && (value?.[field.key] === undefined || value?.[field.key] === null)) {
242 + defaults[field.key] = field.default
243 + }
244 + })
245 + return defaults
246 +}
247 +
248 +/**
249 + * 初始化表单数据
250 + * @param {Object} value - 初始数据
251 + */
261 const initializeForm = (value) => { 252 const initializeForm = (value) => {
262 if (!value) { 253 if (!value) {
263 Object.keys(form).forEach(key => delete form[key]) 254 Object.keys(form).forEach(key => delete form[key])
264 return 255 return
265 } 256 }
266 257
258 + const defaults = getSchemaDefaults(value)
259 +
267 Object.assign(form, { 260 Object.assign(form, {
268 ...value, 261 ...value,
269 - // 默认值 262 + ...defaults,
270 - withdrawal_enabled: value.withdrawal_enabled || '否',
271 - withdrawal_mode: value.withdrawal_mode || '指定提取金额',
272 - withdrawal_method: value.withdrawal_method || '按年岁',
273 - // 新字段默认值(使用 null 以匹配 AmountKeyboard 的 Number 类型)
274 annual_withdrawal_amount: value.annual_withdrawal_amount ?? null, 263 annual_withdrawal_amount: value.annual_withdrawal_amount ?? null,
275 annual_increase_percentage: value.annual_increase_percentage ?? null 264 annual_increase_percentage: value.annual_increase_percentage ?? null
276 }) 265 })
...@@ -298,13 +287,10 @@ watch( ...@@ -298,13 +287,10 @@ watch(
298 previousModelValue = newVal 287 previousModelValue = newVal
299 } else { 288 } else {
300 // 正常更新:合并新字段,保留默认值逻辑 289 // 正常更新:合并新字段,保留默认值逻辑
290 + const defaults = getSchemaDefaults(newVal)
301 Object.assign(form, { 291 Object.assign(form, {
302 ...newVal, 292 ...newVal,
303 - // 默认值 293 + ...defaults,
304 - withdrawal_enabled: newVal.withdrawal_enabled || '否',
305 - withdrawal_mode: newVal.withdrawal_mode || '指定提取金额',
306 - withdrawal_method: newVal.withdrawal_method || '按年岁',
307 - // 新字段默认值(使用 null 以匹配 AmountKeyboard 的 Number 类型)
308 annual_withdrawal_amount: newVal.annual_withdrawal_amount ?? null, 294 annual_withdrawal_amount: newVal.annual_withdrawal_amount ?? null,
309 annual_increase_percentage: newVal.annual_increase_percentage ?? null 295 annual_increase_percentage: newVal.annual_increase_percentage ?? null
310 }) 296 })
...@@ -317,6 +303,7 @@ watch( ...@@ -317,6 +303,7 @@ watch(
317 /** 303 /**
318 * 监听表单数据变化,同步到父组件 304 * 监听表单数据变化,同步到父组件
319 */ 305 */
306 +// 监听提取模式切换,按配置清空脏数据
320 watch( 307 watch(
321 () => form, 308 () => form,
322 (newVal) => { 309 (newVal) => {
...@@ -330,21 +317,12 @@ watch( ...@@ -330,21 +317,12 @@ watch(
330 */ 317 */
331 watch( 318 watch(
332 () => form.withdrawal_mode, 319 () => form.withdrawal_mode,
333 - (newMode, oldMode) => { 320 + (newMode) => {
334 - // 每次切换模式时,只清空输入字段(保留模式选择字段) 321 + const resetFields = resetMap.value?.withdrawal_mode?.[newMode] || []
335 - if (newMode === '最高固定提取金额') { 322 + if (resetFields.length > 0) {
336 - // 清空"指定提取金额"模式的输入字段 323 + resetFields.forEach(key => {
337 - form.annual_withdrawal_amount = null 324 + form[key] = null
338 - form.annual_increase_percentage = null 325 + })
339 - form.withdrawal_start_age = null
340 - form.withdrawal_period = null
341 - // 立即同步给父组件
342 - emit('update:modelValue', { ...form })
343 - } else if (newMode === '指定提取金额') {
344 - // 清空"最高固定提取金额"模式的输入字段
345 - form.withdrawal_start_age = null
346 - form.withdrawal_period = null
347 - // 立即同步给父组件
348 emit('update:modelValue', { ...form }) 326 emit('update:modelValue', { ...form })
349 } 327 }
350 } 328 }
...@@ -354,26 +332,18 @@ watch( ...@@ -354,26 +332,18 @@ watch(
354 * 提取年期选项(从配置读取) 332 * 提取年期选项(从配置读取)
355 * @type {ComputedRef<Array<string>>} 333 * @type {ComputedRef<Array<string>>}
356 */ 334 */
357 -const withdrawalPeriods = computed(() => {
358 - return props.config?.withdrawal_plan?.withdrawal_periods || [
359 - '1年',
360 - '2年',
361 - '3年',
362 - '5年',
363 - '10年',
364 - '15年',
365 - '20年',
366 - '终身'
367 - ]
368 -})
369 -
370 /** 335 /**
371 * 百分比输入限制(实时) 336 * 百分比输入限制(实时)
372 * @description 限制百分比输入为有效数值,最多2位小数 337 * @description 限制百分比输入为有效数值,最多2位小数
373 * 只允许输入数字和一个小数点 338 * 只允许输入数字和一个小数点
374 * @param {string} value - 输入值 339 * @param {string} value - 输入值
375 */ 340 */
376 -const onPercentageInput = (value) => { 341 +/**
342 + * 百分比输入清洗,避免非法字符
343 + * @param {string|number} value - 输入值
344 + * @param {string} key - 目标字段 key
345 + */
346 +const onPercentageInput = (value, key) => {
377 // 转换为字符串(处理 value 为 null 或其他类型的情况) 347 // 转换为字符串(处理 value 为 null 或其他类型的情况)
378 let strValue = String(value ?? '') 348 let strValue = String(value ?? '')
379 349
...@@ -401,92 +371,37 @@ const onPercentageInput = (value) => { ...@@ -401,92 +371,37 @@ const onPercentageInput = (value) => {
401 } 371 }
402 } 372 }
403 373
404 - // 更新表单值 374 + form[key] = cleaned
405 - form.annual_increase_percentage = cleaned
406 } 375 }
407 376
408 /** 377 /**
409 * 表单校验 378 * 表单校验
410 * @returns {boolean} 是否通过校验 379 * @returns {boolean} 是否通过校验
411 */ 380 */
381 +/**
382 + * 表单校验(基于 Schema)
383 + * @returns {boolean} 校验是否通过
384 + */
412 const validate = () => { 385 const validate = () => {
413 - // 基础字段校验 386 + const fields = [...baseFields.value, ...(props.config.withdrawal_plan?.enabled ? withdrawalFields.value : [])]
414 - if (!form.customer_name || !form.customer_name.trim()) {
415 - Taro.showToast({ title: '请输入申请人', icon: 'none' })
416 - return false
417 - }
418 - if (!form.gender) {
419 - Taro.showToast({ title: '请选择性别', icon: 'none' })
420 - return false
421 - }
422 - if (!form.birthday) {
423 - Taro.showToast({ title: '请选择出生年月日', icon: 'none' })
424 - return false
425 - }
426 - if (!form.smoker) {
427 - Taro.showToast({ title: '请选择是否吸烟', icon: 'none' })
428 - return false
429 - }
430 - if (!form.coverage) {
431 - Taro.showToast({ title: '请输入年缴保费', icon: 'none' })
432 - return false
433 - }
434 - if (!form.payment_period) {
435 - Taro.showToast({ title: '请选择缴费年期', icon: 'none' })
436 - return false
437 - }
438 387
439 - // 提取计划校验 388 + for (const field of fields) {
440 - if (props.config.withdrawal_plan?.enabled) { 389 + if (!isFieldVisible(field)) {
441 - // withdrawal_enabled 只是一个可选字段,不需要校验 390 + continue
442 - // 真正需要校验的是提取方案配置
443 -
444 - if (!form.withdrawal_mode) {
445 - Taro.showToast({ title: '请选择提取选项', icon: 'none' })
446 - return false
447 } 391 }
448 392
449 - // 根据选择的提取模式进行校验 393 + if (field.required) {
450 - if (form.withdrawal_mode === '指定提取金额') { 394 + const value = form[field.key]
451 - if (!form.withdrawal_method) { 395 + if (value === undefined || value === null || value === '') {
452 - Taro.showToast({ title: '请选择提取方式', icon: 'none' }) 396 + Taro.showToast({ title: field.label || '请完善必填信息', icon: 'none' })
453 - return false
454 - }
455 -
456 - if (form.withdrawal_start_age === undefined || form.withdrawal_start_age === '') {
457 - Taro.showToast({ title: '请输入开始提取年龄', icon: 'none' })
458 - return false
459 - }
460 -
461 - if (!form.withdrawal_period) {
462 - Taro.showToast({ title: '请选择提取期', icon: 'none' })
463 return false 397 return false
464 } 398 }
399 + }
465 400
466 - if (form.withdrawal_method === '按年岁') { 401 + if (field.type === 'percentage' && isFieldVisible(field)) {
467 - if (!form.annual_withdrawal_amount || form.annual_withdrawal_amount === '') { 402 + const percentage = parseFloat(form[field.key])
468 - Taro.showToast({ title: '请输入每年提取金额', icon: 'none' }) 403 + if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) {
469 - return false 404 + Taro.showToast({ title: '请输入0-100之间的百分比', icon: 'none' })
470 - }
471 - if (form.annual_increase_percentage === undefined || form.annual_increase_percentage === '') {
472 - Taro.showToast({ title: '请输入每年递增提取之百分比', icon: 'none' })
473 - return false
474 - }
475 -
476 - // 验证百分比范围
477 - const percentage = parseFloat(form.annual_increase_percentage)
478 - if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) {
479 - Taro.showToast({ title: '请输入0-100之间的百分比', icon: 'none' })
480 - return false
481 - }
482 - }
483 - } else if (form.withdrawal_mode === '最高固定提取金额') {
484 - if (form.withdrawal_start_age === undefined || form.withdrawal_start_age === '') {
485 - Taro.showToast({ title: '请输入开始提取年龄', icon: 'none' })
486 - return false
487 - }
488 - if (!form.withdrawal_period) {
489 - Taro.showToast({ title: '请选择提取期', icon: 'none' })
490 return false 405 return false
491 } 406 }
492 } 407 }
......
...@@ -19,6 +19,61 @@ ...@@ -19,6 +19,61 @@
19 * form_sn: "life-insurance-wiop3e" // 对应下面的配置 key 19 * form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
20 * } 20 * }
21 */ 21 */
22 +// 基础提交字段映射(适用于人寿/重疾等通用表单)
23 +const baseSubmitMapping = {
24 + customer_name: { api_field: 'customer_name' },
25 + gender: { api_field: 'customer_gender' },
26 + birthday: { api_field: 'customer_birthday' },
27 + smoker: { api_field: 'smoking_status' },
28 + coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
29 + payment_period: { api_field: 'payment_years' },
30 + total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
31 +}
32 +
33 +// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
34 +const savingsSubmitMapping = {
35 + ...baseSubmitMapping,
36 + withdrawal_enabled: { api_field: 'allow_reduce_amount' },
37 + withdrawal_mode: { api_field: 'withdrawal_option' },
38 + withdrawal_start_age: { api_field: 'withdrawal_start_age' },
39 + withdrawal_period: { api_field: 'withdrawal_period' },
40 + withdrawal_method: { api_field: 'withdrawal_method' },
41 + annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
42 + annual_increase_percentage: { api_field: 'annual_increase_percentage' }
43 +}
44 +
45 +// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
46 +const savingsFormSchema = {
47 + // 基础字段:非提取计划部分
48 + base_fields: [
49 + { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
50 + { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
51 + { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
52 + { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
53 + { id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
54 + { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
55 + ],
56 + // 提取计划字段:由 withdrawal_plan 开关控制
57 + withdrawal_fields: [
58 + { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
59 + { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
60 + { id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
61 + { 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: '指定提取金额' }, { field: 'withdrawal_method', equals: '按年岁' }] },
62 + { id: 'withdrawal_start_age_by_year', key: 'withdrawal_start_age', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
63 + { id: 'withdrawal_period_by_year', key: 'withdrawal_period', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
64 + { id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }, { field: 'withdrawal_method', equals: '按年岁' }] },
65 + { id: 'withdrawal_start_age_by_fixed', key: 'withdrawal_start_age', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
66 + { id: 'withdrawal_period_by_fixed', key: 'withdrawal_period', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }
67 + ],
68 + // 提取模式切换时的清空逻辑,避免脏字段影响提交
69 + reset_map: {
70 + withdrawal_mode: {
71 + '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age', 'withdrawal_period'],
72 + '指定提取金额': ['withdrawal_start_age', 'withdrawal_period']
73 + }
74 + }
75 +}
76 +
22 export const PLAN_TEMPLATES = { 77 export const PLAN_TEMPLATES = {
23 // 人寿保险产品 - WIOP3E 78 // 人寿保险产品 - WIOP3E
24 'life-insurance-wiop3e': { 79 'life-insurance-wiop3e': {
...@@ -33,7 +88,8 @@ export const PLAN_TEMPLATES = { ...@@ -33,7 +88,8 @@ export const PLAN_TEMPLATES = {
33 '10 年(0-70 岁)' 88 '10 年(0-70 岁)'
34 ], 89 ],
35 age_range: { min: 0, max: 75 }, // 年龄范围 90 age_range: { min: 0, max: 75 }, // 年龄范围
36 - insurance_period: '终身' // 保险期间 91 + insurance_period: '终身', // 保险期间
92 + submit_mapping: baseSubmitMapping
37 } 93 }
38 }, 94 },
39 95
...@@ -49,7 +105,8 @@ export const PLAN_TEMPLATES = { ...@@ -49,7 +105,8 @@ export const PLAN_TEMPLATES = {
49 '10 年(0-70 岁)' 105 '10 年(0-70 岁)'
50 ], 106 ],
51 age_range: { min: 0, max: 75 }, 107 age_range: { min: 0, max: 75 },
52 - insurance_period: '终身' 108 + insurance_period: '终身',
109 + submit_mapping: baseSubmitMapping
53 } 110 }
54 }, 111 },
55 112
...@@ -65,7 +122,8 @@ export const PLAN_TEMPLATES = { ...@@ -65,7 +122,8 @@ export const PLAN_TEMPLATES = {
65 '25 年(15 日 - 60 岁)' 122 '25 年(15 日 - 60 岁)'
66 ], 123 ],
67 age_range: { min: 0, max: 65 }, 124 age_range: { min: 0, max: 65 },
68 - insurance_period: '终身' 125 + insurance_period: '终身',
126 + submit_mapping: baseSubmitMapping
69 } 127 }
70 }, 128 },
71 129
...@@ -81,7 +139,8 @@ export const PLAN_TEMPLATES = { ...@@ -81,7 +139,8 @@ export const PLAN_TEMPLATES = {
81 '25 年(15 日 - 60 岁)' 139 '25 年(15 日 - 60 岁)'
82 ], 140 ],
83 age_range: { min: 0, max: 65 }, 141 age_range: { min: 0, max: 65 },
84 - insurance_period: '终身' 142 + insurance_period: '终身',
143 + submit_mapping: baseSubmitMapping
85 } 144 }
86 }, 145 },
87 146
...@@ -97,7 +156,8 @@ export const PLAN_TEMPLATES = { ...@@ -97,7 +156,8 @@ export const PLAN_TEMPLATES = {
97 '25 年(15 日 - 60 岁)' 156 '25 年(15 日 - 60 岁)'
98 ], 157 ],
99 age_range: { min: 0, max: 65 }, 158 age_range: { min: 0, max: 65 },
100 - insurance_period: '终身' 159 + insurance_period: '终身',
160 + submit_mapping: baseSubmitMapping
101 } 161 }
102 }, 162 },
103 163
...@@ -124,10 +184,7 @@ export const PLAN_TEMPLATES = { ...@@ -124,10 +184,7 @@ export const PLAN_TEMPLATES = {
124 enabled: true, 184 enabled: true,
125 currencies: ['HKD', 'USD', 'CNY'], // 支持的币种 185 currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
126 default_currency: 'USD', // 统一为美元 186 default_currency: 'USD', // 统一为美元
127 - withdrawal_modes: [ 187 + withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
128 - '年龄指定金额', // 方式1
129 - '最高固定金额' // 方式2
130 - ],
131 withdrawal_periods: [ 188 withdrawal_periods: [
132 '1年', 189 '1年',
133 '2年', 190 '2年',
...@@ -138,7 +195,9 @@ export const PLAN_TEMPLATES = { ...@@ -138,7 +195,9 @@ export const PLAN_TEMPLATES = {
138 '20年', 195 '20年',
139 '终身' 196 '终身'
140 ] 197 ]
141 - } 198 + },
199 + form_schema: savingsFormSchema,
200 + submit_mapping: savingsSubmitMapping
142 } 201 }
143 }, 202 },
144 203
...@@ -160,7 +219,7 @@ export const PLAN_TEMPLATES = { ...@@ -160,7 +219,7 @@ export const PLAN_TEMPLATES = {
160 enabled: true, 219 enabled: true,
161 currencies: ['HKD', 'USD', 'CNY'], 220 currencies: ['HKD', 'USD', 'CNY'],
162 default_currency: 'USD', // 统一为美元 221 default_currency: 'USD', // 统一为美元
163 - withdrawal_modes: ['年龄指定金额', '最高固定金额'], 222 + withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
164 withdrawal_periods: [ 223 withdrawal_periods: [
165 '1年', 224 '1年',
166 '2年', 225 '2年',
...@@ -171,7 +230,9 @@ export const PLAN_TEMPLATES = { ...@@ -171,7 +230,9 @@ export const PLAN_TEMPLATES = {
171 '20年', 230 '20年',
172 '终身' 231 '终身'
173 ] 232 ]
174 - } 233 + },
234 + form_schema: savingsFormSchema,
235 + submit_mapping: savingsSubmitMapping
175 } 236 }
176 }, 237 },
177 238
...@@ -193,7 +254,7 @@ export const PLAN_TEMPLATES = { ...@@ -193,7 +254,7 @@ export const PLAN_TEMPLATES = {
193 enabled: true, 254 enabled: true,
194 currencies: ['HKD', 'USD', 'CNY'], 255 currencies: ['HKD', 'USD', 'CNY'],
195 default_currency: 'USD', // 统一为美元 256 default_currency: 'USD', // 统一为美元
196 - withdrawal_modes: ['年龄指定金额', '最高固定金额'], 257 + withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
197 withdrawal_periods: [ 258 withdrawal_periods: [
198 '1年', 259 '1年',
199 '2年', 260 '2年',
...@@ -204,7 +265,9 @@ export const PLAN_TEMPLATES = { ...@@ -204,7 +265,9 @@ export const PLAN_TEMPLATES = {
204 '20年', 265 '20年',
205 '终身' 266 '终身'
206 ] 267 ]
207 - } 268 + },
269 + form_schema: savingsFormSchema,
270 + submit_mapping: savingsSubmitMapping
208 } 271 }
209 }, 272 },
210 273
...@@ -227,7 +290,7 @@ export const PLAN_TEMPLATES = { ...@@ -227,7 +290,7 @@ export const PLAN_TEMPLATES = {
227 enabled: true, 290 enabled: true,
228 currencies: ['HKD', 'USD', 'CNY'], 291 currencies: ['HKD', 'USD', 'CNY'],
229 default_currency: 'USD', // 统一为美元 292 default_currency: 'USD', // 统一为美元
230 - withdrawal_modes: ['年龄指定金额', '最高固定金额'], 293 + withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
231 withdrawal_periods: [ 294 withdrawal_periods: [
232 '1年', 295 '1年',
233 '2年', 296 '2年',
...@@ -238,7 +301,9 @@ export const PLAN_TEMPLATES = { ...@@ -238,7 +301,9 @@ export const PLAN_TEMPLATES = {
238 '20年', 301 '20年',
239 '终身' 302 '终身'
240 ] 303 ]
241 - } 304 + },
305 + form_schema: savingsFormSchema,
306 + submit_mapping: savingsSubmitMapping
242 } 307 }
243 } 308 }
244 } 309 }
......