<template>
<zy-picker
:visible="visible"
:title="pickerTitle"
:cancel-text="cancelText"
:confirm-text="confirmText"
:columns="displayColumns"
:initial-value="initialIndices"
:insert="insert"
:need-btn="needBtn"
:need-mask="needMask"
:custom-picker-container="customPickerContainer"
@cancel="$emit('cancel')"
@confirm="handleConfirm"
@change="handleChange"
>
<slot></slot>
</zy-picker>
</template>
<script>
function isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
}
function getDaysInMonth(year, month) {
if (month === 2) return isLeapYear(year) ? 29 : 28
const days = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
return days[month]
}
export default {
name: 'ZyPickerDate',
props: {
visible: Boolean,
mode: {
type: String,
default: 'date',
validator: v => ['date', 'datetime-hour', 'datetime-minute', 'datetime-second'].includes(v)
},
date: { type: [String, Date, Number, null], default: null },
title: { type: String, default: '' },
cancelText: { type: String, default: '取消' },
confirmText: { type: String, default: '确定' },
insert: { type: Boolean, default: false },
needBtn: { type: Boolean, default: true },
needMask: { type: Boolean, default: true },
customPickerContainer: { type: Object, default: () => ({}) }
},
emits: ['update:visible', 'confirm', 'cancel', 'change'],
data() {
return {
selected: {
year: 2025,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0
},
years: [],
months: Array.from({ length: 12 }, (_, i) => i + 1),
days: Array.from({ length: 31 }, (_, i) => i + 1) // 预设 1~31 天
}
},
computed: {
columnTypes() {
const map = {
date: ['year', 'month', 'day'],
'datetime-hour': ['year', 'month', 'day', 'hour'],
'datetime-minute': ['year', 'month', 'day', 'hour', 'minute'],
'datetime-second': ['year', 'month', 'day', 'hour', 'minute', 'second']
}
return map[this.mode] || ['year', 'month', 'day']
},
displayColumns() {
return this.columnTypes.map(type => {
switch (type) {
case 'year':
return this.years.map(y => `${y}年`)
case 'month':
return this.months.map(m => `${m}月`)
case 'day':
return this.days.map(d => `${d}日`)
case 'hour':
return Array.from({ length: 24 }, (_, i) => `${i}时`)
case 'minute':
return Array.from({ length: 60 }, (_, i) => `${i}分`)
case 'second':
return Array.from({ length: 60 }, (_, i) => `${i}秒`)
default:
return []
}
})
},
initialIndices() {
return this.columnTypes.map(type => {
let value
if (type === 'year') value = this.selected.year
else if (type === 'month') value = this.selected.month
else if (type === 'day') value = this.selected.day
else value = this.selected[type]
const list = this.getListByType(type)
const idx = list.indexOf(value)
return idx >= 0 ? idx : 0
})
},
pickerTitle() {
return this.title
// (
// this.title ||
// {
// date: '选择日期',
// 'datetime-hour': '选择小时',
// 'datetime-minute': '选择时间',
// 'datetime-second': '选择精确时间'
// }[this.mode]
// )
}
},
watch: {
date: {
handler(newVal) {
if (newVal) {
this.initData() // 不管是否 visible,都同步 selected
}
},
immediate: true // 👈 确保创建时立刻执行一次
},
visible(val) {
if (val) {
// 可选:再次刷新防止异常
this.initData()
}
}
},
created() {
this.generateYears()
},
methods: {
generateYears() {
const y = new Date().getFullYear()
this.years = Array.from({ length: 101 }, (_, i) => y - 50 + i)
},
getListByType(type) {
switch (type) {
case 'year':
return this.years
case 'month':
return this.months
case 'day':
return this.days
case 'hour':
return Array.from({ length: 24 }, (_, i) => i)
case 'minute':
return Array.from({ length: 60 }, (_, i) => i)
case 'second':
return Array.from({ length: 60 }, (_, i) => i)
default:
return []
}
},
initData() {
const d = this.parseDate(this.date)
this.selected.year = d.getFullYear()
this.selected.month = d.getMonth() + 1
this.selected.day = Math.min(d.getDate(), getDaysInMonth(this.selected.year, this.selected.month))
this.selected.hour = d.getHours()
this.selected.minute = d.getMinutes()
this.selected.second = d.getSeconds()
// 必须调用 updateDays 来生成 days 列
this.updateDays()
},
parseDate(input) {
if (!input) return new Date()
// 已经是 Date 对象
if (input instanceof Date) {
return new Date(input)
}
// 时间戳
if (typeof input === 'number') {
return new Date(input)
}
// 字符串处理
if (typeof input === 'string') {
const trimmed = input.trim()
// 检测 YYYY-MM-DD 格式(无时间)
if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) {
// ⚠️ 防止 Safari 将其解析为 UTC
// 强制加上本地时间零点
console.log('[ZyPickerDate] 自动补全日期字符串:', trimmed, '→', trimmed + 'T00:00:00')
return new Date(trimmed + 'T00:00:00')
}
// 其他格式尝试直接解析(如 ISO、含时间等)
const parsed = new Date(trimmed.replace(/-/g, '/')) // 提高兼容性
if (!isNaN(parsed.getTime())) {
return parsed
}
}
// 所有失败情况兜底
console.warn('[ZyPickerDate] 无效日期输入,使用当前时间替代:', input)
return new Date()
},
updateDays() {
const max = getDaysInMonth(this.selected.year, this.selected.month)
this.days = Array.from({ length: max }, (_, i) => i + 1)
if (this.selected.day > max) this.selected.day = max
},
// parseDate(input) {
// const d = input ? new Date(input) : new Date()
// return isNaN(d.getTime()) ? new Date() : d
// },
// updateDays() {
// const max = getDaysInMonth(this.selected.year, this.selected.month)
// this.days = Array.from({ length: max }, (_, i) => i + 1)
// if (this.selected.day > max) this.selected.day = max
// },
handleChange(values) {
values.forEach((val, idx) => {
const num = parseInt(val)
if (isNaN(num)) return
const type = this.columnTypes[idx]
this.selected[type] = num
})
// 联动更新 day
if (this.columnTypes.includes('day')) {
this.$nextTick(() => {
this.updateDays()
const result = new Date(
this.selected.year,
this.selected.month - 1,
this.selected.day,
this.selected.hour || 0,
this.selected.minute || 0,
this.selected.second || 0
)
this.$emit('change', {
date: result,
formatted: {
date: `${this.selected.year}-${String(this.selected.month).padStart(2, '0')}-${String(this.selected.day).padStart(2, '0')}`,
time: `${String(this.selected.hour).padStart(2, '0')}:${String(this.selected.minute).padStart(2, '0')}:${String(this.selected.second).padStart(2, '0')}`
},
values: { ...this.selected },
mode: this.mode
})
})
}
},
handleConfirm(selectedLabels) {
const result = new Date(
this.selected.year,
this.selected.month - 1,
this.selected.day,
this.selected.hour || 0,
this.selected.minute || 0,
this.selected.second || 0
)
this.$emit('confirm', {
date: result,
formatted: {
date: `${this.selected.year}-${String(this.selected.month).padStart(2, '0')}-${String(this.selected.day).padStart(2, '0')}`,
time: `${String(this.selected.hour).padStart(2, '0')}:${String(this.selected.minute).padStart(2, '0')}:${String(this.selected.second).padStart(2, '0')}`
},
values: { ...this.selected },
mode: this.mode
})
this.$emit('update:visible', false)
}
}
}
</script>
新增参数,可设置时间的选中范围,例如日期只能选择今日之前或者今日之后
最新发布