首页 前端知识 Vue3时间轴组件(时间信息收集组件)

Vue3时间轴组件(时间信息收集组件)

2024-11-04 10:11:51 前端知识 前端哥 1012 659 我要收藏

背景

最近公司要求新增一个大屏页面,需要封装一个时间信息收集组件。通过双向绑定响应式数据,父组件监听子组件对数据的操作。最终将信息作为接口参数,请求相应的数据,渲染至页面上。

技术栈

  • Vue3(3.2.45)
  • Element-Plus(2.2.27)

  • dayjs(^1.11.7)

效果

其中时间轴是对Element Plus组件库中的Slider滑块组件的二次封装,使用样式穿透自定义样式,同时大家可以把onMounted生命周期中的代码取消注释,可以做更多的操作。我的另一篇文章也是时间轴组件的封装,不过两者略有区别,所以又单独记录一下。

代码

父组件关键代码

<!-- 时间选择 表单 -->
<h1 class="titleH2">     
    {{ titleTime[0] }} - {{ titleTime[1] }} <br />       
</h1>
<div class="contentCenter_bottom">
    <TimeForm v-model:up="objInfo" v-model:tt="titleTime"></TimeForm>
</div>

<script setup>

// 双向绑定 obj
let objInfo = ref(null);
// 双向绑定 标题时间
let titleTime = ref(null);

// 监听时间表单变化 - 针对左右两侧的数据
watch(objInfo, (newVal) => {
  console.log(newVal, "父组件监听到变化");
});

</script>

时间收集组件代码

关键变量介绍

这些变量实际可以对外暴露出去,也就是让父组件去定义,让组件更具灵活性。

  • optionsHour :每日小时节点配置项(可自行灵活配置)
  • optionsDay :每周天数配置项(可自行灵活配置)
  • LastDay :时间轴根据当前时间该变量往前推多少天,之后for循环会利用该变量为marks赋值,并求出步进值stepValue 
<template>
  <div id="TimeForm">
    <div class="top">
      <div class="topContent">
        <div class="topContent_left">
          <el-button
            v-for="(item, index) in optionsHour"
            :key="index"
            size="small"
            :type="indHour == index ? 'primary' : ''"
            style="
              font-family: 'DIGIB';
              font-size: 15px;
              border-radius: 6px 6px 0px 0px;
            "
            @click="handleHour(index)"
            >{{ item }}</el-button
          >
        </div>
        <el-divider direction="vertical" style="height: 80%" />
        <div class="topContent_right">
          <el-button
            v-for="(item, index) in optionsDay"
            :key="index"
            size="small"
            :type="indDay == index ? 'primary' : ''"
            style="
              font-family: 'DIGIB';
              letter-spacing: 1px;
              font-size: 15px;
              border-radius: 6px 6px 0px 0px;
              padding-left: 7px;
              padding-right: 7px;
            "
            @click="handleDay(index)"
            >{{ item }}</el-button
          >
        </div>
      </div>
    </div>
    <div class="bottom">
      <el-slider
        v-model="value"
        :marks="marks"
        :step="stepValue"
        :show-tooltip="true"
        :format-tooltip="handleFormatTooltip"
      />
    </div>
  </div>
</template>

<script setup>
// vue
import {
  ref,
  reactive,
  watch,
  watchEffect,
  computed,
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  defineProps,
  defineEmits,
} from "vue";

// 时间处理
import dayjs from "dayjs";

// 宏
let props = defineProps(["up", "tt"]);
const emits = defineEmits(["update:up", "update:tt"]);

// 定义变量
let optionsHour = reactive(["2点", "8点", "14点", "20点"]);
let optionsDay = reactive([
  "过去1天",
  "过去2天",
  "过去3天",
  "过去4天",
  "过去5天",
  "过去6天",
  "过去7天",
]);
// 高亮
let indHour = ref(0);
let indDay = ref(0);

// 步长
let stepValue = ref(0);
// slider 日期往前推多少天
let LastDay = ref(7);
// 当前时间
let NowTime = new Date().getTime();
// slider 值
const value = ref(0);
// 标记
const marks = reactive({});

// 方法
function GetOneWeek() {

  // 为marks赋值
  for (let i = LastDay.value, j = 0; i >= 0; i--, j++) {
    let time = new Date(NowTime - i * 24 * 60 * 60 * 1000).getTime();
    let t = dayjs(time).format("YYYY-MM-DD");
    let num = Number(((100 * j) / LastDay.value).toFixed(1));
    // console.log(num);
    marks[num] = `${t}`;
    // console.log(i, j, "ij");
  }

  // 步进值
  stepValue.value = 100 / LastDay.value;
}

GetOneWeek();

// 函数
// 高亮
function handleHour(val) {
  indHour.value = val;
}
function handleDay(val) {
  indDay.value = val;
}

// 自定义 提示信息
function handleFormatTooltip(val) {
  let num = Number(val.toFixed(1));
  return marks[num];
}

// 监听
// watch([indHour, indDay, value],([newHour,newDay,newValue],[oldHour,oldDay,oldValue])=>{
//     console.log(newHour,newDay,newValue);
//     emits("update:objInfo", !props.objInfo);
// })

watchEffect(() => {
  let hour, endTime, hourMinuteSecond;

  let num = Number(value.value.toFixed(1));

  switch (indHour.value) {
    case 0:
      hourMinuteSecond = 2;
      break;
    case 1:
      hourMinuteSecond = 8;
      break;
    case 2:
      hourMinuteSecond = 14;
      break;
    case 3:
      hourMinuteSecond = 20;
      break;
  }

  switch (indDay.value) {
    case 0:
      hour = (indDay.value + 1) * 24;
      break;
    case 1:
      hour = (indDay.value + 1) * 24;
      break;
    case 2:
      hour = (indDay.value + 1) * 24;
      break;
    case 3:
      hour = (indDay.value + 1) * 24;
      break;
    case 4:
      hour = (indDay.value + 1) * 24;
      break;
    case 5:
      hour = (indDay.value + 1) * 24;
      break;
    case 6:
      hour = (indDay.value + 1) * 24;
      break;
  }
  // console.log(marks,value.value);

  endTime = marks[num];

  let obj = {
    hour: hour,
    hourMinuteSecond: hourMinuteSecond,
    endTime: endTime,
  };

  // console.log(marks[0],marks[100]);
  let timeStart = marks[0].replace(/-/, '年').replace(/-/, '月') + '日';
  let timeEnd = marks[100].replace(/-/, '年').replace(/-/, '月') + '日';
  console.log(timeStart);
  
  let timeObj = [timeStart,timeEnd]

  emits("update:up", obj);
  emits("update:tt", timeObj);
});

// 生命周期
onBeforeMount(() => {});

onMounted(() => {
  // 无需删除这段注释 或许日后有用
  //   document.querySelector(".el-slider__bar").innerHTML = `
  //   <div
  //   style="width:50px; height:24px;background:#ccc; position: absolute;right: 0px;top: -34px;transform: translateX(50%); display: flex; justify-content: space-between;"
  //   >
  //     <span id='span1'>1</span><span id='span2'>2</span>
  //   </div>
  //   `;
  //   document.querySelector(".bottom").addEventListener("click", (e) => {
  //     if (e.target.id == "span1") {
  //       console.log("实况");
  //     } else if (e.target.id == "span2") {
  //       console.log("预报");
  //     }
  //   });
});
</script>

<style lang="scss" scoped>
// 变量
$timeform-height: 110px;
$top-height: 30px;
#TimeForm {
  width: 100%;
  min-height: $timeform-height;
  max-height: $timeform-height;

  box-sizing: border-box;

  //   border: 1px dashed rgba(255, 255, 255, 0.5);

  // background-color: white;

  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;

  user-select: none;

  .top {
    width: 100%;
    height: $top-height;

    padding: 0px 20px;

    .topContent {
      width: auto;
      height: 100%;

      border-radius: 3px 3px 0px 0px;

      backdrop-filter: blur(10px); /* 背景模糊效果 */
      background-color: rgba(255, 255, 255, 0.5);

      display: flex;
      justify-content: flex-start;
      align-items: end;

      padding: 0px 10px;

      .topContent_left {
        width: 260px;
        height: 80%;

        display: flex;
        justify-content: space-around;
        align-items: end;
      }

      .topContent_right {
        width: auto;
        height: 80%;

        display: flex;
        justify-content: space-around;
        align-items: end;
        flex-wrap: wrap;
      }
    }
  }

  .bottom {
    width: 100%;
    height: calc($timeform-height - $top-height);
    background-color: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(10px); /* 背景模糊效果 */

    display: flex;
    justify-content: center;
    align-items: center;

    padding: 0px 45px;
  }

  :deep(.bottom .el-slider__stop) {
    width: 2px;
    height: 14px;

    background-color: #005edc;

    border-radius: 0px;
  }

  :deep(.bottom .el-slider__bar) {
    background-color: transparent;
    // position: relative;
  }

  :deep(.bottom .el-slider__marks-text) {
    background-color: #e0eaf8;
    color: #626f80;
    padding-left: 2px;
    padding-right: 2px;
  }

  //   :deep(.bottom .el-slider__button-wrapper::before){
  //     position: absolute;
  //     right: 0px;
  //     top: -17px;
  //     transform: translateX(calc(50% - 18px));
  //     display: inline-block;
  //     content: '123';
  //     width: 60px;
  //     height: 20px;
  //     background: #005edc;

  //     z-index: 9999999;
  //   }
}
</style>

所遇问题

1.由于marks(取值范围在闭区间0-100)的赋值是根据LastDay 变量计算出来的,具体来说是这段代码:

Number(((100 * j) / LastDay.value).toFixed(1));(最初是没有.toFixed(1))

这导致计算有可能存在很长的小数,由于小数的存在,又使得watchEffect当中的

let num = Number(value.value.toFixed(1));endTime = marks[num];(最初没有.toFixed(1))

有可能匹配不上返回 undefined。

也有尝试使用Math中的四舍五入或向上/下取整解决,但这又导致滑块对应不上步进值,最终采取了.toFixed(1)解决该问题。(.toFixed()返回值是字符串)

2.代码中的v-model绑定组件必须采用别名方式,否则无效。

转载请注明出处或者链接地址:https://www.qianduange.cn//article/19946.html
标签
评论
发布的文章
大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!