hookehuyr

feat(plan): 新增提取期自定义输入功能

- 新增 PeriodInput 组件,支持用户自定义输入提取期(1-100整数)
- SelectPickerGlobal 支持自定义选项入口,允许用户选择"自定义输入"
- SavingsTemplate 集成自定义输入功能,支持多阶段提取期自定义
- 自定义值临时保存到本次会话选项列表,跨阶段复用
- 验证规则:整数年期(1-100年)、快捷选项(终身、一笔过)
- 使用 watch 监听输入值变化,防止小程序 @input 事件丢失

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
......@@ -36,6 +36,7 @@ declare module 'vue' {
OfficeViewer: typeof import('./src/components/documents/OfficeViewer.vue')['default']
PaymentPeriodRadio: typeof import('./src/components/plan/PlanFields/PaymentPeriodRadio.vue')['default']
PdfPreview: typeof import('./src/components/documents/PdfPreview.vue')['default']
PeriodInput: typeof import('./src/components/plan/PlanFields/PeriodInput.vue')['default']
PlanFormContainer: typeof import('./src/components/plan/PlanFormContainer.vue')['default']
PlanPopupNew: typeof import('./src/components/plan/PlanPopupNew.vue')['default']
ProductCard: typeof import('./src/components/cards/ProductCard.vue')['default']
......
This diff is collapsed. Click to expand it.
......@@ -39,17 +39,30 @@
*
* @description 使用 NutUI Picker 实现下拉选择功能
* - key 和 value 相同(如"整付(0-75 岁)")
* - 适用于缴费年期等场景
* - 适用于缴费年期、提取期等场景
* - 使用 GlobalPopupManager 管理弹窗层级
* - 支持自定义输入选项(可选功能)
* @author Claude Code
* @version 2.0.0 - 支持全局弹窗管理器
* @version 2.1.0 - 新增自定义输入支持
* @example
* // 基础用法
* <SelectPickerGlobal
* v-model="paymentPeriod"
* label="缴费年期"
* placeholder="请选择缴费年期"
* :options="['整付(0-75 岁)', '5 年(0-70 岁)']"
* />
*
* @example
* // 支持自定义输入
* <SelectPickerGlobal
* v-model="withdrawalPeriod"
* label="提取期"
* placeholder="请选择提取期"
* :options="['1年', '5年', '10年', '终身']"
* :allow-custom="true"
* @custom-select="openCustomInput"
* />
*/
import { ref, computed, onMounted } from 'vue'
import IconFont from '@/components/icons/IconFont.vue'
......@@ -121,6 +134,35 @@ const props = defineProps({
options: {
type: Array,
required: true
},
/**
* 是否允许自定义输入
* @type {boolean}
* @description 开启后,选项列表末尾会添加"自定义输入"选项
*/
allowCustom: {
type: Boolean,
default: false
},
/**
* 自定义选项的显示文本
* @type {string}
*/
customLabel: {
type: String,
default: '📝 自定义输入...'
},
/**
* 分隔符文本
* @type {string}
* @description 自定义选项与标准选项之间的分隔线
*/
dividerLabel: {
type: String,
default: '──────────'
}
})
......@@ -143,7 +185,12 @@ const emit = defineEmits([
* 弹窗关闭事件
* @event close
*/
'close'
'close',
/**
* 用户选择自定义输入选项
* @event custom-select
*/
'custom-select'
])
/**
......@@ -167,15 +214,34 @@ const openPicker = () => {
/**
* 转换为 Picker 格式
* @description 将选项数组转换为 Picker 需要的格式
* 如果允许自定义,会在末尾添加分隔符和自定义选项
* @example
* // options = ['整付(0-75 岁)', '5 年(0-70 岁)']
* // pickerColumns() // 返回: [{ text: '整付(0-75 岁)', value: '整付(0-75 岁)' }, ...]
*/
const pickerColumns = computed(() => {
return props.options.map(option => ({
const standardOptions = props.options.map(option => ({
text: option,
value: option // key 和 value 相同
}))
// 如果允许自定义,添加分隔符和自定义选项
if (props.allowCustom) {
return [
...standardOptions,
{
text: props.dividerLabel,
value: '__divider__',
disabled: true // 分隔符不可选
},
{
text: props.customLabel,
value: '__custom__'
}
]
}
return standardOptions
})
/**
......@@ -197,6 +263,27 @@ const displayValue = computed(() => {
*/
const onConfirm = ({ selectedOptions }) => {
const value = selectedOptions[0]?.value
// 处理自定义选项
if (value === '__custom__') {
// 触发自定义选择事件,由父组件处理
emit('custom-select')
// 停用弹窗
if (popupId.value) {
deactivatePopup(popupId.value)
}
showPicker.value = false
emit('close')
return
}
// 跳过分隔符
if (value === '__divider__') {
return
}
// 标准选项处理
if (value !== undefined) {
emit('update:modelValue', value)
}
......
......@@ -92,7 +92,9 @@
label="提取期"
placeholder="请选择提取期"
:required="true"
:options="multiStagePeriodOptions"
:options="dynamicPeriodOptions"
:allow-custom="isCustomPeriodEnabled"
@custom-select="openPeriodInput(index)"
class="mb-3"
/>
......@@ -142,6 +144,17 @@
<p>⚠️ 模板配置未找到</p>
<p class="text-sm mt-2">请检查产品配置或联系开发人员</p>
</div>
<!-- 自定义提取期输入弹窗 -->
<PeriodInput
v-model:visible="showPeriodInput"
v-model="currentPeriodValue"
inputLabel="请输入提取期"
inputPlaceholder="请输入年数"
:validation-rules="periodValidationRules"
@confirm="onPeriodInputConfirm"
@cancel="onPeriodInputCancel"
/>
</template>
<script setup>
......@@ -167,6 +180,7 @@ import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue'
import PlanFieldRadio from '../PlanFields/RadioGroup.vue'
import PlanFieldSelect from '../PlanFields/SelectPickerGlobal.vue'
import PaymentPeriodRadio from '../PlanFields/PaymentPeriodRadio.vue'
import PeriodInput from '../PlanFields/PeriodInput.vue'
import { useFieldDependencies } from '@/composables/useFieldDependencies'
/**
......@@ -387,6 +401,45 @@ const canRemoveStage = computed(() => {
})
/**
* 自定义提取期输入状态
*/
const showPeriodInput = ref(false) // 自定义输入弹窗显示状态
const currentPeriodValue = ref('') // 当前输入的提取期值
const currentStageIndex = ref(-1) // 当前正在编辑的阶段索引
const customPeriodValues = ref([]) // 用户自定义的提取期值列表(临时保存)
/**
* 自定义提取期是否启用
*/
const isCustomPeriodEnabled = computed(() => {
return multiStageConfig.value.custom_period?.enabled || false
})
/**
* 自定义提取期验证规则
*/
const periodValidationRules = computed(() => {
const config = multiStageConfig.value.custom_period?.validation || {}
return {
min: config.min_years ?? 1,
max: config.max_years ?? 100,
allowed_formats: config.allowed_formats || ['终身', '一笔过'],
custom_validators: config.custom_validators || []
}
})
/**
* 动态提取期选项(预设选项 + 用户自定义选项)
*/
const dynamicPeriodOptions = computed(() => {
const baseOptions = multiStageConfig.value.withdrawal_periods ||
props.config?.withdrawal_plan?.withdrawal_periods ||
[]
// 合并预设选项和用户自定义选项
return [...baseOptions, ...customPeriodValues.value]
})
/**
* 创建空的阶段数据
* @returns {Object} 空阶段对象
*/
......@@ -445,6 +498,50 @@ const removeStage = (index) => {
}
/**
* 打开自定义提取期输入弹窗
* @param {number} stageIndex - 阶段索引
*/
const openPeriodInput = (stageIndex) => {
currentStageIndex.value = stageIndex
const stage = stages.value[stageIndex]
currentPeriodValue.value = stage?.withdrawal_period || ''
showPeriodInput.value = true
}
/**
* 自定义提取期输入确认
* @param {string} value - 确认的提取期值
*/
const onPeriodInputConfirm = (value) => {
// 添加到自定义值列表(去重:不在预设选项和已存在的自定义列表中)
const baseOptions = multiStageConfig.value.withdrawal_periods ||
props.config?.withdrawal_plan?.withdrawal_periods ||
[]
if (!baseOptions.includes(value) && !customPeriodValues.value.includes(value)) {
customPeriodValues.value.push(value)
}
// 更新当前阶段的值
if (currentStageIndex.value >= 0 && currentStageIndex.value < stages.value.length) {
stages.value[currentStageIndex.value].withdrawal_period = value
}
// 关闭弹窗并重置状态
showPeriodInput.value = false
currentStageIndex.value = -1
currentPeriodValue.value = ''
}
/**
* 自定义提取期输入取消
*/
const onPeriodInputCancel = () => {
showPeriodInput.value = false
currentStageIndex.value = -1
currentPeriodValue.value = ''
}
/**
* 同步阶段数据到表单
* @description 将 stages 数组同步到 form.withdrawal_stages,以便父组件获取
* 同时清理 undefined 值为 null,确保提交数据格式正确
......
......@@ -123,6 +123,7 @@ const savingsFormSchema = {
* 多阶段提取计划配置
* @description 用于"宏挚传承保障计划(多阶段)"等支持多阶段提取的产品
* @updated 2026-02-25 - 新增多阶段提取功能
* @updated 2026-02-28 - 新增自定义提取期输入功能
*/
const multiStageWithdrawalConfig = {
enabled: true,
......@@ -133,7 +134,20 @@ const multiStageWithdrawalConfig = {
'10年', '15年', '20年', '终身',
'一笔过' // 新增:一次性提取选项
],
percentage_optional: true // 递增百分比可选
percentage_optional: true, // 递增百分比可选
// 自定义提取期配置(新增)
custom_period: {
enabled: true, // 是否允许自定义输入
custom_label: '📝 自定义输入...', // 自定义选项显示文本
validation: {
min_years: 1, // 最小年期(整数)
max_years: 100, // 最大年期(整数)
allowed_formats: ['终身', '一笔过'], // 允许的非年期格式
// 预留:未来可扩展更多格式
custom_validators: [] // 自定义验证函数数组
}
}
}
export const PLAN_TEMPLATES = {
......