hookehuyr

refactor(plan): 删除未使用的旧版本选择器组件

- 删除 AgePicker.vue(已被 AgePickerGlobal.vue 替代)
- 删除 DatePicker.vue(已被 DatePickerGlobal.vue 替代)
- 删除 SelectPicker.vue(已被 SelectPickerGlobal.vue 替代)

所有模板文件已统一使用 Global 版本组件
1 -<template>
2 - <div>
3 - <!-- 标签 -->
4 - <div v-if="label" class="text-sm text-gray-600 mb-2 flex items-center">
5 - <span v-if="required" class="text-red-500 mr-1">*</span>
6 - <span>{{ label }}</span>
7 - </div>
8 -
9 - <!-- 触发区域 -->
10 - <div
11 - class="flex justify-between items-center border border-gray-200 rounded-lg p-3 bg-gray-50"
12 - @tap="handleTap"
13 - >
14 - <span :class="displayValue ? 'text-gray-900' : 'text-gray-400'" class="text-sm">
15 - {{ displayValue || placeholder }}
16 - </span>
17 - <IconFont name="right" size="14" color="#9CA3AF" />
18 - </div>
19 -
20 - <!-- Picker 弹窗 -->
21 - <nut-popup
22 - position="bottom"
23 - v-model:visible="showPicker"
24 - :z-index="9999"
25 - :overlay="false"
26 - :close-on-click-overlay="false"
27 - :catch-move="true"
28 - >
29 - <nut-picker
30 - v-model="pickerValue"
31 - :columns="ageColumns"
32 - @confirm="onConfirm"
33 - @cancel="onCancel"
34 - />
35 - </nut-popup>
36 - </div>
37 -</template>
38 -
39 -<script setup>
40 -/**
41 - * 年龄选择器组件
42 - *
43 - * @description 使用 NutUI Popup + Picker 实现年龄选择
44 - * - 显示格式:3位数字(如 018 表示 18 岁)
45 - * - 提交格式:数字(如 18)
46 - * - 年龄范围:0-120 岁
47 - * @author Claude Code
48 - * @example
49 - * <AgePicker
50 - * v-model="age"
51 - * label="年龄"
52 - * placeholder="请选择年龄"
53 - * />
54 - */
55 -import { ref, computed, watch, inject } from 'vue'
56 -import IconFont from '@/components/icons/IconFont.vue'
57 -
58 -// 注入父组件提供的弹窗控制函数
59 -const popupControl = inject('popupControl', null)
60 -
61 -/**
62 - * 组件属性
63 - */
64 -const props = defineProps({
65 - /**
66 - * 标签文本
67 - * @type {string}
68 - */
69 - label: {
70 - type: String,
71 - default: ''
72 - },
73 -
74 - /**
75 - * 是否必填
76 - * @type {boolean}
77 - */
78 - required: {
79 - type: Boolean,
80 - default: false
81 - },
82 -
83 - /**
84 - * 占位符文本
85 - * @type {string}
86 - */
87 - placeholder: {
88 - type: String,
89 - default: '请选择年龄'
90 - },
91 -
92 - /**
93 - * 绑定的值(数字)
94 - * @type {number}
95 - */
96 - modelValue: {
97 - type: Number,
98 - default: null
99 - }
100 -})
101 -
102 -/**
103 - * 组件事件
104 - */
105 -const emit = defineEmits([
106 - /**
107 - * 更新值事件
108 - * @event update:modelValue
109 - * @param {number} value - 选中的年龄(数字)
110 - */
111 - 'update:modelValue',
112 - /**
113 - * 值变化事件
114 - * @event change
115 - * @param {number} value - 选中的年龄(数字)
116 - */
117 - 'change',
118 - /**
119 - * 弹窗打开事件
120 - * @event open
121 - */
122 - 'open',
123 - /**
124 - * 弹窗关闭事件
125 - * @event close
126 - */
127 - 'close'
128 -])
129 -
130 -/**
131 - * 控制 Picker 显示
132 - * @type {Ref<boolean>}
133 - */
134 -const showPicker = ref(false)
135 -
136 -/**
137 - * Picker 选中的值
138 - * @type {Ref<Array<number>>}
139 - */
140 -const pickerValue = ref([0, 1, 8]) // 默认 018
141 -
142 -/**
143 - * 同步 Picker 值与 modelValue
144 - */
145 -const syncPickerValue = () => {
146 - // 如果 modelValue 有值(包括 0),则使用 modelValue,否则默认为 18
147 - const age = (props.modelValue !== null && props.modelValue !== undefined)
148 - ? props.modelValue
149 - : 18
150 -
151 - // 确保 age 在 0-199 范围内
152 - const validAge = Math.min(Math.max(0, age), 199)
153 -
154 - const h = Math.floor(validAge / 100)
155 - const t = Math.floor((validAge % 100) / 10)
156 - const u = validAge % 10
157 -
158 - pickerValue.value = [h, t, u]
159 -}
160 -
161 -// 监听 modelValue 变化
162 -watch(() => props.modelValue, syncPickerValue, { immediate: true })
163 -
164 -// 监听弹窗打开,重新同步值(防止上次取消后保留了未确认的值)
165 -watch(showPicker, (val) => {
166 - if (val) {
167 - syncPickerValue()
168 - }
169 -})
170 -
171 -/**
172 - * 处理点击事件
173 - */
174 -const handleTap = () => {
175 - openPicker()
176 -}
177 -
178 -/**
179 - * 打开选择器
180 - */
181 -const openPicker = () => {
182 - // 调用父组件提供的 open 函数
183 - if (popupControl && popupControl.open) {
184 - popupControl.open()
185 - }
186 -
187 - showPicker.value = true
188 -}
189 -
190 -/**
191 - * 年龄选项(3列数字格式)
192 - * @description 生成百位(0-1)、十位(0-9)、个位(0-9)的选项数组
193 - * @returns {Array<Array<{text: string, value: number}>>} Picker 列格式
194 - */
195 -const ageColumns = computed(() => {
196 - // 百位: 0-1
197 - const hundreds = [
198 - { text: '0', value: 0 },
199 - { text: '1', value: 1 }
200 - ]
201 -
202 - // 十位: 0-9
203 - const tens = Array.from({ length: 10 }, (_, i) => ({
204 - text: i.toString(),
205 - value: i
206 - }))
207 -
208 - // 个位: 0-9 (为了支持 10, 20 等年龄,个位必须包含 0)
209 - // 用户需求提及第三列 1-9,但如果是 1-9 则无法选择 10, 20 等整数年龄
210 - // 因此此处使用 0-9 以确保完整性
211 - const units = Array.from({ length: 10 }, (_, i) => ({
212 - text: i.toString(),
213 - value: i
214 - }))
215 -
216 - return [hundreds, tens, units]
217 -})
218 -
219 -/**
220 - * 显示的值(数字格式)
221 - * @description 将数字转换为字符串显示
222 - * @returns {string} 显示文本
223 - */
224 -const displayValue = computed(() => {
225 - return props.modelValue !== null && props.modelValue !== undefined
226 - ? props.modelValue.toString()
227 - : ''
228 -})
229 -
230 -/**
231 - * 确认选择
232 - * @param {Object} params - Picker 返回参数
233 - * @param {Array} params.selectedOptions - 选中的选项数组
234 - * @param {Array} params.selectedValue - 选中的值数组
235 - */
236 -const onConfirm = ({ selectedValue, selectedOptions }) => {
237 - // 优先从 selectedOptions 获取值,因为它包含完整的选项对象
238 - // 某些情况下 selectedValue 可能不完整或类型不一致
239 - let h, t, u
240 -
241 - if (selectedOptions && selectedOptions.length >= 3) {
242 - h = selectedOptions[0]?.value
243 - t = selectedOptions[1]?.value
244 - u = selectedOptions[2]?.value
245 - } else if (Array.isArray(selectedValue) && selectedValue.length >= 3) {
246 - h = selectedValue[0]
247 - t = selectedValue[1]
248 - u = selectedValue[2]
249 - }
250 -
251 - // 确保所有位都有值(0 也是有效值)
252 - if (h !== undefined && t !== undefined && u !== undefined) {
253 - const age = parseInt(h) * 100 + parseInt(t) * 10 + parseInt(u)
254 - if (!Number.isNaN(age)) {
255 - emit('update:modelValue', age)
256 - emit('change', age) // 触发 change 事件,供父组件监听
257 - } else {
258 - console.error('[AgePicker] 计算结果为 NaN', { h, t, u })
259 - }
260 - } else {
261 - console.error('[AgePicker] 选中值无效', { selectedValue, selectedOptions })
262 - }
263 -
264 - // 调用父组件提供的 close 函数
265 - if (popupControl && popupControl.close) {
266 - popupControl.close()
267 - }
268 -
269 - showPicker.value = false
270 -}
271 -
272 -/**
273 - * 取消选择
274 - */
275 -const onCancel = () => {
276 - // 调用父组件提供的 close 函数
277 - if (popupControl && popupControl.close) {
278 - popupControl.close()
279 - }
280 -
281 - showPicker.value = false
282 -}
283 -</script>
284 -
285 -<style lang="less">
286 -/* 组件样式 */
287 -</style>
1 -<template>
2 - <div>
3 - <!-- 标签 -->
4 - <div v-if="label" class="text-sm text-gray-600 mb-2 flex items-center">
5 - <span v-if="required" class="text-red-500 mr-1">*</span>
6 - <span>{{ label }}</span>
7 - </div>
8 -
9 - <!-- 触发区域 -->
10 - <div
11 - class="flex justify-between items-center border border-gray-200 rounded-lg p-3 bg-gray-50"
12 - @tap="openDatePicker"
13 - >
14 - <span :class="displayValue ? 'text-gray-900' : 'text-gray-400'" class="text-sm">
15 - {{ displayValue || placeholder }}
16 - </span>
17 - <IconFont name="right" size="14" color="#9CA3AF" />
18 - </div>
19 -
20 - <!-- DatePicker 弹窗 -->
21 - <nut-popup position="bottom" v-model:visible="showDatePicker" :close-on-click-overlay="false">
22 - <nut-date-picker
23 - v-model="currentDate"
24 - :min-date="minDate"
25 - :max-date="maxDate"
26 - :is-show-chinese="true"
27 - @confirm="onConfirm"
28 - @cancel="onCancel"
29 - >
30 - </nut-date-picker>
31 - </nut-popup>
32 - </div>
33 -</template>
34 -
35 -<script setup>
36 -/**
37 - * 日期选择器组件
38 - *
39 - * @description 使用 NutUI DatePicker + Popup 实现日期选择
40 - * - 支持年龄范围限制(minAge, maxAge)
41 - * - 格式:YYYY-MM-DD
42 - * - 可触发自动计算年龄
43 - * @author Claude Code
44 - * @example
45 - * <DatePicker
46 - * v-model="birthday"
47 - * label="出生年月日"
48 - * placeholder="请选择日期"
49 - * :min-age="0"
50 - * :max-age="120"
51 - * @change="onBirthdayChange"
52 - * />
53 - */
54 -import { ref, computed, watch, inject } from 'vue'
55 -import IconFont from '@/components/icons/IconFont.vue'
56 -
57 -// 注入父组件提供的弹窗控制函数
58 -const popupControl = inject('popupControl', null)
59 -
60 -/**
61 - * 组件属性
62 - */
63 -const props = defineProps({
64 - /**
65 - * 标签文本
66 - * @type {string}
67 - */
68 - label: {
69 - type: String,
70 - default: ''
71 - },
72 -
73 - /**
74 - * 是否必填
75 - * @type {boolean}
76 - */
77 - required: {
78 - type: Boolean,
79 - default: false
80 - },
81 -
82 - /**
83 - * 占位符文本
84 - * @type {string}
85 - */
86 - placeholder: {
87 - type: String,
88 - default: '请选择日期'
89 - },
90 -
91 - /**
92 - * 绑定的值(格式:YYYY-MM-DD)
93 - * @type {string}
94 - */
95 - modelValue: {
96 - type: String,
97 - default: ''
98 - },
99 -
100 - /**
101 - * 最小年龄(用于计算最大出生日期)
102 - * @type {number}
103 - * @default 0
104 - */
105 - minAge: {
106 - type: Number,
107 - default: 0
108 - },
109 -
110 - /**
111 - * 最大年龄(用于计算最小出生日期)
112 - * @type {number}
113 - * @default 120
114 - */
115 - maxAge: {
116 - type: Number,
117 - default: 120 }
118 -})
119 -
120 -/**
121 - * 组件事件
122 - */
123 -const emit = defineEmits([
124 - /**
125 - * 更新值事件
126 - * @event update:modelValue
127 - * @param {string} value - 选中的日期(格式:YYYY-MM-DD)
128 - */
129 - 'update:modelValue',
130 -
131 - /**
132 - * 值变化事件(可用于触发自动计算年龄)
133 - * @event change
134 - * @param {string} value - 选中的日期(格式:YYYY-MM-DD)
135 - */
136 - 'change',
137 - /**
138 - * 弹窗打开事件
139 - * @event open
140 - */
141 - 'open',
142 - /**
143 - * 弹窗关闭事件
144 - * @event close
145 - */
146 - 'close'
147 -])
148 -
149 -/**
150 - * 控制 DatePicker 显示
151 - */
152 -const showDatePicker = ref(false)
153 -
154 -/**
155 - * 当前选中的日期(Date 对象)
156 - * 用于绑定给 nut-date-picker
157 - */
158 -const currentDate = ref(new Date())
159 -
160 -/**
161 - * 打开日期选择器
162 - * @description 打开时将传入的 modelValue 转换为 Date 对象
163 - */
164 -const openDatePicker = () => {
165 - // 调用父组件提供的 open 函数
166 - if (popupControl && popupControl.open) {
167 - popupControl.open()
168 - }
169 -
170 - if (props.modelValue) {
171 - // 兼容 iOS 的日期格式 (YYYY/MM/DD)
172 - const dateStr = props.modelValue.replace(/-/g, '/')
173 - const date = new Date(dateStr)
174 - if (!Number.isNaN(date.getTime())) {
175 - currentDate.value = date
176 - }
177 - } else {
178 - // 如果没有值,默认选中最小日期(通常是18岁或0岁对应的时间)
179 - // 或者默认选中当前时间,视业务需求而定。这里默认选中当前时间。
180 - currentDate.value = new Date()
181 - }
182 - showDatePicker.value = true
183 -}
184 -
185 -/**
186 - * 计算最小可选日期(基于最大年龄)
187 - * @description maxAge 岁对应的出生日期
188 - * @example
189 - * // maxAge = 75, 当前日期 = 2026-02-06
190 - * // minDate() // 返回: 1951-02-06
191 - */
192 -const minDate = computed(() => {
193 - const date = new Date()
194 - date.setFullYear(date.getFullYear() - props.maxAge)
195 - return date
196 -})
197 -
198 -/**
199 - * 计算最大可选日期(基于最小年龄)
200 - * @description minAge 岁对应的出生日期
201 - * @example
202 - * // minAge = 0, 当前日期 = 2026-02-06
203 - * // maxDate() // 返回: 2026-02-06
204 - */
205 -const maxDate = computed(() => {
206 - const date = new Date()
207 - date.setFullYear(date.getFullYear() - props.minAge)
208 - return date
209 -})
210 -
211 -/**
212 - * 显示的值
213 - */
214 -const displayValue = computed(() => {
215 - return props.modelValue || ''
216 -})
217 -
218 -/**
219 - * 确认选择
220 - * @param {Object} { selectedValue } - DatePicker 返回的日期对象
221 - *
222 - * @example
223 - * // 用户选择 2020-01-01
224 - * onConfirm({ selectedValue: ['2020', '01', '01'] })
225 - */
226 -const onConfirm = ({ selectedValue }) => {
227 - // NutUI DatePicker confirm 事件返回 { selectedValue: [year, month, day], selectedOptions: [...] }
228 - // 或者直接返回 Date 对象,取决于版本。
229 - // 安全起见,我们查看 currentDate.value,它会被 v-model 更新
230 -
231 - const date = currentDate.value
232 - const year = date.getFullYear()
233 - const month = String(date.getMonth() + 1).padStart(2, '0')
234 - const day = String(date.getDate()).padStart(2, '0')
235 -
236 - const formattedDate = `${year}-${month}-${day}`
237 - emit('update:modelValue', formattedDate)
238 - emit('change', formattedDate)
239 -
240 - // 调用父组件提供的 close 函数
241 - if (popupControl && popupControl.close) {
242 - popupControl.close()
243 - }
244 -
245 - showDatePicker.value = false
246 -}
247 -
248 -/**
249 - * 取消选择
250 - */
251 -const onCancel = () => {
252 - // 调用父组件提供的 close 函数
253 - if (popupControl && popupControl.close) {
254 - popupControl.close()
255 - }
256 -
257 - showDatePicker.value = false
258 -}
259 -</script>
260 -
261 -<style lang="less">
262 -/* 组件样式 */
263 -</style>
1 -<template>
2 - <div>
3 - <!-- 标签 -->
4 - <div v-if="label" class="text-sm text-gray-600 mb-2 flex items-center">
5 - <span v-if="required" class="text-red-500 mr-1">*</span>
6 - <span>{{ label }}</span>
7 - </div>
8 -
9 - <!-- 触发区域 -->
10 - <div
11 - class="flex justify-between items-center border border-gray-200 rounded-lg p-3 bg-gray-50"
12 - @tap="openPicker"
13 - >
14 - <span :class="displayValue ? 'text-gray-900' : 'text-gray-400'" class="text-sm">
15 - {{ displayValue || placeholder }}
16 - </span>
17 - <IconFont name="right" size="14" color="#9CA3AF" />
18 - </div>
19 -
20 - <!-- Picker 弹窗 -->
21 - <nut-popup
22 - position="bottom"
23 - v-model:visible="showPicker"
24 - :z-index="9999"
25 - :overlay="true"
26 - :close-on-click-overlay="false"
27 - >
28 - <nut-picker
29 - :columns="pickerColumns"
30 - @confirm="onConfirm"
31 - @cancel="onCancel"
32 - />
33 - </nut-popup>
34 - </div>
35 -</template>
36 -
37 -<script setup>
38 -/**
39 - * 下拉选择器组件
40 - *
41 - * @description 使用 NutUI Picker 实现下拉选择功能
42 - * - key 和 value 相同(如"整付(0-75 岁)")
43 - * - 适用于缴费年期等场景
44 - * @author Claude Code
45 - * @example
46 - * <SelectPicker
47 - * v-model="paymentPeriod"
48 - * label="缴费年期"
49 - * placeholder="请选择缴费年期"
50 - * :options="['整付(0-75 岁)', '5 年(0-70 岁)']"
51 - * />
52 - */
53 -import { ref, computed, inject } from 'vue'
54 -import IconFont from '@/components/icons/IconFont.vue'
55 -
56 -// 注入父组件提供的弹窗控制函数
57 -const popupControl = inject('popupControl', null)
58 -
59 -/**
60 - * 组件属性
61 - */
62 -const props = defineProps({
63 - /**
64 - * 标签文本
65 - * @type {string}
66 - */
67 - label: {
68 - type: String,
69 - default: ''
70 - },
71 -
72 - /**
73 - * 是否必填
74 - * @type {boolean}
75 - */
76 - required: {
77 - type: Boolean,
78 - default: false
79 - },
80 -
81 - /**
82 - * 占位符文本
83 - * @type {string}
84 - */
85 - placeholder: {
86 - type: String,
87 - default: '请选择'
88 - },
89 -
90 - /**
91 - * 绑定的值
92 - * @type {string}
93 - */
94 - modelValue: {
95 - type: String,
96 - default: ''
97 - },
98 -
99 - /**
100 - * 选项数组(key 和 value 相同)
101 - * @type {Array<string>}
102 - * @example ['整付(0-75 岁)', '5 年(0-70 岁)', '10 年(0-70 岁)']
103 - */
104 - options: {
105 - type: Array,
106 - required: true
107 - }
108 -})
109 -
110 -/**
111 - * 组件事件
112 - */
113 -const emit = defineEmits([
114 - /**
115 - * 更新值事件
116 - * @event update:modelValue
117 - * @param {string} value - 选中的选项
118 - */
119 - 'update:modelValue',
120 - /**
121 - * 弹窗打开事件
122 - * @event open
123 - */
124 - 'open',
125 - /**
126 - * 弹窗关闭事件
127 - * @event close
128 - */
129 - 'close'
130 -])
131 -
132 -/**
133 - * 控制 Picker 显示
134 - */
135 -const showPicker = ref(false)
136 -
137 -/**
138 - * 打开选择器
139 - */
140 -const openPicker = () => {
141 - // 调用父组件提供的 open 函数
142 - if (popupControl && popupControl.open) {
143 - popupControl.open()
144 - }
145 -
146 - showPicker.value = true
147 -}
148 -
149 -/**
150 - * 转换为 Picker 格式
151 - * @description 将选项数组转换为 Picker 需要的格式
152 - * @example
153 - * // options = ['整付(0-75 岁)', '5 年(0-70 岁)']
154 - * // pickerColumns() // 返回: [{ text: '整付(0-75 岁)', value: '整付(0-75 岁)' }, ...]
155 - */
156 -const pickerColumns = computed(() => {
157 - return props.options.map(option => ({
158 - text: option,
159 - value: option // key 和 value 相同
160 - }))
161 -})
162 -
163 -/**
164 - * 显示的值
165 - */
166 -const displayValue = computed(() => {
167 - return props.modelValue || ''
168 -})
169 -
170 -/**
171 - * 确认选择
172 - * @param {Object} params - Picker 返回参数
173 - * @param {Array} params.selectedOptions - 选中的选项数组
174 - *
175 - * @example
176 - * // 用户选择 '整付(0-75 岁)'
177 - * onConfirm({ selectedOptions: [{ text: '整付(0-75 岁)', value: '整付(0-75 岁)' }] })
178 - * // -> emit('update:modelValue', '整付(0-75 岁)')
179 - */
180 -const onConfirm = ({ selectedOptions }) => {
181 - const value = selectedOptions[0]?.value
182 - if (value !== undefined) {
183 - emit('update:modelValue', value)
184 - }
185 -
186 - // 调用父组件提供的 close 函数
187 - if (popupControl && popupControl.close) {
188 - popupControl.close()
189 - }
190 -
191 - showPicker.value = false
192 -}
193 -
194 -/**
195 - * 取消选择
196 - */
197 -const onCancel = () => {
198 - // 调用父组件提供的 close 函数
199 - if (popupControl && popupControl.close) {
200 - popupControl.close()
201 - }
202 -
203 - showPicker.value = false
204 -}
205 -</script>
206 -
207 -<style lang="less">
208 -/* 组件样式 */
209 -</style>