- 需求背景
- 解决效果
- 视频效果
- balancedTimeElement.vue
需求背景
实现一个分片的时间间隔选择器,需要把显示时间段显示成图表,涉及一下集中数据转换
- [“02:30-05:30”,“07:30-10:30”,“14:30-17:30”]
- ‘[(2,5),(7,10),(14,17)]’
- [4, 5, 6, 7, 8, 9, 10, 14, 15, 16, 17, 18, 19, 20, 28, 29, 30, 31, 32, 33, 34]
解决效果
视频效果
时间间隔选择器
balancedTimeElement.vue
<!--/**
* @author: liuk
* @date: 2023/11/28
* @describe: 时间间隔选择器
* @CSDN:https://blog.csdn.net/hr_beginner?type=blog
*/-->
<template>
<div>
<div class="hours-container">
<div class="hours-item-header-box">
<div class="hours-item-header" v-for="(_, i) in hours.slice(0,24)" :key="i">{{
(i + 1 + '').padStart(2, 0)
}}
</div>
</div>
<div class="hours-item-box" ref="hoursItemRef">
<template v-for="(_, i) in hours" :key="i">
<div class="hours-item" :class="compClass(i)" @click="handleClick(i)" @mouseover="handleHover(i)"></div>
</template>
</div>
</div>
<div class="tips">提示: 向右选中,向左取消选择</div>
</div>
</template>
<script lang="ts" setup>
import {reactive, toRefs, ref, watch} from "vue";
// Props
const props = defineProps(['manual_period'])
// Emits
const emit = defineEmits(['data-passed'])
// Ref
const hoursItemRef = ref(null)
type numOrstr = number | string
type arrOrstr = any[] | string
interface ModelType {
hours: number[]
selectStart: boolean
startIndex: numOrstr
timeRangeList: string[]
timeRangeListIndex: numOrstr[]
tempRangeIndex: number[]
tips: arrOrstr
}
const model: ModelType = reactive({
hours: new Array(48).fill('').map((_, i) => i),
selectStart: false,// 开始
startIndex: '',// 开始下标
timeRangeList: [],// 选择的时间段
timeRangeListIndex: [],// 选中的下标
tempRangeIndex: [],// 预选下标
tips: '',
})
const {
hours,
selectStart,
startIndex,
timeRangeList,
timeRangeListIndex,
tempRangeIndex,
tips,
} = toRefs(model)
watch(() => props.manual_period, (data) => {//'[(2,5),(7,10),(14,17)]'
const str = data.replace(/\(|\)/g, (val) => { // '[[2,5],[7,10],[14,17]]'
switch (val) {
case "(":
return "["
case ")":
return "]"
}
})
model.timeRangeListIndex = JSON.parse(str).map(item => {
const [x, y] = item
return new Array(2 * y - 2 * x + 1).fill(0).map((_, i) => i + 2 * x)
}).flat()// [4, 5, 6, 7, 8, 9, 10, 14, 15, 16, 17, 18, 19, 20, 28, 29, 30, 31, 32, 33, 34]
Array.from(hoursItemRef.value.children).forEach((dom: HTMLDivElement, index) => {
if (model.timeRangeListIndex.includes(index)) {
dom.className += ' selected'
}
})
})
// 下标区间转换成时间区间
const transformedSection = () => {
model.timeRangeList = [];
let startTime = '', endTime = '', len = model.hours.length;
for (let index = model.hours[0] * 2; index < 2 * (len + 1); index++) {
if (model.timeRangeListIndex.indexOf(index) > -1) {
if (startTime) {// 如果有开始时间,直接确定结束时间
let endHour = Math.floor((index + 1) / 2);
let endMin = (index + 1) % 2 === 0 ? "00" : "30";
endTime = `${endHour < 10 ? '0' + endHour : endHour}:${endMin}`;
} else {// 没有开始时间,确定当前点为开始时间
let startHour = Math.floor(index / 2);
let startMin = index % 2 === 0 ? "00" : "30";
startTime = `${startHour < 10 ? '0' + startHour : startHour}:${startMin}`;
}
if (index === 2 * model.hours.length + 1) { // 如果是最后一格,直接结束
endTime = `${Math.floor((index + 1) / 2)}:00`;
model.timeRangeList.push(`${startTime ? startTime : "23:30"}-${endTime}`);
startTime = '';
endTime = '';
}
} else { // 若这个点不在选择区间,确定一个时间段
if (startTime && endTime) {
model.timeRangeList.push(`${startTime}-${endTime}`);
startTime = '';
endTime = '';
} else if (startTime && !endTime) {// 这里可能只选半个小时
let endHour = Math.floor(index / 2);
let endMin = index % 2 === 0 ? "00" : "30";
endTime = `${endHour < 10 ? '0' + endHour : endHour}:${endMin}`;
model.timeRangeList.push(`${startTime}-${endTime}`);
startTime = '';
endTime = '';
}
}
}
model.tips = model.timeRangeList && model.timeRangeList.length > 0 ? model.timeRangeList : '';
emit('data-passed', model.tips);
}
// 点击事件
const handleClick = (index) => {
if (model.selectStart) {
if (index === model.startIndex) {// 双击取反
if (model.timeRangeListIndex.indexOf(index) > -1) {
model.timeRangeListIndex.splice(model.timeRangeListIndex.indexOf(index), 1);
} else {
model.timeRangeListIndex.push(model.startIndex);
}
} else if (index > model.startIndex) {// 选取数据--向右添加,向左取消
while (index >= model.startIndex) {
model.timeRangeListIndex.push(model.startIndex);
model.startIndex = +model.startIndex + 1;
}
model.timeRangeListIndex = Array.from(new Set(model.timeRangeListIndex));
} else {// 删除数据
while (model.startIndex >= index) {
if (model.timeRangeListIndex.indexOf(index) > -1) {
model.timeRangeListIndex.splice(model.timeRangeListIndex.indexOf(index), 1);
}
index++;
}
}
model.startIndex = '';
transformedSection();
model.tempRangeIndex = [];
} else {
model.startIndex = index;
}
model.selectStart = !model.selectStart;
}
// 预选区间
const handleHover = (index) => {
if (model.selectStart) {
model.tempRangeIndex = [];
if (index > model.startIndex) {// 选取数据--向右添加,向左取消
while (index >= model.startIndex) {
model.tempRangeIndex.push(index);
index--;
}
} else {// 删除数据
while (model.startIndex >= index) {
model.tempRangeIndex.push(index);
index++;
}
}
}
}
// 是否选中,计算className
const compClass = (index) => {
if (index === model.startIndex) {
return 'hours-item-left preSelected';
}
if (index >= model.startIndex) {
if (model.tempRangeIndex.indexOf(index) > -1) {
return 'hours-item-left preSelected';
}
} else {
if (model.tempRangeIndex.indexOf(index) > -1) {
return 'hours-item-left unSelected';
}
}
return model.timeRangeListIndex.indexOf(index) > -1 ? 'hours-item-left selected' : 'hours-item-left';
}
</script>
<style lang="scss" scoped>
.hours-container {
cursor: pointer;
color: slategray;
.hours-item-header-box {
display: flex;
width: 100%;
height: 30px;
.hours-item-header {
width: 30px;
height: 30px;
text-align: center;
box-sizing: border-box;
line-height: 30px;
border: 1px solid #5a5a5a;
border-left: none;
border-bottom: none;
&:first-child {
border-left: 1px solid #5a5a5a;
}
}
}
.hours-item-box {
display: flex;
width: 100%;
.hours-item {
width: 15px;
height: 30px;
border: 1px solid #474747;
box-sizing: border-box;
&.selected {
background-color: #ffbf00 !important;
border-bottom: 1px solid #c2d0f3;
}
&.preSelected {
background-color: rgb(255, 191, 0);
border-bottom: 1px solid #c2d0f3;
}
&.unSelected {
background-color: #ffffff;
border-bottom: 1px solid #c2d0f3;
}
}
}
}
.tips {
width: 100%;
line-height: 30px;
margin-top: 10px;
}
</style>