vue3仿高德地图官网路况预测时间选择器

发布于:2025-06-20 ⋅ 阅读:(17) ⋅ 点赞:(0)

<template>
  <div class="time-axis-container">
    <div class="time-axis" ref="axisRef">
      <!-- 刻度线 - 共25个刻度(0-24) -->
      <div
          v-for="hour in 25"
          :key="hour - 1"
          class="tick-mark"
          :class="{
          'major-tick': isMajorTick(hour - 1),
          'active-tick': currentHour === hour - 1
        }"
          :style="{ left: `${(hour - 1) * (100 / 24)}%` }"
      ></div>

      <!-- 刻度文字 -->
      <div
          v-for="hour in 25"
          :key="`label-${hour - 1}`"
          class="tick-label"
          :style="{ left: `${(hour - 1) * (100 / 24)}%` }"
          v-show="isMajorTick(hour - 1)"
      >
        {{ hour - 1 }}:00
      </div>

      <!-- 可拖动滑块 -->
      <div
          class="slider"
          ref="sliderRef"
          :style="{ left: `${currentHour * (100 / 24)}%` }"
          @mousedown="startDrag"
          @touchstart="startDrag"
      >
        <div class="slider-time">{{ formatTime(currentHour) }}</div>
        <div class="slider-icon"></div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, defineExpose } from 'vue';

const props = defineProps<{
  initialHour?: number;
}>();

const emit = defineEmits<{
  (e: 'time-change', hour: number): void;
  (e: 'stopChange', hour: number): void;
}>();

const axisRef = ref<HTMLElement | null>(null);
const sliderRef = ref<HTMLElement | null>(null);
const currentHour = ref(0); // 先初始化为0,在onMounted中设置实际值
const isDragging = ref(false);
// 获取当前时间的小时数
const getCurrentHour = () => {
  const now = new Date();
  const currentMinutes = now.getMinutes();
  return currentMinutes > 0 ?
      Math.min(now.getHours() + 1, 24) :  // 确保不超过24
      now.getHours();
};

// 初始化当前小时
const initCurrentHour = () => {
  currentHour.value = props.initialHour !== undefined ?
      Math.max(0, Math.min(24, props.initialHour)) :
      getCurrentHour();
};

// 判断是否是主要刻度(0/4/8/12/16/20/24)
const isMajorTick = (hour: number) => hour % 4 === 0;

// 格式化时间显示
const formatTime = (hour: number) => {
  return `${hour.toString().padStart(2, '0')}:00`;
};

// 开始拖动
const startDrag = (e: MouseEvent | TouchEvent) => {
  e.preventDefault();
  isDragging.value = true;

  document.addEventListener('mousemove', handleDrag);
  document.addEventListener('touchmove', handleDrag);
  document.addEventListener('mouseup', stopDrag);
  document.addEventListener('touchend', stopDrag);
};
// 共用计算方法
const calculateHourFromEvent = (e: MouseEvent | TouchEvent, axisRect: DOMRect) => {
  let clientX;
  if (e instanceof MouseEvent) {
    clientX = e.clientX;
  } else {
    clientX = e.touches[0].clientX;
  }

  // 严格限制在轴范围内计算
  const position = Math.max(0, Math.min(1, (clientX - axisRect.left) / axisRect.width));
  return Math.round(position * 24);
};
// 处理拖动
const handleDrag = (e: MouseEvent | TouchEvent) => {
  if (!isDragging.value || !axisRef.value) return;

  const axisRect = axisRef.value.getBoundingClientRect();
  const newHour = calculateHourFromEvent(e, axisRect);

  if (newHour !== currentHour.value) {
    currentHour.value = newHour;
    emit('time-change', newHour);
  }
};

// 停止拖动
const stopDrag = () => {
  isDragging.value = false;
  emit('stopChange', currentHour.value);
  document.removeEventListener('mousemove', handleDrag);
  document.removeEventListener('touchmove', handleDrag);
  document.removeEventListener('mouseup', stopDrag);
  document.removeEventListener('touchend', stopDrag);
};

// 点击时间轴直接跳转
const handleAxisClick = (e: MouseEvent) => {
  if (!axisRef.value || isDragging.value) return;

  const axisRect = axisRef.value.getBoundingClientRect();
  // 添加点击位置的安全边距检查
  if (e.clientX < axisRect.left || e.clientX > axisRect.right) return;

  const newHour = calculateHourFromEvent(e, axisRect);

  if (newHour !== currentHour.value) {
    currentHour.value = newHour;
    emit('time-change', newHour);
  }
};

defineExpose({
  getCurrentHour
})

onMounted(() => {
  initCurrentHour()
  if (axisRef.value) {
    axisRef.value.addEventListener('click', handleAxisClick);
  }
});

onUnmounted(() => {
  if (axisRef.value) {
    axisRef.value.removeEventListener('click', handleAxisClick);
  }
  stopDrag();
});
</script>

<style scoped>
.time-axis-container {
  width: 100%;
  padding: 20px 0;
  position: relative;
}

.time-axis {
  position: relative;
  height: 20px;
  width: 100%;
  border-radius: 4px;
  border-bottom: 2px solid #FFFFFF;
  cursor: pointer;
}

.tick-mark {
  position: absolute;
  bottom: 0;
  width: 1px;
  height: 6px;
  background-color: #FFFFFF;
  transform: translateX(-50%);
}

.tick-mark.major-tick {
  height: 12px;
  background-color: #FFFFFF;
}

/*.tick-mark.active-tick {*/
/*  background-color: #ff3d00;*/
/*}*/

.tick-label {
  position: absolute;
  bottom: -36px;
  transform: translateX(-50%);
  font-size: 20px;
  color: #FFFFFF;
}

.slider {
  position: absolute;
  top: -36px;
  transform: translateX(-50%);
  border-radius: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: grab;
  user-select: none;
  z-index: 10;
}

.slider:active {
  cursor: grabbing;
}

.slider-icon {
  width: 32px;
  height: 38px;
  background-image: url("/src/assets/realTimeScheduling/ranged-thumb.png");
  background-repeat: no-repeat;
  background-size: 32px 38px;
  background-position: center center;
}

.slider-time {
  font-size: 24px;
  color: white;
}
</style>


网站公告

今日签到

点亮在社区的每一天
去签到