vue-pure-admin拖拽功能实现:SortableJS与VueDraggable集成

【免费下载链接】vue-pure-admin 全面ESM+Vue3+Vite+Element-Plus+TypeScript编写的一款后台管理系统(兼容移动端) 【免费下载链接】vue-pure-admin 项目地址: https://gitcode.com/GitHub_Trending/vu/vue-pure-admin

引言

在现代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,提供了企业级的拖拽功能解决方案。本文从基础配置到高级应用,全面介绍了拖拽功能的实现细节和最佳实践。

关键要点总结:

  1. 技术选型:SortableJS + VueDraggable 组合提供了最佳的Vue3拖拽体验
  2. 性能优化:通过shallowRef、虚拟滚动等技术确保大数据量下的流畅体验
  3. 移动端适配:forceFallback配置确保跨平台兼容性
  4. 类型安全:完整的TypeScript支持提升开发体验
  5. 组件封装:可复用的拖拽组件设计模式

通过掌握这些技术,您可以在vue-pure-admin基础上快速构建出专业级的拖拽交互功能,为用户提供更加流畅和直观的操作体验。


进一步学习资源

  • 查看 src/views/able/draggable.vue 获取完整实现代码
  • 参考SortableJS官方文档了解更多高级配置选项
  • 探索vue-pure-admin其他功能组件获得更多灵感

希望本文能帮助您更好地理解和应用vue-pure-admin中的拖拽功能,为您的项目开发提供有力支持!

【免费下载链接】vue-pure-admin 全面ESM+Vue3+Vite+Element-Plus+TypeScript编写的一款后台管理系统(兼容移动端) 【免费下载链接】vue-pure-admin 项目地址: https://gitcode.com/GitHub_Trending/vu/vue-pure-admin

更多推荐