vue 封装一个鼠标拖动选择时间段功能

javascript 复制代码
<template>
  <div class="timeRange">
    <div class="calendar">
      <table>
        <thead>
          <tr>
            <th rowspan="6" class="weekRow"><b>周/时间</b></th>
            <th colspan="24"><b>00:00 - 12:00</b></th>
            <th colspan="24"><b>12:00 - 24:00</b></th>
          </tr>
          <tr>
            <td colspan="2" v-for="index in 24" :key="index">{{ index - 1 }}</td>
          </tr>
        </thead>
        <tbody @mousemove.prevent="handleMouseMove">
          <tr v-for="(item, index) in weekDate" :key="index">
            <td>{{ item }}</td>
            <td class="calendar-atom-time" v-for="i in 48" :key="index + '-' + i"
                :class="{ 'active': selectCells[`${index}_${i}`] }" @mousedown.prevent="handleMouseDown(index, i, $event)"
                @mouseup.prevent="handleMouseUp(index, i)">
            </td>
          </tr>
          <div id="box" v-show="moveStartEvent"></div>
        </tbody>
      </table>
      <div class="table-core">
        <div class="clearfix">
          <span class="pull-left tip-text">可拖动鼠标选择时间段</span>
          <button class="clearBtn" @click="handleClear">清除所有</button>
        </div>
        <ul>
          <li v-for="(item, index) in selectDate" :key="index" v-if="item.data && item.data.length">
            <label>{{ item.label }}</label>
            <span v-for="o in item.data" :key="o">{{ o[0] }}~{{ o[1] }}</span>
          </li>
        </ul>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'timeRange',
  data () {
    return {
      // 表列
      weekDate: ['一', '二', '三', '四', '五', '六', '日'],
      // 所选格子
      selectCells: {},
      // 所选时间数据(做提交时使用)
      selectDate: {},
      // 记录鼠标位置
      moveStartEvent: false,
      moveStartColumn: 0, // 列
      moveStarRow: 0, // 行
      moveStartX: 0,
      moveStartY: 0
    }
  },
  created () {

  },
  mounted () {

  },
  methods: {
    // 初始
    init (data) {
      if (data && data instanceof Object) {
        this.selectCells = data
      } else {
        this.selectCells = {}
      }
      this.getSelectDate()
    },

    // 按下
    handleMouseDown (column, row, e) {
      this.moveStartEvent = true
      this.moveStartColumn = column
      this.moveStarRow = row
      this.moveStartX = e.layerX
      this.moveStartY = e.layerY
    },

    // 松开
    handleMouseUp (column, row) {
      if (this.moveStartEvent) {
        this.moveStartEvent = false
        const X = row - this.moveStarRow
        const Y = column - this.moveStartColumn
        const checked = !this.selectCells[`${column}_${row}`]

        if (X > -1 && Y > -1) {
          const obj = this.clone(this.selectCells)
          for (let i = this.moveStartColumn; i <= column; i++) {
            for (let j = this.moveStarRow; j <= row; j++) {
              var key = `${i}_${j}`
              if (checked) {
                obj[key] = checked
              } else if (obj[key]) {
                delete obj[key]
              }
            }
          }

          this.selectCells = obj
          this.$forceUpdate()
          this.getSelectDate()
        }
      }
      this.moveStartDay = 0
      this.moveStarTime = 0
    },

    // 滑动中
    handleMouseMove (e) {
      if (this.moveStartEvent) {
        const dom = this.$el.querySelector('#box')
        const X = e.layerX - this.moveStartX
        const Y = e.layerY - this.moveStartY
        if (X >= 0 && Y >= 0) {
          dom.style.left = this.moveStartX + 'px'
          dom.style.top = this.moveStartY + 'px'
          dom.style.width = X + 'px'
          dom.style.height = Y + 'px'
        }
      }
    },

    // 组合时间数据
    getSelectDate () {
      const arr = []
      this.weekDate.forEach((item, index) => {
        arr.push({
          label: item,
          data: []
        })
        for (var i = 1; i <= 48; i++) {
          var o = this.selectCells[`${index}_${i}`]
          if (o) {
            var endTime = i / 2
            var startTime = endTime - 0.5
            if (startTime < 10) {
              startTime = '0' + startTime
            }
            if (endTime < 10) {
              endTime = '0' + endTime
            }
            startTime += ''
            endTime += ''
            if (startTime.indexOf('.5') > -1) {
              startTime = startTime.replace('.5', ':30')
            } else {
              endTime = endTime.replace('.5', ':30')
            }
            if (startTime.indexOf(':30') < 0) {
              startTime += ':00'
            } else {
              endTime += ':00'
            }
            arr[index].data.push([startTime, endTime])
          }
        }
      })
      arr.forEach(item => {
        for (var i = 0; i < item.data.length; i++) {
          var o = item.data
          if (o[i + 1] && o[i][1] === o[i + 1][0]) {
            o[i + 1][0] = o[i][0]
            item.data.splice(i, 1)
            i--
          }
        }
      })
      this.selectDate = arr
    },

    // 清除选择
    handleClear () {
      this.selectCells = {}
      this.getSelectDate()
      this.$forceUpdate()
    },

    // 获取数据
    getData () {
      return this.selectDate
    }

  }
}
</script>

<style lang="less" scoped>
.timeRange {
  user-select: none;
  position: relative;
  padding: 10px 0;

  .calendar {
    display: inline-block;
  }

  table {
    width: 100%;
    border-radius: 4px;
    border-spacing: 0;
    table-layout: fixed;
    border-collapse: collapse;

    thead {

      th,
      td {
        height: 30px;
      }

      th {
        padding: 5px 0;

      }

      .weekRow {
        width: 100px;
        min-width: 100px;
        padding: 20px 0
      }
    }

    td,
    th {
      outline: 0;
      border: 1px solid #E3E3E3;
      font-size: 12px;
      text-align: center;
      min-width: 11px;
      line-height: 1.6em;
      min-width: 24px;
    }

    tbody {
      position: relative;
      overflow: hidden;

      td {
        height: 20px !important;
      }
    }

    td.active {
      background: #F60457;
    }
  }

  .table-core {
    line-height: 2.4em;
    border: 1px solid #E3E3E3;
    border-top: 0;
    overflow: hidden;
    padding: 10px;

    .clearfix {
      color: #8A8A8A;
      text-align: left;
      height: 22px;
      line-height: 22px;
      margin: 8px 0;
      display: flex;
      font-size: 12px;

      .clearBtn {
        cursor: pointer;
        color: #5775F9;
        font-size: 14px;
        margin-left: auto;
      }
    }

    ul {
      li {
        line-height: 22px;
        margin-bottom: 5px;

        label {
          display: inline-block;
          min-width: 60px;
          color: #8A8A8A;
          text-align: left;
        }

        span {
          font-size: 12px;

          &::after {
            content: "、"
          }

          &:last-child::after {
            display: none;
          }
        }
      }
    }
  }

  #box {
    background: rgba(241, 1, 85, 0.4);
    pointer-events: none;
    position: absolute;
    top: 0;
    left: 0;
  }
}
</style>
相关推荐
Cshaosun23 分钟前
js版本之ES6特性简述【Proxy、Reflect、Iterator、Generator】(五)
开发语言·javascript·es6
Ares码农人生1 小时前
React 高级组件开发:动态逻辑与性能优化
vue.js·前端框架
轻口味1 小时前
【每日学点鸿蒙知识】webview性能优化、taskpool、热更新、Navigation问题、调试时每次都卸载重装问题
javascript·list·harmonyos
涔溪2 小时前
如何在Express.js中定义多个HTTP方法?
javascript·http·express
嘤嘤怪呆呆狗2 小时前
【开发问题记录】执行 git cz 报require() of ES Module…… 错误
前端·javascript·vue.js·git·vue
夕水3 小时前
你可能需要避免的5个react的ref错误用法
javascript·react.js
林小白的日常4 小时前
uniapp中wx.getFuzzyLocation报错如何解决
前端·javascript·uni-app
ganlanA5 小时前
uniapp+vue 前端防多次点击表单,防误触多次请求方法。
前端·vue.js·uni-app
卓大胖_5 小时前
Next.js 新手容易犯的错误 _ 性能优化与安全实践(6)
前端·javascript·安全
CodeClimb5 小时前
【华为OD-E卷 - 猜字谜100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od