likes
comments
collection
share

Element-plus 中 el-table 组件实现行拖拽

作者站长头像
站长
· 阅读数 13

需求

最近要实现一个字段展示表格拖动排序的功能,类似于下图: Element-plus 中 el-table 组件实现行拖拽

思路

当时需求澄清会议,一讲这个需求,我脑袋一啪,很快就想到,element-plus table 应该有内置的拖拽功能吧,毕竟也不算啥特殊需求。话不多说,直接上官网一查,是我多想了,这看来是个不常见的需求。那只能自己手搓了。。。

手搓第一步:想想怎么搓? 重点在于:拖动行到某一位置,拿到这一位置的标识,数据插入进这个位置 vueuse的拖拽hooks useDraggable 可以用;html5 drag能拖动行元素;mounsedown、mounsemove时间实现拖拽。

初步选型

  • vueuse库 的 useDraggable 自定义 hooks。(第三种实现)
  • html5 的新 api draggable 。
  • JavaScript 原生 onomusedown onmouseup onmousemove 等。

分析及实现

useDraggable 自定义hooks 与 onmousedown ❌

如果使用 mousemove 等方法实现:

  1. 在表格每一行注册一个 mousedown 事件
  2. mousedown 事件中监听鼠标的竖向移动位置
  3. 将拖动行采用绝对定位来跟随鼠标移动
  4. 获取鼠标移动到某一行的位置,拿到此位置信息 ❌
  5. 元素插入,数据请求
  6. 表格刷新

当我采用这种方式时,在第四步拖动一行数据,由于是绝对定位,所以不能通过mouseenter方法来拿到元素的信息,所以这一步卡住,这种方法暂时被我放弃。。。

dragable Api

如果使用 dragable 等方法实现:

  1. 在表格的每一行添加 draggable 属性为 true
  2. 拖动目标行
  3. 拖动时 使用drag 传递数据 拿到行数据信息 ✅
  4. 元素插入,数据请求
  5. 表格刷新

实现

  • 在 template 模版中 定义mousedown方法,表示开始拖拽,并拿到记录拖拽元素标识信息
<el-table-column
        header-align="center"
        type="index"
        align="center"
        label=""
        :width="60"
      >
        <template #default="{ row, $index }">
          <el-space
            :class="filedInfoClass['drag-table-item']"
            @mousedown="dragHandle.dragStart(row, $index)"  // 这是重点
          >
            <el-icon><drag-icon /></el-icon>
          </el-space>
        </template>
      </el-table-column>
  • 拖拽时采用原生js获取行维度数据,设置 effectAllowed = 'move' 表示每行都可以放置
  • 拖动到每一行上时拿到行标识,并动态插入交换表格数据,vue通过 diff算法分析,dom变动实现拖动效果
  • 放置时拿到拖动行id ,目标行 id 请求数据,动态刷新表格数据
// script
const dragHandle = {
  // 开始拖拽
  dragStart(row: ColumnModel, i: number) {
    dragI.value = i;  // 拖拽起点
    // 采用原生 js 获取 第 i 行 
    const con = document.querySelector(
      `.${filedInfoClass['field-info-container']} .row-class-${i}`
    ) as HTMLElement;
    const tbody = document.querySelector(
      `.${filedInfoClass['field-info-container']} tbody`
    ) as HTMLElement;
    let tr = document.querySelectorAll(
      `.${filedInfoClass['field-info-container']} tbody tr`
    );
    dragKey.value = row.gid;  // 拖拽的元素标识
    if (con) {
      con.setAttribute('draggable', 'true');

      con.ondragstart = (ev) => {
        console.log('start', ev);
      };
      
      // 设置 effectAllowed = 'move' 表示每行都可以放置
      tbody.ondragover = (ev) => {
        if (ev.dataTransfer) {
          ev.dataTransfer.effectAllowed = 'move';
        }
        ev.preventDefault();
      };
      tbody.ondragenter = (ev) => {
        if (ev.dataTransfer) {
          ev.dataTransfer.effectAllowed = 'move';
        }
        ev.preventDefault();
      };
      
      tr.forEach((el, n) => {
        (el as HTMLElement).ondragover = debounce(
          () => {
          // dataSetDetail.value?.columnModelList 表格数据
            const item = dataSetDetail.value?.columnModelList[
              dragI.value
            ] as ColumnModel;
            // n 移动到 第 n 个
            // i 从 第 i 个
            if (n > dragI.value) {
              dataSetDetail.value?.columnModelList.splice(n + 1, 0, item);
              dataSetDetail.value?.columnModelList.splice(dragI.value, 1);
            } else if (n < dragI.value) {
              const start = n - 1 < 0 ? 0 : n;
              dataSetDetail.value?.columnModelList.splice(start, 0, item);
              dataSetDetail.value?.columnModelList.splice(dragI.value + 1, 1);
            }
            dragI.value = n;
          },
          1000,
          true
        );
      });
      tr = document.querySelectorAll(
        `.${filedInfoClass['field-info-container']} tbody tr`
      );
      tr.forEach((el) => {
        (el as HTMLElement).ondragend = () => {
          const columns = dataSetDetail.value?.columnModelList || [];
          const beforeI =
            dragI.value + 1 >= columns.length ? -1 : dragI.value + 1;
          const columnId = columns[dragI.value].resourceId || '';
          const beforeColumnId = columns[beforeI]?.resourceId || null;
          // 数据请求
          dragField({ beforeColumnId, columnId })
            .then((res) => {
              if (res) {
                emits('update', true); // 更新数据
              }
            })
            .catch((err) => {
              emits('update', true); // 更新数据
              ElMessage({
                message: err.errMsg,
                type: 'warning',
              });
            });
        };
      });

      // 销毁事件
      window.onmouseup = () => {
        con.onmousemove = null;
        tr.forEach((el) => {
          (el as HTMLElement).ondragover = null;
          (el as HTMLElement).ondragend = null;
        });
      };
    }
  },
};