使用sortablejs给vxe-table添加表头左右拖的能力

1,480 阅读4分钟

前言

SortableJS 是一个轻量级的 JavaScript 库,可以实现对 HTML 元素进行排序和拖拽操作。而 VXE-table 是一款基于 Vue.js 的表格组件库,提供了丰富的表格功能和交互方式。将这两者结合起来,可以很方便地为 VXE-table 添加表头左右拖拽的功能,使得用户可以根据自己的需求自由地调整表格的布局和显示效果。本文将详细介绍如何使用 SortableJS 实现这一功能,并提供代码实现和示例演示,帮助读者更好地了解和应用这一技术。

先来看看实际效果

ezgif-5-bbb6ad85da.gif

实现原理

  • 使用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';
                });
            }
        });
    }
};