24格半格区间拖拽选择

需求:整体划分24个单元格,最小单位为半格,,有五个不同的状态

(1)同一状态下可以选择多个时间段,点击选择半格,拖拽选择区间

(2)不同状态不可以选择同一区间,选择区以不同颜色区分,被其他状态选过的区间禁止选择

相邻的区间选中自动合并,点击取消选择当前区间

表格与选择区数据同步,实时变动,可对不同的状态设置其数值

部分代码

时段选择区标签样式

html 复制代码
    <div class="time-grid-section">
      <!-- 时间轴:00:00 ~ 24:00 -->
      <div class="time-ticks">
        <div class="tick" v-for="hour in 25" :key="hour">
          {{ `${(hour - 1).toString().padStart(2, '0')}:00` }}
        </div>
      </div>

      <!-- 5行时段选择:每行包含格子区+时段文字区 -->
      <div class="period-rows">
        <div class="period-row" v-for="(row, rowIndex) in periodRows" :key="row.type">
          <!-- 左侧颜色标签 -->
          <div class="period-label" :style="{ backgroundColor: row.color }">
            {{ row.label }}
          </div>

          <!-- 核心:格子容器 + 时段文字容器 组合 -->
          <div class="grid-and-text-wrap">
            <!-- 24个小时格子容器 -->
            <div class="hour-grid-container" @mousedown="handleMouseDown($event, rowIndex)"
              @mousemove="handleMouseMove($event, rowIndex)" @mouseup="handleMouseUp" @mouseleave="handleMouseUp">
              <!-- 24个小时格子,每个包含2个30分钟半格 -->
              <div class="hour-grid" v-for="hour in 24" :key="hour">
                <div class="half-grid" v-for="half in 2" :key="half" :data-index="(hour - 1) * 2 + half" :class="{
                  'temp-select': isTempSelect(rowIndex, (hour - 1) * 2 + half),
                  'conflict': isConflict(rowIndex, (hour - 1) * 2 + half)
                }"></div>
              </div>

              <!-- 选中区域整体外边框:使用合并后的数据渲染 -->
              <!-- 【仅新增】给选中边框绑定点击取消事件,stop阻止冒泡,不影响原有逻辑 -->
              <div class="selected-wrap" v-for="(period, pIndex) in mergedPeriods[rowIndex]" :key="period.id" :style="{
                left: `${((period.start - 1) / 48) * 100}%`,
                width: `${((period.end - period.start + 1) / 48) * 100}%`,
                borderColor: row.color,
                backgroundColor: `${row.color}10`
              }" @click.stop="handleCancelSelected(period, rowIndex)"></div>
            </div>

            <!-- 时段文字显示容器:使用合并后的数据渲染,与边框同步 -->
            <div class="period-text-container">
              <div class="period-text" v-for="period in mergedPeriods[rowIndex]" :key="period.id" :style="{
                color: row.color,
                left: `${((period.start - 1) / 48) * 100}%`,
                width: `${((period.end - period.start + 1) / 48) * 100}%`
              }">
                {{ formatPeriod(period.start, period.end) }}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

时段格式化核心逻辑

核心规则:

  1. 单半格(start=end):结束时间取 start+1 → 如1→1+1 → 00:00~00:30

  2. 多格(start<end):结束时间直接取 end+1 → 如1~2→2+1 → 00:00~01:00

索引映射:1=00:00,2=00:30,3=01:00,4=01:30...48=23:30,49=24:00

javascript 复制代码
const formatPeriod = (start, end) => {
  // 通用时间计算:索引 → 对应时间(如1→00:00,2→00:30,49→24:00)
  const getTimeByIndex = (index) => {
    const totalMinutes = (index - 1) * 30
    const hour = Math.floor(totalMinutes / 60).toString().padStart(2, '0')
    const minute = (totalMinutes % 60).toString().padStart(2, '0')
    return `${hour}:${minute}`
  }
  // 单格:start=end → 结束时间=start+1
  if (start === end) {
    return `${getTimeByIndex(start)}~${getTimeByIndex(start + 1)}`
  }
  // 多格:start<end → 结束时间=end+1(核心)
  return `${getTimeByIndex(start)}~${getTimeByIndex(end + 1)}`
}

mergedPeriods 计算属性 - 相邻/连续时段自动合并

javascript 复制代码
const mergedPeriods = computed(() => {
  // 强制转为纯数组,避免响应式包装器导致的类型问题
  const rows = [...periodRows]
  // 遍历每行,返回每行的合并后时段数组,最终得到 二维数组
  return rows.map(row => {
    // 空时段直接返回空数组,避免后续遍历报错
    if (!row.periods || row.periods.length === 0) return []
    // 1. 深拷贝并按start升序排序,避免修改原数据+保证合并顺序
    const sortedPeriods = JSON.parse(JSON.stringify(row.periods)).sort((a, b) => a.start - b.start)
    // 2. 初始化合并数组,放入第一个时段
    const merged = [sortedPeriods[0]]
    // 3. 遍历剩余时段,判断是否相邻/连续(核心合并逻辑)
    for (let i = 1; i < sortedPeriods.length; i++) {
      const lastMerged = merged[merged.length - 1]
      const current = sortedPeriods[i]
      // 相邻判断:当前时段start ≤ 上一时段end + 1(如1和2,完美匹配相邻半格)
      if (current.start <= lastMerged.end + 1) {
        // 合并时段:保留原属性,更新end为最大值,拼接唯一ID
        merged[merged.length - 1] = {
          ...lastMerged,
          end: Math.max(lastMerged.end, current.end),
          id: `${lastMerged.id}-${current.id}` // 保证v-for key唯一
        }
      } else {
        // 非相邻时段,直接添加
        merged.push(current)
      }
    }
    return merged
  })
})
相关推荐
daxi1502 小时前
C语言从入门到进阶——第18讲:内存函数
c语言·开发语言·算法
小辉同志2 小时前
17. 电话号码的字母组合
c++·算法·leetcode·深度优先
ytttr8732 小时前
MATLAB ViBe算法视频前景提取完整实现
算法·matlab·音视频
你撅嘴真丑2 小时前
和为给定数 与 最匹配的矩阵
c++·算法·矩阵
Book思议-2 小时前
【数据结构】二叉树小题
数据结构·算法
CoderCodingNo2 小时前
【GESP】C++五级练习题 luogu-P1303 A*B Problem | 高精度计算
数据结构·c++·算法
故事和你912 小时前
洛谷-算法1-1-模拟与高精度2
开发语言·数据结构·c++·算法·动态规划
B1acktion2 小时前
2.6.堆排序——从堆结构到 Top-K,一套思路贯穿排序与选择
数据结构·c++·算法·排序算法
17(无规则自律)2 小时前
【华为机考真题】魔法相册的重复记忆 C++ 实现
c++·算法·华为