单应用项目,可以创建很多独立工具类页面 ,不用登录 初始化的页面
zhangchen
2025-07-24 223644ebb8c5546c7b55ea86ff972898c0de835a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<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>