vxe-table和sortablejs实现行拖动
目的:
在table中实现行拖动排序功能。
问题1:
在使用Sortable对table创建可拖拽排序功能的时候,我们给每一行都锚定了一个可以拖动的图标,上边绑定了一个class,只有拖动这个图标才能拖动整行进行排序。

但是我给table创建拖拽排序功能后,进行拖拽实验,结果不能用。
这是因为在获取tableDOM元素的时候出现了问题。
initSortable () {const tableSort = this.$refs.table// 另外一种获取方法const array = tableSort.$el.querySelectorAll('.vxe-table--body tbody')for (let i = 0; i < array.length; i++) {Sortable.create(array[i], {handle: '.btn_sort',animation: 150,draggable: '.vxe-body--row',onEnd: ({ newIndex, oldIndex }) => {const singleList = this.form.item_listconst item = singleList.splice(oldIndex, 1)[0]singleList.splice(newIndex, 0, item)this.form.item_list = []this.$nextTick(() => {this.form.item_list = [...singleList]})}})}// 所有tbody},
使用的元素js方法querySelectorAll,来获取tbody,这个方法只在获取时"拍一张快照",之后即使 DOM 改变也不会更新。
在table渲染的时候是先渲染出来主体tbody的,而左右两侧固定列是后渲染出来的。
而我们使用querySelectorAll获取DOM的时候,它所拍下的快照只有主体tbody,没有左右两侧固定列的tbody。这就造成如果我用来拖动的图标是固定列,那就没法找到对应的DOM元素,Sortable也创建不了拖动方法了。
那就有人想到使用nextTick,在下次渲染后再来获取DOM了,这个方法我也用了,但是还是不行,还是获取不到固定列的DOM元素。原因应该还是这固定列的渲染时机不再这次渲染事件中,具体原因没去寻找。
那既然js原生获取DOM的方法有获取静态DOM的方法,那也有获取实时DOM的方法。
是的。有些 DOM 获取方法返回的是实时更新的集合(动态) ,而有些返回的是静态快照(静态) 。
DOM 元素获取方法总览
| 方法 | 返回类型 | 是否动态更新 | 常用性 | 说明 |
| getElementById() | Element | 否 | ⭐⭐⭐⭐ | 根据 id 获取单个元素 |
| getElementsByTagName() | HTMLCollection | 是 | ⭐⭐⭐ | 按标签名获取多个元素 |
| getElementsByClassName() | HTMLCollection | 是 | ⭐⭐⭐⭐ | 按类名获取多个元素 |
| getElementsByName() | NodeList(动态) | 是 | ⭐⭐ | 按 name 属性获取表单项 |
| querySelector() | Element | 否 | ⭐⭐⭐⭐⭐ | 返回第一个匹配的元素 |
| querySelectorAll() | NodeList(静态) | 否 | ⭐⭐⭐⭐⭐ | 返回所有匹配的元素(不会自动更新) |
这里我为了解决这个问题又实用了getElementsByTagName方法,确实是获取到了固定列的tbodyDOM。
但是这里需要注意一点。HTMLCollection和NodeList的区别。
| 特性 | HTMLCollection | NodeList |
| 实时性 | 实时更新(live) | 大部分静态(除了 |
| 方法 | 只有 length 和索引访问 | 有 forEach 等方法 |
| 内容 | 只包含 Element 节点 | 包含所有节点类型 |
直接获取的HTMLCollection是不能直接使用的,需要转为数组才能遍历使用上面的方法。
但是转为数组后,问题又来了,转为数组后只有一个主题tbody了。还是因为方法执行时只有主体tbody,后面的两个固定列tbody是后面加进来的。

至于getElementsByName这个直接获取NodeList的方法是需要name参数的,而vxe-table渲染出来的table是没有name属性的,所以不能用,至于先用getElementsByTagName给他们增加个name再使用getElementsByName这个方法也试了,行不通。
解决方案:
至于解决方法也很简单,既然是因为DOM渲染时机和获取时机的问题,那么我们只需要等DOM完全渲染好后再去获取DOM 就行了。使用setTimeout
setTimeout(() => {this.initSortable()}, 200)
问题2:
问题1解决后是可以拖拽进行排序了,但是新问题又来了。
这个table是有一个序号的,这个需要用的是table的index属性。但是我拖动排序后,index没有更新。
这是因为渲染机制造成了。只有当table的数据源有新增和减少后才会触发vxe-table的重新渲染。

解决方案:
this.form.item_list = []this.$nextTick(() => {this.form.item_list = [...singleList]})
先将数据源清空,然后重新赋值。只使用nextTick或者重赋值都不能触发vxe-table的重渲染。
