<template>
|
<div class="time-picker">
|
<div class="picker-column" ref="hourRef" @scroll="onScroll('hour')">
|
<div v-for="h in hours" :key="h" class="picker-item" :class="{ active: h === selectedHour }">{{ h.toString().padStart(2, '0') }}</div>
|
</div>
|
<span class="colon">:</span>
|
<div class="picker-column" ref="minuteRef" @scroll="onScroll('minute')">
|
<div v-for="m in minutes" :key="m" class="picker-item" :class="{ active: m === selectedMinute }">{{ m.toString().padStart(2, '0') }}</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { ref, watch, onMounted, nextTick } from 'vue'
|
|
interface Props {
|
modelValue: string // 格式为 "HH:mm"
|
}
|
const props = defineProps<Props>()
|
const emit = defineEmits(['update:modelValue'])
|
|
const selectedHour = ref(0)
|
const selectedMinute = ref(0)
|
const hourRef = ref<HTMLDivElement | null>(null)
|
const minuteRef = ref<HTMLDivElement | null>(null)
|
|
const hours = Array.from({ length: 24 }, (_, i) => i)
|
const minutes = Array.from({ length: 60 }, (_, i) => i)
|
|
function scrollTo(refEl: HTMLDivElement | null, index: number) {
|
if (!refEl) return
|
refEl.scrollTo({ top: index * 40, behavior: 'smooth' })
|
}
|
|
function updateModel() {
|
const value = `${selectedHour.value.toString().padStart(2, '0')}:${selectedMinute.value.toString().padStart(2, '0')}`
|
emit('update:modelValue', value)
|
}
|
|
function onScroll(type: 'hour' | 'minute') {
|
const el = type === 'hour' ? hourRef.value : minuteRef.value
|
if (!el) return
|
|
clearTimeout((el as any)._timer)
|
;(el as any)._timer = setTimeout(() => {
|
const index = Math.round(el.scrollTop / 40)
|
if (type === 'hour') {
|
selectedHour.value = hours[index]
|
scrollTo(hourRef.value, index)
|
} else {
|
selectedMinute.value = minutes[index]
|
scrollTo(minuteRef.value, index)
|
}
|
updateModel()
|
}, 100)
|
}
|
|
watch(
|
() => props.modelValue,
|
(newVal) => {
|
if (!newVal) return
|
const [h, m] = newVal.split(':').map(Number)
|
selectedHour.value = h
|
selectedMinute.value = m
|
nextTick(() => {
|
scrollTo(hourRef.value, h)
|
scrollTo(minuteRef.value, m)
|
})
|
},
|
{ immediate: true }
|
)
|
</script>
|
|
<style scoped>
|
.time-picker {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
height: 200px;
|
background: #e6efff;
|
font-family: sans-serif;
|
}
|
|
.picker-column {
|
width: 60px;
|
height: 200px;
|
overflow-y: scroll;
|
scroll-snap-type: y mandatory;
|
-webkit-overflow-scrolling: touch;
|
text-align: center;
|
position: relative;
|
padding-top: 80px;
|
padding-bottom: 80px;
|
}
|
|
.picker-item {
|
height: 40px;
|
line-height: 40px;
|
font-size: 14px;
|
color: #666;
|
scroll-snap-align: center;
|
transition: all 0.2s;
|
transform: scale(0.8);
|
opacity: 0.5;
|
}
|
|
.picker-item.active {
|
font-size: 24px;
|
font-weight: bold;
|
color: #333;
|
transform: scale(1.2);
|
opacity: 1;
|
}
|
|
.colon {
|
font-size: 24px;
|
margin: 0 10px;
|
}
|
</style>
|