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>
相关推荐
kyriewen5 小时前
我手写了一个 EventEmitter,面试官追问了 6 个问题——第 4 个我没答上来
前端·javascript·面试
IT_陈寒5 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
小林攻城狮6 小时前
使用 Transport 节流解决 Vercel AI SDK 流式渲染卡死问题
前端·react.js
前端缘梦6 小时前
告别 TS 运行时类型漏洞!Zod 完整入门实战教程(前端 / 全栈必备)
前端·react.js·全栈
the_answer6 小时前
Webpack vs Vite 深度对比分析
前端·webpack
转转技术团队6 小时前
验证码识别实战:前端不写页面,改训模型了?
前端
MomentYY6 小时前
Temperature:AI 的“脑洞旋钮”
前端·llm·ai编程
远航_6 小时前
OpenSpec 完整详细介绍
前端·后端
召钱熏7 小时前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
SkyWalking中文站7 小时前
认识 Horizon UI · 1/17:SkyWalking 新一代可观测性控制台
运维·前端·监控