前言
SortableJS 是一个轻量级的 JavaScript 库,可以实现对 HTML 元素进行排序和拖拽操作。而 VXE-table 是一款基于 Vue.js 的表格组件库,提供了丰富的表格功能和交互方式。将这两者结合起来,可以很方便地为 VXE-table 添加表头左右拖拽的功能,使得用户可以根据自己的需求自由地调整表格的布局和显示效果。本文将详细介绍如何使用 SortableJS 实现这一功能,并提供代码实现和示例演示,帮助读者更好地了解和应用这一技术。
先来看看实际效果
实现原理
- 使用SortableJS完成拖拽功能
- 使用VXE-table的getColumns获取表格列数据
- 结合拖拽信息和列数据完成排序
- 使用VXE-table的loadColumn完成排序表头的重载
- 使用用户ID和表格ID加排序数据,完成用户自定义排序数据的保存
- 最后通过在标签中写入指令v-vxtableDrag="你的表格ID"即可
实现思路
首先,我们需要安装 Sortable.js 库。在 Vue.js 项目中,可以使用 npm 或 yarn 安装该库。安装完成后,我们可以在 Vue 组件中引入该库:
import { Sortable } from 'sortablejs';
接下来,我们需要定义一个 Vue 指令来绑定表格的拖拽排序功能。在 Vue 组件中,可以使用 bind
方法来定义一个指令。在该方法中,我们可以获取到指令绑定的元素、绑定的值和组件实例对象。例如,我们可以这样定义一个指令:
export default {
bind: function (el, binding, vnode) {
// ...
}
}
在这个指令中,el
是指令绑定的元素,binding
是指令绑定的值,vnode
是组件实例对象。接下来,我们可以在该指令中实现表格列的拖拽排序功能。
首先,我们需要获取表格的列信息。在 Vue 组件中,可以通过 $refs
属性来获取到表格组件的实例对象。例如,我们可以这样获取到表格的实例对象:
var table = vnode.child.$refs.table;
接下来,我们可以通过表格实例对象的 getColumns
方法来获取到表格的列信息:
var oldRenderCol = table.getColumns();
接着,我们可以通过 Sortable.js 库来实现表格列的拖拽排序功能。在指令中,我们可以通过 Sortable.create
方法来创建一个 Sortable 实例。在创建该实例时,我们需要传入要排序的元素、排序配置和回调函数。例如,我们可以这样创建一个 Sortable 实例:
Sortable.create(el.querySelector('.vxe-header--row'), {
draggable: 'th',
handle: '.vxe-cell',
animation: 500,
direction: 'vertical',
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
dragClass: 'sortable-drag',
forceFallback: true,
onEnd: async (e) => {
// ...
}
});
在这个 Sortable 实例中,我们指定了要排序的元素、排序方向、动画效果和回调函数。在回调函数中,我们可以获取到排序后的元素的索引值和元素对象。例如,我们可以这样获取到排序后的元素:
var oldTarget = oldCol.splice(e.oldIndex, 1);
oldCol.splice(e.newIndex, 0, oldTarget[0]);
接着,接着,我们可以看到这段代码通过调用 Sortable.create()
方法,创建了一个可排序的表头组件。Sortable
是一个第三方的 JavaScript 库,它提供了一些方法和事件,用于实现拖放排序功能。我们传入了一些参数来配置拖放的行为和样式。例如,draggable
参数指定哪些元素可以被拖动,handle
参数指定拖动的句柄,onEnd
事件在拖动结束时被触发,等等。
在 onEnd
事件处理程序中,我们获取当前表格列的顺序,然后保存到本地存储中,以便在下一次渲染表格时能够保持用户的偏好设置。在这个过程中,我们使用 vnode.child.getColumns()
获取当前的列,使用 splice()
方法从旧的索引位置删除一列,并将其插入到新的索引位置。接下来,我们调用 refreshColumn()
方法重新计算表格的列宽,并调用 loadColumn()
方法重新渲染表格的列。最后,我们将排序信息保存到服务器,以便在下一次用户访问该页面时,能够恢复他们的排序偏好。
在这个过程中,我们还创建了一个名为 dragLoading
的 DOM 元素,用于在拖动列时显示加载状态。我们使用 document.createElement()
方法创建一个 <div>
元素,并设置其类名、样式和 HTML 内容。然后,我们将其附加到表格 DOM 结构中,以便在需要时能够显示出来。
总之,这段代码实现了一个非常实用的功能:让用户可以自定义表格的列顺序,并将其保存到本地或服务器上。这对于某些特定的应用场景非常有用,例如管理大量数据的后台应用程序。如果你想为你的应用程序添加类似的功能,可以参考这段代码并根据自己的需求进行修改和优化。
完整代码
import { Sortable } from 'sortablejs';
import { getUserTableSort, setUserTableSort } from '../service/tableDrag.js';
export default {
bind: function (el, binding, vnode) {
if (binding.value) {
getUserTableSort(
vnode.context.$store.state.userInfo.userInnerSn,
'你的项目名称',
vnode.context.$store.state.userInfo.systemType,
binding.value
).then((res) => {
if (res.code == 0 && res.info && res.info.columns) {
var serverCol = JSON.parse(res.info.columns);
var oldRenderCol = vnode.child.getColumns();
var newRenderCol = [];
for (var i = 0; i < serverCol.length; i++) {
for (var j = 0; j < oldRenderCol.length; j++) {
if (serverCol[i]['field'] == oldRenderCol[j]['property']) {
newRenderCol.push(oldRenderCol[j]);
}
}
}
//本地是否有右边操作按钮
var localHasFixed = false;
var localFixedIndex = -1;
for (var j = 0; j < oldRenderCol.length; j++) {
if (oldRenderCol[j]['fixed'] == 'right') {
localHasFixed = true;
localFixedIndex = j;
break;
}
}
//判断服务器有没有
var serveHasFixed = false;
for (var i = 0; i < serverCol.length; i++) {
if (serverCol[i]['fixed']) {
serveHasFixed = true;
break;
}
}
//如果本地有服务器没有,则写入操作
if (localHasFixed && !serveHasFixed) {
newRenderCol.push(oldRenderCol[localFixedIndex]);
}
//判断是否有左边操作
var localHasFixedLeft = false;
var localFixedLeftIndex = -1;
for (var j = 0; j < oldRenderCol.length; j++) {
if (oldRenderCol[j]['fixed'] == 'left') {
localHasFixedLeft = true;
localFixedLeftIndex = j;
break;
}
}
//判断服务器有没有
var serveHasLeftFixed = false;
for (var i = 0; i < serverCol.length; i++) {
if (serverCol[i]['fixedLeft']) {
serveHasLeftFixed = true;
break;
}
}
//如果本地有服务器没有,则写入操作
if (localHasFixedLeft && !serveHasLeftFixed) {
newRenderCol.push(oldRenderCol[localFixedLeftIndex]);
}
//判断如果没有任何匹配数据,则使用老的表头
if (newRenderCol.length > 0) {
vnode.context.$nextTick(() => {
vnode.child.loadColumn(newRenderCol);
});
}
}
});
}
//拖拽时,生成dom
var dragLoading = document.createElement('div');
dragLoading.className = 'vxe-table--loading vxe-loading is--visible';
dragLoading.style.display = 'none';
dragLoading.innerHTML = `
<div class="vxe-loading--spinner">
</div>
`;
el.querySelector('.vxe-table--body-wrapper').append(dragLoading);
//获取路由参数以及组件顺序
el.style.cursor = 'move';
Sortable.create(el.querySelector('.vxe-header--row'), {
draggable: 'th',
handle: '.vxe-cell',
animation: 500,
direction: 'vertical',
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
dragClass: 'sortable-drag',
forceFallback: true,
onEnd: async (e) => {
var oldCol = vnode.child.getColumns();
var oldTarget = oldCol.splice(e.oldIndex, 1);
oldCol.splice(e.newIndex, 0, oldTarget[0]);
vnode.child.refreshColumn();
await vnode.child.loadColumn([]);
vnode.context.$nextTick(() => {
vnode.child.loadColumn(oldCol);
//保存排序信息
var saveCol = [];
for (var i = 0; i < oldCol.length; i++) {
saveCol.push({
'field': oldCol[i]['property'],
'fixed': oldCol[i]['fixed'] == 'right' ? true : false,
'fixedLeft': oldCol[i]['fixed'] == 'left' ? true : false,
'title': oldCol[i]['title'] || ''
});
}
if (binding.value) {
setUserTableSort({
'userId': vnode.context.$store.state.userInfo.userInnerSn,
'projectName': '你的项目名称',
'systemType': vnode.context.$store.state.userInfo.systemType,
'tableId': binding.value,
'columns': JSON.stringify(saveCol)
});
}
dragLoading.style.display = 'none';
});
}
});
}
};