24格半格区间拖拽选择

张开发
2026/4/4 20:22:01 15 分钟阅读
24格半格区间拖拽选择
需求整体划分24个单元格最小单位为半格有五个不同的状态1同一状态下可以选择多个时间段点击选择半格拖拽选择区间2不同状态不可以选择同一区间选择区以不同颜色区分被其他状态选过的区间禁止选择相邻的区间选中自动合并点击取消选择当前区间表格与选择区数据同步实时变动可对不同的状态设置其数值部分代码时段选择区标签样式div classtime-grid-section !-- 时间轴00:00 ~ 24:00 -- div classtime-ticks div classtick v-forhour in 25 :keyhour {{ ${(hour - 1).toString().padStart(2, 0)}:00 }} /div /div !-- 5行时段选择每行包含格子区时段文字区 -- div classperiod-rows div classperiod-row v-for(row, rowIndex) in periodRows :keyrow.type !-- 左侧颜色标签 -- div classperiod-label :style{ backgroundColor: row.color } {{ row.label }} /div !-- 核心格子容器 时段文字容器 组合 -- div classgrid-and-text-wrap !-- 24个小时格子容器 -- div classhour-grid-container mousedownhandleMouseDown($event, rowIndex) mousemovehandleMouseMove($event, rowIndex) mouseuphandleMouseUp mouseleavehandleMouseUp !-- 24个小时格子每个包含2个30分钟半格 -- div classhour-grid v-forhour in 24 :keyhour div classhalf-grid v-forhalf in 2 :keyhalf :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 classselected-wrap v-for(period, pIndex) in mergedPeriods[rowIndex] :keyperiod.id :style{ left: ${((period.start - 1) / 48) * 100}%, width: ${((period.end - period.start 1) / 48) * 100}%, borderColor: row.color, backgroundColor: ${row.color}10 } click.stophandleCancelSelected(period, rowIndex)/div /div !-- 时段文字显示容器使用合并后的数据渲染与边框同步 -- div classperiod-text-container div classperiod-text v-forperiod in mergedPeriods[rowIndex] :keyperiod.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. 单半格startend结束时间取 start1 → 如1→11 → 00:00~00:302. 多格startend结束时间直接取 end1 → 如1~2→21 → 00:00~01:00索引映射100:00,200:30,301:00,401:30...4823:30,4924:00const formatPeriod (start, end) { // 通用时间计算索引 → 对应时间如1→00:002→00:3049→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} } // 单格startend → 结束时间start1 if (start end) { return ${getTimeByIndex(start)}~${getTimeByIndex(start 1)} } // 多格startend → 结束时间end1核心 return ${getTimeByIndex(start)}~${getTimeByIndex(end 1)} }mergedPeriods 计算属性 - 相邻/连续时段自动合并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 }) })

更多文章