From 909dfdf6ceff702f65bdc15bd958589c6df4195d Mon Sep 17 00:00:00 2001
From: zhangchen <1652267879@qq.com>
Date: 星期六, 26 七月 2025 15:48:48 +0800
Subject: [PATCH] ID1825-定时任务禁止修改
---
src/views/mobile/bedsideAuxiliaryScreen/components/TimePicker.vue | 252 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 252 insertions(+), 0 deletions(-)
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/TimePicker.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/TimePicker.vue
new file mode 100644
index 0000000..261d70c
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/TimePicker.vue
@@ -0,0 +1,252 @@
+<template>
+ <div class="time-picker">
+ <div class="picker-column" ref="hourRef" @scroll="onScroll('hour')" :class="disabled ? 'disabled' : ''">
+ <div
+ v-for="(h, index) in loopHours"
+ :key="index"
+ class="picker-item hours"
+ :class="getClassByIndex(index, selectedIndexHour)"
+ :style="getStyleByIndex(index, selectedIndexHour)"
+ >
+ {{ h.toString().padStart(2, "0") }}
+ </div>
+ </div>
+ <span class="colon">:</span>
+ <div class="picker-column" ref="minuteRef" @scroll="onScroll('minute')" :class="disabled ? 'disabled' : ''">
+ <div
+ v-for="(m, index) in loopMinutes"
+ :key="index"
+ class="picker-item minutes"
+ :class="getClassByIndex(index, selectedIndexMinute)"
+ :style="getStyleByIndex(index, selectedIndexMinute)"
+ >
+ {{ m.toString().padStart(2, "0") }}
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch, onMounted, nextTick } from "vue";
+
+const props = defineProps<{ modelValue: string, disabled: boolean }>();
+const emit = defineEmits(["update:modelValue"]);
+
+const hours = Array.from({ length: 24 }, (_, i) => i);
+const minutes = Array.from({ length: 60 }, (_, i) => i);
+
+// 为循环滚动,前面后面都各补两个,要不要选不中最后一个
+function createLoopArray(arr: number[]) {
+ return [...arr.slice(-2), ...arr, ...arr.slice(0, 2)];
+}
+
+const loopHours = createLoopArray(hours);
+const loopMinutes = createLoopArray(minutes);
+
+const hourRef = ref<HTMLElement | null>(null);
+const minuteRef = ref<HTMLElement | null>(null);
+
+const ITEM_REM = 0.26;
+const itemHeight = remToPx(ITEM_REM);
+
+const selectedIndexHour = ref(2);
+const selectedIndexMinute = ref(2);
+
+const selectedHour = ref(hours[0]);
+const selectedMinute = ref(minutes[0]);
+
+// 标记程序主动滚动,防止滚动事件死循环
+const isProgrammaticScroll = {
+ hour: false,
+ minute: false,
+};
+
+// 初始化滚动,选中传入时间
+onMounted(() => {
+ const [h, m] = props.modelValue.split(":").map(Number);
+ const hourIdx = hours.indexOf(h);
+ const minuteIdx = minutes.indexOf(m);
+
+ selectedIndexHour.value = hourIdx === -1 ? 2 : hourIdx + 2;
+ selectedIndexMinute.value = minuteIdx === -1 ? 2 : minuteIdx + 2;
+
+ selectedHour.value = h;
+ selectedMinute.value = m;
+
+ nextTick(() => {
+ scrollToSelected("hour", selectedIndexHour.value);
+ scrollToSelected("minute", selectedIndexMinute.value);
+ });
+});
+
+watch(
+ () =>
+ `${selectedHour.value.toString().padStart(2, "0")}:${selectedMinute.value
+ .toString()
+ .padStart(2, "0")}`,
+ (val) => {
+ emit("update:modelValue", val);
+ }
+);
+
+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;
+ return;
+ }
+
+ const refEl = type === "hour" ? hourRef.value : minuteRef.value;
+ if (!refEl) return;
+
+ const scrollTop = refEl.scrollTop;
+ let index = Math.round(scrollTop / itemHeight + 2);
+
+ const arrLen = type === "hour" ? hours.length : minutes.length;
+
+ if (index < 2) {
+ isProgrammaticScroll[type] = true;
+ refEl.scrollTop = itemHeight * (arrLen + (index - 2));
+ index = arrLen + (index - 2);
+ } else if (index > arrLen + 1) {
+ isProgrammaticScroll[type] = true;
+ refEl.scrollTop = itemHeight * (index - arrLen - 2);
+ index = index - arrLen - 2;
+ }
+
+ if (type === "hour") {
+ selectedIndexHour.value = index;
+ selectedHour.value = loopHours[index];
+ } else {
+ selectedIndexMinute.value = index;
+ selectedMinute.value = loopMinutes[index];
+ }
+}
+
+function scrollToSelected(type: "hour" | "minute", index: number) {
+ const refEl = type === "hour" ? hourRef.value : minuteRef.value;
+ if (!refEl) return;
+
+ isProgrammaticScroll[type] = true;
+ refEl.scrollTo({
+ top: (index - 2) * itemHeight,
+ behavior: "auto",
+ });
+}
+
+function getClassByIndex(index: number, selectedIndex: number) {
+ const diff = Math.abs(index - selectedIndex);
+ return {
+ active: diff === 0,
+ medium: diff === 1,
+ small: diff === 2,
+ };
+}
+
+function getStyleByIndex(index: number, selectedIndex: number) {
+ const diff = Math.min(Math.abs(index - selectedIndex), 2);
+ return {
+ opacity: diff === 2 ? 0.4 : 1,
+ zIndex: 10 - diff,
+ transition: "opacity 0.3s ease",
+ };
+}
+
+function remToPx(rem: number) {
+ return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
+}
+</script>
+
+<style scoped lang="less">
+.time-picker {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 1.3rem; // 5 * 0.4rem 每个item高度0.4rem
+ overflow: hidden;
+
+ .picker-column {
+ height: 1.3rem;
+ width: 0.7rem;
+ overflow-y: scroll;
+ scroll-snap-type: y mandatory;
+ -webkit-overflow-scrolling: touch;
+ scrollbar-width: none; /* Firefox */
+ -ms-overflow-style: none; /* IE 10+ */
+ &::-webkit-scrollbar {
+ display: none; /* Chrome Safari */
+ }
+ &.disabled{
+ overflow: hidden;
+ }
+
+ .picker-item {
+ height: 0.26rem;
+ line-height: 0.26rem;
+ text-align: center;
+ 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.28rem;
+ font-weight: 700;
+ color: #111;
+ &.hours {
+ text-align: left;
+ }
+ &.minutes {
+ text-align: right;
+ }
+ }
+ &.medium {
+ font-size: 0.22rem;
+ color: #666;
+ &.hours {
+ text-align: left;
+ padding-left: 0.16rem;
+ }
+ &.minutes {
+ text-align: right;
+ padding-right: 0.16rem;
+ }
+ }
+ &.small {
+ font-size: 0.18rem;
+ color: #aaa;
+ }
+ }
+ }
+
+ .colon {
+ font-size: 0.28rem;
+ font-weight: 600;
+ color: #444;
+ user-select: none;
+ }
+}
+</style>
--
Gitblit v1.8.0