hookehuyr

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

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 +<template>
2 + <div class="flex flex-col h-full bg-gray-50">
3 + <!-- Header -->
4 + <div class="flex justify-between items-center px-5 py-5 bg-white rounded-t-xl">
5 + <span class="text-lg font-normal text-gray-900">{{ title }}</span>
6 + <IconFont name="close" size="16" color="#9CA3AF" @click="handleClose" />
7 + </div>
8 +
9 + <!-- Scrollable Content -->
10 + <div class="flex-1 overflow-y-auto p-4">
11 + <div class="bg-white rounded-xl p-5 shadow-sm">
12 + <slot />
13 + </div>
14 + </div>
15 +
16 + <!-- Footer Buttons -->
17 + <div class="p-4 pt-2 pb-8 flex justify-between gap-3 bg-gray-50">
18 + <nut-button
19 + plain
20 + type="primary"
21 + class="flex-1 !h-auto !py-2.5 !text-sm"
22 + @click="handleClose"
23 + >
24 + 取消
25 + </nut-button>
26 + <nut-button
27 + type="primary"
28 + class="flex-1 !h-auto !py-2.5 !text-sm"
29 + @click="handleSubmit"
30 + >
31 + 提交申请
32 + </nut-button>
33 + </div>
34 + </div>
35 +</template>
36 +
37 +<script setup>
38 +/**
39 + * @description 计划书弹窗容器组件
40 + * @description 提供统一的头部、底部按钮和布局结构
41 + *
42 + * @props {string} title - 弹窗标题
43 + *
44 + * @emits close - 关闭弹窗事件
45 + * @emits submit - 提交事件
46 + *
47 + * @example
48 + * <PlanPopup title="申请计划书" @close="handleClose" @submit="handleSubmit">
49 + * <!-- 具体的表单内容 -->
50 + * </PlanPopup>
51 + */
52 +import IconFont from '@/components/IconFont.vue'
53 +
54 +defineProps({
55 + /** 弹窗标题 */
56 + title: {
57 + type: String,
58 + default: '计划书'
59 + }
60 +})
61 +
62 +const emit = defineEmits(['close', 'submit'])
63 +
64 +/**
65 + * 处理关闭事件
66 + */
67 +const handleClose = () => {
68 + emit('close')
69 +}
70 +
71 +/**
72 + * 处理提交事件
73 + */
74 +const handleSubmit = () => {
75 + emit('submit')
76 +}
77 +</script>
78 +
79 +<style lang="less" scoped>
80 +/* 确保 NutUI 按钮样式正确 */
81 +:deep(.nut-button) {
82 + border-radius: 0.5rem /* 8px */;
83 + font-size: 1rem /* 16px */;
84 +}
85 +</style>
This diff is collapsed. Click to expand it.
1 <template> 1 <template>
2 - <div class="flex flex-col h-full bg-gray-50"> 2 + <PlanPopup title="保险计划书申请" @close="close" @submit="submit">
3 - <!-- Header --> 3 + <!-- 币种 -->
4 - <div class="flex justify-between items-center px-5 py-5 bg-white rounded-t-xl"> 4 + <div class="flex justify-between items-start mb-5">
5 - <span class="text-lg font-normal text-gray-900">保险计划书申请</span> 5 + <span class="text-sm text-gray-600 mt-1.5">币种</span>
6 - <IconFont name="close" size="16" color="#9CA3AF" @click="close" /> 6 + <div class="bg-blue-50 rounded-md px-3 py-1.5">
7 + <span class="text-sm text-blue-600">美元保单</span>
8 + </div>
7 </div> 9 </div>
8 10
9 - <!-- Scrollable Content --> 11 + <!-- 计划 -->
10 - <div class="flex-1 overflow-y-auto p-4"> 12 + <div class="flex justify-between items-start mb-5">
11 - <div class="bg-white rounded-xl p-5 shadow-sm"> 13 + <span class="text-sm text-gray-600 mt-1.5">计划</span>
12 - <!-- 币种 --> 14 + <div class="bg-blue-50 rounded-md px-3 py-1.5">
13 - <div class="flex justify-between items-start mb-5"> 15 + <span class="text-sm text-blue-600">基础情景</span>
14 - <span class="text-sm text-gray-600 mt-1.5">币种</span> 16 + </div>
15 - <div class="bg-blue-50 rounded-md px-3 py-1.5"> 17 + </div>
16 - <span class="text-sm text-blue-600">美元保单</span>
17 - </div>
18 - </div>
19 -
20 - <!-- 计划 -->
21 - <div class="flex justify-between items-start mb-5">
22 - <span class="text-sm text-gray-600 mt-1.5">计划</span>
23 - <div class="bg-blue-50 rounded-md px-3 py-1.5">
24 - <span class="text-sm text-blue-600">基础情景</span>
25 - </div>
26 - </div>
27 -
28 - <!-- 附加计划 -->
29 - <div class="mb-5">
30 - <span class="text-base text-gray-900">附加计划</span>
31 - </div>
32 -
33 - <!-- 性别 -->
34 - <div class="text-sm text-gray-600 mb-2">性别</div>
35 - <nut-radio-group v-model="form.gender" direction="horizontal" class="mb-4">
36 - <nut-radio label="female" class="mr-8">女</nut-radio>
37 - <nut-radio label="male">男</nut-radio>
38 - </nut-radio-group>
39 -
40 - <!-- 年龄 -->
41 - <div class="text-sm text-gray-600 mb-2">年龄</div>
42 - <div class="border border-gray-200 rounded-lg mb-4 overflow-hidden">
43 - <nut-input
44 - v-model="form.age"
45 - type="digit"
46 - placeholder="请输入年龄"
47 - class="!p-0 !bg-transparent !text-sm !text-gray-900"
48 - :border="false"
49 - />
50 - </div>
51 18
52 - <!-- 保险期间 --> 19 + <!-- 附加计划 -->
53 - <div class="flex justify-between items-start mb-5"> 20 + <div class="mb-5">
54 - <span class="text-sm text-gray-600 mt-1.5">保险期间</span> 21 + <span class="text-base text-gray-900">附加计划</span>
55 - <div class="bg-blue-50 rounded-md px-3 py-1.5"> 22 + </div>
56 - <span class="text-sm text-blue-600">终身</span>
57 - </div>
58 - </div>
59 23
60 - <!-- 交费期间 --> 24 + <!-- 性别 -->
61 - <div class="text-sm text-gray-600 mb-3">交费期间</div> 25 + <div class="text-sm text-gray-600 mb-2">性别</div>
62 - <nut-radio-group v-model="form.paymentPeriod" direction="horizontal" class="mb-4"> 26 + <nut-radio-group v-model="form.gender" direction="horizontal" class="mb-4">
63 - <nut-radio 27 + <nut-radio label="female" class="mr-8">女</nut-radio>
64 - v-for="period in paymentPeriods" 28 + <nut-radio label="male">男</nut-radio>
65 - :key="period" 29 + </nut-radio-group>
66 - :label="period" 30 +
67 - class="mr-6" 31 + <!-- 年龄 -->
68 - > 32 + <div class="text-sm text-gray-600 mb-2">年龄</div>
69 - {{ period }} 33 + <div class="border border-gray-200 rounded-lg mb-4 overflow-hidden">
70 - </nut-radio> 34 + <nut-input
71 - </nut-radio-group> 35 + v-model="form.age"
36 + type="digit"
37 + placeholder="请输入年龄"
38 + class="!p-0 !bg-transparent !text-sm !text-gray-900"
39 + :border="false"
40 + />
41 + </div>
72 42
73 - <!-- 年交保费 --> 43 + <!-- 保险期间 -->
74 - <div class="text-sm text-gray-600 mb-2">年交保费</div> 44 + <div class="flex justify-between items-start mb-5">
75 - <div class="border border-gray-200 rounded-lg mb-4 flex items-center overflow-hidden"> 45 + <span class="text-sm text-gray-600 mt-1.5">保险期间</span>
76 - <nut-input 46 + <div class="bg-blue-50 rounded-md px-3 py-1.5">
77 - v-model="form.premium" 47 + <span class="text-sm text-blue-600">终身</span>
78 - type="digit"
79 - placeholder="请输入保费"
80 - class="!p-0 !bg-transparent flex-1 !text-sm !text-gray-900"
81 - :border="false"
82 - />
83 - <span class="text-sm text-gray-500 shrink-0 ml-2 mr-5">美元</span>
84 - </div>
85 </div> 48 </div>
86 </div> 49 </div>
87 50
88 - <!-- Footer Buttons --> 51 + <!-- 交费期间 -->
89 - <div class="p-4 pt-2 pb-8 flex justify-between gap-4 bg-gray-50"> 52 + <div class="text-sm text-gray-600 mb-3">交费期间</div>
90 - <div class="flex-1 py-3 text-center border border-blue-600 text-blue-600 rounded-lg text-base bg-white" 53 + <nut-radio-group v-model="form.paymentPeriod" direction="horizontal" class="mb-4">
91 - @click="close"> 54 + <nut-radio
92 - 取消 55 + v-for="period in paymentPeriods"
93 - </div> 56 + :key="period"
94 - <div class="flex-1 py-3 text-center bg-blue-600 text-white rounded-lg text-base" @click="submit"> 57 + :label="period"
95 - 提交申请 58 + class="mr-6"
96 - </div> 59 + >
60 + {{ period }}
61 + </nut-radio>
62 + </nut-radio-group>
63 +
64 + <!-- 年交保费 -->
65 + <div class="text-sm text-gray-600 mb-2">年交保费</div>
66 + <div class="border border-gray-200 rounded-lg mb-4 flex items-center overflow-hidden">
67 + <nut-input
68 + v-model="form.premium"
69 + type="digit"
70 + placeholder="请输入保费"
71 + class="!p-0 !bg-transparent flex-1 !text-sm !text-gray-900"
72 + :border="false"
73 + />
74 + <span class="text-sm text-gray-500 shrink-0 ml-2 mr-5">美元</span>
97 </div> 75 </div>
98 - </div> 76 + </PlanPopup>
99 </template> 77 </template>
100 78
101 <script setup> 79 <script setup>
102 /** 80 /**
103 * @description 录入计划书 - 方案B 内容组件 81 * @description 录入计划书 - 方案B 内容组件
82 + * @description 使用 PlanPopup 容器组件提供统一的布局和按钮
83 + *
104 * @emits close - 关闭弹窗事件 84 * @emits close - 关闭弹窗事件
105 * @emits submit - 提交事件,携带表单数据 85 * @emits submit - 提交事件,携带表单数据
106 */ 86 */
107 -import { reactive, defineEmits } from 'vue'; 87 +import { reactive } from 'vue'
88 +import PlanPopup from './PlanPopup.vue'
108 89
109 -const emit = defineEmits(['close', 'submit']); 90 +const emit = defineEmits(['close', 'submit'])
110 91
92 +/**
93 + * 表单数据
94 + */
111 const form = reactive({ 95 const form = reactive({
112 currency: '美元保单', 96 currency: '美元保单',
113 plan: '基础情景', 97 plan: '基础情景',
...@@ -115,22 +99,32 @@ const form = reactive({ ...@@ -115,22 +99,32 @@ const form = reactive({
115 age: '30', 99 age: '30',
116 insurancePeriod: '终身', 100 insurancePeriod: '终身',
117 paymentPeriod: '10年交', 101 paymentPeriod: '10年交',
118 - premium: '100000', 102 + premium: '100000'
119 -}); 103 +})
120 104
121 -const paymentPeriods = ['10年交', '3年交', '5年交', '2年交']; 105 +/**
106 + * 交费期间选项
107 + */
108 +const paymentPeriods = ['10年交', '3年交', '5年交', '2年交']
122 109
110 +/**
111 + * 关闭弹窗
112 + */
123 const close = () => { 113 const close = () => {
124 - emit('close'); 114 + emit('close')
125 -}; 115 +}
126 116
117 +/**
118 + * 提交表单
119 + */
127 const submit = () => { 120 const submit = () => {
128 - console.log('SchemeB Submit:', form); 121 + console.log('SchemeB Submit:', form)
129 - emit('submit', form); 122 + emit('submit', form)
130 -}; 123 +}
131 </script> 124 </script>
132 125
133 <style lang="less" scoped> 126 <style lang="less" scoped>
127 +/* Override NutUI input styles to match design */
134 :deep(.nut-input) { 128 :deep(.nut-input) {
135 padding: 0; 129 padding: 0;
136 background: transparent; 130 background: transparent;
......