二次封装可拖拽可选择的树状eltable组件

775 阅读10分钟

可选、可拖拽、树状,要是各自实现单个功能的话不难,难就难在组合的使用。

组合使用会出现的问题:

  1. eltable的树状加个type="selection"选中全部子节点,父节点不会自动选中;反过来,选中父节点也不会把全部子节点选中
  2. 全选只会选到一级的节点;
  3. 使用sortablejs选中一个item任意拖拽到别的位置,但不松手再放回原位时,会被sortablejs认为你已经拖动了的致命插件bug。
  4. 不同产品有不同的需求,拖拽时需要按实际需求实现逻辑。 话不多说,先来看看效果

实现效果如下:

PixPin_2024-01-11_10-10-30.gif

原始结构

{
    id:1,
    parentId:null,
    children:[
        {
            id:4,
            parentId:1,
            children:[
                {
                    id:6,
                    parentId:4,
                    children:[],
                }
            ],
        },
        {
            id:5,
            parentId:1,
            children:[],
        }
    ],
},
{
    id:2,
    parentId:null,
    children:[],
}
{
    id:3
    parentId:null,
    children:[],
}

项目需求:

  1. 同层级拖拽
  • 最外层(1,2,3之间拖拽):不调用接口,前端排序
  • 非最外层:
    • 向上拖: 不调用接口,前端排序
    • 向下拖: (4向5下面拖拽)把拖动位置(5)当成parent,调用接口把被拖动的item(4)加入到拖动位置(5)的children(即向后端传入当前拖动行和拖动后属于他的父id,跟后端协商处理)
  1. 非同层级拖拽
  • 向上拖:(4向1上面拖)把拖动位置(1)的parent(null)当成被拖动位置(4)的parent,调用接口
  • 向下拖:(6向5下面拖)把拖动位置(6)当成被拖动位置(5)的parent,调用接口

实现思路

普通的选择框和树状组件elementui官网有案例,实现起来很简单,注意设置:row-key和:tree-props,然后实现@select-all和@select的逻辑,完成全选和单选的功能。
拖拽我们使用了sortablejs,和很多插件的使用思路差不多,先创建一个dom,然后通过插件的api去创建一个实例,最后调用插件的一些函数去实现你的逻辑。

sortablejs详细配置:www.sortablejs.com/options.htm…

部分重要功能完整代码

由于我是对eltable做了个二次封装,也做了比较多注释,显得代码比较长。如果时间紧迫,急需解决问题,这里我给几个重要的函数标识出来,其他可以无视

  1. 在mounted处判断是否开启可拖拽的模式(317行
  2. draggableHandler()用来实现拖拽组件(324行
  3. 处理上面提到的问题3,即松手不放拖拽后再放回原位,被sortablejs认为被拖拽的问题(注意2个参数,分别是currentIndexcurrentTop)。处理的思路是,每次点击一个item准备拖拽时,都记录他当前这个item的高度top,如果这个高度在拖拽前后没有改变,则认为他没有被拖拽,在onEnd的时候直接return出去(308、339、343、361行),最后记得拖动结束后清除标记(456行),这样可以避免插件的bug导致一些误操作。要是我们只是选中一个item,不拖动到其他位置,即在不触发onMove的时候,只需要判断oldIndex和newIndex是否一致即可。(365行
  4. 根据需求写拖动的逻辑(355行
  5. 单选(537行selectFun
  6. 全选(665行selectAllFun
<template>
  <div class="baseTable">
    <!-- 如果需要树状显示,需要添加tree-props,必须添加row-key,el-table会根据树row-key展开 -->
    <!-- 
    ******columnData格式******
        columnData: [
            {
              prop: "date",         设置数据参数
              label: "日期",        设置数据名
              width: "180",         设置表格宽度
              canPullDown:true,     树状表格,可以下拉展开的列,需要确定数据有isExpand字段,
                                    如果没有前端自行调用递归添加:参考账套组管理initExpand()
              //a标签文本,传入一个方法
              click:(val) => {
                //do something
              },                   
              setIcon:"set",       icon图标
              align:"left",         内容对齐方式,默认center,表头对齐固定为center
              //如果有一些自定义的内容,可以传入一个函数slot,如
              slot: (val) => {
                //这里的val是改行对应的prop内容
                //可以保存一些this的指向,再调用当前methods的方法,比如:
                return _this.formatDate(val);
              },
              注意:slot和click尽量不要执行太复杂的js,由于一些操作无法避免eltable重新渲染,数据量大且操作复杂的时候会比较消耗性能,有复杂计算的请后端直接返回数据!
            },
        ]
        
    ******tableData格式******
        tableData: [
            {
              date: "2023-8-11",
            },
        ]

    ******tableOption格式******
    tableoption里的按钮会返回一个handleButton(methods,row)方法到父组件,
    tableOption: {
        label: "操作",
        width: "200",                 option列的宽度
        options: [
          {
            label: "编辑",            按钮名称
            type: "text",             按钮类型
            methods: "edit",          按钮对应的方法
            hasPermi:['assets:assetsList:remove']   按钮权限
            可以传入一个show方法,做特定条件才显示按钮(非权限)
            show: (val) => {
              //这里的val是该行的数据
              return val.name == "啊三" ? true : false;
            },
          },
          {
            label: "删除",
            type: "text",
            methods: "delete",
            hasPermi:['assets:assetsList:remove']
          },
        ],
      }

      ******组件******
      <tableView
        ref="tableView"
        v-if="refresh"                      强制更新组件
        :complexities="true"                true树状表格,false普通表格
        :tableData="tableData"              表格数据
        :columnData="columnData"            表头信息
        :needSelection="true"               默认为false,即不需要复选框
        :loading="loading"                  加载动画
        :rowKey="'accountsId'"              行key,当draggable为true时和树状表格必须填写
        :draggable="true"                   是否可拖拽
        :tableOption="tableOption"          操作列信息
        @handleButton="handleButton"        操作列按钮,返回该行数据
        @iconClick="iconClick"              点击icon返回改行数据
        @selectChange="selectChange"        获取复选框中选中的内容(普通表格complexities=false)
        @getSortableData="getSortableData"  获取拖拽的行的始末位置信息,并可以返回一个设置好的rowkey
        @updateTableDate="updateTableDate"  当不调用接口时,又对数据进行了拖拽可以调用这个改变树组的结构(仅前端展示,刷新后会还原数据)
      >
      </tableView>

      获取复选框中选中的内容有2种情况,但是都是通过调用getSelection这个方法获取的
          ————普通表格:通过selectChange(val)监听勾选数据,会返回勾选的数据给父组件val
          ————树状表格:直接调用refs.xx.getSelection()返回勾选了的数据
              ————如果树状有校验规则,需要通过selectChange监听,然后调用refs.xx.getSelection()获取数据
      
   -->
    <el-table
      ref="tableView"
      border
      class="drop"
      style="width: 100%"
      v-loading="loading"
      :height="height"
      :max-height="maxHeight"
      :data="tableData"
      :tree-props="{ children: 'children' }"
      :row-key="rowKey"
      :default-expand-all="defaultExpandAll"
      @select-all="selectAllFun"
      @select="selectFun"
    >
      <el-table-column
        v-if="needSelection"
        type="selection"
        width="40"
        header-align="center"
      >
        <!-- :selectable="() => false" -->
      </el-table-column>
      <el-table-column
        v-if="needIndex"
        type="index"
        width="50"
        align="center"
        header-align="center"
        class-name="allowDrag"
      >
        <template slot="header">
          <span>序号</span>
        </template>
      </el-table-column>
      <template v-for="(item, index) in columnData">
        <!-- 处理第canPullDown下拉树 -->
        <template v-if="item.canPullDown">
          <el-table-column
            :key="index"
            :prop="item.prop"
            :label="item.label"
            :width="item.width"
            :min-width="item.minWidth"
            :align="item.align ? item.align : 'center'"
            header-align="center"
          >
            <template slot-scope="scope">
              <a v-if="item.click" @click="item.click">{{
                scope.row[item.prop]
              }}</a>
              <span v-else>{{ scope.row[item.prop] }}</span>
              <i
                style="color: #235bda"
                class="el-icon-caret-right"
                v-show="
                  !scope.row.isExpand &&
                  scope.row.children &&
                  scope.row.children.length > 0
                "
                @click="expandFun(scope.row)"
              ></i>
              <i
                style="color: #235bda"
                class="el-icon-caret-bottom"
                v-show="
                  scope.row.isExpand &&
                  scope.row.children &&
                  scope.row.children.length > 0
                "
                @click="expandFun(scope.row)"
              ></i>
              <svg-icon
                v-if="item.setIcon"
                style="position: absolute; right: 5px; height: 23px"
                :icon-class="item.setIcon"
                @click.stop="iconClick(scope.row)"
                class="setSvg"
              ></svg-icon>
            </template>
          </el-table-column>
        </template>

        <template v-else>
          <el-table-column
            :key="index"
            :prop="item.prop"
            :label="item.label"
            :width="item.width"
            :min-width="item.minWidth"
            :align="item.align ? item.align : 'center'"
            type=""
            header-align="center"
          >
            <template slot-scope="scope">
              <!-- 处理有slot、click数据的情况,有click的是a标签 -->
              <template>
                <span>{{ scope.row[item.prop] }}</span>
              </template>
              <!-- 处理Icon -->
              <svg-icon
                v-if="item.setIcon"
                style="position: absolute; right: 5px; height: 23px"
                :icon-class="item.setIcon"
                @click.stop="iconClick(scope.row)"
                class="setSvg"
              ></svg-icon>
            </template>
          </el-table-column>
        </template>
      </template>

      <!-- 操作列 -->
      <el-table-column
        v-if="tableOption && tableOption.label"
        :width="tableOption.width"
        :label="tableOption.label"
        fixed="right"
        :align="tableOption.align ? tableOption.align : 'center'"
        class-name="small-padding fixed-width"
        header-align="center"
      >
        <template slot-scope="scope">
          <el-button
            v-for="(item, index) in tableOption.options"
            :key="index"
            :type="item.type || 'text'"
            :icon="item.icon"
            :disabled="
              typeof item.disabled == 'function'
                ? item.disabled(scope.row)
                : true
                ? item.disabled
                : false
            "
            :v-hasPermi="item.hasPermi"
            v-show="(item.show && item.show(scope.row)) || !item.show"
            @click="handleButton(item.methods, scope.row)"
            size="mini"
          >
            {{ item.label }}
          </el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
import Sortable from "sortablejs";
import { treeToArray } from "@/utils";
export default {
  props: {
    //表头/行
    columnData: {
      type: Array,
      default: () => [],
    },
    //表格数据
    tableData: {
      type: Array,
      default: () => [],
    },
    height: {
      type: Number || String,
      default: null,
    },
    maxHeight: {
      type: Number || String,
      default: null,
    },
    //需要选择器
    needSelection: {
      type: Boolean,
      default: false,
    },
    //需要排序器
    needIndex: {
      type: Boolean,
      default: false,
    },
    //操作列
    tableOption: {
      type: Object,
      default: () => {},
    },
    rowKey: {
      type: String,
      default: "id",
    },
    //树表格默认展开全部
    defaultExpandAll: {
      type: Boolean,
      default: true,
    },
    //如果要实现拖拽,必须要设置唯一值rowkey
    draggable: {
      type: Boolean,
      default: false,
    },
    //默认为普通checkbox选项表格,如果为true表示可下拉的数据
    complexities: {
      type: Boolean,
      default: false,
    },
    loading: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      //记录当前拖动行的高度
      currentTop: null,
      //记录当前拖动行的数据
      currentIndex: null,
      isAllSelect: false,
    };
  },
  watch: {
    currentIndex: {
      handler(newVal, oldVal) {
        if (newVal !== oldVal) {
          //每次鼠标点击的时候如果选择的行变了,currentTop清0,拖动的时候再赋值
          this.currentTop = null;
        }
      },
    },
  },
  mounted() {
    if (this.draggable) {
      this.draggableHandler();
    }
  },

  methods: {
    draggableHandler() {
      const el = this.$refs.tableView.$el.querySelectorAll(
        ".el-table__body-wrapper > table > tbody"
      )[0];
      const _this = this;
      Sortable.create(el, {
        disabled: false, // 是否开启拖拽
        ghostClass: "sortable-ghost", //拖拽样式
        animation: 150, // 拖拽延时,效果更好看
        group: {
          // 是否开启跨表拖拽
          pull: false,
          put: false,
        },
        onChoose(e) {
          if (_this.currentIndex == null) {
            _this.currentIndex = e.oldIndex;
          }
        },
        onMove: function (e, originalEven) {
          /*
            e.dragged; // 被拖拽的对象
            e.related; // 被替换的对象
           */
          //拖动之前记住原始的物理位置
          if (_this.currentTop == null) {
            _this.currentTop = e.draggedRect.top || null;
          }
        },
        onEnd: (e) => {
          // 这里主要进行数据的处理,拖拽实际并不会改变绑定数据的顺序,这里需要自己做数据的顺序更改
          let arr = [];
          let flag = false;
          //sortablejs在对有children的列进行拖拽时,e.oldIndex会离谱地变成最低一级,而不是自身,所以放回自身位置的时候会触发其他操作
          //可以用他的位置去判断有没有发生改变来决定要不要做其他操作
          //物理位置没有发生改变
          let top = e.item.toRect ? e.item.toRect.top : null;
          if (top == _this.currentTop) {
            flag = true;
          }
          //拖动时放回原位
          if (e.oldIndex == e.newIndex || flag) return;
          //扁平化数组
          _this.tableData.map((item) => {
            treeToArray(item, arr);
          });

          //同层级之间拖拽的处理:
          // 1:向下拖-》把拖动前位置的accountid作为parentid,调接口getSortableData
          // 2: 向上拖-》不调接口但更节点数据updateTableDate
          //        ->同层级单不是最外层跟节点之间的拖动:交换节点,如果有children也一并带过去
          //        ->最外层跟节点之间的拖动:交换最外层的2棵树
          //不是同层级的时候
          //1:向上拖-》parentid就是拖动前位置上一级的accountid
          //2:像下拖-》把拖动前位置的accountid作为parentid,调接口getSortableData
          if (arr[e.oldIndex].parentId == arr[e.newIndex].parentId) {
            if (e.newIndex > e.oldIndex) {
              //同层级往下拖
              _this.$emit(
                "getSortableData",
                arr[e.oldIndex], //被托动的数据,
                arr[e.newIndex][_this.rowKey]
              );
            } else {
              //同层级往上拖
              if (
                arr[e.oldIndex].parentId == null &&
                arr[e.newIndex].parentId == null
              ) {
                //(同层级,最外层,上拖)最外层根节点互换
                let oldDataIndex = null;
                let newDataIndex = null;
                _this.tableData.forEach((item, index) => {
                  if (item[_this.rowKey] == arr[e.oldIndex][_this.rowKey]) {
                    oldDataIndex = index;
                  }
                  if (item[_this.rowKey] == arr[e.newIndex][_this.rowKey]) {
                    newDataIndex = index;
                  }
                });
                if (newDataIndex > 0) {
                  //不是拖到最上面
                  _this.$emit(
                    "getSortableData",
                    arr[e.oldIndex], //被托动的数据,
                    arr[e.newIndex][_this.rowKey]
                  );
                } else {
                  //最外成根节点互换
                  let currentIndex = _this.tableData.splice(oldDataIndex, 1)[0];
                  _this.tableData.unshift(currentIndex);
                  _this.$emit("updateTableDate", _this.tableData);
                }
              } else {
                // 同层级但不是最外层级之间拖拽
                _this.swapTreeNodes(
                  _this,
                  _this.tableData,
                  arr[e.oldIndex][_this.rowKey],
                  arr[e.newIndex][_this.rowKey]
                );
                _this.$emit("updateTableDate", _this.tableData);
              }
            }
          } else {
            // 不同层级往下拖
            if (e.newIndex > e.oldIndex) {
              _this.$emit(
                "getSortableData",
                arr[e.oldIndex], //被托动的数据,
                arr[e.newIndex][_this.rowKey]
              );
            } else {
              //不同层级向上拖
              if (e.newIndex > 0) {
                //不同层级向上拖,不是拖到最上面
                _this.$emit(
                  "getSortableData",
                  arr[e.oldIndex], //被托动的数据,
                  arr[e.newIndex - 1][_this.rowKey]
                );
              } else {
                //不同层级向上拖,拖到最上面
                _this.$emit(
                  "getSortableData",
                  arr[e.oldIndex], //被托动的数据,
                  arr[e.newIndex].parentId
                );
              }
            }
          }
          //拖动结束后重新置空
          _this.currentTop = null;
        },
      });
    },
    swapTreeNodes(_this, tree, id1, id2) {
      // 定义一个递归函数来查找节点
      function findNode(node, id) {
        if (node[_this.rowKey] === id) {
          return node;
        } else if (node.children) {
          for (const child of node.children) {
            const found = findNode(child, id);
            if (found) {
              return found;
            }
          }
        }
        return null;
      }

      // 查找要交换的两个节点和它们的父节点
      let node1 = null; //原来节点
      let node2 = null; //拖动位置原来的节点
      let parent1 = null;
      let parent2 = null;

      for (const node of tree) {
        const found1 = findNode(node, id1);
        const found2 = findNode(node, id2);

        if (found1) {
          node1 = found1;
          parent1 = findNode(node, found1.parentId);
        }

        if (found2) {
          node2 = found2;
          parent2 = findNode(node, found2.parentId);
        }
      }
      // 如果找到了两个节点以及它们的父节点,进行交换
      if (node1 && node2 && parent1 && parent2) {
        const index1 = parent1.children.indexOf(node1);
        const index2 = parent2.children.indexOf(node2);
        //插入节点,先删除被拖动的节点
        parent1.children.splice(index1, 1);
        parent1.children.splice(index2, 0, node1);
      }
      // }
    },
    //调用按钮方法
    handleButton(methods, row) {
      this.$emit("handleButton", methods, row);
    },
    //当为树状复选框表格————父组件调用获取勾选的数据
    //通过row的isSelect来判断,如果页面是用toggleRowSelection去勾选数据的,需要手动row.isSelect = true
    getSelection() {
      const result = [];
      const traverse = (node) => {
        if (node.isSelect) {
          const { children, ...res } = node;
          result.push(res);
        }
        if (node.children) {
          node.children.forEach(traverse);
        }
      };
      this.tableData.forEach(traverse);
      return result;
    },
    //复选框点击事件
    selectFun(selection, row) {
      //有父子集合的表格
      if (this.complexities) {
        this.setRowIsSelect(row);
        this.$emit("selectChange");
      } else {
        //普通表格
        this.$emit("selectChange", selection);
      }
    },
    //复选框点击事件
    setRowIsSelect(row) {
      let _this = this;
      //当点击父级点复选框时,当前的状态可能为未知状态,所以当前行状态设为false并选中,即可实现子级点全选效果
      if (row.isSelect === "") {
        row.isSelect = false;
        _this.$refs.tableView.toggleRowSelection(row, true);
      }
      row.isSelect = !row.isSelect;

      function selectAllChildrens(data) {
        data.forEach((item) => {
          item.isSelect = row.isSelect;
          _this.$refs.tableView.toggleRowSelection(item, row.isSelect);
          if (item.children && item.children.length) {
            selectAllChildrens(item.children);
          }
        });
      }

      function getSelectStatus(selectStatuaArr, data) {
        data.forEach((childrenItem) => {
          selectStatuaArr.push(childrenItem.isSelect);
          if (childrenItem.children && childrenItem.children.length) {
            getSelectStatus(selectStatuaArr, childrenItem.children);
          }
        });
        return selectStatuaArr;
      }
      function getLevelStatus(row) {
        //如果当前节点的parantId =0 并且有子节点,则为1
        //如果当前节点的parantId !=0 并且子节点没有子节点 则为3
        if (row.parentId == null) {
          if (row.children && row.children.length) {
            return 1;
          } else {
            return 4;
          }
        } else {
          if (!row.children || !row.children.length) {
            return 3;
          } else {
            return 2;
          }
        }
      }
      let result = {};
      //获取明确的节点,找到父节点
      function getExplicitNode(data, parentId) {
        data.forEach((item) => {
          if (item[_this.rowKey] == parentId) {
            result = item;
          }
          if (item.children && item.children.length > 0) {
            getExplicitNode(item.children, parentId);
          }
        });
        return result;
      }
      function operateLastLeve(row) {
        //操作的是子节点  1、获取父节点  2、判断子节点选中个数,如果全部选中则父节点设为选中状态,如果都不选中,则为不选中状态,如果部分选择,则设为不明确状态
        let selectStatuaArr = [];
        let item = getExplicitNode(_this.tableData, row.parentId);
        //item为row的父节点
        selectStatuaArr = getSelectStatus(selectStatuaArr, item.children);
        //判断父节点的所有孩子isSelect的状态
        if (
          selectStatuaArr.every((selectItem) => {
            return true == selectItem;
          })
        ) {
          //全部子节点选中
          item.isSelect = true;
          _this.$nextTick(() => {
            _this.$refs.tableView.toggleRowSelection(item, true);
          });
        } else if (
          selectStatuaArr.some((selectItem) => {
            return false == selectItem;
          })
        ) {
          //非全部子节点选中
          item.isSelect = false;
          _this.$refs.tableView.toggleRowSelection(item, false);
        } else {
          item.isSelect = "";
        }
        //则还有父级
        if (item.parentId != null) {
          operateLastLeve(item);
        }
      }
      //判断操作的是子级点复选框还是父级点复选框,如果是父级点,则控制子级点的全选和不全选

      //1、只是父级 2、既是子集,又是父级 3、只是子级
      let levelSataus = getLevelStatus(row);
      if (levelSataus == 1) {
        selectAllChildrens(row.children);
      } else if (levelSataus == 2) {
        selectAllChildrens(row.children);
        operateLastLeve(row);
      } else if (levelSataus == 3) {
        operateLastLeve(row);
      }
    },
    //检测表格数据是否全选
    checkIsAllSelect() {
      let oneDataIsSelect = [];
      this.tableData.forEach((item) => {
        oneDataIsSelect.push(item.isSelect);
      });
      //判断一级产品是否是全选.如果一级产品全为true,则设置为取消全选,否则全选
      let isAllSelect = oneDataIsSelect.every((selectStatusItem) => {
        return true == selectStatusItem;
      });
      return isAllSelect;
    },
    //表格全选事件
    selectAllFun(selection) {
      if (this.complexities) {
        let isAllSelect = this.checkIsAllSelect();
        this.tableData.forEach((item) => {
          item.isSelect = isAllSelect;
          this.$refs.tableView.toggleRowSelection(item, !isAllSelect);
          this.selectFun(selection, item);
        });
        this.$emit("selectChange");
      } else {
        this.$emit("selectChange", selection);
      }
    },

    iconClick(row) {
      this.$emit("iconClick", row);
    },
    //被选中的节点展开
    setDefaultExpand() {
      const selectedLeafNodes = [];
      for (const node of this.tableData) {
        this.findNodePath(node, [], selectedLeafNodes);
      }
      let data = Array.from(new Set(selectedLeafNodes.flat(Infinity)));
      data.forEach((item) => {
        this.$refs.tableView.toggleRowExpansion(item);
      });
    },
    //查找isselect节点的路径
    findNodePath(node, path = [], selectedLeaves = []) {
      path.push(node);

      if (node.isSelect && (!node.children || node.children.length === 0)) {
        selectedLeaves.push([...path]);
      }

      if (node.children && node.children.length > 0) {
        for (const child of node.children) {
          this.findNodePath(child, path, selectedLeaves);
        }
      }
      path.pop();
    },
    expandFun(row) {
      row.isExpand = !row.isExpand;
      this.$refs.tableView.toggleRowExpansion(row);
    },
  },
};
</script>

<style lang="scss" scoped>
a {
  color: blue;
  text-decoration: none; //取消下划线
}
a:hover {
  //移动到超链接出现下划线
  text-decoration: underline;
}
.baseTable ::v-deep .el-table__expand-icon {
  display: none;
}
.baseTable ::v-deep .el-table__placeholder {
  display: none;
}
</style>
//上面import进来的的treeToArray是这样的
export function treeToArray(obj, res = []) { // 默认初始结果数组为[]
  res.push(obj); // 当前元素入栈
  // 若元素包含children,则遍历children并递归调用使每一个子元素入栈
  if (obj.children && obj.children.length) {
    for (const item of obj.children) {
      treeToArray(item, res);
    }
  }
  return res;
}

组件的使用

<tableView
          ref="tableView"
          v-if="refresh"
          :maxHeight="600"
          :complexities="true"
          :needSelection="true"
          :columnData="columnData"
          :tableData="tableData"
          :tableOption="tableOption"
          :maxLevel="maxLevel"
          :rowKey="'id'"
          :loading="loading"
          :draggable="true"
          @handleButton="handleButton"
          @iconClick="shareholdingRatioUp"
          @getSortableData="getSortableData"
          @updateTableDate="updateTableDate"
        >
        </tableView>
        
      getSortableData(oldRow, parentId) {
      //oldrow为被拖动的一整条数据,parentid为被拖动后属于哪个父节点,拿到数据后给后台处理
          console.log('oldRow, parentId',oldRow, parentId)
      }
      updateTableDate(tableData) {
      this.$nextTick(() => {
        this.tableData = tableData;
      });
    },

最后

如果有什么问题,可以下方留言评论,有空的话会尽快给各位解答!觉得有用可以点赞+收藏。