<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>精确时间刻度选择器(10分钟刻度)可点击拖拽</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      font-family: 'Segoe UI', Arial, sans-serif;
    }
    body {
      background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
      min-height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      padding: 20px;
    }
    .container {
      width: 100%;
      min-width: 800px;
      background: white;
      border-radius: 12px;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
      padding: 30px;
    }
    .header {
      text-align: center;
      margin-bottom: 30px;
    }
    .header h1 {
      color: #2c3e50;
      font-size: 28px;
      margin-bottom: 8px;
    }
    .header p {
      color: #7f8c8d;
      font-size: 16px;
    }
    .date-display {
      text-align: center;
      font-size: 18px;
      color: #3498db;
      margin-bottom: 20px;
      font-weight: 600;
    }
    .timeline-container {
      position: relative;
      margin: 40px 0;
      padding: 20px 0;
      width: 100%;
    }
    .timeline-axis {
      position: relative;
      height: 4px;
      background: #48db54;
      border-radius: 2px; 
    }
    .timeline-ticks {
      display: flex;
      justify-content: space-between;
      position: relative;
      top: -10px;
    }
    .tick {
      position: relative;
      text-align: center;
      flex: 1;
      min-width: 0;
    }
    .tick.major::before {
      content: '';
      position: absolute;
      width: 2px;
      height: 15px;
      background: #3498db;
      top: -19px;
      left: 50%;
      transform: translateX(-50%);
    }
    .tick.minor::before {
      content: '';
      position: absolute;
      width: 1px;
      height: 8px;
      background: #3498db;
      top: -12px;
      left: 50%;
      transform: translateX(-50%);
    }
    .tick-label {
      position: absolute;
      top: -35px;
      left: 50%;
      transform: translateX(-50%);
      font-size: 12px;
      color: #2c3e50;
      white-space: nowrap;
    }
    .marker {
      position: absolute;
      bottom: 0;
      transform: translateX(-50%);
      cursor: grab;
      z-index: 10;
    }
    .marker:active {
      cursor: grabbing;
    }
    .marker-triangle {
      width: 0;
      height: 0;
      border-left: 10px solid transparent;
      border-right: 10px solid transparent;
      border-bottom: 16px solid #e74c3c;
      border-top: none;
      position: absolute;
      bottom: 0;
      left: 50%;
      transform: translateX(-50%);
    }
    .marker-line {
      position: absolute;
      bottom: 16px;
      left: 50%;
      width: 2px;
      height: 50px;
      background: #e74c3c;
      transform: translateX(-50%);
    }
    .marker-label {
      position: absolute;
      bottom: 70px;
      left: 50%;
      transform: translateX(-50%);
      background: #e74c3c;
      color: white;
      padding: 3px 6px;
      border-radius: 4px;
      font-size: 12px;
      white-space: nowrap;
    }
    .range-highlight {
      position: absolute;
      height: 8px;
      background: rgba(52, 152, 219, 0.3);
      top: 50%;
      transform: translateY(-50%);
      border-radius: 4px;
      z-index: 5;
    }
    .range-background {
      position: absolute;
      height: 66px;
      background: rgba(52, 152, 219, 0.1);
      bottom: 0;
      z-index: 1;
      border-radius: 4px;
    }
    .controls {
      display: flex;
      justify-content: center;
      gap: 15px;
      margin-top: 30px;
    }
    button {
      padding: 8px 16px;
      background: #3498db;
      color: white;
      border: none;
      border-radius: 6px;
      cursor: pointer;
      font-size: 14px;
      transition: all 0.3s;
    }
    button:hover {
      background: #2980b9;
      transform: translateY(-2px);
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="container">
      <div class="header">
        <h1>精确时间刻度选择器(10分钟刻度)</h1>
        <p>拖动三角形标记选择时间范围</p>
      </div>
      
      <div class="date-display">{{ currentDate }}</div>
      
      <div class="timeline-container">
        <div class="timeline-axis">
          <div 
            class="range-background" 
            :style="{
              left: `${rangeStartPercent}%`,
              width: `${rangeEndPercent - rangeStartPercent}%`
            }"
          ></div>
          <div 
            class="range-highlight" 
            :style="{
              left: `${rangeStartPercent}%`,
              width: `${rangeEndPercent - rangeStartPercent}%`
            }"
          ></div>
        </div>
        
        <div class="timeline-ticks">
          <div 
            class="tick" 
            :class="isMajorTick(tick) ? 'major' : 'minor'" 
            v-for="tick in ticks" 
            :key="tick"
            @click="handleTickClick(tick)"
          >
            <span class="tick-label" v-if="isMajorTick(tick)">{{ formatTime(tick) }}</span>
          </div>
        </div>
        
        <!-- 开始标记 -->
        <div 
          class="marker" 
          :style="{ left: `${rangeStartPercent}%` }"
          @mousedown="startDrag('start', $event)"
          @touchstart="startDrag('start', $event)"
        >
          <div class="marker-triangle"></div>
          <div class="marker-line"></div>
          <div class="marker-label">{{ formatTime(rangeStart) }}</div>
        </div>
        
        <!-- 结束标记 -->
        <div 
          class="marker" 
          :style="{ left: `${rangeEndPercent}%` }"
          @mousedown="startDrag('end', $event)"
          @touchstart="startDrag('end', $event)"
        >
          <div class="marker-triangle"></div>
          <div class="marker-line"></div>
          <div class="marker-label">{{ formatTime(rangeEnd) }}</div>
        </div>
      </div>
      
      <div class="controls">
        <button @click="setDefaultRange">重置范围</button>
        <button @click="setRandomRange">随机范围</button>
      </div>
    </div>
  </div>

  <script>
    const { createApp, ref, computed } = Vue;
    
    createApp({
      setup() {
        // 初始数据
        const currentDate = ref('2025年10月9日');
        const minTime = 0;
        const maxTime = 24 * 60;
        const rangeStart = ref(8 * 60); // 08:00
        const rangeEnd = ref(18 * 60);  // 18:00
        const lastClickTime = ref(0);
        const lastClickType = ref(null);
        
        // 刻度生成 - 每10分钟一个刻度
        const ticks = computed(() => {
          const result = [];
          for (let i = 0; i <= 24 * 60; i += 10) {
            result.push(i);
          }
          return result;
        });
        
        // 主要刻度 - 每小时一个主要刻度
        const isMajorTick = (minutes) => {
          return minutes % 60 === 0;
        };
        
        // 精确计算百分比位置
        const calculatePosition = (minutes) => {
          const totalMinutes = maxTime - minTime;
          const percentage = (minutes - minTime) / totalMinutes;
          return percentage * 100;
        };
        
        const rangeStartPercent = computed(() => calculatePosition(rangeStart.value));
        const rangeEndPercent = computed(() => calculatePosition(rangeEnd.value));
        
        // 格式化时间显示
        const formatTime = (minutes) => {
          const hours = Math.floor(minutes / 60);
          const mins = minutes % 60;
          return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}`;
        };
        
        // 重置范围
        const setDefaultRange = () => {
          rangeStart.value = 8 * 60;
          rangeEnd.value = 18 * 60;
        };
        
        // 随机范围
        const setRandomRange = () => {
          const start = Math.floor(Math.random() * (22 * 60 - 0 * 60)) + 0 * 60;
          const end = Math.min(maxTime, start + Math.floor(Math.random() * 240) + 60);
          rangeStart.value = start;
          rangeEnd.value = end;
        };

        // 处理刻度点击
        const handleTickClick = (tick) => {
          const now = Date.now();
          
          // 判断是否为双击
          if (now - lastClickTime.value < 300 && lastClickType.value === 'start') {
            // 双击设置结束时间
            rangeEnd.value = tick;
            lastClickType.value = 'end';
          } else {
            // 单击设置开始时间
            rangeStart.value = tick;
            lastClickType.value = 'start';
          }
          
          lastClickTime.value = now;
          
          // 确保开始时间不超过结束时间
          if (rangeStart.value > rangeEnd.value) {
            [rangeStart.value, rangeEnd.value] = [rangeEnd.value, rangeStart.value];
          }
        };
        
        // 拖拽逻辑
        let dragging = null;
        let startX = 0;
        let startValue = 0;
        
        const startDrag = (type, event) => {
          dragging = type;
          startX = event.type.includes('touch') ? event.touches[0].clientX : event.clientX;
          startValue = type === 'start' ? rangeStart.value : rangeEnd.value;
          
          document.addEventListener('mousemove', onDrag);
          document.addEventListener('touchmove', onDrag, { passive: false });
          document.addEventListener('mouseup', stopDrag);
          document.addEventListener('touchend', stopDrag);
          
          if (event.type.includes('touch')) {
            event.preventDefault();
          }
        };
        
        const onDrag = (event) => {
          if (!dragging) return;
          
          event.preventDefault();
          const clientX = event.type.includes('touch') ? event.touches[0].clientX : event.clientX;
          const timeline = document.querySelector('.timeline-axis');
          const rect = timeline.getBoundingClientRect();
          
          // 修正后的位置计算
          const position = (clientX - rect.left) / rect.width;
          const timeValue = Math.round((minTime + position * (maxTime - minTime)) / 10) * 10;
          
          if (dragging === 'start') {
            rangeStart.value = Math.max(minTime, Math.min(timeValue, rangeEnd.value - 10));
          } else {
            rangeEnd.value = Math.min(maxTime, Math.max(timeValue, rangeStart.value + 10));
          }
        };
        
        const stopDrag = () => {
          dragging = null;
          document.removeEventListener('mousemove', onDrag);
          document.removeEventListener('touchmove', onDrag);
          document.removeEventListener('mouseup', stopDrag);
          document.removeEventListener('touchend', stopDrag);
        };
        
        return {
          currentDate,
          rangeStart,
          rangeEnd,
          ticks,
          isMajorTick,
          rangeStartPercent,
          rangeEndPercent,
          formatTime,
          setDefaultRange,
          setRandomRange,
          startDrag,
          handleTickClick
        };
      }
    }).mount('#app');
  </script>
</body>
</html>

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐