hookehuyr

fix(plan): 修复储蓄类多阶段模版保存问题

- 添加 stages 数据从 props.modelValue 恢复的逻辑
- 修复 validate() 中单阶段字段在多阶段模式下被错误校验
- 添加详细的调试日志

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
......@@ -335,3 +335,13 @@
**变更摘要**:
- CHANGELOG 自动更新功能配置
### 2026-02-28 - 修复储蓄类多阶段模版保存问题
**影响文件**:
- `src/components/plan/PlanTemplates/SavingsTemplate.vue`
**变更摘要**:
- 修复多阶段模式下 stages 数据无法从 props.modelValue 恢复的问题
- 修复 validate() 函数中单阶段字段被错误校验的问题
- 添加详细的调试日志以方便排查问题
......
......@@ -390,12 +390,16 @@ const canRemoveStage = computed(() => {
* 创建空的阶段数据
* @returns {Object} 空阶段对象
*/
const createStage = () => ({
annual_withdrawal_amount: null,
withdrawal_start_age: null,
withdrawal_period: null,
annual_increase_percentage: null
})
const createStage = () => {
const stage = {
annual_withdrawal_amount: null,
withdrawal_start_age: null,
withdrawal_period: null,
annual_increase_percentage: null
}
console.log('createStage() 创建:', stage)
return stage
}
/**
* 初始化阶段数据
......@@ -404,16 +408,22 @@ const createStage = () => ({
* - 年龄 ≥ 12岁:初始 1 组
*/
const initializeStages = () => {
console.log('=== initializeStages() 调用 ===')
console.log('form.age:', form.age)
const age = parseInt(form.age) || 0
const threshold = multiStageConfig.value.age_threshold || 12
console.log('age:', age, 'threshold:', threshold)
if (age < threshold) {
// 固定 3 组
stages.value = [createStage(), createStage(), createStage()]
console.log('初始化 3 组阶段')
} else {
// 初始 1 组
stages.value = [createStage()]
console.log('初始化 1 组阶段')
}
console.log('stages.value:', stages.value)
}
/**
......@@ -442,6 +452,8 @@ const removeStage = (index) => {
watch(
stages,
(newStages) => {
console.log('=== stages watch 触发 ===')
console.log('newStages:', JSON.parse(JSON.stringify(newStages)))
// 清理每个阶段的 undefined 值为 null
const cleanedStages = newStages.map(stage => ({
annual_withdrawal_amount: stage.annual_withdrawal_amount ?? null,
......@@ -449,7 +461,10 @@ watch(
withdrawal_period: stage.withdrawal_period ?? null,
annual_increase_percentage: stage.annual_increase_percentage ?? null
}))
console.log('cleanedStages:', cleanedStages)
console.log('设置 form.withdrawal_stages')
form.withdrawal_stages = cleanedStages
console.log('form.withdrawal_stages 已更新:', form.withdrawal_stages)
},
{ deep: true }
)
......@@ -476,8 +491,14 @@ watch(
watch(
isMultiStageMode,
(enabled) => {
console.log('=== isMultiStageMode watch ===')
console.log('enabled:', enabled)
console.log('stages.value.length:', stages.value.length)
if (enabled && stages.value.length === 0) {
console.log('条件满足,调用 initializeStages()')
initializeStages()
} else {
console.log('跳过初始化')
}
},
{ immediate: true }
......@@ -529,8 +550,13 @@ const initializeForm = (value) => {
watch(
() => props.modelValue,
(newVal) => {
console.log('=== modelValue watch 触发 ===')
console.log('newVal:', newVal)
console.log('isMultiStageMode:', isMultiStageMode.value)
if (!newVal) {
// null 或 undefined:清空
console.log('newVal 为空,清空表单')
Object.keys(form).forEach(key => delete form[key])
previousModelValue = null
return
......@@ -543,10 +569,12 @@ watch(
if (isReset) {
// 父组件重置了:清空表单
console.log('检测到重置,清空表单')
initializeForm(newVal)
previousModelValue = newVal
} else {
// 正常更新:合并新字段,保留默认值逻辑
console.log('正常更新表单数据')
const defaults = getSchemaDefaults(newVal)
Object.assign(form, {
...newVal,
......@@ -559,6 +587,36 @@ watch(
withdrawal_period_fixed: newVal.withdrawal_period_fixed ?? null
})
previousModelValue = newVal
// 恢复 stages 数据(多阶段模式)
console.log('检查是否需要恢复 stages 数据...')
console.log(' isMultiStageMode:', isMultiStageMode.value)
console.log(' newVal.withdrawal_stages:', newVal.withdrawal_stages)
if (isMultiStageMode.value && newVal.withdrawal_stages && Array.isArray(newVal.withdrawal_stages)) {
console.log('✅ 开始恢复 stages 数据')
console.log(' 当前 stages.value:', stages.value)
console.log(' 新数据 withdrawal_stages:', newVal.withdrawal_stages)
// 深度比较,避免覆盖用户正在编辑的数据
const currentStagesStr = JSON.stringify(stages.value)
const newStagesStr = JSON.stringify(newVal.withdrawal_stages)
console.log(' 深度比较:', currentStagesStr === newStagesStr ? '相同' : '不同')
if (currentStagesStr !== newStagesStr) {
stages.value = newVal.withdrawal_stages.map(stage => ({
annual_withdrawal_amount: stage.annual_withdrawal_amount ?? null,
withdrawal_start_age: stage.withdrawal_start_age ?? null,
withdrawal_period: stage.withdrawal_period ?? null,
annual_increase_percentage: stage.annual_increase_percentage ?? null
}))
console.log(' stages 已恢复:', stages.value)
} else {
console.log(' 跳过恢复(数据相同)')
}
} else {
console.log(' 不需要恢复 stages')
}
}
},
{ immediate: true }
......@@ -674,10 +732,18 @@ const onPercentageInput = (value, key) => {
}
const isEmptyValue = (value) => {
if (value === null || value === undefined) return true
if (typeof value === 'string' && value.trim() === '') return true
if (Array.isArray(value) && value.length === 0) return true
return false
const result = (() => {
if (value === null || value === undefined) return true
if (typeof value === 'string' && value.trim() === '') return true
if (Array.isArray(value) && value.length === 0) return true
return false
})()
// 只在调用栈包含 validate 时打印日志,避免过多输出
const stack = new Error().stack || ''
if (stack.includes('validate')) {
console.log(`isEmptyValue(${value} [${typeof value}]) = ${result}`)
}
return result
}
const getRequiredMessage = (field) => {
......@@ -703,8 +769,14 @@ const isFieldRequired = (field) => {
* @returns {boolean} 校验是否通过
*/
const validate = () => {
console.log('=== validate() 开始 ===')
console.log('form 数据:', form)
console.log('form.withdrawal_mode:', form.withdrawal_mode)
console.log('isMultiStageMode:', isMultiStageMode.value)
// 1. 基础字段校验(单阶段和多阶段通用)
const fields = [...baseFields.value, ...(props.config.withdrawal_plan?.enabled ? withdrawalFields.value : [])]
console.log('需要校验的基础字段数:', fields.length)
// 年龄与出生年月日二选一校验
const hasAge = !isEmptyValue(form.age)
......@@ -731,6 +803,21 @@ const validate = () => {
// 跳过年龄字段的单独校验(已和生日一起校验)
if (field.key === 'age') continue
// 多阶段模式 + 选择"指定提取金额"时,跳过单阶段字段的校验
// 这些字段在多阶段模式下由 validateMultiStage() 校验
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)) {
console.log(`跳过单阶段字段校验: ${field.key}`)
continue
}
}
if (isFieldRequired(field)) {
const value = form[field.key]
if (isEmptyValue(value)) {
......@@ -766,25 +853,42 @@ const validate = () => {
* @returns {boolean} 校验是否通过
*/
const validateMultiStage = () => {
console.log('=== validateMultiStage 开始 ===')
console.log('stages.value:', stages.value)
console.log('stages.value.length:', stages.value.length)
// 多阶段字段校验
for (let i = 0; i < stages.value.length; i++) {
const stage = stages.value[i]
const stageLabel = `阶段${i + 1}`
console.log(`--- 校验 ${stageLabel} ---`)
console.log('stage 对象:', stage)
console.log(' annual_withdrawal_amount:', stage.annual_withdrawal_amount, '类型:', typeof stage.annual_withdrawal_amount)
console.log(' withdrawal_start_age:', stage.withdrawal_start_age, '类型:', typeof stage.withdrawal_start_age)
console.log(' withdrawal_period:', stage.withdrawal_period, '类型:', typeof stage.withdrawal_period)
console.log(' annual_increase_percentage:', stage.annual_increase_percentage, '类型:', typeof stage.annual_increase_percentage)
// 每年提取金额(必填)
console.log('检查 annual_withdrawal_amount isEmptyValue:', isEmptyValue(stage.annual_withdrawal_amount))
if (isEmptyValue(stage.annual_withdrawal_amount)) {
console.log(`❌ ${stageLabel}:每年提取金额为空`)
Taro.showToast({ title: `${stageLabel}:请输入每年提取金额`, icon: 'none' })
return false
}
// 由几岁开始(必填)
console.log('检查 withdrawal_start_age isEmptyValue:', isEmptyValue(stage.withdrawal_start_age))
if (isEmptyValue(stage.withdrawal_start_age)) {
console.log(`❌ ${stageLabel}:withdrawal_start_age 为空`)
Taro.showToast({ title: `${stageLabel}:请输入由几岁开始`, icon: 'none' })
return false
}
// 提取期(必填)
console.log('检查 withdrawal_period isEmptyValue:', isEmptyValue(stage.withdrawal_period))
if (isEmptyValue(stage.withdrawal_period)) {
console.log(`❌ ${stageLabel}:withdrawal_period 为空`)
Taro.showToast({ title: `${stageLabel}:请选择提取期`, icon: 'none' })
return false
}
......@@ -793,12 +897,15 @@ const validateMultiStage = () => {
if (!isEmptyValue(stage.annual_increase_percentage)) {
const percentage = parseFloat(stage.annual_increase_percentage)
if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) {
console.log(`❌ ${stageLabel}:递增百分比超出范围`)
Taro.showToast({ title: `${stageLabel}:递增百分比请输入0-100之间的数值`, icon: 'none' })
return false
}
}
console.log(`✅ ${stageLabel} 校验通过`)
}
console.log('=== validateMultiStage 全部通过 ===')
return true
}
......