PrimeVue DataTable 行拖拽实现方案 (Vue.Draggable)
2024-12-18 20:25:27
PrimeVue DataTable 组件实现行拖拽功能
在基于 Vue.js 和 PrimeVue 的开发过程中,经常会遇到需要为表格(DataTable)组件增加行拖拽功能的需求,这让数据操作更灵活。遗憾的是,PrimeVue 的 DataTable 组件本身并不直接支持行的拖拽排序。但是借助第三方库,就能轻易实现此功能。
问题:如何在 PrimeVue 的 DataTable 中启用行拖拽功能
在 DataTable 中,通过v-for
将数据呈现在表格中, 为了保持数据的准确性和高效,DataTable
的每一行都是根据特定的dataKey
来确定的。
问题是在v-for
的循环里使用了draggable
, 会破坏这种数据关系。
解决方案
使用 Vue.Draggable.Next 和 Sortable.js
Vue.Draggable.Next 是一个流行的 Vue.js 库,它基于 Sortable.js,专门为 Vue.js 提供了拖拽组件的能力。由于 PrimeVue 的 DataTable 没有自带的功能,那么就可以通过这个组件去手动实现拖拽。
原理
利用 Vue.Draggable.Next
组件包裹 DataTable
的每一行 ( template #body
), 利用:style
绑定左侧填充和可扩展节点的图标,使其视觉上和原本的表格树几乎无差别。利用draggable
组件的@change
事件处理节点移动后的重新排序等,可以方便地控制元素的拖拽和排序。在排序逻辑完成后,调用外部的保存函数保存节点数据。
操作步骤
-
安装依赖。 使用 npm 或 yarn 安装
vuedraggable
和sortablejs
。npm install vuedraggable@next sortablejs
-
引入组件。 在需要使用拖拽功能的组件中引入
vuedraggable
。import draggable from "vuedraggable";
-
修改
DataTable
结构。 使用draggable
组件包装原本DataTable
中的Element
组件, 并配置相应选项。<template> <div class="card"> <DataTable :value="visibleRows" dataKey="data.id" class="elements-list"> <Column> <template #body="slotProps"> <div :style="{ paddingLeft: slotProps.data.level * 20 + 'px', display: 'flex', alignItems: 'center', }" class="expandable-row" > <span v-if="hasChildren(slotProps.data)" class="expander-icon" @click="toggleRow(slotProps.data)" > <i :class="[ 'pi', expandedRows[slotProps.data.data.id] ? 'pi-chevron-down' : 'pi-chevron-right', ]" ></i> </span> <span v-else style="width: 1em"></span> <draggable v-model="slotProps.data.childElements" :group="{ name: 'elements' }" handle=".handle" @change="onDragChange" item-key="id"> <template #item="{element}"> <Element :element-id="element.id"/> </template> </draggable> </div> </template> </Column> </DataTable> </div> </template>
在这里
draggable
被用来代替Element
, 需要利用:style
使拖拽后各个层级的缩进与之前保持一致。需要添加handle
属性用来确定哪个部分被点击才可以拖拽, 如果没有这个限制那么所有子组件都可被单独拖动。需要利用@change
来添加Element
移动的逻辑。 -
处理拖拽事件。 监听
draggable
组件的事件,如@change
,以更新数据。import { ref, computed, watch, defineProps } from "vue"; import draggable from "vuedraggable"; import Element from "./Element.vue"; import DataTable from "primevue/datatable"; import Column from "primevue/column"; const props = defineProps({ elements: { type: Array as () => any[], required: true, default: () => [], }, expanded: { type: Boolean, required: false, default: false, }, }); const saveNodes = () => { //API to save your changed nodes, eg: //put('/api/nodes', { nodes: props.elements }); } const onDragChange = (event) => { //prevent event from multiple invocation if (event?.moved) { saveNodes(); } }; //other codes..
saveNodes
方法是一个外部方法, 需要将重新排序好的节点发送给服务器并保存, 例子中使用/api/nodes
来保存数据。
* 安全建议:务必校验后端数据正确性。另外要注意在API的返回里根据情况调用expandAllNodes
或者toggleRow
方法重新渲染节点状态, 以防节点状态被清空。
注意事项
利用draggable
替代Element
的方式将树形的表格完全变成了同级别之间的组件, 由于每个元素都可以拖动, 必须要使用handle
指定只有某个class
的子组件才能触发拖动, 如果不指定的话会导致Element
的每一个子组件都可以拖动。由于PrimeVue是自带DataTable
和 draggable
功能的, 但是它和树状结构的DataTable
的底层逻辑差异较大, 不适宜改造。这种方案的主要思路是利用CSS 去模拟表格中的行为, 实际使用draggable
去拖动组件。需要注意:由于失去了DataTable
中v-for
逻辑的强关联, 如果在修改数据的操作外改变了原本数组顺序可能会引起BUG, 请尽量只在拖拽后触发修改数组顺序的逻辑, 避免复杂操作。