el-table实现行拖拽(包含展开项)

效果:

借助第三方插件sortablejs来实现

具体步骤:

1.安装sortablejs

npm install sortablejs

2.在vue文件中引入sortablejs

TypeScript 复制代码
import Sortable from 'sortablejs'

3.在el-table指定row-key,这个row-key必须是唯一的,否则无法正确排序

TypeScript 复制代码
<el-table
    ref="tableRef"
    v-bind="$attrs"
    :data="localData"
    :row-key="rowKey"
    :class="tableClass"
    :tree-props="treeProps"
    :row-class-name="rowClassName"
    :empty-text="emptyText"
    @expand-change="handleExpandChange"
  >
    <template v-for="column in columns" :key="column.key || column.prop || column.label">
      <el-table-column v-if="column.type === 'expand'" type="expand" :width="column.width" :fixed="column.fixed">
        <template #default="scope">
          <slot :name="column.slotName || 'expand'" v-bind="scope" :column-config="column" />
        </template>
      </el-table-column>
      <el-table-column
        v-else
        :prop="column.prop"
        :label="column.label"
        :width="column.width"
        :min-width="column.minWidth"
        :align="column.align"
        :fixed="column.fixed"
        :show-overflow-tooltip="column.showOverflowTooltip"
      >
        <template #default="scope">
          <slot v-if="column.slotName" :name="column.slotName" v-bind="scope" :column-config="column" />
          <slot v-else name="cell" v-bind="scope" :column-config="column">
            {{ column.prop ? scope.row[column.prop] : '' }}
          </slot>
        </template>
      </el-table-column>
    </template>
  </el-table>

4.具体代码实现(ts)

TypeScript 复制代码
nextTick(() => {
    const tbody = tableRef.value?.$el?.querySelectorAll('.el-table__body-wrapper tbody')[1]
    if (!tbody) return

    sortableInstance = Sortable.create(tbody, {
      animation: 150, 
      disabled: false, //false 为启用
      onChoose(e: any) {}, //选中行时
      onEnd(evt: any) { //拖拽完成
        const { oldIndex, newIndex } = evt

        if (oldIndex === newIndex) return

        const newData = [...props.data]
        const moved = newData.splice(oldIndex, 1)[0]
        newData.splice(newIndex, 0, moved)
        emit('update:data', newData)
      },
    })
  })

**注意:**如果无法实现拖拽,可检查一下tbody元素的查找是否有问题。

我的是:tableRef.value?.$el?.querySelectorAll('.el-table__body-wrapper tbody')1

我这里使用了1是因为我查找到三个tbody,我用到的是第二个。

5.完整代码(这是我封装的表格组件,数据由父组件传入)

TypeScript 复制代码
<template>
  <el-table
    ref="tableRef"
    v-bind="$attrs"
    :data="localData"
    :row-key="rowKey"
    :class="tableClass"
    :tree-props="treeProps"
    :row-class-name="rowClassName"
    :empty-text="emptyText"
    @expand-change="handleExpandChange"
  >
    <template v-for="column in columns" :key="column.key || column.prop || column.label">
      <el-table-column v-if="column.type === 'expand'" type="expand" :width="column.width" :fixed="column.fixed">
        <template #default="scope">
          <slot :name="column.slotName || 'expand'" v-bind="scope" :column-config="column" />
        </template>
      </el-table-column>
      <el-table-column
        v-else
        :prop="column.prop"
        :label="column.label"
        :width="column.width"
        :min-width="column.minWidth"
        :align="column.align"
        :fixed="column.fixed"
        :show-overflow-tooltip="column.showOverflowTooltip"
      >
        <template #default="scope">
          <slot v-if="column.slotName" :name="column.slotName" v-bind="scope" :column-config="column" />
          <slot v-else name="cell" v-bind="scope" :column-config="column">
            {{ column.prop ? scope.row[column.prop] : '' }}
          </slot>
        </template>
      </el-table-column>
    </template>
  </el-table>
</template>

<script setup lang="ts">
import { ref, computed, onBeforeUnmount, nextTick, watch } from 'vue'
import Sortable from 'sortablejs'
import type { TableInstance } from 'element-plus'
import type { OperationTableColumn, OperationTableRow, OperationTableRowClassName } from '@/views/statistics/types'

defineOptions({ inheritAttrs: false })

const props = withDefaults(
  defineProps<{
    columns: OperationTableColumn[]
    data: OperationTableRow[]
    rowKey?: string | ((row: OperationTableRow) => string)
    treeProps?: Record<string, string | boolean>
    rowClassName?: OperationTableRowClassName
    emptyText?: string
    tableClass?: string
    isSort?: boolean
  }>(),
  {
    rowKey: 'id',
    treeProps: () => ({ children: 'children' }),
    rowClassName: '',
    emptyText: '',
    tableClass: '',
  },
)

const emit = defineEmits<{
  'update:data': [val: OperationTableRow[]]
  'expand-change': [row: OperationTableRow, expanded: OperationTableRow[] | boolean]
}>()
let sortableInstance: Sortable | null = null

const localData = computed({
  get() {
    return props.data
  },
  set(val) {
    emit('update:data', val)
  },
})
const tableRef = ref<TableInstance>()

const handleExpandChange = (row: OperationTableRow, expanded: OperationTableRow[] | boolean) => {
  emit('expand-change', row, expanded)
}

const toggleRowExpansion = (row: OperationTableRow, expanded?: boolean) => {
  tableRef.value?.toggleRowExpansion(row, expanded)
}
const initSortable = () => {
  if (!props.isSort) return

  nextTick(() => {
    const tbody = tableRef.value?.$el?.querySelectorAll('.el-table__body-wrapper tbody')[1]
    if (!tbody) return

    sortableInstance = Sortable.create(tbody, {
      animation: 150,
      disabled: false,
      onChoose(e: any) {},
      onEnd(evt: any) {
        const { oldIndex, newIndex } = evt

        if (oldIndex === newIndex) return

        const newData = [...props.data]
        const moved = newData.splice(oldIndex, 1)[0]
        newData.splice(newIndex, 0, moved)
        emit('update:data', newData)
      },
    })
  })
}

const destroySortable = () => {
  if (sortableInstance) {
    sortableInstance.destroy()
    sortableInstance = null
  }
}

watch(
  () => props.isSort,
  val => {
    if (val) {
      initSortable()
    } else {
      destroySortable()
    }
  },
  { immediate: true },
)

onBeforeUnmount(() => {
  destroySortable()
})
defineExpose({
  tableRef,
  toggleRowExpansion,
})
</script>
相关推荐
麦麦麦当劳大王8 小时前
【油猴脚本】Boss直聘 岗位职责复制
开发语言·前端·javascript
小lu飞9 小时前
2026小红书小工具怎么制作:纯前端 ZIP 打包、沙箱限制与笔记挂载流程
前端·笔记·ai·ai编程
smallcelebration10 小时前
151 从0开始搭建后端项目
前端·firefox
wangruofeng10 小时前
手机和电脑传文件,除了微信还有 LocalSend
前端
excel10 小时前
基于 GPT 的自动发布文章接口方案:从一句话或一张图片生成并发布内容
前端·javascript·后端
其实防守也摸鱼12 小时前
个人学习 Pwn 思路:从基础到漏洞利用的进阶路径
大数据·前端·人工智能·chrome·算法·安全·网络安全
朱涛的自习室13 小时前
212 个需求、22 万行代码:Agent 接管开源项目的 19 天
android·前端·人工智能
aZhe的全栈知识分享13 小时前
AI 还原 Figma 效果总达不到预期?先别急着让它写代码
前端·后端·面试
review4454313 小时前
Codex Hooks 入门实战:生命周期事件、matcher 与5分钟跑通一个钩子
前端·agent·vibecoding
函数小陈13 小时前
阿里云部署DSH与RAM完成运维
前端·后端