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
  })
})
相关推荐
启观川8 小时前
数据结构与算法 -第 3 章 常用算法-动态规划
数据结构·笔记·python·算法
码爸8 小时前
排序算法介绍
python·算法·排序算法
动词ing9 小时前
【题目练习】算法 组合总和||
算法
longlongzihan9 小时前
链表找环:Floyd 判圈算法(LeetCode 141 & 142 )
c++·算法·leetcode·链表
专业程序开发源9 小时前
flask家电故障预测系统92491-计算机课程设计/毕业设计
vscode·python·sql·算法·flask·课程设计
Logic1019 小时前
C语言/数据结构欧几里得算法题解:长方形切割最大正方形——贪心划分计数
c语言·数据结构·算法·贪心算法·时间复杂度·空间复杂度·欧几里得算法
yi01110 小时前
LeetCode 643. 子数组最大平均数 I|从暴力枚举到滑动窗口
人工智能·笔记·python·算法·leetcode·滑动窗口
极光通讯10 小时前
服务器内存来料检验(IQC)实操:批次追溯、外观判定与上机验证
java·服务器·算法
淡海水10 小时前
02-04-原理篇-GC Roots与对象图遍历
算法·unity·c#·图·gc·遍历·roots
ClickHouseDB10 小时前
ClickHouse 大规模 OpenTelemetry 数据接入架构的可靠性演进
算法