前端拖拽-vuedraggable的使用

6,396 阅读5分钟

本文将对vuedraggable官方的示例增加说明,帮助友友们更快的实现拖拽的效果

官网示例地址

本文代码地址

1. 拖拽后,数据能够更新

image.png

如上图,我们把Joao拖拽和Jean交换,希望数据List能够发生改变

import draggable from 'vuedraggable' // 如果没有这个包的,自行npm下载vuedraggable
export default {
  name: 'dragable',
  components: {
    draggable // 注册组件
  },

如上代码,先引入包,再注册组件

<draggable 
  :list="list"
>
  <div
      class="list-group-item"
      v-for="element in list"
      :key="element.name"
    >
      {{ element.name }}
    </div>
</draggable>

如上,使用draggable组件包裹要拖拽的列表,增加list属性,传入列表数据list,这样后,数据就能够在拖拽同时发生更新。

image.png 上图为修改后的效果

2. 两个list之间的拖拽

vuedraggable能够支持两个列表之间进行拖拽 image.png 如上图,想把Joao拖拽到Edgard下面去

<div class="list-group">
+  <draggable 
    :list="list1"
+    group="people"
  >
    <div
        class="list-group-item"
        v-for="element in list1"
        :key="element.name"
      >
        {{ element.name }}
    </div>
  </draggable>
</div>
<div class="list-group">
+   <draggable 
    :list="list2"
+    group="people"
   >
    <div
        class="list-group-item"
        v-for="element in list2"
        :key="element.name"
      >
        {{ element.name }}
    </div>
  </draggable>
</div>


如上代码,增加group属性其值为people,两个list的group属性值必须一致拖拽才会生效

image.png 效果如上,能够拖拽成功

3. 拖拽复制

希望拖拽后是复制而不是移除的效果 image.png

<div class="list-group">
  <draggable 
    :list="list1"
+    :group="{name: 'people', pull: 'clone', put: false}"
  >
    <div
        class="list-group-item"
        v-for="element in list1"
        :key="element.name"
      >
        {{ element.name }}
    </div>
  </draggable>
</div>
<div class="list-group">
   <draggable 
    :list="list2"
    group="people"
   >
    <div
        class="list-group-item"
        v-for="element in list2"
        :key="element.name"
      >
        {{ element.name }}
    </div>
  </draggable>
</div>

如上代码,需要把group属性,修改为一个对象,其中:

  • name值依然是'people',
  • pull值若为clone,意味着左侧列表拖动到其他列表是复制,若值为false表示不能拖走,为true表示能移走;
  • put改为false,表示左侧列表别人无法拖动插入进来,true表示可以;put没有没有clone属性

具体参考选项image.png

注意复制时,id可能会发生重复 image.png 解决方案:

<div class="box">
<!-- 拖拽复制 -->
  <div class="list-group">
    <draggable 
    :list="list"
+    :clone="clone"
    :group="{name: 'people', pull: 'clone', put: false}"
    >
      <div
        class="list-group-item"
        v-for="element in list"
        :key="element.name"
      >
        {{ element.name }}
      </div>
    </draggable>

在methods中增加方法

  methods: {
    clone ({ name }) {
      // 复制过去的数据项,是list2长度+1
      return { name, id: this.list2.length }
    }
  }

4. 按住ctrl进行复制

希望按住ctrl拖动就是复制;没按ctrl键进行拖拽就是移动

<draggable 
    :list="list1"
+    :group="{name: 'people', pull: pullFunction}"
    @start="start"
>
    <div
        class="list-group-item"
        v-for="element in list1"
        :key="element.name"
      >
        {{ element.name }}
    </div>
</draggable>
pullFunction () {
  return this.onControlStart ? 'clone': true
},
start ({ originalEvent }) {
  this.onControlStart = originalEvent.ctrlKey
}

如上代码中,group对象的pull使用自定义函数,pullFunction方法判断onControlStart变量,如果为true则开启复制,否则是移动;在draggable组件上绑定start事件,拖拽开始触发,在start方法里面修改onControlStart变量的值,originalEvent变量的ctrlKey为true表示按了ctrl键

image.png

image.png

如上两幅图,能够复制成功,但是当复制多个时,发现报错,原因是id重复了,所以我们还应该添加如下:

  <draggable 
    :list="list1"
    :group="{name: 'people', pull: pullFunction}"
+    :clone="clone"
    @start="start"
  >
    <div
        class="list-group-item"
        v-for="element in list1"
        :key="element.name"
      >
        {{ element.name }}
    </div>
  </draggable>
<script>
import draggable from 'vuedraggable'
+let idGlobal = 8
export default {
  name: 'dragable',
  components: {
    draggable
  },
  data () {
    return {
      list1: [
        { name: "John", id: 0 },
        { name: "Joao", id: 1 },
        { name: "Jean", id: 2 }
      ],
      list2: [
        { name: "Juan", id: 5 },
        { name: "Edgard", id: 6 },
        { name: "Johnson", id: 7 }
      ],
      onControlStart: true, // 是否复制
    }
  },
  props: {
  },
  watch: {
  },
  created () {

  },
  mounted () { },
  methods: {
+    clone ({ name }) {
      return { name, id: idGlobal++} // 每次复制的时候,id应该++
    },
    pullFunction () {
      return this.onControlStart ? 'clone': true
    },
    start ({ originalEvent }) {
      this.onControlStart = originalEvent.ctrlKey
    }
  }
}
</script>

5. 点击拖拽按钮,才可以进行拖动

希望点击图标才可以进行拖拽 image.png

  <draggable 
    :list="list"
+    handle=".handle"
    :group="{name: 'people', pull: pullFunction, put: false}"
    tag="ul"
  >
    <li
        class="list-group-item"
        v-for="element in list"
        :key="element.name"
      >
         <!-- 该图标添加handle类 -->
+        <i class="el-icon-rank handle"></i>
        <span> {{ element.name }}</span>
        <input type="text" class="form-control" v-model="element.text" />
    </li>
  </draggable>

对应css代码

.box {
  display: flex;
  width: 300px;
}
.list-group:first-child {
  margin-right: 20px;
}
.list-group-item {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: .75rem 1.25rem;
  margin-bottom: -1px;
  background-color: #fff;
  border: 1px solid rgba(0, 0, 0, .125);
  span {
    margin-left: 20px;
  }
  input {
    margin-left: 20px;
  }
}

6 过渡效果

希望拖拽移动能有过渡效果:示例效果

  • 添加transition-group标签
  • type改为transition
  • name值任取,需要和下面的标签类名对应
<draggable 
    :list="list"
    :group="{name: 'people', pull: true, put: false}"
    tag="ul"
    v-bind="dragOptions"
>
+    <transition-group 
+        type="transition" 
+        name="flip-list"
    >
      <li
        class="list-group-i tem"
        v-for="element in list"
        :key="element.name"
      >
        <span> {{ element.name }}</span>
      </li>
    </transition-group>
</draggable>

增加样式,如下类名需要和transition-group里面的name进行对应

.flip-list-move {
  transition: transform 0.5s;
}

把dragOptions的animation改为200,是另一种过渡效果,详见官网实例

  computed: {
    dragOptions() {
      return {
+        animation: 200,
        group: 'description',
        disabled: false,
        ghostClass: "ghost"
      }
    }
  },

animation属性控制动画的速度,设置为200后更加丝滑一些~,如下描述:

image.png

7. 表格拖拽移动行

希望在表格的行与行之间进行拖拽。如下图,把id为1的数据拖拽和id为2的数据进行交换;通过控制台看到list2数据也发生了改变 image.png

表格拖拽移动时:

调整结构

<div class="box">
  <!-- 拖拽有transition过渡效果 -->
  <div class="list-group">
+    <table class="table table-striped">
      <thead class="thead-dark">
        <tr>
          <th scope="col" width="100">Id</th>
          <th scope="col" width="200">Name</th>
          <th scope="col" width="100">Sport</th>
        </tr>
      </thead>
      <draggable 
        :list="list"
        :group="{name: 'people', pull: true, put: false}"
+        tag="tbody"
        v-bind="dragOptions"
      >
+        <tr v-for="item in list" :key="item.id">
          <td scope="row">{{ item.id }}</td>
          <td>{{ item.name }}</td>
          <td>{{ item.sport }}</td>
        </tr>
      </draggable>
    </table>
  </div>
</div>

数据调整

  data () {
    return {
      list: [
        { id: 1, name: "Abby", sport: "basket" },
        { id: 2, name: "Brooke", sport: "foot" },
        { id: 3, name: "Courtenay", sport: "volley" },
        { id: 4, name: "David", sport: "rugby" }
      ],
    }
  },

样式调整

<style scoped lang='less'>
.table .thead-dark {
  color: #fff;
  background-color: #343a40;
  border-color: #454d55;
}
</style>

8. 表格拖拽移动列

希望拖拽表格的列。和拖拽行的不同点:

  • draggable包裹的是thead里面的th标签,一个th就是一个表头,注意不要包裹整个thead了
  • draggable不要忘记绑定v-model="headers", tag="tr"
  • tbody里面的tr用v-for遍历list值,td用v-for遍历headers值,最终渲染的值是item[header],不能像原来那样只v-for遍历list,否则拖拽列后下面的td数据不更新
<template>
    <div class="box">
      <!-- 表格拖拽移动列 -->
      <div class="list-group">
        <table class="table table-striped">
+          <thead class="thead-dark">
+            <draggable v-model="headers" tag="tr">
+              <th v-for="header in headers" :key="header" width="100" scope="col">{{ header }}</th>
+            </draggable>
+          </thead>
        
          <tbody>
+            <tr v-for="item in list" :key="item.id">
+              <td v-for="header in headers" :key="header" width="100">{{ item[header] }}</td>
+            </tr>
          </tbody>
        </table>
      </div>
    </div>
</template>
  data () {
    return {
+      headers: ["id", "name", "sport"],
      list: [
        { id: 1, name: "Abby", sport: "basket" },
        { id: 2, name: "Brooke", sport: "foot" },
        { id: 3, name: "Courtenay", sport: "volley" },
        { id: 4, name: "David", sport: "rugby" }
      ],
    }
  },

9. 嵌套数据的拖拽

希望嵌套的数据支持拖拽,页面效果如下:

image.png

拖拽后,数据也发生了改变

image.png

列表数据

list: [
    {
      name: "task 1",
      tasks: [
        {
          name: "task 2",
          tasks: []
        }
      ]
    },
    {
      name: "task 3",
      tasks: [
        {
          name: "task 4",
          tasks: []
        }
      ]
    },
    {
      name: "task 5",
      tasks: []
    }
]

组件注册

<template>
 <div class="Layout">
   <nestedDraggable :tasks="list"></nestedDraggable>
 </div>
</template>

<script>
import nestedDraggable from '@/components/nested-draggable.vue'
export default {
 name: 'Layout',
 components: {
   nestedDraggable
 },

在nested-draggable.vue组件中

<template>
   <div class="box">
     <!-- 嵌套列表拖拽 -->
     <draggable
       class="dragArea" 
       :list="tasks" 
       tag="ul"
       :group="{name: 'g1'}"
     >
       <li 
         v-for="el in tasks" 
         :key="el.name" 
       >
         <p>{{ el.name }}</p>
+          在这里进行嵌套,关键!
+          <nested-draggable :tasks="el.tasks"></nested-draggable>
       </li>
     </draggable>
   </div>
</template>

必须接受props

  props: {
   tasks: {
     type: Array,
     required: true
   }
 },