170 lines
3.7 KiB
Vue
170 lines
3.7 KiB
Vue
<template>
|
||
<div class="base-picker">
|
||
<!-- 使用 van-field 作为展示区域 -->
|
||
<van-field
|
||
:modelValue="displayValue"
|
||
:label="label"
|
||
:required="required"
|
||
:placeholder="placeholder"
|
||
:disabled="disabled"
|
||
:readonly="true"
|
||
:right-icon="rightIcon"
|
||
:clickable="!disabled"
|
||
@click="openPicker"
|
||
/>
|
||
|
||
<!-- 弹出层选择器 -->
|
||
<van-popup v-model:show="showPicker" position="bottom" round>
|
||
<van-picker
|
||
:modelValue="getPickerValue()"
|
||
:columns="columns"
|
||
:title="pickerTitle"
|
||
:loading="loading"
|
||
show-toolbar
|
||
@confirm="onConfirm"
|
||
@cancel="showPicker = false"
|
||
/>
|
||
</van-popup>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue'
|
||
import { Field, Popup, Picker } from 'vant'
|
||
|
||
// Props 定义
|
||
const props = defineProps({
|
||
// 双向绑定值 (v-model)
|
||
modelValue: {
|
||
type: [String, Number, Boolean],
|
||
default: null
|
||
},
|
||
// 选项数据,默认格式 [{ label: '显示名', value: '值' }]
|
||
options: {
|
||
type: Array,
|
||
required: true,
|
||
default: () => []
|
||
},
|
||
// 左侧标签文字
|
||
label: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
required: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
// 占位符
|
||
placeholder: {
|
||
type: String,
|
||
default: '请选择'
|
||
},
|
||
// 是否禁用
|
||
disabled: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
// Picker 顶部标题
|
||
pickerTitle: {
|
||
type: String,
|
||
default: '请选择'
|
||
},
|
||
// 加载状态
|
||
loading: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
// 右侧图标(可自定义)
|
||
rightIcon: {
|
||
type: String,
|
||
default: 'arrow-down'
|
||
},
|
||
// 自定义选项的 label 字段名
|
||
labelKey: {
|
||
type: String,
|
||
default: 'label'
|
||
},
|
||
// 自定义选项的 value 字段名
|
||
valueKey: {
|
||
type: String,
|
||
default: 'value'
|
||
}
|
||
})
|
||
|
||
// Emits 定义
|
||
const emit = defineEmits(['update:modelValue', 'change'])
|
||
|
||
// 控制弹出层显示
|
||
const showPicker = ref(false)
|
||
|
||
const getPickerValue = () => {
|
||
// 布尔值需要转换为数字交给picker, 在设置的时候,又需要转换回来
|
||
if(props.modelValue === true) return [1]
|
||
if(props.modelValue === false) return [0]
|
||
return [props.modelValue]
|
||
}
|
||
|
||
// 处理 Picker 数据格式:将 options 转为 Picker 需要的文本数组
|
||
const columns = computed(() => {
|
||
return props.options.map(item => {
|
||
let value = item[props.valueKey]
|
||
let isBoolean = false
|
||
// vant不支持布尔值,需要做转换
|
||
if(value === true) {
|
||
value = 1
|
||
isBoolean = true
|
||
}
|
||
if(value === false) {
|
||
value = 0
|
||
isBoolean = true
|
||
}
|
||
|
||
return {
|
||
text: item[props.labelKey],
|
||
value,
|
||
isBoolean
|
||
}
|
||
})
|
||
})
|
||
|
||
// 根据选中的值,获取显示文本
|
||
const displayValue = computed(() => {
|
||
if (props.modelValue === null || props.modelValue === undefined || props.modelValue === '') {
|
||
return ''
|
||
}
|
||
const selected = props.options.find(item => item[props.valueKey] === props.modelValue)
|
||
return selected ? selected[props.labelKey] : ''
|
||
})
|
||
|
||
// 打开选择器
|
||
const openPicker = () => {
|
||
if (!props.disabled) {
|
||
showPicker.value = true
|
||
}
|
||
}
|
||
|
||
// 确认选择
|
||
const onConfirm = ({ selectedValues, selectedOptions }) => {
|
||
let value = selectedOptions[0][props.valueKey]
|
||
// 如果是布尔值,需要转换为布尔值
|
||
if(selectedOptions[0].isBoolean) {
|
||
value = value === 1 ? true : false
|
||
}
|
||
const label = selectedOptions[0][props.labelKey]
|
||
emit('update:modelValue', value)
|
||
emit('change', { value, label })
|
||
showPicker.value = false
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.base-picker {
|
||
width: 100%;
|
||
}
|
||
|
||
/* 可选:调整 Field 的只读样式 */
|
||
:deep(.van-field__control--readonly) {
|
||
cursor: pointer;
|
||
}
|
||
</style>
|