You need to sign in or sign up before continuing.
hookehuyr

refactor(plan): 完善计划书模板字段配置

- 更新 README.md 文档说明
- 更新 CHANGELOG.md 记录变更
- 优化 CriticalIllnessTemplate 字段配置
- 优化 LifeInsuranceTemplate 字段配置
- 优化 SavingsTemplate 字段配置

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
......@@ -58,6 +58,7 @@ pnpm lint
-**Schema 驱动** - 储蓄类模板字段由配置驱动渲染与校验
-**提交映射下沉** - 提交字段映射从容器迁移到模板配置
-**人寿/重疾同步** - 人寿与重疾模板改为 Schema 驱动
-**校验提示优化** - 必填提示与百分比校验统一更准确
### 字段命名优化
-**提取方式字段** - 统一将 specified_amount_type 重命名为 withdrawal_method
......
#### [2026-02-14] - 计划书必填校验与提示优化
### 修复
- 计划书模板必填规则统一判断,避免缺失字段未触发校验
- 必填提示文案按输入/选择类型调整为更准确的提示语
- 百分比字段空值不再误报校验错误
### 测试
- pnpm test(通过)
- pnpm lint(存在历史警告)
---
**详细信息**
- **影响文件**: src/components/plan/PlanTemplates/SavingsTemplate.vue, src/components/plan/PlanTemplates/LifeInsuranceTemplate.vue, src/components/plan/PlanTemplates/CriticalIllnessTemplate.vue, README.md
- **技术栈**: Vue 3, Taro, Vitest
- **测试状态**: 已通过(lint 有警告)
- **备注**: 统一必填与提示逻辑,补齐空值校验边界
---
#### [2026-02-14] - 提取字段分组修正
### 变更
......
......@@ -283,6 +283,27 @@ const onPercentageInput = (value, key) => {
form[key] = cleaned
}
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 getRequiredMessage = (field) => {
if (field?.placeholder) return field.placeholder
const label = field?.label || '必填信息'
const selectTypes = ['radio', 'select', 'date', 'payment_period', 'age']
if (selectTypes.includes(field?.type)) {
return `请选择${label}`
}
return `请输入${label}`
}
const isFieldRequired = (field) => {
return field?.required === true || field?.required === undefined
}
/**
* 表单校验(基于 Schema)
* @returns {boolean} 校验是否通过
......@@ -295,22 +316,25 @@ const validate = () => {
continue
}
if (field.required) {
if (isFieldRequired(field)) {
const value = form[field.key]
if (value === undefined || value === null || value === '') {
Taro.showToast({ title: field.label || '请完善必填信息', icon: 'none' })
if (isEmptyValue(value)) {
Taro.showToast({ title: getRequiredMessage(field), icon: 'none' })
return false
}
}
if (field.type === 'percentage' && isFieldVisible(field.key)) {
const percentage = parseFloat(form[field.key])
const value = form[field.key]
if (!isEmptyValue(value)) {
const percentage = parseFloat(value)
if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) {
Taro.showToast({ title: '请输入0-100之间的百分比', icon: 'none' })
return false
}
}
}
}
return true
}
......
......@@ -285,6 +285,27 @@ const onPercentageInput = (value, key) => {
form[key] = cleaned
}
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 getRequiredMessage = (field) => {
if (field?.placeholder) return field.placeholder
const label = field?.label || '必填信息'
const selectTypes = ['radio', 'select', 'date', 'payment_period', 'age']
if (selectTypes.includes(field?.type)) {
return `请选择${label}`
}
return `请输入${label}`
}
const isFieldRequired = (field) => {
return field?.required === true || field?.required === undefined
}
/**
* 表单校验(基于 Schema)
* @returns {boolean} 校验是否通过
......@@ -297,22 +318,25 @@ const validate = () => {
continue
}
if (field.required) {
if (isFieldRequired(field)) {
const value = form[field.key]
if (value === undefined || value === null || value === '') {
Taro.showToast({ title: field.label || '请完善必填信息', icon: 'none' })
if (isEmptyValue(value)) {
Taro.showToast({ title: getRequiredMessage(field), icon: 'none' })
return false
}
}
if (field.type === 'percentage' && isFieldVisible(field.key)) {
const percentage = parseFloat(form[field.key])
const value = form[field.key]
if (!isEmptyValue(value)) {
const percentage = parseFloat(value)
if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) {
Taro.showToast({ title: '请输入0-100之间的百分比', icon: 'none' })
return false
}
}
}
}
return true
}
......
......@@ -377,6 +377,27 @@ const onPercentageInput = (value, key) => {
form[key] = cleaned
}
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 getRequiredMessage = (field) => {
if (field?.placeholder) return field.placeholder
const label = field?.label || '必填信息'
const selectTypes = ['radio', 'select', 'date', 'payment_period', 'age']
if (selectTypes.includes(field?.type)) {
return `请选择${label}`
}
return `请输入${label}`
}
const isFieldRequired = (field) => {
return field?.required === true || field?.required === undefined
}
/**
* 表单校验
* @returns {boolean} 是否通过校验
......@@ -393,22 +414,25 @@ const validate = () => {
continue
}
if (field.required) {
if (isFieldRequired(field)) {
const value = form[field.key]
if (value === undefined || value === null || value === '') {
Taro.showToast({ title: field.label || '请完善必填信息', icon: 'none' })
if (isEmptyValue(value)) {
Taro.showToast({ title: getRequiredMessage(field), icon: 'none' })
return false
}
}
if (field.type === 'percentage' && isFieldVisible(field.key)) {
const percentage = parseFloat(form[field.key])
const value = form[field.key]
if (!isEmptyValue(value)) {
const percentage = parseFloat(value)
if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) {
Taro.showToast({ title: '请输入0-100之间的百分比', icon: 'none' })
return false
}
}
}
}
return true
}
......