最近又开始写前端了,所以碰到问题是难免的,不过有问题就解决问题,这才是程序员的生存之道。闲话少说,这次是el-table的拖拽排序问题。需要实现如下需求,现有一个表格,每行数据都能进行拖拽,进行上下移动,比如,将图中第二行的排序字段拖拽到第四行,同时原本的第三行第四行数据向上平移。
这样的UI效果是可以有组件支持的,那就是SortTable.js。别人造好的轮子,直接拿来用就行了。那么问题在哪里呢?在vue中使用sortTable组件拖拽表格行数据后,影响的只是页面展示效果,实际的数据并没有发生变化,还是原来的顺序。vue是基于数据驱动的,做一些交互操作都是直接修改数据,页面直接渲染的,比如当前页面的字段编码下拉框的选择,这里如果值改变,同时要更改本行的全部数据。所以,如果拖拽后不影响实际的数据,那其他相关的数据交互操作就会出现问题。
解决思路:
1、先缓存一个与实际数据一致的数组。
2、在拖拽时,获取到原下标和拖拽后的下标,根据等同页面展示效果的拖拽算法,修改缓存的数组的顺序。
3、根据缓存的数组,重新渲染表格数据,保证页面效果与实际数据一致
代码实现:
1、实现等同页面效果的拖拽排序方法,利用新数组实现修改数组元素顺序的功能
/** * 根据下标重新获取拖拽排序数组 * @param oldIndex 从下标拖拽 * @param newIndex 拖拽到的下标 * @param oldList 拖拽的数组 * @return [] 拖拽后的数组 * 例如: * 1->3 * 下标值 0 1 2 3 4 5 * 原数组 a b c d e f * 新数组 a c d b e f * newList[5] = oldList[5] * newList[4] = oldList[4] * newList[3] = oldList[1] * newList[2] = oldList[3] * newList[1] = oldList[2] * newList[0] = oldList[0] * * 3->1 * 下标值 0 1 2 3 4 5 * 原数组 a b c d e f * 新数组 a d b c e f * newList[5] = oldList[5] * newList[4] = oldList[4] * newList[3] = oldList[2] * newList[2] = oldList[1] * newList[1] = oldList[3] * newList[0] = oldList[0] */ getOrderList(oldIndex, newIndex, oldList) { // 新数据集合 const newList = [] // 第一种情况 oldIndex < newIndex 1->3 if (oldIndex < newIndex) { for (let i = 0; i < oldList.length; i++) { // 超过移动下标范围的 数据(小于第一个下标或者大于最后一个下标) 直接赋值 if (i < oldIndex || i > newIndex) { newList[i] = oldList[i] } else { // 当前下标 小于 最后一个下标时 if (i < newIndex) { // 取下标向后偏移1的数据 newList[i] = oldList[i + 1] // 当前下标 等于 最后一个下标时 } else { // 取第一个下标的数据 newList[i] = oldList[oldIndex] } } } // 第二种情况 oldIndex > newIndex 3->1 } else if (oldIndex > newIndex) { for (let i = 0; i < oldList.length; i++) { // 超过移动下标范围的 数据(大于第一个下标或者小于最后一个下标) 直接赋值 if (i > oldIndex || i < newIndex) { newList[i] = oldList[i] } else { // 当前下标 等于 最后一个下标时 if (i === newIndex) { // 取第一个下标的数据 newList[i] = oldList[oldIndex] // 当前下标 大于 最后一个下标时 } else if (i > newIndex) { // 取下标向前偏移1的数据 newList[i] = oldList[i - 1] } } } // 第三种情况 oldIndex = newIndex 1->1 } else { for (let i = 0; i < oldList.length; i++) { // 直接赋值 newList[i] = oldList[i] } } return newList }
2、初始化拖拽方法,在其中重新生成排序后的数组,并且重新渲染表格
// 拖拽初始化方法 rowDrop() { const tbody = document.querySelector('.fields-table tbody') const _this = this this.$nextTick(() => { Sortable.create(tbody, { handle: '.fields-move', animation: 150, onEnd({ oldIndex, newIndex }) { // 转换缓存数组顺序 _this.backupsData = _this.getOrderList(oldIndex, newIndex, _this.backupsData) // 缓存数据数组 赋值给 实际数据数组 _this.dataSetFieldList = JSON.parse(JSON.stringify(_this.backupsData)) // 重新渲染表格(必须重新渲染,否则页面表格拖拽后,显示数据不正确) _this.randomKey = Math.random() _this.$forceUpdate() } }) }) }
3、由于重新渲染表格,导致拖拽的事件被移除,监听表格数据变化,重新渲染拖拽事件
watch: { /** * 监听表格数据变化,重新渲染拖拽事件 */ dataSetFieldList: { handler(val) { this.$nextTick(() => { // 绑定拖拽事件 this.rowDrop() }) }, immediate: true } },
遇到的问题:
1、缓存数据重新赋值,采用JSON转换方式,避免引用传值,防止出现数据混乱问题。
2、重新给实际数据赋值后,必须要重新渲染,否则页面显示的数据会不正确,即与实际数据中显示的顺序不一致。
3、重新渲染表格采用给table绑定key,赋值为随机数的方式,仅使用$forceUpdate()方法不能生效。
4、重新渲染表格会导致已绑定的拖拽事件被移除,表现为拖拽一次后就不能拖拽了,而直接在重新渲染表格后再次调用拖拽初始化方法,会存在数据还未渲染就进入了拖拽初始化方法,所以采用了监听数据变化的方法,并且在其中加入数据渲染完成后调用,保证数据渲染完成再进行拖拽初始化,重新绑定事件。
发表评论