hookehuyr

refactor(plan): 优化计划书字段配置管理

- 重构 useFieldValueTransform 为可复用组件,支持动态字段定义
- 优化 PlanFormContainer 字段映射逻辑,简化代码
- 添加计划书模板字段配置:withdrawal_start_age_simplified 等字段
- 改进字段值转换 composable 的灵活性和可配置性
- 更新 CHANGELOG.md 记录本次优化内容

影响文件:
- src/components/plan/PlanFormContainer.vue
- src/components/plan/PlanTemplates/*.vue
- src/composables/useFieldValueTransform.js
- src/composables/useFieldDependencies.js
- src/config/plan-templates.js

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
...@@ -75,6 +75,11 @@ pnpm lint ...@@ -75,6 +75,11 @@ pnpm lint
75 -**转换逻辑修正** - 分元双向转换统一使用转换器 75 -**转换逻辑修正** - 分元双向转换统一使用转换器
76 -**依赖检测测试** - 补充循环依赖检测单测与分组工具测试 76 -**依赖检测测试** - 补充循环依赖检测单测与分组工具测试
77 77
78 +### 计划书配置核查
79 +-**配置应用核查** - 确认 plan-templates 已驱动表单渲染与提交映射,plan-fields 与字段关联/转换 composable 尚未接入生成链路
80 +-**依赖与转换接入** - 表单可见性接入 useFieldDependencies,提交金额转换接入 useFieldValueTransform
81 +-**提取字段拆分** - 指定提取金额与最高固定提取金额字段独立显示与提交映射
82 +
78 ## 🆕 最新更新(2026-02-13) 83 ## 🆕 最新更新(2026-02-13)
79 84
80 ### 权限与测试 85 ### 权限与测试
......
1 +#### [2026-02-14] - 提取字段分组修正
2 +
3 +### 变更
4 +- 指定提取金额字段改为独立 key,避免与固定提取字段混用
5 +- 最高固定提取金额仅保留“按年岁:由几岁开始/提取期”
6 +- 提交映射按提取模式覆盖正确的年龄与年期字段
7 +
8 +### 测试
9 +- pnpm test(通过)
10 +- pnpm lint(存在历史警告)
11 +
12 +---
13 +
14 +**详细信息**
15 +- **影响文件**: src/config/plan-templates.js, src/components/plan/PlanTemplates/SavingsTemplate.vue, src/components/plan/PlanTemplates/LifeInsuranceTemplate.vue, src/components/plan/PlanTemplates/CriticalIllnessTemplate.vue, src/components/plan/PlanFormContainer.vue, README.md
16 +- **技术栈**: Vue 3, Taro, Vitest
17 +- **测试状态**: 已通过(lint 有警告)
18 +- **备注**: 提取字段按模式隔离,避免字段互相污染
19 +
20 +---
21 +
22 +#### [2026-02-14] - 计划书字段依赖与转换接入
23 +
24 +### 变更
25 +- useFieldDependencies 支持 Schema show_when 并接入模板可见性判断
26 +- useFieldValueTransform 支持自定义映射并用于提交金额转换
27 +
28 +### 测试
29 +- pnpm test(通过,有组件解析警告)
30 +- pnpm lint(存在历史警告)
31 +
32 +---
33 +
34 +**详细信息**
35 +- **影响文件**: src/composables/useFieldDependencies.js, src/composables/useFieldValueTransform.js, src/components/plan/PlanTemplates/LifeInsuranceTemplate.vue, src/components/plan/PlanTemplates/CriticalIllnessTemplate.vue, src/components/plan/PlanTemplates/SavingsTemplate.vue, src/components/plan/PlanFormContainer.vue, README.md
36 +- **技术栈**: Vue 3, Taro, Vitest
37 +- **测试状态**: 已通过(lint 有警告)
38 +- **备注**: 接入字段可见性与金额转换,保持现有表单输入单位为分
39 +
40 +---
41 +
42 +#### [2026-02-14] - 计划书配置链路核查
43 +
44 +### 核查
45 +- 确认 plan-templates.js 配置用于表单渲染与提交映射
46 +- 确认 plan-fields.js 与字段关联/转换 composable 尚未接入计划书生成链路
47 +
48 +### 测试
49 +- 未运行(仅核查)
50 +
51 +---
52 +
53 +**详细信息**
54 +- **影响文件**: src/config/plan-templates.js, src/config/plan-fields.js, src/composables/useFieldDependencies.js, src/composables/useFieldValueTransform.js, src/components/plan/PlanFormContainer.vue, src/components/plan/PlanTemplates/SavingsTemplate.vue, src/components/plan/PlanTemplates/LifeInsuranceTemplate.vue, src/components/plan/PlanTemplates/CriticalIllnessTemplate.vue
55 +- **技术栈**: Vue 3, Taro
56 +- **测试状态**: 未运行
57 +- **备注**: 仅进行链路核查,无代码改动
58 +
59 +---
60 +
1 #### [2026-02-14] - 计划书字段分组与转换补齐 61 #### [2026-02-14] - 计划书字段分组与转换补齐
2 62
3 ### 修复 63 ### 修复
......
...@@ -49,6 +49,7 @@ import CriticalIllnessTemplate from './PlanTemplates/CriticalIllnessTemplate.vue ...@@ -49,6 +49,7 @@ import CriticalIllnessTemplate from './PlanTemplates/CriticalIllnessTemplate.vue
49 import SavingsTemplate from './PlanTemplates/SavingsTemplate.vue' 49 import SavingsTemplate from './PlanTemplates/SavingsTemplate.vue'
50 import { PLAN_TEMPLATES } from '@/config/plan-templates' 50 import { PLAN_TEMPLATES } from '@/config/plan-templates'
51 import { addAPI } from '@/api/plan' 51 import { addAPI } from '@/api/plan'
52 +import { useFieldValueTransform } from '@/composables/useFieldValueTransform'
52 53
53 /** 54 /**
54 * 组件属性 55 * 组件属性
...@@ -278,11 +279,13 @@ const submit = async () => { ...@@ -278,11 +279,13 @@ const submit = async () => {
278 payment_period: { api_field: 'payment_years' }, 279 payment_period: { api_field: 'payment_years' },
279 withdrawal_enabled: { api_field: 'allow_reduce_amount' }, 280 withdrawal_enabled: { api_field: 'allow_reduce_amount' },
280 withdrawal_mode: { api_field: 'withdrawal_option' }, 281 withdrawal_mode: { api_field: 'withdrawal_option' },
281 - withdrawal_start_age: { api_field: 'withdrawal_start_age' },
282 - withdrawal_period: { api_field: 'withdrawal_period' },
283 withdrawal_method: { api_field: 'withdrawal_method' }, 282 withdrawal_method: { api_field: 'withdrawal_method' },
284 annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' }, 283 annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
285 annual_increase_percentage: { api_field: 'annual_increase_percentage' }, 284 annual_increase_percentage: { api_field: 'annual_increase_percentage' },
285 + withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
286 + withdrawal_period_specified: { api_field: 'withdrawal_period' },
287 + withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
288 + withdrawal_period_fixed: { api_field: 'withdrawal_period' },
286 total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' } 289 total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
287 } 290 }
288 291
...@@ -293,6 +296,7 @@ const submit = async () => { ...@@ -293,6 +296,7 @@ const submit = async () => {
293 296
294 // 映射表单字段到 API 字段 297 // 映射表单字段到 API 字段
295 const submitMapping = templateConfig.value?.config?.submit_mapping || defaultMapping 298 const submitMapping = templateConfig.value?.config?.submit_mapping || defaultMapping
299 + const { toYuan } = useFieldValueTransform(formData, submitMapping)
296 300
297 Object.keys(formData.value).forEach(key => { 301 Object.keys(formData.value).forEach(key => {
298 const mapping = submitMapping[key] 302 const mapping = submitMapping[key]
...@@ -301,7 +305,7 @@ const submit = async () => { ...@@ -301,7 +305,7 @@ const submit = async () => {
301 let value = formData.value[key] 305 let value = formData.value[key]
302 // 金额字段从分转换为元 306 // 金额字段从分转换为元
303 if (typeof mapping === 'object' && mapping.transform === 'fen_to_yuan' && value !== null && value !== undefined && value !== '') { 307 if (typeof mapping === 'object' && mapping.transform === 'fen_to_yuan' && value !== null && value !== undefined && value !== '') {
304 - value = (value / 100).toFixed(2) 308 + value = toYuan(key, value)
305 } 309 }
306 requestData[apiField] = value 310 requestData[apiField] = value
307 } else { 311 } else {
...@@ -309,6 +313,28 @@ const submit = async () => { ...@@ -309,6 +313,28 @@ const submit = async () => {
309 } 313 }
310 }) 314 })
311 315
316 + if (formData.value?.withdrawal_mode === '指定提取金额') {
317 + const specifiedStart = formData.value.withdrawal_start_age_specified
318 + const specifiedPeriod = formData.value.withdrawal_period_specified
319 + if (specifiedStart !== undefined && specifiedStart !== null && specifiedStart !== '') {
320 + requestData.withdrawal_start_age = specifiedStart
321 + }
322 + if (specifiedPeriod !== undefined && specifiedPeriod !== null && specifiedPeriod !== '') {
323 + requestData.withdrawal_period = specifiedPeriod
324 + }
325 + }
326 +
327 + if (formData.value?.withdrawal_mode === '最高固定提取金额') {
328 + const fixedStart = formData.value.withdrawal_start_age_fixed
329 + const fixedPeriod = formData.value.withdrawal_period_fixed
330 + if (fixedStart !== undefined && fixedStart !== null && fixedStart !== '') {
331 + requestData.withdrawal_start_age = fixedStart
332 + }
333 + if (fixedPeriod !== undefined && fixedPeriod !== null && fixedPeriod !== '') {
334 + requestData.withdrawal_period = fixedPeriod
335 + }
336 + }
337 +
312 // 添加币种类型(如果有配置) 338 // 添加币种类型(如果有配置)
313 if (templateConfig.value?.config?.currency) { 339 if (templateConfig.value?.config?.currency) {
314 requestData.currency_type = templateConfig.value.config.currency 340 requestData.currency_type = templateConfig.value.config.currency
......
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
2 <div v-if="config"> 2 <div v-if="config">
3 <template v-for="field in baseFields" :key="field.id || field.key"> 3 <template v-for="field in baseFields" :key="field.id || field.key">
4 <component 4 <component
5 - v-if="isFieldVisible(field) && field.type !== 'percentage'" 5 + v-if="isFieldVisible(field.key) && field.type !== 'percentage'"
6 :is="getFieldComponent(field)" 6 :is="getFieldComponent(field)"
7 v-model="form[field.key]" 7 v-model="form[field.key]"
8 v-bind="getFieldProps(field)" 8 v-bind="getFieldProps(field)"
9 class="mb-5" 9 class="mb-5"
10 /> 10 />
11 - <div v-else-if="isFieldVisible(field) && field.type === 'percentage'" class="mb-5"> 11 + <div v-else-if="isFieldVisible(field.key) && field.type === 'percentage'" class="mb-5">
12 <div class="text-sm text-gray-700 mb-2 flex items-center"> 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> 13 <span v-if="field.required" class="text-red-500 mr-1">*</span>
14 <span>{{ field.label }}</span> 14 <span>{{ field.label }}</span>
...@@ -51,6 +51,7 @@ import PlanFieldAmount from '../PlanFields/AmountKeyboard.vue' ...@@ -51,6 +51,7 @@ import PlanFieldAmount from '../PlanFields/AmountKeyboard.vue'
51 import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue' 51 import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue'
52 import PlanFieldRadio from '../PlanFields/RadioGroup.vue' 52 import PlanFieldRadio from '../PlanFields/RadioGroup.vue'
53 import PaymentPeriodRadio from '../PlanFields/PaymentPeriodRadio.vue' 53 import PaymentPeriodRadio from '../PlanFields/PaymentPeriodRadio.vue'
54 +import { useFieldDependencies } from '@/composables/useFieldDependencies'
54 55
55 /** 56 /**
56 * 组件属性 57 * 组件属性
...@@ -117,6 +118,13 @@ const fieldComponentMap = { ...@@ -117,6 +118,13 @@ const fieldComponentMap = {
117 // Schema 配置入口 118 // Schema 配置入口
118 const baseFields = computed(() => props.config?.form_schema?.base_fields || []) 119 const baseFields = computed(() => props.config?.form_schema?.base_fields || [])
119 120
121 +const fieldDefinitions = computed(() => {
122 + return baseFields.value.reduce((result, field) => {
123 + result[field.key] = field
124 + return result
125 + }, {})
126 +})
127 +
120 /** 128 /**
121 * 获取字段对应的渲染组件 129 * 获取字段对应的渲染组件
122 * @param {Object} field - 字段配置 130 * @param {Object} field - 字段配置
...@@ -160,20 +168,7 @@ const getFieldProps = (field) => { ...@@ -160,20 +168,7 @@ const getFieldProps = (field) => {
160 return fieldProps 168 return fieldProps
161 } 169 }
162 170
163 -/** 171 +const { isFieldVisible } = useFieldDependencies(form, fieldDefinitions)
164 - * 判断字段是否可见
165 - * @param {Object} field - 字段配置
166 - * @returns {boolean} 是否显示
167 - */
168 -const isFieldVisible = (field) => {
169 - if (!field.show_when || field.show_when.length === 0) {
170 - return true
171 - }
172 -
173 - return field.show_when.every(condition => {
174 - return form[condition.field] === condition.equals
175 - })
176 -}
177 172
178 /** 173 /**
179 * 获取 Schema 默认值 174 * 获取 Schema 默认值
...@@ -296,7 +291,7 @@ const validate = () => { ...@@ -296,7 +291,7 @@ const validate = () => {
296 const fields = [...baseFields.value] 291 const fields = [...baseFields.value]
297 292
298 for (const field of fields) { 293 for (const field of fields) {
299 - if (!isFieldVisible(field)) { 294 + if (!isFieldVisible(field.key)) {
300 continue 295 continue
301 } 296 }
302 297
...@@ -308,7 +303,7 @@ const validate = () => { ...@@ -308,7 +303,7 @@ const validate = () => {
308 } 303 }
309 } 304 }
310 305
311 - if (field.type === 'percentage' && isFieldVisible(field)) { 306 + if (field.type === 'percentage' && isFieldVisible(field.key)) {
312 const percentage = parseFloat(form[field.key]) 307 const percentage = parseFloat(form[field.key])
313 if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) { 308 if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) {
314 Taro.showToast({ title: '请输入0-100之间的百分比', icon: 'none' }) 309 Taro.showToast({ title: '请输入0-100之间的百分比', icon: 'none' })
......
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
2 <div v-if="config"> 2 <div v-if="config">
3 <template v-for="field in baseFields" :key="field.id || field.key"> 3 <template v-for="field in baseFields" :key="field.id || field.key">
4 <component 4 <component
5 - v-if="isFieldVisible(field) && field.type !== 'percentage'" 5 + v-if="isFieldVisible(field.key) && field.type !== 'percentage'"
6 :is="getFieldComponent(field)" 6 :is="getFieldComponent(field)"
7 v-model="form[field.key]" 7 v-model="form[field.key]"
8 v-bind="getFieldProps(field)" 8 v-bind="getFieldProps(field)"
9 class="mb-5" 9 class="mb-5"
10 /> 10 />
11 - <div v-else-if="isFieldVisible(field) && field.type === 'percentage'" class="mb-5"> 11 + <div v-else-if="isFieldVisible(field.key) && field.type === 'percentage'" class="mb-5">
12 <div class="text-sm text-gray-700 mb-2 flex items-center"> 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> 13 <span v-if="field.required" class="text-red-500 mr-1">*</span>
14 <span>{{ field.label }}</span> 14 <span>{{ field.label }}</span>
...@@ -51,6 +51,7 @@ import PlanFieldAmount from '../PlanFields/AmountKeyboard.vue' ...@@ -51,6 +51,7 @@ import PlanFieldAmount from '../PlanFields/AmountKeyboard.vue'
51 import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue' 51 import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue'
52 import PlanFieldRadio from '../PlanFields/RadioGroup.vue' 52 import PlanFieldRadio from '../PlanFields/RadioGroup.vue'
53 import PaymentPeriodRadio from '../PlanFields/PaymentPeriodRadio.vue' 53 import PaymentPeriodRadio from '../PlanFields/PaymentPeriodRadio.vue'
54 +import { useFieldDependencies } from '@/composables/useFieldDependencies'
54 55
55 /** 56 /**
56 * 组件属性 57 * 组件属性
...@@ -119,6 +120,13 @@ const fieldComponentMap = { ...@@ -119,6 +120,13 @@ const fieldComponentMap = {
119 // Schema 配置入口 120 // Schema 配置入口
120 const baseFields = computed(() => props.config?.form_schema?.base_fields || []) 121 const baseFields = computed(() => props.config?.form_schema?.base_fields || [])
121 122
123 +const fieldDefinitions = computed(() => {
124 + return baseFields.value.reduce((result, field) => {
125 + result[field.key] = field
126 + return result
127 + }, {})
128 +})
129 +
122 /** 130 /**
123 * 获取字段对应的渲染组件 131 * 获取字段对应的渲染组件
124 * @param {Object} field - 字段配置 132 * @param {Object} field - 字段配置
...@@ -162,20 +170,7 @@ const getFieldProps = (field) => { ...@@ -162,20 +170,7 @@ const getFieldProps = (field) => {
162 return fieldProps 170 return fieldProps
163 } 171 }
164 172
165 -/** 173 +const { isFieldVisible } = useFieldDependencies(form, fieldDefinitions)
166 - * 判断字段是否可见
167 - * @param {Object} field - 字段配置
168 - * @returns {boolean} 是否显示
169 - */
170 -const isFieldVisible = (field) => {
171 - if (!field.show_when || field.show_when.length === 0) {
172 - return true
173 - }
174 -
175 - return field.show_when.every(condition => {
176 - return form[condition.field] === condition.equals
177 - })
178 -}
179 174
180 /** 175 /**
181 * 获取 Schema 默认值 176 * 获取 Schema 默认值
...@@ -298,7 +293,7 @@ const validate = () => { ...@@ -298,7 +293,7 @@ const validate = () => {
298 const fields = [...baseFields.value] 293 const fields = [...baseFields.value]
299 294
300 for (const field of fields) { 295 for (const field of fields) {
301 - if (!isFieldVisible(field)) { 296 + if (!isFieldVisible(field.key)) {
302 continue 297 continue
303 } 298 }
304 299
...@@ -310,7 +305,7 @@ const validate = () => { ...@@ -310,7 +305,7 @@ const validate = () => {
310 } 305 }
311 } 306 }
312 307
313 - if (field.type === 'percentage' && isFieldVisible(field)) { 308 + if (field.type === 'percentage' && isFieldVisible(field.key)) {
314 const percentage = parseFloat(form[field.key]) 309 const percentage = parseFloat(form[field.key])
315 if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) { 310 if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) {
316 Taro.showToast({ title: '请输入0-100之间的百分比', icon: 'none' }) 311 Taro.showToast({ title: '请输入0-100之间的百分比', icon: 'none' })
......
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
2 <div v-if="config"> 2 <div v-if="config">
3 <template v-for="field in baseFields" :key="field.id || field.key"> 3 <template v-for="field in baseFields" :key="field.id || field.key">
4 <component 4 <component
5 - v-if="isFieldVisible(field) && field.type !== 'percentage'" 5 + v-if="isFieldVisible(field.key) && field.type !== 'percentage'"
6 :is="getFieldComponent(field)" 6 :is="getFieldComponent(field)"
7 v-model="form[field.key]" 7 v-model="form[field.key]"
8 v-bind="getFieldProps(field)" 8 v-bind="getFieldProps(field)"
9 class="mb-5" 9 class="mb-5"
10 /> 10 />
11 - <div v-else-if="isFieldVisible(field) && field.type === 'percentage'" class="mb-5"> 11 + <div v-else-if="isFieldVisible(field.key) && field.type === 'percentage'" class="mb-5">
12 <div class="text-sm text-gray-700 mb-2 flex items-center"> 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> 13 <span v-if="field.required" class="text-red-500 mr-1">*</span>
14 <span>{{ field.label }}</span> 14 <span>{{ field.label }}</span>
...@@ -31,13 +31,13 @@ ...@@ -31,13 +31,13 @@
31 {{ field.section_title }} 31 {{ field.section_title }}
32 </h3> 32 </h3>
33 <component 33 <component
34 - v-if="isFieldVisible(field) && field.type !== 'percentage'" 34 + v-if="isFieldVisible(field.key) && field.type !== 'percentage'"
35 :is="getFieldComponent(field)" 35 :is="getFieldComponent(field)"
36 v-model="form[field.key]" 36 v-model="form[field.key]"
37 v-bind="getFieldProps(field)" 37 v-bind="getFieldProps(field)"
38 class="mb-5" 38 class="mb-5"
39 /> 39 />
40 - <div v-else-if="isFieldVisible(field) && field.type === 'percentage'" class="mb-5"> 40 + <div v-else-if="isFieldVisible(field.key) && field.type === 'percentage'" class="mb-5">
41 <div class="text-sm text-gray-700 mb-2 flex items-center"> 41 <div class="text-sm text-gray-700 mb-2 flex items-center">
42 <span v-if="field.required" class="text-red-500 mr-1">*</span> 42 <span v-if="field.required" class="text-red-500 mr-1">*</span>
43 <span>{{ field.label }}</span> 43 <span>{{ field.label }}</span>
...@@ -84,6 +84,7 @@ import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue' ...@@ -84,6 +84,7 @@ import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue'
84 import PlanFieldRadio from '../PlanFields/RadioGroup.vue' 84 import PlanFieldRadio from '../PlanFields/RadioGroup.vue'
85 import PlanFieldSelect from '../PlanFields/SelectPickerGlobal.vue' 85 import PlanFieldSelect from '../PlanFields/SelectPickerGlobal.vue'
86 import PaymentPeriodRadio from '../PlanFields/PaymentPeriodRadio.vue' 86 import PaymentPeriodRadio from '../PlanFields/PaymentPeriodRadio.vue'
87 +import { useFieldDependencies } from '@/composables/useFieldDependencies'
87 88
88 /** 89 /**
89 * 组件属性 90 * 组件属性
...@@ -161,6 +162,13 @@ const baseFields = computed(() => props.config?.form_schema?.base_fields || []) ...@@ -161,6 +162,13 @@ const baseFields = computed(() => props.config?.form_schema?.base_fields || [])
161 const withdrawalFields = computed(() => props.config?.form_schema?.withdrawal_fields || []) 162 const withdrawalFields = computed(() => props.config?.form_schema?.withdrawal_fields || [])
162 const resetMap = computed(() => props.config?.form_schema?.reset_map || {}) 163 const resetMap = computed(() => props.config?.form_schema?.reset_map || {})
163 164
165 +const fieldDefinitions = computed(() => {
166 + return [...baseFields.value, ...withdrawalFields.value].reduce((result, field) => {
167 + result[field.key] = field
168 + return result
169 + }, {})
170 +})
171 +
164 /** 172 /**
165 * 获取字段对应的渲染组件 173 * 获取字段对应的渲染组件
166 * @param {Object} field - 字段配置 174 * @param {Object} field - 字段配置
...@@ -214,20 +222,7 @@ const getFieldProps = (field) => { ...@@ -214,20 +222,7 @@ const getFieldProps = (field) => {
214 return fieldProps 222 return fieldProps
215 } 223 }
216 224
217 -/** 225 +const { isFieldVisible } = useFieldDependencies(form, fieldDefinitions)
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 226
232 /** 227 /**
233 * 获取 Schema 默认值 228 * 获取 Schema 默认值
...@@ -261,7 +256,11 @@ const initializeForm = (value) => { ...@@ -261,7 +256,11 @@ const initializeForm = (value) => {
261 ...value, 256 ...value,
262 ...defaults, 257 ...defaults,
263 annual_withdrawal_amount: value.annual_withdrawal_amount ?? null, 258 annual_withdrawal_amount: value.annual_withdrawal_amount ?? null,
264 - annual_increase_percentage: value.annual_increase_percentage ?? null 259 + annual_increase_percentage: value.annual_increase_percentage ?? null,
260 + withdrawal_start_age_specified: value.withdrawal_start_age_specified ?? null,
261 + withdrawal_period_specified: value.withdrawal_period_specified ?? null,
262 + withdrawal_start_age_fixed: value.withdrawal_start_age_fixed ?? null,
263 + withdrawal_period_fixed: value.withdrawal_period_fixed ?? null
265 }) 264 })
266 } 265 }
267 266
...@@ -292,7 +291,11 @@ watch( ...@@ -292,7 +291,11 @@ watch(
292 ...newVal, 291 ...newVal,
293 ...defaults, 292 ...defaults,
294 annual_withdrawal_amount: newVal.annual_withdrawal_amount ?? null, 293 annual_withdrawal_amount: newVal.annual_withdrawal_amount ?? null,
295 - annual_increase_percentage: newVal.annual_increase_percentage ?? null 294 + annual_increase_percentage: newVal.annual_increase_percentage ?? null,
295 + withdrawal_start_age_specified: newVal.withdrawal_start_age_specified ?? null,
296 + withdrawal_period_specified: newVal.withdrawal_period_specified ?? null,
297 + withdrawal_start_age_fixed: newVal.withdrawal_start_age_fixed ?? null,
298 + withdrawal_period_fixed: newVal.withdrawal_period_fixed ?? null
296 }) 299 })
297 previousModelValue = newVal 300 previousModelValue = newVal
298 } 301 }
...@@ -386,7 +389,7 @@ const validate = () => { ...@@ -386,7 +389,7 @@ const validate = () => {
386 const fields = [...baseFields.value, ...(props.config.withdrawal_plan?.enabled ? withdrawalFields.value : [])] 389 const fields = [...baseFields.value, ...(props.config.withdrawal_plan?.enabled ? withdrawalFields.value : [])]
387 390
388 for (const field of fields) { 391 for (const field of fields) {
389 - if (!isFieldVisible(field)) { 392 + if (!isFieldVisible(field.key)) {
390 continue 393 continue
391 } 394 }
392 395
...@@ -398,7 +401,7 @@ const validate = () => { ...@@ -398,7 +401,7 @@ const validate = () => {
398 } 401 }
399 } 402 }
400 403
401 - if (field.type === 'percentage' && isFieldVisible(field)) { 404 + if (field.type === 'percentage' && isFieldVisible(field.key)) {
402 const percentage = parseFloat(form[field.key]) 405 const percentage = parseFloat(form[field.key])
403 if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) { 406 if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) {
404 Taro.showToast({ title: '请输入0-100之间的百分比', icon: 'none' }) 407 Taro.showToast({ title: '请输入0-100之间的百分比', icon: 'none' })
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
7 * @created 2026-02-14 7 * @created 2026-02-14
8 */ 8 */
9 9
10 -import { computed, reactive } from 'vue' 10 +import { computed, reactive, isRef } from 'vue'
11 import { PLAN_FIELD_DEFINITIONS } from '@/config/plan-fields' 11 import { PLAN_FIELD_DEFINITIONS } from '@/config/plan-fields'
12 12
13 /** 13 /**
...@@ -24,7 +24,7 @@ import { PLAN_FIELD_DEFINITIONS } from '@/config/plan-fields' ...@@ -24,7 +24,7 @@ import { PLAN_FIELD_DEFINITIONS } from '@/config/plan-fields'
24 * detectCircularDeps('B') // true 24 * detectCircularDeps('B') // true
25 * detectCircularDeps('C') // true 25 * detectCircularDeps('C') // true
26 */ 26 */
27 -function detectCircularDeps(fieldKey, visited = new Set()) { 27 +function detectCircularDeps(fieldKey, fieldDefinitions, visited = new Set()) {
28 // 防止无限递归 28 // 防止无限递归
29 if (visited.size > 50) { 29 if (visited.size > 50) {
30 console.error('[useFieldDependencies] 依赖层级过深,可能存在循环依赖') 30 console.error('[useFieldDependencies] 依赖层级过深,可能存在循环依赖')
...@@ -38,12 +38,12 @@ function detectCircularDeps(fieldKey, visited = new Set()) { ...@@ -38,12 +38,12 @@ function detectCircularDeps(fieldKey, visited = new Set()) {
38 } 38 }
39 visited.add(fieldKey) 39 visited.add(fieldKey)
40 40
41 - const definition = PLAN_FIELD_DEFINITIONS[fieldKey] 41 + const definition = fieldDefinitions[fieldKey]
42 if (!definition?.affects) return false 42 if (!definition?.affects) return false
43 43
44 // 递归检查依赖字段 44 // 递归检查依赖字段
45 for (const depKey of definition.affects) { 45 for (const depKey of definition.affects) {
46 - if (detectCircularDeps(depKey, visited)) { 46 + if (detectCircularDeps(depKey, fieldDefinitions, visited)) {
47 return true 47 return true
48 } 48 }
49 } 49 }
...@@ -73,13 +73,18 @@ function detectCircularDeps(fieldKey, visited = new Set()) { ...@@ -73,13 +73,18 @@ function detectCircularDeps(fieldKey, visited = new Set()) {
73 * // 获取所有可见字段 73 * // 获取所有可见字段
74 * const visible = visibleFields.value 74 * const visible = visibleFields.value
75 */ 75 */
76 -export function useFieldDependencies(formData) { 76 +export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEFINITIONS) {
77 // 字段显示状态映射 77 // 字段显示状态映射
78 const fieldVisibility = reactive({}) 78 const fieldVisibility = reactive({})
79 79
80 // 字段启用状态映射 80 // 字段启用状态映射
81 const fieldEnabled = reactive({}) 81 const fieldEnabled = reactive({})
82 82
83 + const getFieldDefinitions = () => {
84 + const definitions = isRef(fieldDefinitions) ? fieldDefinitions.value : fieldDefinitions
85 + return definitions || {}
86 + }
87 +
83 /** 88 /**
84 * 检查字段是否应该显示 89 * 检查字段是否应该显示
85 * 90 *
...@@ -87,12 +92,22 @@ export function useFieldDependencies(formData) { ...@@ -87,12 +92,22 @@ export function useFieldDependencies(formData) {
87 * @returns {boolean} 是否显示 92 * @returns {boolean} 是否显示
88 */ 93 */
89 function isFieldVisible(fieldKey) { 94 function isFieldVisible(fieldKey) {
90 - const definition = PLAN_FIELD_DEFINITIONS[fieldKey] 95 + const definitions = getFieldDefinitions()
96 + const definition = definitions[fieldKey]
91 if (!definition) return false 97 if (!definition) return false
92 98
93 // 检查是否有 show_when 条件 99 // 检查是否有 show_when 条件
94 if (definition.show_when) { 100 if (definition.show_when) {
95 const conditions = definition.show_when 101 const conditions = definition.show_when
102 + if (Array.isArray(conditions)) {
103 + for (const condition of conditions) {
104 + if (!condition) continue
105 + const currentValue = formData[condition.field]
106 + if (currentValue !== condition.equals) {
107 + return false
108 + }
109 + }
110 + } else if (conditions && typeof conditions === 'object') {
96 for (const [depKey, expectedValue] of Object.entries(conditions)) { 111 for (const [depKey, expectedValue] of Object.entries(conditions)) {
97 const currentValue = formData[depKey] 112 const currentValue = formData[depKey]
98 if (currentValue !== expectedValue) { 113 if (currentValue !== expectedValue) {
...@@ -100,9 +115,10 @@ export function useFieldDependencies(formData) { ...@@ -100,9 +115,10 @@ export function useFieldDependencies(formData) {
100 } 115 }
101 } 116 }
102 } 117 }
118 + }
103 119
104 // 检查是否被依赖字段影响 120 // 检查是否被依赖字段影响
105 - for (const [key, def] of Object.entries(PLAN_FIELD_DEFINITIONS)) { 121 + for (const [key, def] of Object.entries(definitions)) {
106 if (def.affects?.includes(fieldKey)) { 122 if (def.affects?.includes(fieldKey)) {
107 // 依赖字段必须为 true 才显示 123 // 依赖字段必须为 true 才显示
108 if (formData[key] !== true) { 124 if (formData[key] !== true) {
...@@ -121,7 +137,7 @@ export function useFieldDependencies(formData) { ...@@ -121,7 +137,7 @@ export function useFieldDependencies(formData) {
121 * @returns {boolean} 是否启用 137 * @returns {boolean} 是否启用
122 */ 138 */
123 function isFieldEnabled(fieldKey) { 139 function isFieldEnabled(fieldKey) {
124 - const definition = PLAN_FIELD_DEFINITIONS[fieldKey] 140 + const definition = getFieldDefinitions()[fieldKey]
125 if (!definition) return false 141 if (!definition) return false
126 142
127 // 如果有依赖字段,检查依赖字段是否满足 143 // 如果有依赖字段,检查依赖字段是否满足
...@@ -143,7 +159,7 @@ export function useFieldDependencies(formData) { ...@@ -143,7 +159,7 @@ export function useFieldDependencies(formData) {
143 formData[fieldKey] = value 159 formData[fieldKey] = value
144 160
145 // 更新受影响字段的显示状态 161 // 更新受影响字段的显示状态
146 - const definition = PLAN_FIELD_DEFINITIONS[fieldKey] 162 + const definition = getFieldDefinitions()[fieldKey]
147 if (definition?.affects) { 163 if (definition?.affects) {
148 for (const affectedKey of definition.affects) { 164 for (const affectedKey of definition.affects) {
149 fieldVisibility[affectedKey] = isFieldVisible(affectedKey) 165 fieldVisibility[affectedKey] = isFieldVisible(affectedKey)
...@@ -158,21 +174,22 @@ export function useFieldDependencies(formData) { ...@@ -158,21 +174,22 @@ export function useFieldDependencies(formData) {
158 * @returns {string[]} 可见字段键名数组 174 * @returns {string[]} 可见字段键名数组
159 */ 175 */
160 const visibleFields = computed(() => { 176 const visibleFields = computed(() => {
161 - return Object.keys(PLAN_FIELD_DEFINITIONS).filter(key => isFieldVisible(key)) 177 + return Object.keys(getFieldDefinitions()).filter(key => isFieldVisible(key))
162 }) 178 })
163 179
164 /** 180 /**
165 * 初始化所有字段的显示状态(包含循环依赖检测) 181 * 初始化所有字段的显示状态(包含循环依赖检测)
166 */ 182 */
167 function initFieldStates() { 183 function initFieldStates() {
184 + const definitions = getFieldDefinitions()
168 // 开发环境检测循环依赖 185 // 开发环境检测循环依赖
169 if (process.env.NODE_ENV === 'development') { 186 if (process.env.NODE_ENV === 'development') {
170 - for (const key of Object.keys(PLAN_FIELD_DEFINITIONS)) { 187 + for (const key of Object.keys(definitions)) {
171 - detectCircularDeps(key) 188 + detectCircularDeps(key, definitions)
172 } 189 }
173 } 190 }
174 191
175 - for (const key of Object.keys(PLAN_FIELD_DEFINITIONS)) { 192 + for (const key of Object.keys(definitions)) {
176 fieldVisibility[key] = isFieldVisible(key) 193 fieldVisibility[key] = isFieldVisible(key)
177 fieldEnabled[key] = isFieldEnabled(key) 194 fieldEnabled[key] = isFieldEnabled(key)
178 } 195 }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
8 * @version 1.1.0 - 简化转换逻辑,减少重复代码 8 * @version 1.1.0 - 简化转换逻辑,减少重复代码
9 */ 9 */
10 10
11 -import { computed } from 'vue' 11 +import { computed, isRef } from 'vue'
12 import { PLAN_FIELD_DEFINITIONS, TRANSFORM_TYPES } from '@/config/plan-fields' 12 import { PLAN_FIELD_DEFINITIONS, TRANSFORM_TYPES } from '@/config/plan-fields'
13 import { transformFieldValue, batchTransformFields } from '@/utils/planFieldTransformers' 13 import { transformFieldValue, batchTransformFields } from '@/utils/planFieldTransformers'
14 14
...@@ -29,7 +29,12 @@ import { transformFieldValue, batchTransformFields } from '@/utils/planFieldTran ...@@ -29,7 +29,12 @@ import { transformFieldValue, batchTransformFields } from '@/utils/planFieldTran
29 * toFen('coverage', '100.00') // 10000 29 * toFen('coverage', '100.00') // 10000
30 */ 30 */
31 // eslint-disable-next-line react-hooks/rules-of-hooks 31 // eslint-disable-next-line react-hooks/rules-of-hooks
32 -export function useFieldValueTransform(formData) { 32 +export function useFieldValueTransform(formData, fieldDefinitions = PLAN_FIELD_DEFINITIONS) {
33 + const getFieldDefinitions = () => {
34 + const definitions = isRef(fieldDefinitions) ? fieldDefinitions.value : fieldDefinitions
35 + return definitions || {}
36 + }
37 +
33 const getReverseTransform = (transform) => { 38 const getReverseTransform = (transform) => {
34 if (!transform || transform === TRANSFORM_TYPES.NONE) return TRANSFORM_TYPES.NONE 39 if (!transform || transform === TRANSFORM_TYPES.NONE) return TRANSFORM_TYPES.NONE
35 if (transform === TRANSFORM_TYPES.FEN_TO_YUAN) return TRANSFORM_TYPES.YUAN_TO_FEN 40 if (transform === TRANSFORM_TYPES.FEN_TO_YUAN) return TRANSFORM_TYPES.YUAN_TO_FEN
...@@ -37,7 +42,12 @@ export function useFieldValueTransform(formData) { ...@@ -37,7 +42,12 @@ export function useFieldValueTransform(formData) {
37 return TRANSFORM_TYPES.NONE 42 return TRANSFORM_TYPES.NONE
38 } 43 }
39 44
40 - const reverseFieldDefinitions = Object.entries(PLAN_FIELD_DEFINITIONS).reduce((result, [key, definition]) => { 45 + const getReverseFieldDefinitions = () => {
46 + return Object.entries(getFieldDefinitions()).reduce((result, [key, definition]) => {
47 + if (!definition || typeof definition === 'string') {
48 + result[key] = { transform: TRANSFORM_TYPES.NONE }
49 + return result
50 + }
41 const reverseTransform = getReverseTransform(definition.transform) 51 const reverseTransform = getReverseTransform(definition.transform)
42 result[key] = { 52 result[key] = {
43 ...definition, 53 ...definition,
...@@ -45,6 +55,7 @@ export function useFieldValueTransform(formData) { ...@@ -45,6 +55,7 @@ export function useFieldValueTransform(formData) {
45 } 55 }
46 return result 56 return result
47 }, {}) 57 }, {})
58 + }
48 59
49 /** 60 /**
50 * 转换为分值(用于显示) 61 * 转换为分值(用于显示)
...@@ -61,8 +72,8 @@ export function useFieldValueTransform(formData) { ...@@ -61,8 +72,8 @@ export function useFieldValueTransform(formData) {
61 const toYuan = (fieldKey, value) => { 72 const toYuan = (fieldKey, value) => {
62 if (value === undefined) return undefined 73 if (value === undefined) return undefined
63 if (value === null) return null 74 if (value === null) return null
64 - const definition = PLAN_FIELD_DEFINITIONS[fieldKey] 75 + const definition = getFieldDefinitions()[fieldKey]
65 - if (!definition) return value 76 + if (!definition || typeof definition === 'string') return value
66 77
67 if (!definition.transform || definition.transform === TRANSFORM_TYPES.NONE) { 78 if (!definition.transform || definition.transform === TRANSFORM_TYPES.NONE) {
68 return value 79 return value
...@@ -87,8 +98,8 @@ export function useFieldValueTransform(formData) { ...@@ -87,8 +98,8 @@ export function useFieldValueTransform(formData) {
87 const toFen = (fieldKey, value) => { 98 const toFen = (fieldKey, value) => {
88 if (value === undefined) return undefined 99 if (value === undefined) return undefined
89 if (value === null) return null 100 if (value === null) return null
90 - const definition = PLAN_FIELD_DEFINITIONS[fieldKey] 101 + const definition = getFieldDefinitions()[fieldKey]
91 - if (!definition) return value 102 + if (!definition || typeof definition === 'string') return value
92 103
93 const reverseTransform = getReverseTransform(definition.transform) 104 const reverseTransform = getReverseTransform(definition.transform)
94 if (!reverseTransform || reverseTransform === TRANSFORM_TYPES.NONE) { 105 if (!reverseTransform || reverseTransform === TRANSFORM_TYPES.NONE) {
...@@ -110,7 +121,7 @@ export function useFieldValueTransform(formData) { ...@@ -110,7 +121,7 @@ export function useFieldValueTransform(formData) {
110 * // { coverage: '100.00', name: 'Test' } 121 * // { coverage: '100.00', name: 'Test' }
111 */ 122 */
112 const batchToYuan = (sourceData) => { 123 const batchToYuan = (sourceData) => {
113 - return batchTransformFields(sourceData, PLAN_FIELD_DEFINITIONS) 124 + return batchTransformFields(sourceData, getFieldDefinitions())
114 } 125 }
115 126
116 /** 127 /**
...@@ -125,7 +136,7 @@ export function useFieldValueTransform(formData) { ...@@ -125,7 +136,7 @@ export function useFieldValueTransform(formData) {
125 * // { coverage: 10000, name: 'Test' } 136 * // { coverage: 10000, name: 'Test' }
126 */ 137 */
127 const batchToFen = (yuanData) => { 138 const batchToFen = (yuanData) => {
128 - return batchTransformFields(yuanData, reverseFieldDefinitions) 139 + return batchTransformFields(yuanData, getReverseFieldDefinitions())
129 } 140 }
130 141
131 // 计算属性:表单显示数据(元值转分值显示) 142 // 计算属性:表单显示数据(元值转分值显示)
......
...@@ -47,11 +47,13 @@ const savingsSubmitMapping = { ...@@ -47,11 +47,13 @@ const savingsSubmitMapping = {
47 ...baseSubmitMapping, 47 ...baseSubmitMapping,
48 withdrawal_enabled: { api_field: 'allow_reduce_amount' }, 48 withdrawal_enabled: { api_field: 'allow_reduce_amount' },
49 withdrawal_mode: { api_field: 'withdrawal_option' }, 49 withdrawal_mode: { api_field: 'withdrawal_option' },
50 - withdrawal_start_age: { api_field: 'withdrawal_start_age' },
51 - withdrawal_period: { api_field: 'withdrawal_period' },
52 withdrawal_method: { api_field: 'withdrawal_method' }, 50 withdrawal_method: { api_field: 'withdrawal_method' },
53 annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' }, 51 annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
54 - annual_increase_percentage: { api_field: 'annual_increase_percentage' } 52 + annual_increase_percentage: { api_field: 'annual_increase_percentage' },
53 + withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
54 + withdrawal_period_specified: { api_field: 'withdrawal_period' },
55 + withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
56 + withdrawal_period_fixed: { api_field: 'withdrawal_period' }
55 } 57 }
56 58
57 // 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口) 59 // 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
...@@ -70,18 +72,18 @@ const savingsFormSchema = { ...@@ -70,18 +72,18 @@ const savingsFormSchema = {
70 { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' }, 72 { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
71 { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' }, 73 { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
72 { id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] }, 74 { id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
73 - { 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: '按年岁' }] }, 75 + { 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: '指定提取金额' }] },
74 - { id: 'withdrawal_start_age_by_year', key: 'withdrawal_start_age', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] }, 76 + { id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
75 - { 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: '指定提取金额' }] }, 77 + { id: 'withdrawal_period_specified', key: 'withdrawal_period_specified', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
76 - { id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }, { field: 'withdrawal_method', equals: '按年岁' }] }, 78 + { id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
77 - { id: 'withdrawal_start_age_by_fixed', key: 'withdrawal_start_age', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }, 79 + { id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
78 - { 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: '最高固定提取金额' }] } 80 + { id: 'withdrawal_period_fixed', key: 'withdrawal_period_fixed', type: 'select', label: '按年岁:提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }
79 ], 81 ],
80 // 提取模式切换时的清空逻辑,避免脏字段影响提交 82 // 提取模式切换时的清空逻辑,避免脏字段影响提交
81 reset_map: { 83 reset_map: {
82 withdrawal_mode: { 84 withdrawal_mode: {
83 - '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age', 'withdrawal_period'], 85 + '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
84 - '指定提取金额': ['withdrawal_start_age', 'withdrawal_period'] 86 + '指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
85 } 87 }
86 } 88 }
87 } 89 }
......