基于vxe-table+Sortable的树形表格行拖动

背景

目前在公司负责的项目是低代码平台,有个需求是需要在工作流中配置工作表的字段权限并且可以拖动排序。在工作表编辑中拖拽配置控件使用的 sortablejs,表格渲染涉及到单元格编辑使用的 vxe-table,所以毫无疑问就是通过这两个去实现啦。

普通表格行拖动排序

拖动结束事件中有oldIndexnewIndex ,所以就是两个位置上的元素互换一下就好了。 点击查看 vxe-table 官网 demo

那树形表格如何实现呢

官网也有 demo,但不符合我们的需求。只能在同一层级才允许相互拖动排序。所以想的就是在拖动事件中判断是否在同一层级,不在同一层级就不允许拖动。然后把树形数据转为列表再进行排序。

代码实现

js 复制代码
const createTableSort = () => {
  const el = document.querySelector('.body--wrapper>.vxe-table--body tbody') as HTMLElement
  if (!el) return
  if (sortTable.value) return
  sortTable.value = Sortable.create(el, {
    disabled: false, // 是否开启拖拽
    animation: 150, // 拖拽延时,效果更好看
    handle: '.icon-move',
    sort: true,
    onMove: (evt: any) => {
      const { dragged, related } = evt

      const dragElClassList: string[] = []
      dragged.classList.forEach((element: string) => {
        if (element.includes('row--level')) {
          dragElClassList.push(element)
        }
      })

      const relatelClassList: string[] = []
      related.classList.forEach((element: string) => {
        if (element.includes('row--level')) {
          relatelClassList.push(element)
        }
      })
      // 不同层级的不能互相拖动
      // 这里采用比较笨的方法实现,大佬们指点一下有什么更好的实现
      // isEqual采用lodash的方法,判断两个值是否相等
      if (!isEqual(dragElClassList, relatelClassList)) return false
      return true
    },
    onEnd: async (evt: any) => {
      const { newIndex, oldIndex } = evt
      if (newIndex === oldIndex) return
      const expandRow = tableRef.value.getTreeExpandRecords()
      // 展开的数据id
      const expandId = expandRow.map((item: RowVO) => item.id)
      const newTable: any[] = []
      // 将多维数据展开存为一维数据
      const cloneData = tableData.value.map((o) => o)
      cloneData.forEach((item) => {
        // 如果有子类并且该树已经开展,将子类也push进去
        if (expandId.includes(item.id) && item.children && item.children.length > 0) {
          newTable.push({
            ...item,
            useChild: false
          })
          item.children.forEach((i) => {
            i.parentId = item.id
            newTable.push(i)
          })
        } else {
          newTable.push({
            ...item,
            useChild: true
          })
        }
      })
      const currRow = cloneDeep(newTable[oldIndex])
      newTable?.splice(oldIndex, 1)
      newTable?.splice(newIndex, 0, currRow)
      // 然后把排序成功后的一维数据转为树形数据
      const result: any[] = []
      newTable.forEach((item) => {
        // 一级
        if (!item.parentId) {
          // 设置子级
          if (!item.useChild) {
            item.children = newTable.filter((one) => one.parentId === item.id)
          }
          result.push(item)
        }
      })
      // 赋值到数据,可以传给后端
      tableData.value = result
      nextTick(() => {
        // 重新加载数据
        tableRef.value.reloadData(result)
        nextTick(() => {
            // 之前展开的数据重新展开
          tableRef.value.setTreeExpand(expandRow, true)
        })
      })
    }
  })
}

思考更多:如果树的层级有很深呢

层级很深的话呢...🤔 如果只拖动第三层,按照我上面的方法实现思路起来就很耗性能,每拖动一次就得来回转换数据,但其实我只要更换拖动那一层元素的顺序就好了。但是在拖动事件中我们只能拿到显示数据的索引,怎么和真实树形数据对应上呢?好像建立一个映射是不是可以呢,嗯,那就试试!

你们有更好的方法快来告诉我呀~~

查看演示效果

查看完整代码

js 复制代码
// 主要是 onEnd 事件的逻辑有区别。
onEnd: (evt: any) => {
    const { newIndex, oldIndex } = evt
    if (newIndex === oldIndex) return
    const expandRow = tableRef.value.getTreeExpandRecords()
    // 展开的数据id
    const expandId = expandRow.map((item: RowVO) => item.id)
    // 将多维数据展开存为一维数据
    const cloneData = tableData.value.map((o) => o)
    let count = -1
    const result: any = {}
    const getMap = (data: any[], parentIndex = '') => {
        data.forEach((item, index) => {
            // 如果有子类,将子类也push进去
            item.selfIndex = parentIndex === '' ? `${index}` : `${parentIndex}-${index}`
            result[++count] = item.selfIndex
            if (expandId.includes(item.id) && item.children && item.children.length > 0) {
                getMap(item.children, item.selfIndex)
            }
        })
    }
    getMap(cloneData)
    // 交换对应的
    const oldRealIndex = result[oldIndex]
    const newRealIndex = result[newIndex]
    if (oldRealIndex.length === 1 && newRealIndex.length === 1) {
        const currRow = cloneDeep(tableData.value[oldRealIndex])
        tableData.value?.splice(oldRealIndex, 1)
        tableData.value?.splice(newRealIndex, 0, currRow)
    } else {
        const parentIndex = oldRealIndex.slice(0, -2)
        const tempOldIndex = oldRealIndex.slice(oldRealIndex.length - 1)
        const tempNewIndex = newRealIndex.slice(oldRealIndex.length - 1)
        const getCurrent = (arr: RowVO[]) => {
            arr.forEach((item) => {
                if (item.selfIndex === parentIndex && item.children?.length) {
                    const currRow = item.children[tempOldIndex]
                    item.children?.splice(tempOldIndex, 1)
                    item.children?.splice(tempNewIndex, 0, currRow)
                }
                if (item.children?.length) {
                    getCurrent(item.children)
                }
            })
        }
        getCurrent(tableData.value)
    }
    console.log('tableData', tableData.value)
    // 赋值到数据,可以传给后端
    tableRef.value.reloadData(tableData.value)
    nextTick(() => {
        tableRef.value.setTreeExpand(expandRow, true)
    })
}

相关文章

相关推荐
everyStudy1 小时前
前端五种排序
前端·算法·排序算法
甜兒.2 小时前
鸿蒙小技巧
前端·华为·typescript·harmonyos
Jiaberrr5 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy6 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白6 小时前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、6 小时前
Web Worker 简单使用
前端
web_learning_3216 小时前
信息收集常用指令
前端·搜索引擎
tabzzz6 小时前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
LvManBa6 小时前
Vue学习记录之六(组件实战及BEM框架了解)
vue.js·学习·rust
200不是二百6 小时前
Vuex详解
前端·javascript·vue.js