bxztApp/packages/mobile/src/components/BaseDatePicker.vue

447 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="base-date-time-picker">
<!-- 使用 van-field 作为展示区域 - 修复 bug1改为 modelValue -->
<van-field
v-model="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">
<div class="picker-container">
<div class="picker-header">
<span class="picker-cancel" @click="showPicker = false">取消</span>
<span class="picker-title">{{ pickerTitle }}</span>
<span class="picker-confirm" @click="onConfirm">确定</span>
</div>
<div class="picker-wrapper" :class="{ 'has-time': hasTime }">
<!-- 日期选择器 -->
<van-date-picker
v-model="currentDate"
:min-date="minDate"
:max-date="maxDate"
:columns-type="dateColumnsType"
:formatter="formatter"
:filter="filter"
:loading="loading"
:show-toolbar="false"
/>
<!-- 时间选择器当有小时列时显示 -->
<van-time-picker
v-if="hasTime"
v-model="currentTime"
:columns-type="timeColumnsType"
:min-hour="minHour"
:max-hour="maxHour"
:min-minute="minMinute"
:max-minute="maxMinute"
:formatter="formatter"
:show-toolbar="false"
/>
</div>
</div>
</van-popup>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { Field, Popup, DatePicker, TimePicker } from 'vant'
import { formatDate } from '@shared/utils'
// Props 定义
const props = defineProps({
// 双向绑定值 (v-model)
modelValue: {
type: String,
default: ''
},
// 左侧标签文字
label: {
type: String,
default: ''
},
required: {
type: Boolean,
default: false
},
// 占位符
placeholder: {
type: String,
default: '请选择日期时间'
},
// 是否禁用
disabled: {
type: Boolean,
default: false
},
// Picker 顶部标题
pickerTitle: {
type: String,
default: '选择日期时间'
},
// 列类型:支持 'year', 'month', 'day', 'hour', 'minute', 'second'
columnsType: {
type: Array,
default: () => ['year', 'month', 'day', 'hour', 'minute']
},
// 最小日期
minDate: {
type: Date,
default: () => new Date(1900, 0, 1)
},
// 最大日期
maxDate: {
type: Date,
default: () => new Date(2100, 11, 31)
},
// 最小小时
minHour: {
type: Number,
default: 0
},
// 最大小时
maxHour: {
type: Number,
default: 23
},
// 最小分钟
minMinute: {
type: Number,
default: 0
},
// 最大分钟
maxMinute: {
type: Number,
default: 59
},
// 加载状态
loading: {
type: Boolean,
default: false
},
// 右侧图标
rightIcon: {
type: String,
default: 'arrow-down'
},
// 日期时间格式化函数
formatter: {
type: Function,
default: (type, option) => {
if (type === 'year') option.text += '年'
if (type === 'month') option.text += '月'
if (type === 'day') option.text += '日'
if (type === 'hour') option.text += '时'
if (type === 'minute') option.text += '分'
if (type === 'second') option.text += '秒'
return option
}
},
// 返回结果格式化
resultFormat: {
type: String,
default: 'YYYY-MM-DD HH:mm:ss'
},
// 选项过滤函数
filter: {
type: Function,
default: null
}
})
// Emits 定义
const emit = defineEmits(['update:modelValue', 'change'])
// 控制弹出层显示
const showPicker = ref(false)
// 当前选中的日期(数组格式,如 ['2024', '01', '01']
const currentDate = ref([])
// 当前选中的时间(数组格式,如 ['14', '30', '00']
const currentTime = ref([])
// 分离日期列类型和时间列类型
const dateColumnsType = computed(() => {
return props.columnsType.filter(col => ['year', 'month', 'day'].includes(col))
})
const timeColumnsType = computed(() => {
return props.columnsType.filter(col => ['hour', 'minute', 'second'].includes(col))
})
// 是否有时间选择
const hasTime = computed(() => {
return timeColumnsType.value.length > 0
})
// 格式化日期显示(修复 bug2正确处理分钟显示
const displayValue = computed(() => {
if (!props.modelValue) return ''
// 根据 columnsType 格式化显示
const hasYear = props.columnsType.includes('year')
const hasMonth = props.columnsType.includes('month')
const hasDay = props.columnsType.includes('day')
const hasHour = props.columnsType.includes('hour')
const hasMinute = props.columnsType.includes('minute')
const hasSecond = props.columnsType.includes('second')
// 解析日期时间
let datePart = ''
let timePart = ''
if (props.modelValue.includes(' ')) {
[datePart, timePart] = props.modelValue.split(' ')
} else if (props.modelValue.includes('-')) {
datePart = props.modelValue
} else if (props.modelValue.includes(':')) {
timePart = props.modelValue
}
// 格式化日期部分
let result = ''
if (datePart && (hasYear || hasMonth || hasDay)) {
const dateArr = datePart.split('-')
if (hasYear && dateArr[0]) result += dateArr[0]
if (hasMonth && dateArr[1]) result += (result ? '-' : '') + dateArr[1]
if (hasDay && dateArr[2]) result += (result ? '-' : '') + dateArr[2]
}
// 格式化时间部分 - 修复 bug2确保分钟正确显示
if (timePart && (hasHour || hasMinute || hasSecond)) {
const timeArr = timePart.split(':')
let timeResult = ''
if (hasHour && timeArr[0]) timeResult += timeArr[0]
if (hasMinute && timeArr[1]) timeResult += (timeResult ? ':' : '') + timeArr[1]
if (hasSecond && timeArr[2]) timeResult += (timeResult ? ':' : '') + timeArr[2]
if (timeResult) {
result += (result ? ' ' : '') + timeResult
}
}
return result || props.modelValue
})
// 获取默认时间值(根据列类型)
const getDefaultTime = () => {
const now = new Date()
const timeValues = []
if (timeColumnsType.value.includes('hour')) {
timeValues.push(String(now.getHours()).padStart(2, '0'))
}
if (timeColumnsType.value.includes('minute')) {
timeValues.push(String(now.getMinutes()).padStart(2, '0'))
}
if (timeColumnsType.value.includes('second')) {
timeValues.push(String(now.getSeconds()).padStart(2, '0'))
}
return timeValues
}
// 初始化当前值
const initValue = () => {
// 初始化日期部分
if (props.modelValue) {
// 解析 YYYY-MM-DD HH:mm:ss 格式
let datePart = ''
let timePart = ''
if (props.modelValue.includes(' ')) {
[datePart, timePart] = props.modelValue.split(' ')
} else if (props.modelValue.includes('-')) {
datePart = props.modelValue
} else if (props.modelValue.includes(':')) {
timePart = props.modelValue
}
if (datePart) {
const dateArr = datePart.split('-')
currentDate.value = []
if (dateColumnsType.value.includes('year') && dateArr[0]) {
currentDate.value.push(dateArr[0])
}
if (dateColumnsType.value.includes('month') && dateArr[1]) {
currentDate.value.push(dateArr[1])
}
if (dateColumnsType.value.includes('day') && dateArr[2]) {
currentDate.value.push(dateArr[2])
}
} else {
const now = new Date()
currentDate.value = []
if (dateColumnsType.value.includes('year')) {
currentDate.value.push(String(now.getFullYear()))
}
if (dateColumnsType.value.includes('month')) {
currentDate.value.push(String(now.getMonth() + 1).padStart(2, '0'))
}
if (dateColumnsType.value.includes('day')) {
currentDate.value.push(String(now.getDate()).padStart(2, '0'))
}
}
// 初始化时间部分(根据列类型)
if (hasTime.value) {
if (timePart) {
const timeArr = timePart.split(':')
const result = []
if (timeColumnsType.value.includes('hour')) {
result.push(timeArr[0] || '00')
}
if (timeColumnsType.value.includes('minute')) {
result.push(timeArr[1] || '00')
}
if (timeColumnsType.value.includes('second')) {
result.push(timeArr[2] || '00')
}
currentTime.value = result
} else {
currentTime.value = getDefaultTime()
}
}
} else {
// 默认当前日期
const now = new Date()
currentDate.value = []
if (dateColumnsType.value.includes('year')) {
currentDate.value.push(String(now.getFullYear()))
}
if (dateColumnsType.value.includes('month')) {
currentDate.value.push(String(now.getMonth() + 1).padStart(2, '0'))
}
if (dateColumnsType.value.includes('day')) {
currentDate.value.push(String(now.getDate()).padStart(2, '0'))
}
// 默认当前时间(根据列类型)
if (hasTime.value) {
currentTime.value = getDefaultTime()
}
}
}
// 打开选择器
const openPicker = () => {
if (!props.disabled) {
initValue()
showPicker.value = true
}
}
// 格式化日期部分
const formatDatePart = () => {
if (!currentDate.value || currentDate.value.length === 0) return ''
let result = currentDate.value.join('-')
return result
}
// 格式化时间部分
const formatTimePart = () => {
if (!hasTime.value) return ''
if (!currentTime.value || currentTime.value.length === 0) return ''
let result = currentTime.value.join(':')
return result
}
// 确认选择
const onConfirm = () => {
let finalValue = formatDatePart()
if (hasTime.value) {
const timePart = formatTimePart()
if (timePart) {
finalValue += ' ' + timePart
}
}
finalValue = formatDate(finalValue)
emit('update:modelValue', finalValue)
emit('change', finalValue)
showPicker.value = false
}
// 监听外部 modelValue 变化,重新初始化
watch(() => props.modelValue, () => {
initValue()
}, { immediate: true })
</script>
<style scoped>
.base-date-time-picker {
width: 100%;
}
:deep(.van-field__control--readonly) {
cursor: pointer;
}
.picker-container {
background: #fff;
border-radius: 16px 16px 0 0;
overflow: hidden;
}
.picker-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
border-bottom: 1px solid #ebedf0;
}
.picker-cancel {
font-size: 14px;
color: #969799;
cursor: pointer;
}
.picker-title {
font-size: 16px;
font-weight: 500;
color: #323233;
}
.picker-confirm {
font-size: 14px;
color: #1989fa;
cursor: pointer;
}
.picker-wrapper {
display: flex;
}
.picker-wrapper .van-picker {
flex: 1;
}
/* 只有日期选择器时占满宽度 */
.picker-wrapper:not(.has-time) .van-picker {
width: 100%;
}
/* 同时有日期和时间时调整宽度比例 */
.picker-wrapper.has-time .van-date-picker {
flex: 2;
}
.picker-wrapper.has-time .van-time-picker {
flex: 1;
}
</style>