vue-pure-admin拖拽功能实现:SortableJS与VueDraggable集成
vue-pure-admin拖拽功能实现:SortableJS与VueDraggable集成
引言
在现代Web应用开发中,拖拽功能已成为提升用户体验的重要交互方式。无论是任务管理、数据排序还是界面布局调整,优雅的拖拽实现都能显著提升应用的专业性和易用性。vue-pure-admin作为一款基于Vue3+TypeScript+Element Plus的后台管理系统,通过集成SortableJS和VueDraggable,提供了强大而灵活的拖拽解决方案。
本文将深入解析vue-pure-admin中拖拽功能的实现原理,从基础配置到高级应用,为您全面展示如何在实际项目中实现专业级的拖拽交互。
技术栈概览
在深入了解实现细节之前,让我们先了解vue-pure-admin拖拽功能所依赖的核心技术:
| 技术组件 | 版本 | 主要功能 |
|---|---|---|
| SortableJS | ^1.15.6 | 提供原生JavaScript拖拽功能 |
| VueDraggable | ^4.1.0 | Vue3专用的拖拽组件封装 |
| Vue3 | ^3.5.18 | 现代响应式框架 |
| TypeScript | ^5.8.3 | 类型安全的开发体验 |
环境配置与依赖安装
安装核心依赖
首先需要在项目中安装必要的依赖包:
pnpm add sortablejs vuedraggable
TypeScript类型支持
为了获得完整的类型提示,建议安装对应的类型定义:
pnpm add -D @types/sortablejs
基础拖拽实现
1. Grid网格拖拽
vue-pure-admin提供了网格布局的拖拽实现,适合用于仪表盘组件排序等场景:
<script setup lang="ts">
import { ref } from "vue";
import draggable from "vuedraggable/src/vuedraggable";
const gridLists = ref<Array<Object>>([
{ grid: "cn", num: 1 },
{ grid: "cn", num: 2 },
{ grid: "cn", num: 3 },
{ grid: "cn", num: 4 },
{ grid: "cn", num: 5 },
{ grid: "cn", num: 6 },
{ grid: "cn", num: 7 },
{ grid: "cn", num: 8 },
{ grid: "cn", num: 9 }
]);
</script>
<template>
<draggable
v-model="gridLists"
class="grid-container"
item-key="grid"
animation="300"
chosenClass="chosen"
forceFallback="true"
>
<template #item="{ element }">
<div :class="'item' + ' ' + 'item-' + element.num">
{{ element.num }}
</div>
</template>
</draggable>
</template>
<style lang="scss" scoped>
.grid-container {
display: grid;
grid-template-rows: 33.3% 33.3% 33.3%;
grid-template-columns: 33.3% 33.3% 33.3%;
}
.item {
font-size: 2em;
line-height: 100px;
text-align: center;
cursor: move;
border: 1px solid #e5e4e9;
// 为每个网格项设置不同的背景色
&-1 { background-color: #ef342a; }
&-2 { background-color: #f68f26; }
&-3 { background-color: #4ba946; }
// ... 更多样式
}
.chosen {
border: solid 2px #3089dc !important;
}
</style>
2. 单列列表拖拽
对于传统的列表排序场景,vue-pure-admin提供了单列拖拽实现:
<script setup lang="ts">
import { ref } from "vue";
import draggable from "vuedraggable/src/vuedraggable";
const lists = ref<Array<Object>>([
{ people: "cn", id: 1, name: "www.itxst.com" },
{ people: "cn", id: 2, name: "www.baidu.com" },
{ people: "cn", id: 3, name: "www.taobao.com" },
{ people: "cn", id: 4, name: "www.google.com" }
]);
const change = (evt): void => {
console.log("拖拽变化事件: ", evt);
};
</script>
<template>
<draggable
v-model="lists"
item-key="name"
chosen-class="chosen"
force-fallback="true"
animation="300"
@change="change"
>
<template #item="{ element, index }">
<div class="item-single">
{{ element.name }} {{ index }}
</div>
</template>
</draggable>
</template>
高级拖拽功能
1. 元素位置交换
vue-pure-admin还支持通过SortableJS原生API实现元素位置交换功能:
<script setup lang="ts">
import { ref, onMounted } from "vue";
import Sortable, { Swap } from "sortablejs";
import { useAppStoreHook } from "@/store/modules/app";
const cutLists = ref([
{ people: "cn", id: 1, name: "cut1" },
{ people: "cn", id: 2, name: "cut2" },
{ people: "cn", id: 3, name: "cut3" },
{ people: "cn", id: 4, name: "cut4" }
]);
onMounted(() => {
if (!useAppStoreHook().sortSwap) Sortable.mount(new Swap());
new Sortable(document.querySelector(".cut-container"), {
swap: true, // 启用交换模式
forceFallback: true, // 强制使用fallback
chosenClass: "chosen", // 选中时的样式类
swapClass: "highlight", // 交换时的样式类
animation: 300 // 动画时长
});
});
</script>
<template>
<div class="cut-container">
<div
v-for="(item, index) in cutLists"
:key="index"
class="item-cut"
>
<p>{{ item.name }}</p>
</div>
</div>
</template>
2. 拖拽事件处理
vue-pure-admin提供了完整的拖拽事件处理机制:
// 拖拽事件类型定义
interface DragEvent {
moved?: {
element: any;
oldIndex: number;
newIndex: number;
};
added?: {
element: any;
newIndex: number;
};
removed?: {
element: any;
oldIndex: number;
};
}
// 事件处理函数
const handleDragChange = (evt: DragEvent): void => {
if (evt.moved) {
console.log('元素移动:', evt.moved);
}
if (evt.added) {
console.log('元素添加:', evt.added);
}
if (evt.removed) {
console.log('元素移除:', evt.removed);
}
};
性能优化策略
1. 避免不必要的重渲染
<script setup lang="ts">
import { shallowRef } from "vue";
// 使用shallowRef避免深度响应式带来的性能开销
const dragItems = shallowRef([...]);
</script>
2. 虚拟滚动集成
对于大数据量的拖拽场景,建议结合虚拟滚动:
<template>
<ReVirtualList :items="largeList">
<draggable v-model="largeList">
<template #item="{ element }">
<div class="drag-item">{{ element.name }}</div>
</template>
</draggable>
</ReVirtualList>
</template>
常见问题解决方案
1. 移动端适配
// 强制使用fallback模式确保移动端兼容性
const draggableOptions = {
forceFallback: true,
fallbackClass: 'draggable-fallback',
fallbackOnBody: true
};
2. 拖拽边界限制
// 限制拖拽范围
const sortable = new Sortable(container, {
dragClass: "drag-ghost",
ghostClass: "sortable-ghost",
chosenClass: "sortable-chosen",
filter: ".ignore-elements", // 过滤不可拖拽元素
preventOnFilter: false
});
3. 拖拽状态持久化
// 保存拖拽状态到本地存储
const saveDragState = () => {
localStorage.setItem('drag-state', JSON.stringify(dragItems.value));
};
// 恢复拖拽状态
const restoreDragState = () => {
const saved = localStorage.getItem('drag-state');
if (saved) {
dragItems.value = JSON.parse(saved);
}
};
最佳实践指南
1. 组件封装策略
<!-- components/ReDraggableList.vue -->
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';
import draggable from 'vuedraggable/src/vuedraggable';
const props = defineProps({
items: { type: Array, required: true },
itemKey: { type: String, default: 'id' }
});
const emit = defineEmits(['update:items', 'change']);
const handleChange = (evt) => {
emit('change', evt);
};
</script>
<template>
<draggable
:model-value="props.items"
:item-key="props.itemKey"
@update:model-value="$emit('update:items', $event)"
@change="handleChange"
>
<template #item="{ element }">
<slot name="item" :element="element" />
</template>
</draggable>
</template>
2. 类型安全实现
// types/draggable.ts
export interface DraggableItem {
id: string | number;
[key: string]: any;
}
export interface DragEventDetail {
type: 'move' | 'add' | 'remove';
item: DraggableItem;
oldIndex?: number;
newIndex?: number;
}
export const useDraggable = (initialItems: DraggableItem[]) => {
const items = ref<DraggableItem[]>(initialItems);
const handleDragEnd = (event: DragEventDetail) => {
// 类型安全的处理逻辑
};
return { items, handleDragEnd };
};
实战案例:任务看板实现
下面是一个完整的任务看板拖拽示例:
<script setup lang="ts">
import { ref } from "vue";
import draggable from "vuedraggable/src/vuedraggable";
interface Task {
id: number;
title: string;
status: 'todo' | 'in-progress' | 'done';
}
const todoTasks = ref<Task[]>([
{ id: 1, title: '需求分析', status: 'todo' },
{ id: 2, title: 'UI设计', status: 'todo' }
]);
const inProgressTasks = ref<Task[]>([
{ id: 3, title: '前端开发', status: 'in-progress' }
]);
const doneTasks = ref<Task[]>([
{ id: 4, title: '测试验收', status: 'done' }
]);
const onTaskMove = (evt: any) => {
const { moved, added } = evt;
if (moved || added) {
// 更新任务状态逻辑
console.log('任务状态变更:', evt);
}
};
</script>
<template>
<div class="kanban-board">
<div class="kanban-column">
<h3>待处理</h3>
<draggable
v-model="todoTasks"
group="tasks"
@change="onTaskMove"
>
<template #item="{ element }">
<div class="task-card">
{{ element.title }}
</div>
</template>
</draggable>
</div>
<div class="kanban-column">
<h3>进行中</h3>
<draggable
v-model="inProgressTasks"
group="tasks"
@change="onTaskMove"
>
<template #item="{ element }">
<div class="task-card">
{{ element.title }}
</div>
</template>
</draggable>
</div>
<div class="kanban-column">
<h3>已完成</h3>
<draggable
v-model="doneTasks"
group="tasks"
@change="onTaskMove"
>
<template #item="{ element }">
<div class="task-card">
{{ element.title }}
</div>
</template>
</draggable>
</div>
</div>
</template>
总结
vue-pure-admin通过深度集成SortableJS和VueDraggable,提供了企业级的拖拽功能解决方案。本文从基础配置到高级应用,全面介绍了拖拽功能的实现细节和最佳实践。
关键要点总结:
- 技术选型:SortableJS + VueDraggable 组合提供了最佳的Vue3拖拽体验
- 性能优化:通过shallowRef、虚拟滚动等技术确保大数据量下的流畅体验
- 移动端适配:forceFallback配置确保跨平台兼容性
- 类型安全:完整的TypeScript支持提升开发体验
- 组件封装:可复用的拖拽组件设计模式
通过掌握这些技术,您可以在vue-pure-admin基础上快速构建出专业级的拖拽交互功能,为用户提供更加流畅和直观的操作体验。
进一步学习资源:
- 查看
src/views/able/draggable.vue获取完整实现代码 - 参考SortableJS官方文档了解更多高级配置选项
- 探索vue-pure-admin其他功能组件获得更多灵感
希望本文能帮助您更好地理解和应用vue-pure-admin中的拖拽功能,为您的项目开发提供有力支持!
更多推荐

所有评论(0)