本文将详细介绍如何使用 SortableJS 在 Vue3 + Element Plus 项目中实现 el-table 表格行的拖拽排序功能。
快速前往:
SortableJS GitHub
SortableJS中文网
实现效果

技术栈
- Vue 3
- Element Plus
- SortableJS
核心实现代码
1. 安装依赖
bash
npm install sortablejs
2. 表格组件实现
js
<template>
<div class="table-draggable">
<el-table :data="dataList" row-key="id" v-loading="loading">
<el-table-column label="排序" width="80" align="center">
<img class="icon-touch" src="@/assets/icon/touchMove.svg" />
</el-table-column>
<el-table-column
v-for="item in head"
:key="item.prop"
:label="item.label"
:prop="item.prop"
:width="item.width">
</el-table-column>
<el-table-column label="操作" width="140" align="center" fixed="right">
<template #default="{ row }">
<el-button type="danger" link @click="handleDelele(row)">删除</el-button>
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
import sortable from 'sortablejs'
import { onMounted, ref } from 'vue'
// 表格数据
const dataList = ref([])
const loading = ref(false)
// 表头配置
const head = [
{ label: '阶段序号', prop: 'stageNo' },
{ label: '开业阶段', prop: 'stageName' },
{ label: '奖励说明', prop: 'rewardDesc' }
]
// 初始化拖拽功能
const initSortable = () => {
// 获取表格的 tbody 元素
const tbody = document.querySelector('.table-draggable .el-table__body-wrapper tbody')
if (!tbody) return
// 创建 Sortable 实例
sortable.create(tbody, {
// 指定可拖拽的子元素
draggable: '.table-draggable .el-table__row',
// 动画效果
animation: 150,
// 拖拽结束回调
onEnd({ newIndex, oldIndex }) {
// newIndex 拖动到的新的索引;oldIndex 没拖动前的索引
// 处理数据重新排序
const currRow = dataList.value.splice(oldIndex, 1)[0]
dataList.value.splice(newIndex, 0, currRow)
// 调用 API 保存排序结果
saveSortOrder()
}
})
}
// 保存排序结果到后端
const saveSortOrder = () => {
const stageIds = dataList.value.map(x => x.id)
// 调用 API 接口
api.dragSortOpeningStageList({ stageIds })
}
// 组件挂载后初始化拖拽
onMounted(() => {
loadData()
initSortable()
})
// 加载数据
const loadData = () => {
loading.value = true
api.getOpeningStageList()
.then(res => {
dataList.value = res.data
})
.finally(() => {
loading.value = false
})
}
</script>
<style scoped>
.table-draggable {
margin-top: 8px;
}
.icon-touch {
width: 14px;
cursor: pointer;
}
</style>
实现要点解析
1. SortableJS 初始化
javascript
sortable.create(tbody, {
draggable: '.table-draggable .el-table__row',
animation: 150,
onEnd({ newIndex, oldIndex }) {
// 处理排序逻辑
}
})
关键参数说明:
draggable
: 指定可拖拽的元素选择器animation
: 拖拽动画时长(毫秒)onEnd
: 拖拽结束时的回调函数
2. 数据同步处理
当用户完成拖拽后,我们需要同步更新前端数据和后端存储:
javascript
onEnd({ newIndex, oldIndex }) {
// 1. 更新前端数据
const currRow = dataList.value.splice(oldIndex, 1)[0]
dataList.value.splice(newIndex, 0, currRow)
// 2. 保存到后端
const stageIds = dataList.value.map(x => x.id)
api.dragSortOpeningStageList({ stageIds })
}
3. 获取正确的 DOM 元素
由于 Element Plus 的表格结构比较复杂,需要准确找到 tbody 元素:
javascript
const tbody = document.querySelector('.table-draggable .el-table__body-wrapper tbody')
进阶:拖拽表格组件
可以把上述代码整合为可复用的拖拽表格组件 DragTable.vue
js
<template>
<div class="drag-table-container">
<div class="table-header">
<span class="title">{{ title }}</span>
<el-button type="primary" @click="$emit('add')">添加</el-button>
</div>
<div class="table-draggable">
<el-table :data="data" row-key="id" v-loading="loading" :element-loading-text="loadingText">
<el-table-column label="排序" width="80" align="center">
<img class="drag-handle" :src="`${mdFileBaseUrl}/stand/icon/touchMove.svg`" />
</el-table-column>
<el-table-column
v-for="column in columns"
:key="column.prop"
:label="column.label"
:prop="column.prop"
:width="column.width"
:formatter="column.formatter">
</el-table-column>
<el-table-column
v-if="showActions"
label="操作"
width="140"
align="center"
fixed="right">
<template #default="{ row }">
<slot name="actions" :row="row">
<el-button type="danger" link @click="$emit('delete', row)">删除</el-button>
<el-button type="primary" link @click="$emit('edit', row)">编辑</el-button>
</slot>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue'
import sortable from 'sortablejs'
const props = defineProps({
data: {
type: Array,
default: () => []
},
columns: {
type: Array,
default: () => []
},
loading: {
type: Boolean,
default: false
},
loadingText: {
type: String,
default: '正在加载中'
},
title: {
type: String,
default: '数据列表'
},
showActions: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['sort-end', 'add', 'edit', 'delete'])
// 初始化拖拽
const initSortable = () => {
nextTick(() => {
const tbody = document.querySelector('.table-draggable .el-table__body-wrapper tbody')
if (!tbody) return
sortable.create(tbody, {
draggable: '.table-draggable .el-table__row',
animation: 150,
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
onEnd({ newIndex, oldIndex }) {
if (newIndex === oldIndex) return
const newData = [...props.data]
const [movedItem] = newData.splice(oldIndex, 1)
newData.splice(newIndex, 0, movedItem)
emit('sort-end', {
newIndex,
oldIndex,
newData,
movedItem
})
}
})
})
}
onMounted(() => {
initSortable()
})
</script>
<style scoped>
.drag-table-container {
width: 100%;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.table-header .title {
font-size: 16px;
font-weight: bold;
}
.drag-handle {
width: 14px;
cursor: grab;
}
:deep(.sortable-ghost) {
opacity: 0.5;
background: #c8ebfb;
}
:deep(.sortable-chosen) {
background-color: #ecf5ff;
}
</style>
使用示例
js
<template>
<DragTable
:data="list"
:columns="columns"
:loading="loading"
title="测试阶段管理"
@sort-end="handleSortEnd"
@add="handleAdd"
@edit="handleEdit"
@delete="handleDelete"
/>
</template>
<script setup>
import { ref } from 'vue'
import DragTable from './components/DragTable.vue'
const list = ref([
{ id: 1, stageName: '阶段1', rewardDesc: '奖励100元' },
{ id: 2, stageName: '阶段2', rewardDesc: '奖励200元' },
{ id: 3, stageName: '阶段3', rewardDesc: '奖励300元' },
{ id: 4, stageName: '阶段4', rewardDesc: '奖励400元' }
])
const loading = ref(false)
const columns = [
{ label: '阶段名称', prop: 'stageName', width: 100},
{ label: '奖励说明', prop: 'rewardDesc' }
]
const handleSortEnd = ({ newData }) => {
// 调用 API 保存排序
console.log('排序后的数组:', newData)
}
const handleAdd = () => {
// 添加逻辑
}
const handleEdit = (row) => {
// 编辑逻辑
}
const handleDelete = (row) => {
// 删除逻辑
}
</script>
注意事项
- DOM 元素获取时机:确保在表格渲染完成后初始化 SortableJS
- 数据同步:拖拽后要及时更新数据和调用后端接口
- 性能优化:对于大数据量的表格,考虑虚拟滚动
- 移动端适配:SortableJS 支持触摸事件,但需要测试移动端体验
总结
通过 SortableJS 实现 Element Plus 表格的拖拽排序功能,可以大大提升用户体验。关键点在于正确获取 DOM 元素、处理数据同步以及提供良好的视觉反馈。本文提供的组件可以作为一个基础模板,根据实际需求进行扩展和优化。