SortableJS 实现 Element UI Table行拖拽排序功能
Element UI Table组件基本使用(官方文档)Sortable.js 官方文档
实现步骤
1. 安装SortableJS
通过npm安装:
npm install sortablejs --save或使用国内CDN(推荐):
2. 基础拖拽功能实现
在Vue组件中,通过ref获取Table的body部分,初始化Sortable实例:
<template>
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>ref="dragTable"
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>:data="tableData"
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>row-key="id"
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>border
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>>
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table-column type="index" width="50"></el-table-column>
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table-column prop="name" label="名称"></el-table-column>
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table-column prop="order" label="排序"></el-table-column>
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table></el-table>
</template>
3. 实现原理详解
拖拽排序功能的实现主要分为三个核心步骤:
3.1 初始化Sortable实例
在Vue的mounted生命周期钩子中,通过Table组件的ref获取到表格的DOM元素,并找到包含行数据的tbody元素。SortableJS通过监听这个tbody元素来实现拖拽功能。
关键代码位于packages/table/src/table.vue的渲染结构中,表格主体使用了.el-table__body-wrapper类包裹,其中的tbody就是我们需要监听的目标元素。
3.2 拖拽事件处理
SortableJS提供了丰富的事件回调,我们主要使用onEnd事件在拖拽结束后触发数据更新。拖拽过程中,SortableJS会自动处理DOM元素的位置变化,我们只需要关注数据层面的调整。
3.3 数据排序与同步
当拖拽结束后,通过oldIndex和newIndex确定数据移动的方向和距离,然后调整数据数组中元素的顺序,并更新排序号。最后可以选择将新的排序结果同步到后端数据库。
4. 高级功能扩展
4.1 禁用特定行拖拽
有时我们需要禁止某些行的拖拽功能,可以通过Sortable的filter配置实现:
initSortable() {
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>const tbody = this.$refs.dragTable.$el.querySelector('.el-table__body-wrapper tbody')
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>this.sortable = new Sortable(tbody, {
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>animation: 150,
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// 过滤不可拖拽的行
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>filter: '.no-drag',
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// 拖拽结束后的回调
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>onEnd: (evt) => {
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>this.handleDataSort(evt.oldIndex, evt.newIndex)
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>}
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>})
}然后在Table组件中为特定行添加no-drag类:
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>methods: {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>rowClassName({row}) {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// 对id为2的行禁用拖拽<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>return row.id === 2 ? 'no-drag' : ''<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>}}4.2 拖拽时样式自定义
通过CSS可以自定义拖拽过程中的样式:
/* 拖拽过程中的行样式 */.el-table__body tr.sortable-ghost {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>opacity: 0.8;<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>background-color: #f5f5f5;} /* 拖拽时的占位符样式 */.el-table__body tr.sortable-placeholder {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>background-color: #e9f7ef;<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>border: 1px dashed #409eff;} /* 禁止拖拽的行样式 */.el-table__body tr.no-drag {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>opacity: 0.6;<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>cursor: not-allowed;}4.3 结合后端实现持久化
在实际应用中,我们需要将排序结果保存到后端,实现数据持久化:
methods: {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>async handleDataSort(oldIndex, newIndex) {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// 处理数据排序(同上)<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// ...<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// 保存到后端<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>try {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>await this.$api.saveSortOrder(newArray.map(item => ({<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>id: item.id,<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>order: item.order<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>})))<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>this.$message.success('排序已保存')<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>} catch (error) {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>this.$message.error('保存失败,请重试')<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// 保存失败时恢复原排序<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>this.tableData = [...this.originalData]<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>}<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>}}5. 性能优化建议
对于数据量较大的表格,建议添加以下优化措施:
[*]虚拟滚动:结合Element UI的InfiniteScroll(packages/infinite-scroll)实现虚拟滚动,只渲染可见区域的行。
[*]节流处理:如果需要在拖拽过程中实时更新某些数据,可以对更新函数进行节流处理:
import { throttle } from 'throttle-debounce' // 在methods中updateDuringDrag: throttle(100, function(row, position) {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// 实时更新逻辑})
[*]禁用不必要的动画:对于数据量超过100行的表格,可以考虑关闭Sortable的animation选项以提高性能。
6. 常见问题解决方案
6.1 拖拽后表格行高度异常
这通常是由于Table组件的高度计算问题导致的,可以在数据更新后调用Table的doLayout方法重新计算布局:
this.$nextTick(() => {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>this.$refs.dragTable.doLayout()})相关代码位于packages/table/src/table.vue的doLayout方法:
doLayout() {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>if (this.shouldUpdateHeight) {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>this.layout.updateElsHeight();<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>}<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>this.layout.updateColumnsWidth();}6.2 固定列(fixed)拖拽问题
当表格使用了fixed列时,拖拽可能会出现视觉错位。解决方案是同时监听固定列和主表格的拖拽事件:
initSortable() {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// 主表格tbody<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>const mainTbody = this.$refs.dragTable.$el.querySelector('.el-table__body-wrapper tbody')<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// 左侧固定列tbody<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>const fixedLeftTbody = this.$refs.dragTable.$el.querySelector('.el-table__fixed-body-wrapper tbody')<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// 右侧固定列tbody<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>const fixedRightTbody = this.$refs.dragTable.$el.querySelector('.el-table__fixed-right-body-wrapper tbody')<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// 为三个tbody都初始化Sortable<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>.forEach(tbody => {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>if (tbody) {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>new Sortable(tbody, {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>// 配置同上,但只在主表格上处理数据更新<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>onEnd: (evt) => {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>if (tbody === mainTbody) {<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>this.handleDataSort(evt.oldIndex, evt.newIndex)<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>}<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>}<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>})<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table><el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>}<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
>
</el-table>})}
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! 新版吗?好像是停更了吧。 喜欢鼓捣这些软件,现在用得少,谢谢分享! 感谢分享,下载保存了,貌似很强大 分享、互助 让互联网精神温暖你我 喜欢鼓捣这些软件,现在用得少,谢谢分享! 懂技术并乐意极积无私分享的人越来越少。珍惜
页:
[1]