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>
相关推荐
林恒smileZAZ1 小时前
CSS 滚动驱动动画(scroll-timeline):无 JS 实现滚动特效
前端·javascript·css
LIO1 小时前
React Router 极简指南(v6+)
前端·react.js
明月_清风1 小时前
从 AST 视角看透前端工程化:一条编译管线如何串联起所有工具
前端
架构源启1 小时前
2026 进阶篇:Spring Boot响应式编程 + Spring AI 1.1.4 流式实战 + Vue前端完整实现(避坑指南)
java·前端·vue.js·人工智能·spring boot·spring·ai编程
白开水都有人用1 小时前
前端 AES 加密 + 后端解密 + MD5 校验登录
前端
懒人村杂货铺1 小时前
Express + TypeScript 后端通用标准规范
javascript·typescript·express
OpenTiny社区2 小时前
还在手写 AI 聊天页?这款 Vue3 气泡组件,直接搞定流式对话!
前端·vue.js·ai编程
毛骗导演2 小时前
Cladue Code 源码解析-键盘事件与 Vim 模式:parse-keypress 解析状态机
前端·架构
渐儿2 小时前
GLB 模型压缩 — 完整流程与代码映射
前端