ElTable组件行可以拖动排序了?

695 阅读3分钟

平时业务开发中可能会遇到Table组件行拖动排序的需求,把排序后的数据给后端。ElTable组件暂时不支持,那来看看我是如果扩展这个功能的。

1. 先看下扩展后的基本使用姿势

<template>
  <el-draggable-table
    row-key="id"
    :data="tableData"
    style="width: 100%"
    @sort-data="handleSortData"
  >
    <el-table-column prop="date" label="日期" width="180" />
    <el-table-column prop="name" label="姓名" width="180" />
    <el-table-column prop="address" label="地址" />
  </el-draggable-table>
</template>

<script setup>
const tableData = [
  {
    date: '2024-08-01',
    name: 'Summer',
    address: 'No. 189, Grove St, Los Angeles',
    id: 1,
  },
  {
    date: '2024-08-02',
    name: 'Summer',
    address: 'No. 189, Grove St, Los Angeles',
    id: 2,
  },
  {
    date: '2024-08-03',
    name: 'Summer',
    address: 'No. 189, Grove St, Los Angeles',
    id: 3,
  },
  {
    date: '2024-08-04',
    name: 'Summer',
    address: 'No. 189, Grove St, Los Angeles',
    id: 4,
  },
  {
    date: '2024-08-05',
    name: 'Summer',
    address: 'No. 189, Grove St, Los Angeles',
    id: 5,
  },
]
const handleSortData = (data) => {
  console.log('排序后的数据', data)
}
</script>

嗯,使用姿势相比el-table只是把el-table -> el-draggable-table,这样就结束了。 看下运行效果

1.gif

2. 指定某列拖动

<template>
  <el-draggable-table
    border
    row-key="id"
    :data="tableData"
    style="width: 100%"
    :draggable-props="{
      handle: '.move',
    }"
    @sort-data="handleSortData"
  >
    <el-table-column prop="drag" label="" width="40" class-name="move">
      <el-icon><Rank /></el-icon>
    </el-table-column>
    <el-table-column prop="date" label="日期" width="180" />
    <el-table-column prop="name" label="姓名" width="180" />
    <el-table-column prop="address" label="地址" />
  </el-draggable-table>
</template>

<script setup>
import { Rank } from '@element-plus/icons-vue'

const tableData = [
  {
    date: '2024-08-01',
    name: 'Summer',
    address: 'No. 189, Grove St, Los Angeles',
    id: 1,
  },
  {
    date: '2024-08-02',
    name: 'Summer',
    address: 'No. 189, Grove St, Los Angeles',
    id: 2,
  },
  {
    date: '2024-08-03',
    name: 'Summer',
    address: 'No. 189, Grove St, Los Angeles',
    id: 3,
  },
  {
    date: '2024-08-04',
    name: 'Summer',
    address: 'No. 189, Grove St, Los Angeles',
    id: 4,
  },
  {
    date: '2024-08-05',
    name: 'Summer',
    address: 'No. 189, Grove St, Los Angeles',
    id: 5,
  },
]
const handleSortData = (data) => {
  console.log('排序后的数据', data)
}
</script>

可以看的出来,只需给指定需要拖动的列,加上一个类名,注意这个类名属性class-name本身就是el-table-column组件自带的,然后给el-draggable-table组件指定handle: '.move'属性即可,这样整行拖动就改成了指定某列拖动了,看下效果

2.gif

3. 可扩展

有了上述功能后且保留了ElTable组件的所有功能,那么就可以基于该组件封装其它组件了,比如下面的这个,大家可能也遇到过类似的需求

3.gif 这个demo的代码就不黏贴了,因为不属于本次帖子的主题,想看的同学可以点这个文档地址

4. ElDraggableTable组件实现

直接贴代码

import { defineComponent, h, nextTick } from 'vue'
import { ElTable, useNamespace } from 'element-plus'
import { UseSortable } from '@vueuse/integrations/useSortable/component'
import { moveArrayElement } from '@vueuse/integrations/useSortable'
import { isArray } from '@element-plus/utils'
import { draggableTableProps } from './draggable-table'
import type { VNode } from 'vue'

const ElTableComp: any = { ...ElTable }

const SORT_DATA_EVENT = 'sort-data'

// 重写 TableBody render 函数
const TableBody = {
  ...ElTableComp.components.TableBody,
  render() {
    const { wrappedRowRender, store } = this
    const data = store.states.data.value || []

    const tableIns = this.context
    const { draggableProps } = tableIns.props

    const options = {
      animation: 300,
      ...draggableProps,
      onUpdate: (e: any) => {
        const { oldIndex, newIndex } = e
        // update list
        if (isArray(store.states.data.value)) {
          moveArrayElement(store.states.data, oldIndex, newIndex, e)
          nextTick(() => {
            const newData = store.states.data.value
            store.commit('setData', newData)
            tableIns.emit(SORT_DATA_EVENT, newData, { e })
          })
        }
      },
    }

    return h(
      UseSortable,
      { options, tag: 'tbody', tabIndex: -1, modelValue: [] },
      {
        default: () =>
          data.reduce((acc: VNode[], row: any) => {
            return acc.concat(wrappedRowRender(row, acc.length))
          }, []),
      }
    )
  },
}

const ElTableClone = {
  ...ElTableComp,
  name: 'ElTableClone',
  components: { ...ElTableComp.components, TableBody },
  emits: [...ElTableComp.emits, SORT_DATA_EVENT],
  props: {
    ...ElTableComp.props,
    ...draggableTableProps,
  },
}

const ElDraggableTable = defineComponent({
  name: 'ElDraggableTable',
  setup(props, { slots, attrs }) {
    const ns = useNamespace('draggable-table')

    return () =>
      h(
        ElTableClone,
        {
          ...attrs,
          class: ns.b(),
        },
        slots
      )
  },
})

export default ElDraggableTable

  • 需要熟悉vue render函数,不太熟悉的同学可以去vue官网学习下
  • 需要了解el-table组件的内部实现(大概看看即可,不必深究,深究的话当然更好)
  • 接下来就是上面重写 TableBody render 函数即可
  • 因为是修改ElTable组件的render,整个组件并不涉及css

该组件 DraggableTable文档地址

该组件 DraggableTable源码地址

喜欢的同学,希望可以给个小星星,谢谢