| | |
| | | <template> |
| | | <div class="time-picker"> |
| | | <div class="picker-column" ref="hourRef" @scroll="onScroll('hour')"> |
| | | <div class="picker-column" ref="hourRef" @scroll="onScroll('hour')" :class="disabled ? 'disabled' : ''"> |
| | | <div |
| | | v-for="(h, index) in loopHours" |
| | | :key="index" |
| | |
| | | </div> |
| | | </div> |
| | | <span class="colon">:</span> |
| | | <div class="picker-column" ref="minuteRef" @scroll="onScroll('minute')"> |
| | | <div class="picker-column" ref="minuteRef" @scroll="onScroll('minute')" :class="disabled ? 'disabled' : ''"> |
| | | <div |
| | | v-for="(m, index) in loopMinutes" |
| | | :key="index" |
| | |
| | | <script setup lang="ts"> |
| | | import { ref, watch, onMounted, nextTick } from "vue"; |
| | | |
| | | const props = defineProps<{ modelValue: string }>(); |
| | | const props = defineProps<{ modelValue: string, disabled: boolean }>(); |
| | | const emit = defineEmits(["update:modelValue"]); |
| | | |
| | | const hours = Array.from({ length: 24 }, (_, i) => i); |
| | |
| | | const hourRef = ref<HTMLElement | null>(null); |
| | | const minuteRef = ref<HTMLElement | null>(null); |
| | | |
| | | const ITEM_REM = 0.4; |
| | | const ITEM_REM = 0.26; |
| | | const itemHeight = remToPx(ITEM_REM); |
| | | |
| | | const selectedIndexHour = ref(2); |
| | |
| | | } |
| | | ); |
| | | |
| | | watch( |
| | | () => props.modelValue, |
| | | (newVal) => { |
| | | const [h, m] = newVal.split(":").map(Number); |
| | | const hourIdx = hours.indexOf(h); |
| | | const minuteIdx = minutes.indexOf(m); |
| | | |
| | | if (hourIdx !== -1) { |
| | | selectedIndexHour.value = hourIdx + 2; |
| | | selectedHour.value = h; |
| | | scrollToSelected("hour", selectedIndexHour.value); |
| | | } |
| | | |
| | | if (minuteIdx !== -1) { |
| | | selectedIndexMinute.value = minuteIdx + 2; |
| | | selectedMinute.value = m; |
| | | scrollToSelected("minute", selectedIndexMinute.value); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | function onScroll(type: "hour" | "minute") { |
| | | if (props.disabled) return; |
| | | if (isProgrammaticScroll[type]) { |
| | | // 程序滚动,忽略,避免死循环 |
| | | isProgrammaticScroll[type] = false; |
| | |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | height: 2rem; // 5 * 0.4rem 每个item高度0.4rem |
| | | height: 1.3rem; // 5 * 0.4rem 每个item高度0.4rem |
| | | overflow: hidden; |
| | | |
| | | .picker-column { |
| | | height: 2rem; |
| | | width: 0.9rem; |
| | | height: 1.3rem; |
| | | width: 0.7rem; |
| | | overflow-y: scroll; |
| | | scroll-snap-type: y mandatory; |
| | | -webkit-overflow-scrolling: touch; |
| | |
| | | &::-webkit-scrollbar { |
| | | display: none; /* Chrome Safari */ |
| | | } |
| | | &.disabled{ |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .picker-item { |
| | | height: 0.4rem; |
| | | line-height: 0.4rem; |
| | | height: 0.26rem; |
| | | line-height: 0.26rem; |
| | | text-align: center; |
| | | font-size: 0.24rem; |
| | | font-size: 0.2rem; |
| | | scroll-snap-align: center; |
| | | user-select: none; |
| | | cursor: pointer; |
| | | transition: color 0.3s ease, font-size 0.3s ease; |
| | | |
| | | |
| | | &.active { |
| | | font-size: 0.5rem; |
| | | font-size: 0.28rem; |
| | | font-weight: 700; |
| | | color: #111; |
| | | &.hours { |
| | |
| | | } |
| | | } |
| | | &.medium { |
| | | font-size: 0.3rem; |
| | | font-size: 0.22rem; |
| | | color: #666; |
| | | &.hours { |
| | | text-align: left; |
| | | padding-left: 0.2rem; |
| | | padding-left: 0.16rem; |
| | | } |
| | | &.minutes { |
| | | text-align: right; |
| | | padding-right: 0.2rem; |
| | | padding-right: 0.16rem; |
| | | } |
| | | } |
| | | &.small { |
| | | font-size: 0.24rem; |
| | | font-size: 0.18rem; |
| | | color: #aaa; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .colon { |
| | | font-size: 0.5rem; |
| | | font-size: 0.28rem; |
| | | font-weight: 600; |
| | | color: #444; |
| | | user-select: none; |