hookehuyr

refactor(components): 重构计划书方案组件,提取公共布局

- 新增 PlanPopup 容器组件,统一头部和底部按钮
- 使用 NutUI Button 组件替换原生 div 按钮
- 重构 SchemeA 和 SchemeB,使用 PlanPopup 容器
- 减少约 60 行重复代码
- 添加详细的 JSDoc 注释

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
<template>
<div class="flex flex-col h-full bg-gray-50">
<!-- Header -->
<div class="flex justify-between items-center px-5 py-5 bg-white rounded-t-xl">
<span class="text-lg font-normal text-gray-900">{{ title }}</span>
<IconFont name="close" size="16" color="#9CA3AF" @click="handleClose" />
</div>
<!-- Scrollable Content -->
<div class="flex-1 overflow-y-auto p-4">
<div class="bg-white rounded-xl p-5 shadow-sm">
<slot />
</div>
</div>
<!-- Footer Buttons -->
<div class="p-4 pt-2 pb-8 flex justify-between gap-3 bg-gray-50">
<nut-button
plain
type="primary"
class="flex-1 !h-auto !py-2.5 !text-sm"
@click="handleClose"
>
取消
</nut-button>
<nut-button
type="primary"
class="flex-1 !h-auto !py-2.5 !text-sm"
@click="handleSubmit"
>
提交申请
</nut-button>
</div>
</div>
</template>
<script setup>
/**
* @description 计划书弹窗容器组件
* @description 提供统一的头部、底部按钮和布局结构
*
* @props {string} title - 弹窗标题
*
* @emits close - 关闭弹窗事件
* @emits submit - 提交事件
*
* @example
* <PlanPopup title="申请计划书" @close="handleClose" @submit="handleSubmit">
* <!-- 具体的表单内容 -->
* </PlanPopup>
*/
import IconFont from '@/components/IconFont.vue'
defineProps({
/** 弹窗标题 */
title: {
type: String,
default: '计划书'
}
})
const emit = defineEmits(['close', 'submit'])
/**
* 处理关闭事件
*/
const handleClose = () => {
emit('close')
}
/**
* 处理提交事件
*/
const handleSubmit = () => {
emit('submit')
}
</script>
<style lang="less" scoped>
/* 确保 NutUI 按钮样式正确 */
:deep(.nut-button) {
border-radius: 0.5rem /* 8px */;
font-size: 1rem /* 16px */;
}
</style>
<template>
<div class="flex flex-col h-full bg-gray-50">
<div class="flex justify-between items-center px-5 py-5 bg-white rounded-t-xl">
<span class="text-lg font-normal text-gray-900">申请计划书</span>
<IconFont name="close" size="16" color="#9CA3AF" @click="close" />
</div>
<div class="flex-1 overflow-y-auto p-4">
<div class="bg-white rounded-xl p-5 shadow-sm">
<PlanPopup title="申请计划书" @close="close" @submit="submit">
<!-- 客户姓名 -->
<div class="text-sm text-gray-600 mb-2">客户姓名</div>
<div class="border border-gray-200 rounded-lg mb-4 overflow-hidden">
<nut-input
......@@ -17,12 +11,14 @@
/>
</div>
<!-- 性别 -->
<div class="text-sm text-gray-600 mb-2">性别</div>
<nut-radio-group v-model="form.gender" direction="horizontal" class="mb-4">
<nut-radio label="male" class="mr-8">男</nut-radio>
<nut-radio label="female">女</nut-radio>
</nut-radio-group>
<!-- 年龄 -->
<div class="text-sm text-gray-600 mb-2">年龄</div>
<div class="border border-gray-200 rounded-lg mb-4 overflow-hidden">
<nut-input
......@@ -34,6 +30,7 @@
/>
</div>
<!-- 行业 -->
<div class="text-sm text-gray-600 mb-2">行业</div>
<div
class="flex justify-between items-center border border-gray-200 rounded-lg p-3 mb-4 overflow-hidden"
......@@ -45,6 +42,7 @@
<IconFont name="right" size="14" color="#9CA3AF" />
</div>
<!-- 年收入区间 -->
<div class="text-sm text-gray-600 mb-2">年收入区间</div>
<div class="border border-gray-200 rounded-lg mb-4 flex items-center overflow-hidden">
<nut-input
......@@ -57,6 +55,7 @@
<span class="text-sm text-gray-500 shrink-0 ml-2 mr-5">万元</span>
</div>
<!-- 家庭结构 -->
<div class="text-sm text-gray-600 mb-3">家庭结构(多选)</div>
<div class="flex flex-wrap gap-3 mb-5">
<div
......@@ -70,6 +69,7 @@
</div>
</div>
<!-- 保险需求 -->
<div class="text-sm text-gray-600 mb-3">保险需求(多选)</div>
<div class="flex flex-wrap gap-3 mb-5">
<div
......@@ -83,6 +83,7 @@
</div>
</div>
<!-- 期望收益率 -->
<div class="text-sm text-gray-600 mb-2">期望收益率</div>
<div class="border border-gray-200 rounded-lg mb-4 flex items-center overflow-hidden">
<nut-input
......@@ -94,23 +95,6 @@
/>
<span class="text-sm text-gray-500 shrink-0 ml-2 mr-5">%</span>
</div>
</div>
</div>
<div class="p-4 pt-2 pb-8 flex justify-between gap-4 bg-gray-50">
<div
class="flex-1 py-3 text-center border border-blue-600 text-blue-600 rounded-lg text-base bg-white"
@click="close"
>
取消
</div>
<div
class="flex-1 py-3 text-center bg-blue-600 text-white rounded-lg text-base"
@click="submit"
>
提交申请
</div>
</div>
<!-- Industry Picker -->
<nut-popup position="bottom" v-model:visible="showIndustryPicker">
......@@ -121,19 +105,26 @@
@cancel="showIndustryPicker = false"
/>
</nut-popup>
</div>
</PlanPopup>
</template>
<script setup>
/**
* @description 录入计划书 - 方案A 内容组件
* @description 使用 PlanPopup 容器组件提供统一的布局和按钮
*
* @emits close - 关闭弹窗事件
* @emits submit - 提交事件,携带表单数据
*/
import { ref, reactive } from 'vue';
import { ref, reactive } from 'vue'
import PlanPopup from './PlanPopup.vue'
import IconFont from '@/components/IconFont.vue'
const emit = defineEmits(['close', 'submit']);
const emit = defineEmits(['close', 'submit'])
/**
* 表单数据
*/
const form = reactive({
name: '',
gender: '', // 'male' | 'female'
......@@ -142,56 +133,83 @@ const form = reactive({
income: '',
family: [],
insurance: [],
returnRate: '',
});
returnRate: ''
})
const showIndustryPicker = ref(false);
/**
* 控制行业选择器显示
*/
const showIndustryPicker = ref(false)
/**
* 行业选项
*/
const industryColumns = [
{ text: 'IT/互联网', value: 'it' },
{ text: '金融', value: 'finance' },
{ text: '教育', value: 'education' },
{ text: '医疗', value: 'medical' },
{ text: '其他', value: 'other' },
];
{ text: '其他', value: 'other' }
]
/**
* 家庭结构选项
*/
const familyOptions = [
{ label: '配偶', value: 'spouse' },
{ label: '子女', value: 'children' },
{ label: '父母', value: 'parents' },
{ label: '其他', value: 'others' },
];
{ label: '其他', value: 'others' }
]
/**
* 保险需求选项
*/
const insuranceOptions = [
{ label: '人身保障', value: 'life' },
{ label: '财富传承', value: 'wealth' },
{ label: '子女教育', value: 'education' },
{ label: '养老规划', value: 'pension' },
];
{ label: '养老规划', value: 'pension' }
]
/**
* 切换多选项的选择状态
* @param {string} field - 字段名
* @param {string} value - 选项值
*/
const toggleSelection = (field, value) => {
const index = form[field].indexOf(value);
const index = form[field].indexOf(value)
if (index === -1) {
form[field].push(value);
form[field].push(value)
} else {
form[field].splice(index, 1);
form[field].splice(index, 1)
}
};
}
const confirmIndustry = ({ selectedValue, selectedOptions }) => {
form.industry = selectedOptions[0].text;
showIndustryPicker.value = false;
};
/**
* 确认行业选择
* @param {Object} params - 选择器返回参数
* @param {Array} params.selectedOptions - 选中的选项
*/
const confirmIndustry = ({ selectedOptions }) => {
form.industry = selectedOptions[0].text
showIndustryPicker.value = false
}
/**
* 关闭弹窗
*/
const close = () => {
emit('close');
};
emit('close')
}
/**
* 提交表单
*/
const submit = () => {
// Validate form if needed
console.log('Submit form:', form);
emit('submit', form);
};
console.log('SchemeA Submit:', form)
emit('submit', form)
}
</script>
<style lang="less" scoped>
......
<template>
<div class="flex flex-col h-full bg-gray-50">
<!-- Header -->
<div class="flex justify-between items-center px-5 py-5 bg-white rounded-t-xl">
<span class="text-lg font-normal text-gray-900">保险计划书申请</span>
<IconFont name="close" size="16" color="#9CA3AF" @click="close" />
</div>
<!-- Scrollable Content -->
<div class="flex-1 overflow-y-auto p-4">
<div class="bg-white rounded-xl p-5 shadow-sm">
<PlanPopup title="保险计划书申请" @close="close" @submit="submit">
<!-- 币种 -->
<div class="flex justify-between items-start mb-5">
<span class="text-sm text-gray-600 mt-1.5">币种</span>
......@@ -82,32 +73,25 @@
/>
<span class="text-sm text-gray-500 shrink-0 ml-2 mr-5">美元</span>
</div>
</div>
</div>
<!-- Footer Buttons -->
<div class="p-4 pt-2 pb-8 flex justify-between gap-4 bg-gray-50">
<div class="flex-1 py-3 text-center border border-blue-600 text-blue-600 rounded-lg text-base bg-white"
@click="close">
取消
</div>
<div class="flex-1 py-3 text-center bg-blue-600 text-white rounded-lg text-base" @click="submit">
提交申请
</div>
</div>
</div>
</PlanPopup>
</template>
<script setup>
/**
* @description 录入计划书 - 方案B 内容组件
* @description 使用 PlanPopup 容器组件提供统一的布局和按钮
*
* @emits close - 关闭弹窗事件
* @emits submit - 提交事件,携带表单数据
*/
import { reactive, defineEmits } from 'vue';
import { reactive } from 'vue'
import PlanPopup from './PlanPopup.vue'
const emit = defineEmits(['close', 'submit']);
const emit = defineEmits(['close', 'submit'])
/**
* 表单数据
*/
const form = reactive({
currency: '美元保单',
plan: '基础情景',
......@@ -115,22 +99,32 @@ const form = reactive({
age: '30',
insurancePeriod: '终身',
paymentPeriod: '10年交',
premium: '100000',
});
premium: '100000'
})
const paymentPeriods = ['10年交', '3年交', '5年交', '2年交'];
/**
* 交费期间选项
*/
const paymentPeriods = ['10年交', '3年交', '5年交', '2年交']
/**
* 关闭弹窗
*/
const close = () => {
emit('close');
};
emit('close')
}
/**
* 提交表单
*/
const submit = () => {
console.log('SchemeB Submit:', form);
emit('submit', form);
};
console.log('SchemeB Submit:', form)
emit('submit', form)
}
</script>
<style lang="less" scoped>
/* Override NutUI input styles to match design */
:deep(.nut-input) {
padding: 0;
background: transparent;
......