vue3+ts 实现时间间隔选择器

  • 需求背景
  • 解决效果
  • 视频效果
  • 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]

解决效果

vue3+ts 实现时间间隔选择器_第1张图片

视频效果

时间间隔选择器

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>

你可能感兴趣的:(#,vue实践,javascript,vue.js,低代码)