前端开发那些事儿每天学一点Vue3

封装第三方组件(20)完善td的拖拽功能,以及实现排序等功能

2021-08-16  本文已影响0人  自然框架

昨天做了一个可以拖拽td的自定义指令,实现基本功能后开始写td的排序的功能,其间发现了几个小问题。

几个小问题

改进

const dragInfo = reactive({
  offsetX: 0,
  isLeft: true, // 是否在 th 的左侧结束拖拽
  ctrl: false, // 是否按下了ctrl
  source: '',
  target: '',
  sourceIndex: 0, // 开始拖拽的位置
  targetIndex: 0 // 结束拖拽的位置
})

/**
 * 拖拽 table 的 th,返回拖拽信息
 */
const tableDrag = (app, options) => {
  app.directive('tabledrag', {
    // 指令的定义
    mounted (el, binding) {
      /**
       * 实现 th 的拖拽
       * @param {string} className 用于找到目标的 class 名称。
       * @param {reactive} dragInfo reactive 返回拖拽信息。
       * @returns 没有返回
       * * const dragInfo = {
       * *  offsetX: 0,
       * *  isLeft: true, // th 左侧结束拖拽
       * *  ctrl: false, // 是否按下ctrl
       * *  source: '', // 开始拖拽的th
       * *  target: '', // 结束拖拽的th
       * *  sourceIndex: 0, // 开始拖拽的序号
       * *  targetIndex: 0 // 结束拖拽的序号
       * * })
       */
      const setThforDrag = (className, dragInfo) => {
        const table = el.getElementsByClassName(className)[0]
        const tr = table.rows[0]
        const tdCount = tr.cells.length
        // 记录 th 的序号和宽度
        const thIndex = {}
        // 记录临时的源
        let src1 = ''
        let src2 = 1
        // 设置th的拖拽
        for (let i = 0; i < tdCount; i++) {
          const th = tr.cells[i]
          thIndex[th.innerText] = {
            index: i, // 记录th的序号
            width: th.offsetWidth // 记录 th 的宽度
          }
          // 设置可以拖拽
          th.setAttribute('draggable', true)
          // 拖拽时经过
          th.ondragover = (event) => {
            event.preventDefault()
          }
          // 开始拖拽
          th.ondragstart = (event) => {
            src1 = event.target.innerText
            src2 = thIndex[event.target.innerText].index
          }
          // 结束拖拽
          th.ondrop = (event) => {
            dragInfo.offsetX = event.offsetX
            dragInfo.ctrl = event.ctrlKey
            dragInfo.source = src1
            dragInfo.sourceIndex = src2
            dragInfo.target = event.target.innerText
            // 设置 th 的序号
            dragInfo.targetIndex = thIndex[event.target.innerText].index
            dragInfo.isLeft = dragInfo.offsetX < thIndex[event.target.innerText].width / 2
          }
        }
      }
      binding.value.setThforDrag = setThforDrag
    }
  })
}
export default tableDrag

实现调整 th 的排序

具体实现方式就不能在内部实现了,因为不同的项目有不同的实现方式,外面自己写就好。

这里基于自己的项目实现一下。

th 的实现方式

我比较懒,不喜欢一个一个的设置 el-table-column,所以用了v-for

<el-table-column
      v-for="(id) in colOrder"
      :colId="id"
      :column-key="id"
      :key="'grid_list_' + id"
      :fixed="id < fixedIndex"
      :prop="itemMeta[id].colName"
      :label="itemMeta[id].label"
      :width="itemMeta[id].width"
      :min-width="50"
      :align="itemMeta[id].align"
      :header-align="itemMeta[id]['header-align']"
      :filter-multiple="false"
      :show-overflow-tooltip="true"
      :formatter="myformatter"
    >
    </el-table-column>

colOrder 是一个纯数组,元素只有number,那么调整排序就是调整这个数组里面的元素。

插入 th

把 th1 插入到 th2 的前面或者后面。这个应该是比较常见的一种调整方式吧。

  /**
   * 插入 th 后调整顺序
   */
  const _order = (cols) => {
    // 判断前插、后插。后插:偏移 0;前插:偏移 1
    const offsetTarget = dragInfo.isLeft ? 1 : 0
    // 判断前后顺序。》 1;《 0
    const offsetSource = dragInfo.sourceIndex < dragInfo.targetIndex ? 1 : 0
    // 插入源
    girdMeta.colOrder.splice(dragInfo.targetIndex - offsetTarget, 0, cols.id1)
    nextTick(() => {
      // 删除源
      girdMeta.colOrder.splice(dragInfo.sourceIndex - offsetSource, 1)
      // table被重置了,所以需要重新加拖拽事件,重新排序。
      nextTick(() => {
        tableInfo.setThforDrag(tableClass, dragInfo)
      })
    })
  }

几次调试之后,代码写成了这样。这里使用 ES6 的 array.splice 来实现数组的调整。
他可以删除指定位置的数组元素,也可以在指定位置添加数组元素,这个就很方便了。

位置确定好了之后就方便了,先把第一个 th 插入 第二个 th 的附近,然后删掉第一个th即可。

交换两个th的位置

有的时候需要直接交换 th1 和 th2 的位置,其他 th 的位置不用变,这时我们直接交换就好。

  /**
   * 交换两个th的位置
   */
  const _changeSet = (cols) => {
    // 交换
    girdMeta.colOrder[dragInfo.sourceIndex - 1] = cols.id2
    girdMeta.colOrder[dragInfo.targetIndex - 1] = cols.id1
    // 强制响应
    girdMeta.colOrder.push(cols.id1)
    nextTick(() => {
      // 删除多余的col
      girdMeta.colOrder.splice(girdMeta.colOrder.length - 1, 1)
      // table被重置了,所以需要重新加拖拽事件,重新排序。
      nextTick(() => {
        tableInfo.setThforDrag(tableClass, dragInfo)
      })
    })
  }

还是老问题,交换之后 v-for 不会响应重置,所以只好先加一个,然后 nextTick 里面再删掉一个。好无语。有没有更好的方法呢?

设置 th、td 的对齐方式

一般td有三种对齐方式:左、中、右。标题一般都是居中对齐,内容就不一定了,文本内容一般左对齐,数字一般右对齐,时间、状态等一般居中对齐,这时候需要设置一下。

我们可以做一个规定:

  1. 拖拽时没有离开 th,则表示要修改对齐方式,而不是调整排序。
  2. 往左拖拽表示对齐方式“向左”,向右同理。
  3. 按住ctrl表示,要修改 th 的对齐方式,否则表示修改td。一般表头都是居中的。

实现代码

  /**
   * 设置th的对齐方式
   */
  const _setThAlgin = (cols) => {
    // 判断 th 还是 td
    const alignKind = (dragInfo.ctrl) ? 'header-align' : 'align'
    const col = girdMeta.itemMeta[cols.id1]
    // 判断:左中右
    switch (col[alignKind]) {
      case 'left':
        if (dragInfo.isLeft) {
          col[alignKind] = 'left'
        } else {
          col[alignKind] = 'center'
        }
        break
      case 'center':
        if (dragInfo.isLeft) {
          col[alignKind] = 'left'
        } else {
          col[alignKind] = 'right'
        }
        break
      case 'right':
        if (dragInfo.isLeft) {
          col[alignKind] = 'center'
        } else {
          col[alignKind] = 'right'
        }
        break
    }
  }

不多解释了,反正就是各种判断各种修改。

入口

上面是分别介绍功能,但是没有写入口代码。

  /**
   * 设置th的顺序
   */
  const setThOrder = () => {
    // 获取colId
    const cols = {
      id1: girdMeta.colOrder[dragInfo.sourceIndex - 1],
      id2: girdMeta.colOrder[dragInfo.targetIndex - 1]
    }

    if (dragInfo.sourceIndex !== dragInfo.targetIndex) {
      // 源和目标不同,排序
      if (dragInfo.ctrl) {
        // 交换
        _changeSet(cols)
      } else {
        // 插入
        _order(cols)
      }
    } else {
      // 源和目标相同,设置对齐方式
      _setThAlgin(cols)
    }
  }

这样分解一下,代码应该可以更好维护。

好吧,还有最后一个函数


const dragInfo = reactive({
  offsetX: 0,
  isLeft: true, // 是否在 th 的左侧结束拖拽
  ctrl: false, // 是否按下了ctrl
  source: '',
  target: '',
  sourceIndex: 0, // 开始拖拽的位置
  targetIndex: 0 // 结束拖拽的位置
})

const { setThOrder } = manageTable(girdMeta, dragInfo, tableInfo)
onMounted(() => {
  nextTick(() => {
    tableInfo.setThforDrag(tableClass, dragInfo)
    watch(() => dragInfo, () => {
      // console.log('外部:', dragInfo)
      setThOrder()
    },
    { deep: true })
  })
})

这样就ok了。

做了一个简单的演示:

拖拽效果
上一篇 下一篇

猜你喜欢

热点阅读