vue2一天时间段选择库(drag-weektime)代码学习

Posted 胖鹅68

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2一天时间段选择库(drag-weektime)代码学习相关的知识,希望对你有一定的参考价值。

文章目录

一、参考

  1. 今日头条选择时间段组件封装(开箱即用)
  2. andt-components github

二、问题描述

看着这个组件可以选择时间段范围,而且又选中表格的过程,好奇心想了解实现过程

三、原理说明

  1. 将一天24小时分为48个半个小时,一周七天,按照这个逻辑就知道每个单元格的坐标

  2. 每个时间单元格添加 mouseenter 、 mousedown 、 mouseup 事件

  3. 鼠标任意选中一个节点触发 mousedown事件,鼠标移动到最终节点 触发 mouseUp 时间,就知道起始单元格和最终单元格

  4. 利用 mouseenter 事件 就可以计算出选中区域的 起始点的坐标 和 距离最终点的坐标的 width(宽度) 和 height(高度),画出用户选择的范围(实际上是一个绝对定位的范围,在表格的上面)

  5. 为了让选择范围的过程不那么突兀,利用了 css3的过渡补间动画 transition 就可以让样式的变动产生柔和动画效果

这个动画时间不能太短:就没有选中过程了,只有起始点到终点的跳跃,用户感受不好
这个动画时间不能太长: 用户选择完全,动画还没有完成

四、代码过程解析

  1. 引用组件
<template>
  <div class="about">
    <drag-weektime
        v-model="mult_timeRange"
        :data="weektimeData"
        @on-clear="clearWeektime">
      </drag-weektime>
</div>
</template>
<script>
import moment from 'moment'
import weektimeData from '@/data/weektime_data'
import DragWeektime from '@/components/drag-weektime'

function splicing (list) 
  let same
  let i = -1
  let len = list.length
  let arr = []

  if (!len) return
  while (++i < len) 
    const item = list[i]
    if (item.check) 
      if (item.check !== Boolean(same)) 
        arr.push(...['、', item.begin, '~', item.end])
       else if (arr.length) 
        arr.pop()
        arr.push(item.end)
      
    
    same = Boolean(item.check)
  
  arr.shift()
  return arr.join('')


export default 
  name: 'About',
  components:  DragWeektime , //, DatePicker
  computed: 
    mult_timeRange () 
      return this.weektimeData.map(item => 
        return 
          id: item.row,
          week: item.value,
          value: splicing(item.child)
        
      )
    
  ,
  data () 
    return 
      weektimeData: weektimeData
    
  ,
  methods: 
    moment,
    // 清空时间段
    clearWeektime () 
      this.weektimeData.forEach(item => 
        item.child.forEach(t => 
          this.$set(t, 'check', false)
        )
      )
    
  

</script>
  1. drag-weektime 组件内容
<template>
  <div class="c-weektime">
    <div class="c-schedue"></div>
    <!-- mode 为1 表示正在选中, 'c-schedue-notransi' 样式就是选择淡蓝色 框的 -->
    <div :class="'c-schedue': true, 'c-schedue-notransi': mode" :style="styleValue"></div>

    <table class="c-weektime-table" :class="'c-min-table': colspan < 2">
      <thead class="c-weektime-head">
        <tr>
          <th rowspan="8" class="week-td">星期/时间</th>
          <th :colspan="12 * colspan">00:00 - 12:00</th>
          <th :colspan="12 * colspan">12:00 - 24:00</th>
        </tr>
        <tr>
          <td v-for="t in theadArr" :key="t" :colspan="colspan">t</td>
        </tr>
      </thead>
      <tbody class="c-weektime-body">
        <tr v-for="t in data" :key="t.row">
          <td>t.value</td>
          <td
            v-for="n in t.child"
            :key="`$n.row-$n.col`"
            :data-week="n.row"
            :data-time="n.col"
            @mouseenter="cellEnter(n)"
            @mousedown="cellDown(n)"
            @mouseup="cellUp(n)"
            :class="selectClasses(n)"
            class="weektime-atom-item">
          </td>
        </tr>
        <tr>
          <td colspan="49" class="c-weektime-preview">
            <div class="g-clearfix c-weektime-con">
              <span class="g-pull-left">selectState ? '已选择时间段' : '可拖动鼠标选择时间段'</span>
              <a class="g-pull-right" @click.prevent="$emit('on-clear')">清空选择</a>
            </div>
            <div v-if="selectState" class="c-weektime-time">
              <div v-for="t in selectValue" :key="t.id">
                <p v-if="t.value">
                  <span class="g-tip-text">t.week:</span>
                  <span>t.value</span>
                </p>
              </div>
            </div>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>
<script>
const createArr = len => 
  return Array.from(Array(len)).map((ret, id) => id)

export default 
  name: 'DragWeektime',
  props: 
    value: 
      type: Array
    ,
    data: 
      type: Array
    ,
    colspan: 
      type: Number,
      default () 
        return 2
      
    
  ,
  computed: 
    // 用于计算 用户选择时间段的范围
    styleValue () 
      return 
        width: `$this.widthpx`,
        height: `$this.heightpx`,
        left: `$this.leftpx`,
        top: `$this.toppx`
      
    ,
    selectValue () 
      return this.value
    ,
    selectState () 
      return this.value.some(ret => ret.value)
    ,
    selectClasses () 
      return n => n.check ? 'ui-selected' : ''
    
  ,
  methods: 
    // 鼠标悬浮的最后位置
    cellEnter (item) 
      console.log('cellEnter')
      // 找到鼠标最终悬停位置的 表格
      const ele = document.querySelector(`td[data-week='$item.row'][data-time='$item.col']`)
      if (ele && !this.mode) 
        this.left = ele.offsetLeft
        this.top = ele.offsetTop
       else  // 根据起始点 和 最终点, 计算出 选中框的 left、top 属性 和 width 和 height 高度
        if (item.col <= this.col && item.row <= this.row)  // 最终的节点,在起始点的 —— 左上角
          this.width = (this.col - item.col + 1) * ele.offsetWidth
          this.height = (this.row - item.row + 1) * ele.offsetHeight
          this.left = ele.offsetLeft
          this.top = ele.offsetTop
         else if (item.col >= this.col && item.row >= this.row)  // 最终的节点,在起始点的 —— 右下角
          this.width = (item.col - this.col + 1) * ele.offsetWidth
          this.height = (item.row - this.row + 1) * ele.offsetHeight
          if (item.col > this.col && item.row === this.row) this.top = ele.offsetTop
          if (item.col === this.col && item.row > this.row) this.left = ele.offsetLeft
         else if (item.col > this.col && item.row < this.row)  // 最终的节点,在起始点的 —— 右上角
          this.width = (item.col - this.col + 1) * ele.offsetWidth
          this.height = (this.row - item.row + 1) * ele.offsetHeight
          this.top = ele.offsetTop
         else if (item.col < this.col && item.row > this.row)  // 最终的节点,在起始点的 —— 左下角
          this.width = (this.col - item.col + 1) * ele.offsetWidth
          this.height = (item.row - this.row + 1) * ele.offsetHeight
          this.left = ele.offsetLeft
        
      
    ,
    cellDown (item) 
      console.log('cellDown')
      debugger
      // item = 
      //   'week': '星期一',
      //   'value': '01:00~01:30',
      //   'begin': '01:00',
      //   'end': '01:30',
      //   'row': 0, // 代表星期几, 0 代表星期一 6 代表星期 天
      //   'col': 2 // 代表哪个时段 (半个小时为一个时段,一天有48个时段)
      // 
      // 获取用户选中的点
      const ele = document.querySelector(`td[data-week='$item.row'][data-time='$item.col']`)
      // 判断用户选中的格子是否被选中
      this.check = Boolean(item.check) // 用于样式的渲染
      this.mode = 1 //  mode 为1 表示正在选中 的样式
      if (ele)  // with 和 height 是用户手动选择的范围
        this.width = ele.offsetWidth
        this.height = ele.offsetHeight
      
      // 起始行
      this.row = item.row
      // 起始列
      this.col = item.col
    ,
    cellUp (item) 
      console.log('cellUp')
      // 最终的节点,在起始点的 —— 左上角
      if (item.col <= this.col && item.row <= this.row) 
        this.selectWeek([item.row, this.row], [item.col, this.col], !this.check)
       else if (item.col >= this.col && item.row >= this.row)  // 最终的节点,在起始点的 —— 右下角
        this.selectWeek([this.row, item.row], [this.col, item.col], !this.check)
       else if (item.col > this.col && item.row < this.row)  // 最终的节点,在起始点的 —— 右上角
        this.selectWeek([item.row, this.row], [this.col, item.col], !this.check)
       else if (item.col < this.col && item.row > this.row)  // 最终的节点,在起始点的 —— 左下角
        this.selectWeek([this.row, item.row], [item.col, this.col], !this.check)
      
      this.width = 0 // 选中的宽度为 0
      this.height = 0 // 选中的高度为 0
      this.mode = 0 // 标记不是选中的状态
    ,
    // 计算出 起始点  和  最重点  范围的节点,然后将它们的状态值改为 “选中状态", 会计算出样式
    selectWeek (row, col, check) 
      const [minRow, maxRow] = row
      const [minCol, maxCol] = col
      this.data.forEach(item => 
        item.child.forEach(t => 
          if (t.row >= minRow && t.row <= maxRow && t.col >= minCol && t.col <= maxCol) 
            this.$set(t, 'check', check)
          
        )
      )
    
  ,
  data () 
    return 
      width: 0,
      height:以上是关于vue2一天时间段选择库(drag-weektime)代码学习的主要内容,如果未能解决你的问题,请参考以下文章

vue2和vue3中路由的区别和写法?

vue2.0 技巧汇总

如何在 Vue2 中的 Sass 后缀 (&__) 选择器上实现 ::v-deep?

vue2+echarts:后台传递一天有多类数据的时候,如何渲染柱状图

vue2+echarts:后台传递一天有多类数据的时候,如何渲染柱状图

vue2.0构建单页应用最佳实战