使用vue-draggable-plus实现拖拽排序

 更新时间:2024年01月11日 11:39:01   作者:迪士尼在逃保安  
最近遇到一个需求,在 Vue3 的一个 H5 页面当中点击拖拽图标上下拖动 tab 子项,然后点击保存可以保存最新的 tab 项顺序,同事说可以用 vue-draggable-plus 这个库来实现拖拽,所以本文给大家介绍了如何使用vue-draggable-plus实现拖拽排序,需要的朋友可以参考下

最近实习期间遇到了一个需求:在 Vue3 的一个 H5 页面当中点击拖拽图标上下拖动 tab 子项,然后点击保存可以保存最新的 tab 项顺序,当时王哥和我说可以用 vue-draggable-plus 这个库来实现拖拽,因为公司的项目组件用的都是 TSX 写法,是我之前没接触过的,所以写起来比较慢,整个需求整整花了两天才完成。下面的代码展示还是使用传统的 SFC 写法。

vue-draggable-plus

首先在项目安装好 vue-draggable-plus

npm install vue-draggable-plus

具体使用可以参考中文文档:vue-draggable-plus | vue-draggable-plus (gitee.io)

基本布局

vue-draggable-plus 的使用有三种方式,这里我采用的是组件方式引入:

<template>
    ...
<VueDraggable
   ref="el"
   v-model="sortList"
   :disabled="disabled"
   :animation="150"
   ghostClass="ghost"
   class="flex flex-col gap-2 p-4 w-300px h-300px m-auto bg-gray-500/5 rounded"
   @start="onStart"
   @update="onUpdate"
   filter=".undraggable"
   handle=".handle"
 >
   <div
     class="sort-item"
     v-for="item in sortList"
     :key="item.id"
     :class="item.id === 1 ? 'undraggable' : ''"
     :style="item.id === 1 ? 'opacity:0.3' : ''"
   >
     <img
       class="delete"
       src="./assets/ic_delete.svg"
       @click="removeItem(item.id)"
     />
     <span>{{ item.name }}</span>
     <img class="handle" src="./assets/ic_items.svg" />
   </div>
 </VueDraggable>
    ...
</template>
<script setup lang="ts">
import { ref } from "vue";
import { type UseDraggableReturn, VueDraggable } from "vue-draggable-plus";
...
const sortShow = ref(false);
const el = ref<UseDraggableReturn>();
const disabled = ref(false);
...
</script>

其中@start@update 两个自定义事件分别在拖拽开始和拖拽完成后调用,可以在这个期间进行一些逻辑操作,像监听数组的前后变换等。

禁止部分元素拖动

因为当时项目还有一些其他的特殊需求:有些默认的子项保持不变,不允许拖拽,这边也可以用 vue-draggable-plus 实现,如上述代码所示:使用 filter=".undraggable" 过滤类名为 undraggable 的元素,只要是带有这个类名的元素都无法被拖拽。

指定元素触发拖拽

当时 UI 还有一个需求,只能点击侧边的拖拽按钮才能实现拖动,可以通过 handle 属性传递一个选择器,来指定拖拽的句柄,这里我指定的是 handle类名,只有点击携带这个类名的拖拽图标才能实现拖拽。

实现需求基本逻辑:

首先定义两个数组 sortListconstList,其中 sortList 用来接收后端传过来的数据,后面实现排序完的数据也是存在这个数组当中,而 constList 则是作为一个参照数组与 sortList 进行比较实现元素的动态增添。最后点击保存的时候可将 sortList 作为参数请求保存当前顺序的接口,因为当时后端需要通过 sortOrder字段来判断排序,所以最后在保存的时候给 sortList 的每个子项都按顺序添加一个 sortOrder字段再请求接口,完整代码如下:

完整代码

<template>
  <van-button type="success" @click="sortClick">排列菜单</van-button>
  <div v-for="item in sortList" :key="item.id">{{ item }}</div>
  <van-action-sheet v-model:show="sortShow" title="排序管理">
    <div class="content">
      <!-- 当前排序 -->
      <div class="current-sort">
        <span class="label">当前排序</span>
        <VueDraggable
          ref="el"
          v-model="sortList"
          :disabled="disabled"
          :animation="150"
          ghostClass="ghost"
          class="flex flex-col gap-2 p-4 w-300px h-300px m-auto bg-gray-500/5 rounded"
          @start="onStart"
          @update="onUpdate"
          filter=".undraggable"
          handle=".handle"
        >
          <div
            class="sort-item"
            v-for="item in sortList"
            :key="item.id"
            :class="item.id === 1 ? 'undraggable' : ''"
            :style="item.id === 1 ? 'opacity:0.3' : ''"
          >
            <img
              class="delete"
              src="./assets/ic_delete.svg"
              @click="removeItem(item.id)"
            />
            <span>{{ item.name }}</span>
            <img class="handle" src="./assets/ic_items.svg" />
          </div>
        </VueDraggable>
      </div>
      <!-- 默认子项 -->
      <div class="change-sort">
        <span class="label" style="margin-top: 24px">默认子项</span>
        <div
          class="sort-item"
          v-for="item in constList"
          :key="item.id"
          :style="sortList.find((el) => el.id === item.id) ? 'opacity:0.3' : ''"
        >
          <img
            class="add"
            src="./assets/ic_add.svg"
            @click="addItem(item.id)"
          />
          <span>{{ item.name }}</span>
        </div>
      </div>
    </div>
    <div class="sheet-footer">
      <van-button>取消</van-button>
      <van-button type="primary" @click="onConfirm">保存</van-button>
    </div>
  </van-action-sheet>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { type UseDraggableReturn, VueDraggable } from "vue-draggable-plus";
interface SortItemType {
  name: string;
  id: number;
}
// 当前排列项
let sortList = ref<SortItemType[]>([
  { id: 1, name: "Vue" },
  { id: 2, name: "React" },
  { id: 3, name: "TypeScript" },
  { id: 4, name: "Uniapp" },
  { id: 5, name: "Vite" },
]);
// 对比项(内容不变,作为参照与sortList进行对比)
let constList = ref<SortItemType[]>([
  { id: 2, name: "React" },
  { id: 3, name: "TypeScript" },
  { id: 4, name: "Uniapp" },
  { id: 5, name: "Vite" },
]);
const sortShow = ref(false);
const el = ref<UseDraggableReturn>();
const disabled = ref(false);
const onStart = () => {
  console.log("拖拽前-sortList:", sortList.value);
};
​
const onUpdate = () => {
  console.log("拖拽后-sortList:", sortList.value);
};
​
const sortClick = () => {
  sortShow.value = true;
};
/**
 * 移除当前排列项
 */
const removeItem = (id: number) => {
  if (id === 1) return; // 默认不能删除
  let idx = sortList.value.findIndex((item: SortItemType) => item.id === id);
  if (idx !== -1) {
    sortList.value.splice(idx, 1);
  }
};
/**
 * 新增排列项
 */
const addItem = (id: number) => {
  let idx = sortList.value.findIndex((item: SortItemType) => item.id === id);
  if (idx === -1) {
    sortList.value.push(
      constList.value.find((item: SortItemType) => item.id === id),
    );
  }
};
/**
 * 保存当前排序
 */
const onConfirm = () => {
  sortList.value.map((item: any, index: number) => {
    item.sortOrder = index + 1;
  });
  sortShow.value = false;
};
</script>
​
<style lang="less" scoped>
.content {
  .label {
    display: block;
    font-weight: 600;
    font-size: 13px;
    color: #969799;
    height: 18px;
    padding-left: 16px;
    margin-bottom: 16px;
  }
  .sort-item {
    margin-bottom: 16px;
    font-weight: 600;
    font-size: 15px;
    color: #323233;
    position: relative;
    .delete,
    .add {
      vertical-align: middle;
      margin: 0 12px 0 19px;
    }
    .handle {
      vertical-align: middle;
      position: absolute;
      right: 15px;
    }
  }
}
.sheet-footer {
  display: flex;
  justify-content: center;
  padding: 10px;
  .van-button {
    width: 165px;
    height: 40px;
    margin: 0 6px;
  }
}
</style>
​

最终实现效果图

以上就是使用vue-draggable-plus实现拖拽排序的详细内容,更多关于vue-draggable-plus拖拽排序的资料请关注脚本之家其它相关文章!

相关文章

  • 详解vue3.0 的 Composition API 的一种使用方法

    详解vue3.0 的 Composition API 的一种使用方法

    这篇文章主要介绍了vue3.0 的 Composition API 的一种使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • Vue的computed计算属性你了解吗

    Vue的computed计算属性你了解吗

    这篇文章主要为大家详细介绍了Vue的computed计算属性,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • vue3 锚点定位的多种实现方式

    vue3 锚点定位的多种实现方式

    这篇文章主要介绍了vue3 多种方法的锚点定位,使用 Vue Router 导航守卫可以简化导航逻辑、统一管理导航逻辑和进行权限控制,但需要学习和理解相关概念,并且需要手动编写和管理导航守卫的逻辑,本文给大家介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • Vue3中各种灵活传递数据的方式小结

    Vue3中各种灵活传递数据的方式小结

    Vue 3 提供了多种数据传递的方式,让我们的组件之间可以尽情地交流,接下来,我们就直接一个个来看,这些方式都是怎么工作的,感兴趣的小伙伴跟着小编一起来看看吧
    2024-07-07
  • 如何重置vue打印变量的显示方式

    如何重置vue打印变量的显示方式

    这篇文章主要给大家介绍了关于如何重置vue打印变量显示方式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起看看吧。
    2017-12-12
  • VUE项目IIS部署使用nginx代理访问后端接口方式

    VUE项目IIS部署使用nginx代理访问后端接口方式

    文章浏览阅读704次。文章讲述了项目使用vue-element-admin遇到的跨域问题,尝试了浏览器JS、打包路径、路由模式和IIS代理等方法均无效,最终通过配置Nginx进行后端接口代理解决了问题,同时提供了参考文档链接。
    2025-12-12
  • vue router2.0二级路由的简单使用

    vue router2.0二级路由的简单使用

    这篇文章主要为大家详细介绍了vue router2.0二级路由的简单使用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • 自带气泡提示的vue校验插件(vue-verify-pop)

    自带气泡提示的vue校验插件(vue-verify-pop)

    这篇文章主要为大家详细介绍了自带气泡提示的vue校验插件,vue-verify-pop的使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • Vue+Bootstrap实现简易学生管理系统

    Vue+Bootstrap实现简易学生管理系统

    这篇文章主要为大家详细介绍了Vue+Bootstrap实现简易学生管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-02-02
  • vue父组件调用子组件方法报错问题及解决

    vue父组件调用子组件方法报错问题及解决

    这篇文章主要介绍了vue父组件调用子组件方法报错问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09

最新评论