refactor(plan): 重构多阶段提取方案 UI 渲染逻辑
- 统一单阶段和多阶段的 UI 渲染流程 - 新增 shouldRenderField() 方法智能控制字段显示 - 多阶段模式现在支持"指定提取金额"和"最高固定提取金额"两种方式 - 优化校验逻辑:基础字段 → withdrawal_fields → 多阶段卡片 - 关闭 Mock 数据(准备联调测试) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Showing
4 changed files
with
86 additions
and
71 deletions
| ... | @@ -83,3 +83,20 @@ | ... | @@ -83,3 +83,20 @@ |
| 83 | 83 | ||
| 84 | **变更摘要**: | 84 | **变更摘要**: |
| 85 | - 无详细描述 | 85 | - 无详细描述 |
| 86 | +## 2026-02-27 | ||
| 87 | + | ||
| 88 | +### 09:56:15 - 完成任务 | ||
| 89 | + | ||
| 90 | +**影响文件**: | ||
| 91 | +- `src/utils/README.md` | ||
| 92 | + | ||
| 93 | +**变更摘要**: | ||
| 94 | +- 无详细描述 | ||
| 95 | + | ||
| 96 | +### 10:00:56 - 完成任务 | ||
| 97 | + | ||
| 98 | +**影响文件**: | ||
| 99 | +- `src/utils/README.md` | ||
| 100 | + | ||
| 101 | +**变更摘要**: | ||
| 102 | +- 无详细描述 | ... | ... |
| ... | @@ -25,12 +25,38 @@ | ... | @@ -25,12 +25,38 @@ |
| 25 | 25 | ||
| 26 | <div class="border-t border-gray-200 my-6"></div> | 26 | <div class="border-t border-gray-200 my-6"></div> |
| 27 | 27 | ||
| 28 | - <!-- 多阶段提取计划(优先显示) --> | 28 | + <!-- 提取计划(单阶段和多阶段通用逻辑) --> |
| 29 | - <div v-if="isMultiStageMode" class="multi-stage-withdrawal-section"> | 29 | + <div v-if="config.withdrawal_plan?.enabled" class="withdrawal-plan-section"> |
| 30 | - <h3 class="text-base font-semibold text-gray-900 mb-4"> | 30 | + <!-- 1. 渲染 withdrawal_fields(包含 withdrawal_enabled、withdrawal_mode、withdrawal_method 等) --> |
| 31 | - 款项提取(允许减少名义金额) | 31 | + <!-- 注意:多阶段模式 + 选择"指定提取金额"时,跳过单组字段渲染 --> |
| 32 | + <template v-for="field in withdrawalFields" :key="field.id || field.key"> | ||
| 33 | + <h3 v-if="field.section_title" class="text-base font-semibold text-gray-900 mb-4"> | ||
| 34 | + {{ field.section_title }} | ||
| 32 | </h3> | 35 | </h3> |
| 36 | + <component | ||
| 37 | + v-if="shouldRenderField(field) && field.type !== 'percentage'" | ||
| 38 | + :is="getFieldComponent(field)" | ||
| 39 | + v-model="form[field.key]" | ||
| 40 | + v-bind="getFieldProps(field)" | ||
| 41 | + class="mb-5" | ||
| 42 | + /> | ||
| 43 | + <div v-else-if="shouldRenderField(field) && field.type === 'percentage'" class="mb-5"> | ||
| 44 | + <div class="text-sm text-gray-700 mb-2 flex items-center"> | ||
| 45 | + <span v-if="field.required" class="text-red-500 mr-1">*</span> | ||
| 46 | + <span>{{ field.label }}</span> | ||
| 47 | + </div> | ||
| 48 | + <nut-input | ||
| 49 | + v-model="form[field.key]" | ||
| 50 | + type="digit" | ||
| 51 | + :placeholder="field.placeholder" | ||
| 52 | + @input="(value) => onPercentageInput(value, field.key)" | ||
| 53 | + class="w-full" | ||
| 54 | + /> | ||
| 55 | + </div> | ||
| 56 | + </template> | ||
| 33 | 57 | ||
| 58 | + <!-- 2. 多阶段模式 + 选择"指定提取金额":显示多组阶段卡片 --> | ||
| 59 | + <div v-if="isMultiStageMode && form.withdrawal_mode === '指定提取金额'" class="multi-stage-withdrawal-section"> | ||
| 34 | <!-- 阶段卡片列表 --> | 60 | <!-- 阶段卡片列表 --> |
| 35 | <div | 61 | <div |
| 36 | v-for="(stage, index) in stages" | 62 | v-for="(stage, index) in stages" |
| ... | @@ -108,34 +134,6 @@ | ... | @@ -108,34 +134,6 @@ |
| 108 | + 添加阶段 | 134 | + 添加阶段 |
| 109 | </nut-button> | 135 | </nut-button> |
| 110 | </div> | 136 | </div> |
| 111 | - | ||
| 112 | - <!-- 单阶段提取计划(原有逻辑) --> | ||
| 113 | - <div v-else-if="config.withdrawal_plan?.enabled" class="withdrawal-plan-section"> | ||
| 114 | - <template v-for="field in withdrawalFields" :key="field.id || field.key"> | ||
| 115 | - <h3 v-if="field.section_title" class="text-base font-semibold text-gray-900 mb-4"> | ||
| 116 | - {{ field.section_title }} | ||
| 117 | - </h3> | ||
| 118 | - <component | ||
| 119 | - v-if="isFieldVisible(field.key) && field.type !== 'percentage'" | ||
| 120 | - :is="getFieldComponent(field)" | ||
| 121 | - v-model="form[field.key]" | ||
| 122 | - v-bind="getFieldProps(field)" | ||
| 123 | - class="mb-5" | ||
| 124 | - /> | ||
| 125 | - <div v-else-if="isFieldVisible(field.key) && field.type === 'percentage'" class="mb-5"> | ||
| 126 | - <div class="text-sm text-gray-700 mb-2 flex items-center"> | ||
| 127 | - <span v-if="field.required" class="text-red-500 mr-1">*</span> | ||
| 128 | - <span>{{ field.label }}</span> | ||
| 129 | - </div> | ||
| 130 | - <nut-input | ||
| 131 | - v-model="form[field.key]" | ||
| 132 | - type="digit" | ||
| 133 | - :placeholder="field.placeholder" | ||
| 134 | - @input="(value) => onPercentageInput(value, field.key)" | ||
| 135 | - class="w-full" | ||
| 136 | - /> | ||
| 137 | - </div> | ||
| 138 | - </template> | ||
| 139 | </div> | 137 | </div> |
| 140 | </div> | 138 | </div> |
| 141 | 139 | ||
| ... | @@ -309,6 +307,32 @@ const getFieldProps = (field) => { | ... | @@ -309,6 +307,32 @@ const getFieldProps = (field) => { |
| 309 | 307 | ||
| 310 | const { isFieldVisible } = useFieldDependencies(form, fieldDefinitions) | 308 | const { isFieldVisible } = useFieldDependencies(form, fieldDefinitions) |
| 311 | 309 | ||
| 310 | +/** | ||
| 311 | + * 判断字段是否应该渲染 | ||
| 312 | + * @description 多阶段模式 + 选择"指定提取金额"时,跳过单组字段渲染 | ||
| 313 | + * @param {Object} field - 字段配置 | ||
| 314 | + * @returns {boolean} 是否应该渲染 | ||
| 315 | + */ | ||
| 316 | +const shouldRenderField = (field) => { | ||
| 317 | + // 基础可见性检查 | ||
| 318 | + if (!isFieldVisible(field.key)) return false | ||
| 319 | + | ||
| 320 | + // 多阶段模式 + 选择"指定提取金额"时,跳过部分单组字段 | ||
| 321 | + // 注意:withdrawal_method(提取方式)需要保留 | ||
| 322 | + if (isMultiStageMode.value && form.withdrawal_mode === '指定提取金额') { | ||
| 323 | + // 需要跳过的字段:单阶段"指定提取金额"的金额和期数相关字段 | ||
| 324 | + const skipFields = [ | ||
| 325 | + 'annual_withdrawal_amount', | ||
| 326 | + 'withdrawal_start_age_specified', | ||
| 327 | + 'withdrawal_period_specified', | ||
| 328 | + 'annual_increase_percentage' | ||
| 329 | + ] | ||
| 330 | + if (skipFields.includes(field.key)) return false | ||
| 331 | + } | ||
| 332 | + | ||
| 333 | + return true | ||
| 334 | +} | ||
| 335 | + | ||
| 312 | // ====== 多阶段提取计划逻辑 ====== | 336 | // ====== 多阶段提取计划逻辑 ====== |
| 313 | 337 | ||
| 314 | /** | 338 | /** |
| ... | @@ -681,12 +705,7 @@ const isFieldRequired = (field) => { | ... | @@ -681,12 +705,7 @@ const isFieldRequired = (field) => { |
| 681 | * @returns {boolean} 校验是否通过 | 705 | * @returns {boolean} 校验是否通过 |
| 682 | */ | 706 | */ |
| 683 | const validate = () => { | 707 | const validate = () => { |
| 684 | - // 多阶段模式校验 | 708 | + // 1. 基础字段校验(单阶段和多阶段通用) |
| 685 | - if (isMultiStageMode.value) { | ||
| 686 | - return validateMultiStage() | ||
| 687 | - } | ||
| 688 | - | ||
| 689 | - // 单阶段模式校验(原有逻辑) | ||
| 690 | const fields = [...baseFields.value, ...(props.config.withdrawal_plan?.enabled ? withdrawalFields.value : [])] | 709 | const fields = [...baseFields.value, ...(props.config.withdrawal_plan?.enabled ? withdrawalFields.value : [])] |
| 691 | 710 | ||
| 692 | // 年龄与出生年月日二选一校验 | 711 | // 年龄与出生年月日二选一校验 |
| ... | @@ -705,6 +724,7 @@ const validate = () => { | ... | @@ -705,6 +724,7 @@ const validate = () => { |
| 705 | form.age = currentYear - birthYear | 724 | form.age = currentYear - birthYear |
| 706 | } | 725 | } |
| 707 | 726 | ||
| 727 | + // 2. 校验基础字段和 withdrawal_fields 中可见的必填字段 | ||
| 708 | for (const field of fields) { | 728 | for (const field of fields) { |
| 709 | if (!isFieldVisible(field.key)) { | 729 | if (!isFieldVisible(field.key)) { |
| 710 | continue | 730 | continue |
| ... | @@ -733,44 +753,21 @@ const validate = () => { | ... | @@ -733,44 +753,21 @@ const validate = () => { |
| 733 | } | 753 | } |
| 734 | } | 754 | } |
| 735 | 755 | ||
| 756 | + // 3. 多阶段模式 + 选择"指定提取金额":额外校验多阶段卡片 | ||
| 757 | + if (isMultiStageMode.value && form.withdrawal_mode === '指定提取金额') { | ||
| 758 | + return validateMultiStage() | ||
| 759 | + } | ||
| 760 | + | ||
| 736 | return true | 761 | return true |
| 737 | } | 762 | } |
| 738 | 763 | ||
| 739 | /** | 764 | /** |
| 740 | * 多阶段表单校验 | 765 | * 多阶段表单校验 |
| 741 | - * @description 校验每个阶段的必填字段 | 766 | + * @description 校验多阶段卡片的必填字段 |
| 767 | + * @note 基础字段和 withdrawal_fields 已在 validate() 中校验 | ||
| 742 | * @returns {boolean} 校验是否通过 | 768 | * @returns {boolean} 校验是否通过 |
| 743 | */ | 769 | */ |
| 744 | const validateMultiStage = () => { | 770 | const validateMultiStage = () => { |
| 745 | - // 基础字段校验(年龄与出生年月日) | ||
| 746 | - const hasAge = !isEmptyValue(form.age) | ||
| 747 | - const hasBirthday = !isEmptyValue(form.birthday) | ||
| 748 | - | ||
| 749 | - if (!hasAge && !hasBirthday) { | ||
| 750 | - Taro.showToast({ title: '年龄与出生年月日至少填写一项', icon: 'none' }) | ||
| 751 | - return false | ||
| 752 | - } | ||
| 753 | - | ||
| 754 | - // 如果都填写了,以生日为准,重新计算年龄 | ||
| 755 | - if (hasAge && hasBirthday) { | ||
| 756 | - const birthYear = new Date(form.birthday).getFullYear() | ||
| 757 | - const currentYear = new Date().getFullYear() | ||
| 758 | - form.age = currentYear - birthYear | ||
| 759 | - } | ||
| 760 | - | ||
| 761 | - // 基础字段校验(其他字段) | ||
| 762 | - for (const field of baseFields.value) { | ||
| 763 | - if (field.key === 'age') continue // 已处理 | ||
| 764 | - | ||
| 765 | - if (isFieldRequired(field)) { | ||
| 766 | - const value = form[field.key] | ||
| 767 | - if (isEmptyValue(value)) { | ||
| 768 | - Taro.showToast({ title: getRequiredMessage(field), icon: 'none' }) | ||
| 769 | - return false | ||
| 770 | - } | ||
| 771 | - } | ||
| 772 | - } | ||
| 773 | - | ||
| 774 | // 多阶段字段校验 | 771 | // 多阶段字段校验 |
| 775 | for (let i = 0; i < stages.value.length; i++) { | 772 | for (let i = 0; i < stages.value.length; i++) { |
| 776 | const stage = stages.value[i] | 773 | const stage = stages.value[i] | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2026-02-13 01:05:52 | 2 | * @Date: 2026-02-13 01:05:52 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2026-02-25 17:18:05 | 4 | + * @LastEditTime: 2026-02-27 10:22:51 |
| 5 | * @FilePath: /manulife-weapp/src/config/app.js | 5 | * @FilePath: /manulife-weapp/src/config/app.js |
| 6 | * @Description: 应用配置 | 6 | * @Description: 应用配置 |
| 7 | */ | 7 | */ |
| ... | @@ -29,7 +29,7 @@ | ... | @@ -29,7 +29,7 @@ |
| 29 | * // 关闭 Mock 数据(生产环境) | 29 | * // 关闭 Mock 数据(生产环境) |
| 30 | * USE_MOCK_DATA = false | 30 | * USE_MOCK_DATA = false |
| 31 | */ | 31 | */ |
| 32 | -export const USE_MOCK_DATA = true | 32 | +export const USE_MOCK_DATA = false |
| 33 | 33 | ||
| 34 | /** | 34 | /** |
| 35 | * 根据 NODE_ENV 自动判断是否使用 Mock | 35 | * 根据 NODE_ENV 自动判断是否使用 Mock | ... | ... |
| ... | @@ -393,7 +393,8 @@ export const PLAN_TEMPLATES = { | ... | @@ -393,7 +393,8 @@ export const PLAN_TEMPLATES = { |
| 393 | enabled: true, | 393 | enabled: true, |
| 394 | currencies: ['HKD', 'USD', 'CNY'], | 394 | currencies: ['HKD', 'USD', 'CNY'], |
| 395 | default_currency: 'USD', | 395 | default_currency: 'USD', |
| 396 | - withdrawal_modes: ['指定提取金额'], // 多阶段模式只支持指定提取金额 | 396 | + // 多阶段模式:支持指定提取金额(多组)和最高固定提取金额(单组) |
| 397 | + withdrawal_modes: ['指定提取金额', '最高固定提取金额'], | ||
| 397 | withdrawal_periods: multiStageWithdrawalConfig.withdrawal_periods | 398 | withdrawal_periods: multiStageWithdrawalConfig.withdrawal_periods |
| 398 | }, | 399 | }, |
| 399 | form_schema: savingsFormSchema, | 400 | form_schema: savingsFormSchema, | ... | ... |
-
Please register or login to post a comment