如何玩转sortablejs-vuedraggable实现表单嵌套拖拽功能

5,882 阅读4分钟

最近几天在研究有关vue实现拖拽的功能,不过跟一般的拖拽排序有点不同,这个需求可能出现多行多列嵌套的表单元素,数据也是递归形式的出现。我也是在vuedraggable的基础上扩展实现的,如何想了解更多的拖拽排序功能可以参考sortablejs.github.io/Vue.Draggab…

需要实现的功能

  1. 表单元素可能出现嵌套,数据出现树形结构
  2. 实现拖拽功能,表单元素可以移动到空的列里面,但是表单元素内容的不能来回拖拽排序
  3. 行与行之间可以拖动排序,列与列直接不能移动排序,能移动的只是字段数据也就是表单元素
  4. 右边列表里的字段可以拖拽添加到左边的空白没内容的列里面

用的技术点

  1. vue组件递归实现
  2. vuedraggable拖拽排序
  3. vuedraggable的例子Functional third party,主要是元素移动
  4. vuedraggable实现拖拽复制功能
  5. vuetify :vue ui组件,这里面主要用了它的删格系统和vcard卡片

实现功能的部分代码

Drag组件也是要递归的组件代码

<template>
  <draggable
    v-model="datas"
    tag="v-layout"
    class="row wrap fill-height align-center sortable-list"
    style="background: grey;"
  >
    <v-flex
      v-for="row in datas"
      :key="row.index"
      class="sorttable"
      xs12
      my-2
      style="background: red"
    >
      <div class="row wrap justify-space-around">
        <v-flex
          v-for="item in row.items"
          :key="item.id"
          xs4
          pa-3
          class="row-v"
        >
          <!-- 加判断如果item存在rows数组,则递归继续执行这个组件-->
          <template v-if="item.rows && Array.isArray(item.rows)">
            <drag :data="item.rows" />
          </template>
          <draggable
            v-else
            :list="item.data"
            tag="div"
            :group="{ name: 'row'}"
            :move="getData"
            :animation="100"
            :empty-insert-threshold="60"
            @change="log"
          >
            <v-card
              v-for="item2 in item.data"
              :key="item2.title"
              style="height: 100px;"
            >
              {{ item2.title }}
            </v-card>
          </draggable>
        </v-flex>
      </div>
    </v-flex>
  </draggable>
</template>

<script>
import draggable from 'vuedraggable'
import Vue from 'vue'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
Vue.use(Vuetify)
export default {
  name: 'Drag',
  order: 17,
  components: {
    draggable
  },
  props: {
    data: {
      type: Array,
      default () {
        return []
      }
    }
  },
  data () {
    return {
      datas: this.data,
      controlOnStart: true
    }
  },
  methods: {
  // 限制移动的方法
    getData (e, d) {
      if (e.relatedContext.list.length > 0) {
        return false
      }
    },
    log: function (evt) {
      // window.console.log(evt)
      // console.log(this.data)
      if (Object.keys(evt)[0] === 'added') {
        this.arrLoop(this.data, evt.added.element)
      }
    },
    addHandler (e, d) {
      // console.log(e)
    },
    endHandler (e, b) {
      console.log(b)
    },
    // 递归实现遍历数据
    arrLoop (arr, ele) {
      arr.forEach(item => {
        const itemArr = item.data
        if (itemArr && itemArr.length > 1) {
          for (let i = 0; i < itemArr.length; i++) {
            if (itemArr[i].title === ele.title) {
              itemArr.splice(i, 1)
            }
          }
        }
        if (item.items && item.items.length) {
          this.arrLoop(item.items, ele)
        }
      })
    }
  }
}
</script>
<style>
.buttons {
  margin-top: 35px;
}
.row-v {
  /* height: 150px;
  width: 200px; */
  width: 33%;
  height: 100px;
  display: inline-block;
  background: blue;
  border: 1px solid #ebebeb;
}
.row {
  margin-left: 0;
  margin-right: 0;
}
.ghost {
  opacity: 0.5;
  background: #c8ebfb;
}
</style>

注意:实现递归一定定义Drag组件的name值,要不就容易报错

在这里插入图片描述
emptyInsertThreshold:拖动时,鼠标必须与空的可排序对象之间的距离(以像素为单位),以便将拖动元素插入到该可排序对象中。默认为5。设置为0禁用此功能。这个参数要适当的设置,如果是默认值,当列为空的时候,很难把元素拖进去,这个也是一个比较难解决的点,因为需要把右边字段元素拖动到左边空列中,或者左边的元素移动到空的列里。 move对应方法getData的方法主要实现如果relatedContext.list.length 大于0,则取消移动功能。 Drag的数据:

rows: [
        {
          index: 1,
          items: [
            {
              id: 1,
              data: [{
                title: 'item 1'
              }]
              
            },
            {
              id: 11,
              data: [{
                title: 'item 11'
              }]
            },
            {
              id: 12,
              data: [
              ]
            }
          ]
        },
        {
          index: 2,
          items: [
            {
              id: 0,
              rows: [
                {
                  index: 1,
                  items: [
                    {
                      id: 2,
                      data: [{
                        title: 'item 211'
                      }]
                    },
                    {
                      id: 3,
                      data: [{
                        title: 'item 212'
                      }]
                      
                    }
                  ]
                },
                {
                  index: 2,
                  items: [
                    {
                      id: 4,
                      data: [
                        {
                          title: 'item 222'
                        }
                      ]
                    }
                  ]
                }
              ]
            },
            {
              id: 5,
              data: [{
                title: 'item 3'
              }]
            }
          ]
        },
        {
          index: 3,
          items: [
            {
              id: 6,
              data: [{
                title: 'item 4'
              }]
            },
            {
              id: 7,
              data: [{
                title: 'item 5'
              }]
            },
            {
              id: 8,
              data: []
            }
            
          ]
        }
      ]

右边列表的组件代码:

<template>
  <div>
    <div
      v-for="item in datas"
      :key="item.id"
      class="item-box"
    >
      <h2>{{ item.title }}</h2>
      <div class="item-con">
        <draggable
          class="dragArea list-group"
          :list="item.items"
          :group="{ name: 'row', pull: 'clone', put: false }"
          :clone="cloneDog"
        >
          <span
            v-for="item2 in item.items"
            :key="item2.id"
          >
            {{ item2.title }}
          </span>
        </draggable>
      </div>
    </div>
  </div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
  name: 'Drag',
  components: {
    draggable
  },
  props: {
    data: {
      type: Array,
      default () {
        return []
      }
    }
  },
  data () {
    return {
      datas: [
        {
          id: 1,
          title: '标题1',
          items: [
            {
              id: 11,
              title: 'item 11'
            },
            {
              id: 12,
              title: 'item 12'
            }
          ]
        },
        {
          id: 2,
          title: '标题2',
          items: [
            {
              id: 21,
              title: 'item 21'
            },
            {
              id: 22,
              title: 'item 22'
            }
          ]
        }
      ]
    }
  },
  methods: {
    cloneDog (ele) {
      // console.log(ele)
      let b = this.arrLoop(this.rows, ele)
      if (!b) {
        return ele
      }
    },
    arrLoop (arr, ele) {
      for (let i = 0; i < arr.length; i++) {
        if (arr[i].id === ele.id) {
          return true
        }
        if (arr[i].items && arr[i].items.length) {
          return this.arrLoop(arr[i].items, ele)
        }
      }
    }
  }
}
</script>
<style lang="scss" scoped>
 .list-group{
   span {
     display: inline-block;
     padding: 0 12px;
     border-radius: 4px;
     border: 1px solid #ebebeb;
     line-height:  32px;
     height: 32px;
     background: #f5f5f5;
     margin-right: 15px;
   }
 }
</style>

clone的cloneDog方法实现复制功能,首先递归循环数据判断是需要复制的元素在左边的列表中是否存在,若是存在,则取消复制,不存在,则复制。

效果如下图:

在这里插入图片描述

总结

这篇文章分享的主要技术点就是vuedraggable拖拽排序和复制、嵌套拖拽排序功能、vue组件递归功能,这仅代表个人观点,想了解更多请扫描二维码:

在这里插入图片描述