基于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)
    })
}

相关文章

相关推荐
安晴晚风1 小时前
从0开始在linux服务器上部署SpringBoot和Vue
linux·运维·前端·数据库·后端·运维开发
前端小小王2 小时前
pnpm、Yarn 和 npm 的区别?
前端·npm·node.js
supermapsupport2 小时前
使用npm包的工程如何引入mapboxgl-enhance/maplibre-gl-enhance扩展包
前端·webpack·npm·supermap·mapboxgl
牛奔2 小时前
windows nvm 切换node版本后,npm找不到
前端·windows·npm·node.js
鱼大大博客2 小时前
Edge SCDN酷盾安全重塑高效安全内容分发新生态
前端·安全·edge
鸭梨山大。2 小时前
NPM组件包 vant部分版本内嵌挖矿代码
前端·安全·npm·node.js·vue
蟾宫曲7 小时前
在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)
前端·npm·vue3·vite·element-plus·计时器
秋雨凉人心7 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
liuxin334455667 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
qq13267029407 小时前
运行Zr.Admin项目(前端)
前端·vue2·zradmin前端·zradmin vue·运行zradmin·vue2版本zradmin