最近几天在研究有关vue实现拖拽的功能,不过跟通常的拖拽排序有点不一样,这个需求可能出现多行多列嵌套的表单元素,数据也是递归形式的出现。我也是在vuedraggable的基础上扩展实现的,如何想了解更多的拖拽排序功能能够参考https://sortablejs.github.io/Vue.Draggable/#/simplejavascript
须要实现的功能
- 表单元素可能出现嵌套,数据出现树形结构
- 实现拖拽功能,表单元素能够移动到空的列里面,可是表单元素内容的不能来回拖拽排序
- 行与行之间能够拖动排序,列与列直接不能移动排序,能移动的只是字段数据也就是表单元素
- 右边列表里的字段能够拖拽添加到左边的空白没内容的列里面
用的技术点
- vue组件递归实现
- vuedraggable拖拽排序
- vuedraggable的例子Functional third party,主要是元素移动
- vuedraggable实现拖拽复制功能
- vuetify :vue ui组件,这里面主要用了它的删格系统和vcard卡片
实现功能的部分代码
Drag组件也是要递归的组件代码css
<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值,要不就容易报错vue
emptyInsertThreshold:拖动时,鼠标必须与空的可排序对象之间的距离(以像素为单位),以便将拖动元素插入到该可排序对象中。默认为5。设置为0禁用此功能。这个参数要适当的设置,若是是默认值,当列为空的时候,很难把元素拖进去,这个也是一个比较难解决的点,由于须要把右边字段元素拖动到左边空列中,或者左边的元素移动到空的列里。
move对应方法getData的方法主要实现若是relatedContext.list.length 大于0,则取消移动功能。
Drag的数据:java
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: [] } ] } ]
右边列表的组件代码:git
<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方法实现复制功能,首先递归循环数据判断是须要复制的元素在左边的列表中是否存在,如果存在,则取消复制,不存在,则复制。github
效果以下图:
数组
总结
这篇文章分享的主要技术点就是vuedraggable拖拽排序和复制、嵌套拖拽排序功能、vue组件递归功能。oop