xb18
xb18
文章39
标签0
分类0
虚拟列表拖动排序

虚拟列表拖动排序

vue2

usage

1
2
3
4
5
<virtualListSortable  v-slot="{item, index}" data-key="nid" :data-list.sync="dataList" :remain="30" :size="30" style="height: 100%">
<div class="preview-list-item">
{{ item.name }}
</div>
</virtualListSortable>

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<template>
<virtual-list ref="vls" class="vls-list" :size="vlsSize" :remain="vlsRemain" rtag="div" wclass="vls-list-wrap">
<div class="vls-list-li" :data-key="item[vlsKey]" :key="item[vlsKey]" v-for="(item, index) in vlsDataList">
<slot :item="item" :index="index"></slot>
</div>
</virtual-list>
</template>

<script>
import virtualList from 'vue-virtual-scroll-list'; // v1.2.8
import Sortable from '../../assets/js/sortablejs.js'; // v1.15.0
export default {
props: {
dataList: {
type: Array,
default: () => [],
required: true
},
dataKey: {
type: String,
default: 'id',
required: true
},
size: {
type: Number,
default: 30,
},
remain: {
type: Number,
default: 30,
}
},
components: {
virtualList
},
data() {
return {
vlsKey: this.dataKey,
vlsDataList: [...this.dataList],
sortableFlag: false,
sortableInstance: null,
}
},
computed: {
vlsSize() {
return this.size;
},
vlsRemain() {
return this.remain;
}
},
watch: {
dataKey() {
this.vlsKey = this.dataKey;
},
dataList() {
if (this.sortableFlag) return; // 排序时不允许数据变动
this.vlsDataList = this.dataList;
this.$refs.vls.forceRender();
}
},
mounted() {
console.log('this.$refs.vls', this.$refs.vls.$refs.vsl);
this.initSortable(this.$refs.vls.$refs.vsl.querySelector('.vls-list-wrap'));
this.initDrop();
},
beforeDestroy() {
this.destroySortable();
},
methods: {
destroySortable() {
this.sortableInstance && this.sortableInstance.destroy();
this.sortableInstance = null;
},
initSortable(elem) {
if (this.sortableInstance) return;
let elemParentNode = elem.parentNode;
// 切换到自由排序 撤销排序
let oldSortIndex = 0;
let newSortIndex = 0;
let related = null;
let willInsertAfter = null;
this.sortableInstance = new Sortable(elem, {
animation: 100,
ghostClass: 'sortable-ghost',
dragClass: 'sortable-drag',
onMove: (evt) => {
this.sortableFlag = true;
related = evt.related;
willInsertAfter = evt.willInsertAfter;
},
onEnd: (evt) => {
if (!related) return;
let dragged = evt.item;
let draggedNid = dragged.getAttribute('data-key');
let relatedNid = related.getAttribute('data-key');
oldSortIndex = this.vlsDataList.findIndex(item => item[this.vlsKey] === draggedNid);
// Sortable修改实际DOM一次 vue监听到数据修改再次更新DOM 会出现无法预料情况 会出现拖拽回弹
let oldItem = this.vlsDataList.splice(oldSortIndex, 1)[0];
newSortIndex = this.vlsDataList.findIndex(item => item[this.vlsKey] === relatedNid);
if (willInsertAfter) {
newSortIndex = newSortIndex + 1;
}
this.vlsDataList.splice(newSortIndex, 0, oldItem);
console.log('onEnd event===draggedNid=relatedNid=>', draggedNid, relatedNid);
console.log('onEnd event===oldSortIndex=newSortIndex=>', oldSortIndex, newSortIndex);
if (oldSortIndex === newSortIndex) {
return;
}
let elemParentNodeScrollTop = elemParentNode.scrollTop;
const vlsDataListRes = [...this.vlsDataList];
// this.$emit('update:data-list', this.vlsDataList);
// 销毁Sortable修改后的真实DOM VUE重新渲染
elem.innerHTML = '';
this.vlsDataList = [];
this.$nextTick(() => {
this.sortableFlag = false;
this.$emit('update:data-list', vlsDataListRes);
// this.vlsDataList = vlsDataListRes;
// 恢复滚动条位置
elemParentNode.scrollTo(0, 0);
this.$nextTick(() => { elemParentNode.scrollTo(0, elemParentNodeScrollTop); });
});
willInsertAfter = null;
related = null;
}
});
},
initDrop() {
document.body.ondrop = function (event) {
event.preventDefault();
event.stopPropagation();
};
},
}
}
</script>
本文作者:xb18
本文链接:http://xb18.github.io/2023/11/15/%E8%99%9A%E6%8B%9F%E5%88%97%E8%A1%A8%E6%8B%96%E5%8A%A8%E6%8E%92%E5%BA%8F/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可