hookehuyr

refactor(plan): 重构多阶段提取方案 UI 渲染逻辑

- 统一单阶段和多阶段的 UI 渲染流程
- 新增 shouldRenderField() 方法智能控制字段显示
- 多阶段模式现在支持"指定提取金额"和"最高固定提取金额"两种方式
- 优化校验逻辑:基础字段 → withdrawal_fields → 多阶段卡片
- 关闭 Mock 数据(准备联调测试)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
......@@ -83,3 +83,20 @@
**变更摘要**:
- 无详细描述
## 2026-02-27
### 09:56:15 - 完成任务
**影响文件**:
- `src/utils/README.md`
**变更摘要**:
- 无详细描述
### 10:00:56 - 完成任务
**影响文件**:
- `src/utils/README.md`
**变更摘要**:
- 无详细描述
......
......@@ -25,12 +25,38 @@
<div class="border-t border-gray-200 my-6"></div>
<!-- 多阶段提取计划(优先显示) -->
<div v-if="isMultiStageMode" class="multi-stage-withdrawal-section">
<h3 class="text-base font-semibold text-gray-900 mb-4">
款项提取(允许减少名义金额)
<!-- 提取计划(单阶段和多阶段通用逻辑) -->
<div v-if="config.withdrawal_plan?.enabled" class="withdrawal-plan-section">
<!-- 1. 渲染 withdrawal_fields(包含 withdrawal_enabledwithdrawal_modewithdrawal_method 等) -->
<!-- 注意:多阶段模式 + 选择"指定提取金额"时,跳过单组字段渲染 -->
<template v-for="field in withdrawalFields" :key="field.id || field.key">
<h3 v-if="field.section_title" class="text-base font-semibold text-gray-900 mb-4">
{{ field.section_title }}
</h3>
<component
v-if="shouldRenderField(field) && field.type !== 'percentage'"
:is="getFieldComponent(field)"
v-model="form[field.key]"
v-bind="getFieldProps(field)"
class="mb-5"
/>
<div v-else-if="shouldRenderField(field) && field.type === 'percentage'" class="mb-5">
<div class="text-sm text-gray-700 mb-2 flex items-center">
<span v-if="field.required" class="text-red-500 mr-1">*</span>
<span>{{ field.label }}</span>
</div>
<nut-input
v-model="form[field.key]"
type="digit"
:placeholder="field.placeholder"
@input="(value) => onPercentageInput(value, field.key)"
class="w-full"
/>
</div>
</template>
<!-- 2. 多阶段模式 + 选择"指定提取金额":显示多组阶段卡片 -->
<div v-if="isMultiStageMode && form.withdrawal_mode === '指定提取金额'" class="multi-stage-withdrawal-section">
<!-- 阶段卡片列表 -->
<div
v-for="(stage, index) in stages"
......@@ -108,34 +134,6 @@
+ 添加阶段
</nut-button>
</div>
<!-- 单阶段提取计划(原有逻辑) -->
<div v-else-if="config.withdrawal_plan?.enabled" class="withdrawal-plan-section">
<template v-for="field in withdrawalFields" :key="field.id || field.key">
<h3 v-if="field.section_title" class="text-base font-semibold text-gray-900 mb-4">
{{ field.section_title }}
</h3>
<component
v-if="isFieldVisible(field.key) && field.type !== 'percentage'"
:is="getFieldComponent(field)"
v-model="form[field.key]"
v-bind="getFieldProps(field)"
class="mb-5"
/>
<div v-else-if="isFieldVisible(field.key) && field.type === 'percentage'" class="mb-5">
<div class="text-sm text-gray-700 mb-2 flex items-center">
<span v-if="field.required" class="text-red-500 mr-1">*</span>
<span>{{ field.label }}</span>
</div>
<nut-input
v-model="form[field.key]"
type="digit"
:placeholder="field.placeholder"
@input="(value) => onPercentageInput(value, field.key)"
class="w-full"
/>
</div>
</template>
</div>
</div>
......@@ -309,6 +307,32 @@ const getFieldProps = (field) => {
const { isFieldVisible } = useFieldDependencies(form, fieldDefinitions)
/**
* 判断字段是否应该渲染
* @description 多阶段模式 + 选择"指定提取金额"时,跳过单组字段渲染
* @param {Object} field - 字段配置
* @returns {boolean} 是否应该渲染
*/
const shouldRenderField = (field) => {
// 基础可见性检查
if (!isFieldVisible(field.key)) return false
// 多阶段模式 + 选择"指定提取金额"时,跳过部分单组字段
// 注意:withdrawal_method(提取方式)需要保留
if (isMultiStageMode.value && form.withdrawal_mode === '指定提取金额') {
// 需要跳过的字段:单阶段"指定提取金额"的金额和期数相关字段
const skipFields = [
'annual_withdrawal_amount',
'withdrawal_start_age_specified',
'withdrawal_period_specified',
'annual_increase_percentage'
]
if (skipFields.includes(field.key)) return false
}
return true
}
// ====== 多阶段提取计划逻辑 ======
/**
......@@ -681,12 +705,7 @@ const isFieldRequired = (field) => {
* @returns {boolean} 校验是否通过
*/
const validate = () => {
// 多阶段模式校验
if (isMultiStageMode.value) {
return validateMultiStage()
}
// 单阶段模式校验(原有逻辑)
// 1. 基础字段校验(单阶段和多阶段通用)
const fields = [...baseFields.value, ...(props.config.withdrawal_plan?.enabled ? withdrawalFields.value : [])]
// 年龄与出生年月日二选一校验
......@@ -705,6 +724,7 @@ const validate = () => {
form.age = currentYear - birthYear
}
// 2. 校验基础字段和 withdrawal_fields 中可见的必填字段
for (const field of fields) {
if (!isFieldVisible(field.key)) {
continue
......@@ -733,44 +753,21 @@ const validate = () => {
}
}
// 3. 多阶段模式 + 选择"指定提取金额":额外校验多阶段卡片
if (isMultiStageMode.value && form.withdrawal_mode === '指定提取金额') {
return validateMultiStage()
}
return true
}
/**
* 多阶段表单校验
* @description 校验每个阶段的必填字段
* @description 校验多阶段卡片的必填字段
* @note 基础字段和 withdrawal_fields 已在 validate() 中校验
* @returns {boolean} 校验是否通过
*/
const validateMultiStage = () => {
// 基础字段校验(年龄与出生年月日)
const hasAge = !isEmptyValue(form.age)
const hasBirthday = !isEmptyValue(form.birthday)
if (!hasAge && !hasBirthday) {
Taro.showToast({ title: '年龄与出生年月日至少填写一项', icon: 'none' })
return false
}
// 如果都填写了,以生日为准,重新计算年龄
if (hasAge && hasBirthday) {
const birthYear = new Date(form.birthday).getFullYear()
const currentYear = new Date().getFullYear()
form.age = currentYear - birthYear
}
// 基础字段校验(其他字段)
for (const field of baseFields.value) {
if (field.key === 'age') continue // 已处理
if (isFieldRequired(field)) {
const value = form[field.key]
if (isEmptyValue(value)) {
Taro.showToast({ title: getRequiredMessage(field), icon: 'none' })
return false
}
}
}
// 多阶段字段校验
for (let i = 0; i < stages.value.length; i++) {
const stage = stages.value[i]
......
/*
* @Date: 2026-02-13 01:05:52
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2026-02-25 17:18:05
* @LastEditTime: 2026-02-27 10:22:51
* @FilePath: /manulife-weapp/src/config/app.js
* @Description: 应用配置
*/
......@@ -29,7 +29,7 @@
* // 关闭 Mock 数据(生产环境)
* USE_MOCK_DATA = false
*/
export const USE_MOCK_DATA = true
export const USE_MOCK_DATA = false
/**
* 根据 NODE_ENV 自动判断是否使用 Mock
......
......@@ -393,7 +393,8 @@ export const PLAN_TEMPLATES = {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ['指定提取金额'], // 多阶段模式只支持指定提取金额
// 多阶段模式:支持指定提取金额(多组)和最高固定提取金额(单组)
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: multiStageWithdrawalConfig.withdrawal_periods
},
form_schema: savingsFormSchema,
......