1.背景
最近需要维护一个老项目,vue2写的,要求是需要用户拖拽然后合并关联数据,这么一个业务就涉及到了需要在拖拽过程和结束的节点控制列表重新渲染的流程。
2.常规拖拽方案和实现方式(vue2)
主要用到库是 #vueDraggable(支持vue2,vue3) 我简化业务需求,利用一个简单的列表实现我的业务,整体思路应该是差不多的。
3.常规拖拽方案和实现方式(vue3)
主要用到的库是# vueDraggablePlus
3.1解决痛点
在 Sortablejs 官方以往的 Vue 组件中,都是通过使用组件作为列表的直接子元素来实现拖拽列表,当我们使用一些组件库时,如果组件库中没有提供列表根元素的插槽,我们很难实现拖拽列表,vue-draggable-plus 完美解决了这个问题,它可以让你在任何元素上使用拖拽列表,我们可以使用指定元素的选择器,来获取到列表根元素,然后将列表根元素作为 Sortablejs 的 container,详情参考指定目标容器。
3.2注意第三方库适配的vue的关联版本,支持 Vue3 或 Vue 2.7+
3.3实现table行拖拽
<div class="demo-2-1">
<VueDraggable
v-model="tableData"
class="flex flex-col gap-2 p-4 w-300px bg-gray-500/5 rounded"
target=".target-table tbody"
:scroll="true"
>
<el-table
:data="tableData"
height="250"
style="width: 100%"
class="target-table"
>
<el-table-column prop="date" label="Date" width="180" />
<el-table-column prop="name" label="Name" width="180" />
<el-table-column prop="address" label="Address" />
</el-table>
</VueDraggable>
</div>
<script setup lang="ts">
import { ref } from 'vue';
import { VueDraggable, DraggableEvent } from 'vue-draggable-plus';
const tableData = [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-08',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-06',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-07',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
];
</script>
官网也有列表的行拖摘和列拖拽的DEMO
3.4实现一个简单的文字合并拖拽效果
<div class="flex justify-between">
<VueDraggable
v-model="list"
class="flex flex-col gap-2 p-4 w-300px bg-gray-500/5 rounded"
target=".sort-target"
:scroll="true"
@start="onStart"
@end="onEnd"
>
<TransitionGroup
type="transition"
tag="ul"
name="fade"
class="sort-target"
>
<li
v-for="(item, index) in list"
:key="item.id"
class="h-50px bg-gray-500/5 rounded flex items-center justify-between px-2"
>
<span class="items">{{ item.name }}</span>
<el-icon
><Close
class="cursor-pointer"
@click="remove(index)"
/></el-icon>
</li>
</TransitionGroup>
</VueDraggable>
</div>
<script setup lang="ts">
import { ref } from 'vue';
import { VueDraggable, DraggableEvent } from 'vue-draggable-plus';
const listData = [
{
name: '有',
id: '1',
},
{
name: '志',
id: '2',
},
{
name: '者',
id: '3',
},
{
name: '事',
id: '4',
},
{
name: '竟',
id: '5',
},
{
name: '成',
id: '6',
},
];
const list = ref(listData);
let cloneList = window.structuredClone(listData);
function onStart(event: DraggableEvent) {
console.log('开始拖拽', event);
}
function onEnd(event: DraggableEvent) {
console.log('拖拽结束', event);
const { oldIndex, newIndex } = event;
console.log(cloneList[oldIndex].id, cloneList[newIndex].id);
const compObj = {
id: cloneList[oldIndex].id + cloneList[newIndex].id,
name: cloneList[oldIndex].name + cloneList[newIndex].name,
};
cloneList = cloneList.filter(
(item) =>
item.id != cloneList[oldIndex].id &&
item.id != cloneList[newIndex].id,
);
cloneList.unshift(customSortObj(compObj));
list.value = cloneList;
}
// 对于name按照id排序处理
function customSortObj(compObj) {
let sortedId = compObj.id.split('').sort().join('');
// 重新排列name字段中的汉字,根据排序后的id顺序
let newName = '';
for (let char of sortedId) {
let index = compObj.id.indexOf(char);
newName += compObj.name[index];
}
// 更新对象
compObj.id = sortedId;
compObj.name = newName;
return compObj;
}
function handleAdd() {
const length = list.value.length + 1;
list.value.push({
name: `Juan ${length}`,
id: `${length}`,
});
}
function remove(index: number) {
list.value.splice(index, 1);
}
</script>
<style>
.fade-move,
.fade-enter-active,
.fade-leave-active {
transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: scaleY(0.01) translate(30px, 0);
}
.fade-leave-active {
position: absolute;
}
.demo-2 {
margin: 50px 0;
}
.items {
width: 100px;
display: inline-block;
margin: 10px 0;
cursor: move;
}
.items:hover {
background-color: aqua;
}
.target-table tbody > tr {
cursor: move;
}
</style>
不管你怎么拖拽最后文字合并的结果都是有志者事竟成,希望每个人都能够实现自己的目标!
4.小结
Vue.Draggable 和 vue-draggable-plus 是两个基于 Vue.js 的拖拽组件库,它们各有特点,但也存在一些异同点。以下是它们的异同点分析:
4.1相同点
- 目标与应用场景:两者都旨在提供高效的拖拽排序功能,适用于列表排序等多种需要拖拽操作的场景。
- Vue集成:均支持Vue.js框架,能够与Vue的响应式系统紧密结合,实现数据的双向绑定和自动更新。
- 事件支持:都提供了丰富的拖拽相关事件,如拖拽开始、拖拽中、拖拽结束等,方便开发者在拖拽过程中执行特定的逻辑操作。
4.2异同点
| Vue.Draggable | vue-draggable-plus | |
|---|---|---|
| 基础 | 基于SortableJS的Vue组件库 | 基于SortableJS的Vue组件库,针对Vue.js进行了优化 |
| 版本兼容性 | 支持Vue 2.x及Vue 3.x | 明确支持Vue 2.x和3.x版本,以及Vue Router和Vuex |
| 功能特性 | 提供拖拽排序、拖拽交换等功能 | 除了基本拖拽功能外,还提供虚拟滚动集成、完全可定制化、丰富的API和事件支持等 |
| 性能 | 具有良好的性能表现 | 利用Vue的特性,确保在大规模数据处理时保持流畅,并提供虚拟滚动集成以提高性能 |
| 社区支持 | 长期未更新 | 社区活跃,作者积极维护,更新频繁,社区中有许多示例和解决方案可供参考 |
| 定制化 | 可通过配置选项进行一定程度的定制化 | 完全可定制化,包括拖放行为、拖放效果、拖放区域等 |
| 集成与扩展 | 易于集成到Vue项目中 | 易于集成和扩展,可以与其他Vue库(如Vuex或Pinia)集成以管理全局状态 |
| API设计 | 简洁明了,方便使用 | API设计简洁,文档详细,快速上手 |
| 额外特性 | (未明确提及) | 继承了SortableJS的优点,如原生拖放API、多元素拖放和动画效果 |
4.3总结
Vue.Draggable 和 vue-draggable-plus 都是优秀的Vue.js拖拽组件库,但vue-draggable-plus在功能特性、性能优化、社区支持、定制化程度等方面表现出更强的优势。它基于SortableJS并针对Vue.js进行了深度优化,提供了更加丰富和灵活的拖拽功能,适用于更多复杂的拖拽场景。然而,选择哪个库取决于项目的具体需求和个人偏好。如果项目对拖拽功能的需求较为简单,Vue.Draggable 可能是一个轻量级且易于上手的选择;而如果项目需要更高级的拖拽功能或更好的性能表现,vue-draggable-plus 则可能是更合适的选择。