背景:因为项目的需求,需要多组拖拽分配,还能支持排序
效果图
最近太忙了,没时间描述,直接上完整代码,中文文档看这里
完整代码
<template>
<div class="draggable_box">
<el-row :gutter="15">
<el-col :span="6">
<div></div>
</el-col>
<el-col :span="6">
<div class="area">
<div class="card">
<div class="card_top">
<div class="title">一级菜单选项</div>
</div>
<draggable v-model="menus" chosenClass="chosen"
:options="{group:{name: 'first',pull:'clone',put: true},sort: true}" forceFallback="true"
animation="100"
filter=".unmover"
handle=".mover"
@start="onStart" @end="onEndMenu" :move="onMoveMenu">
<transition-group class="menu_area">
<div class="item" :class="element.disabled?'unmover':'mover'" v-for="element in menus"
:key="element.id+'-menus'">{{ element.name }}
</div>
</transition-group>
</draggable>
</div>
<div class="card">
<div class="card_top">
<div class="title">二级菜单选项</div>
</div>
<draggable v-model="submenus" chosenClass="chosen" forceFallback="true"
:options="{group:{name: 'second',pull:'clone',put: true},sort: true}" animation="100"
@start="onStart" @end="onEnd" :move="onMoveSubmenu">
<transition-group class="submenu_area">
<div class="item" v-for="element in submenus" :key="element.id+'-submenus'">{{ element.name }}</div>
</transition-group>
</draggable>
</div>
</div>
</el-col>
<el-col :span="12">
<div class="area card">
<div class="card_top">
<div class="title">权限分配结果</div>
</div>
<draggable v-model="resultArr" chosenClass="chosen" forceFallback="true"
:group="{name: 'first',pull: true,put: true}" animation="100"
@start="onStart" @end="onEndResultMenu" :move="onMoveResultMenu">
<transition-group class="result_menu_area">
<div v-for="(firstEl,index) in resultArr" :key="firstEl.id+'firstEl'">
<div class="item" v-if="firstEl.type===1">
<div>{{ firstEl.name }}</div>
<draggable v-model="firstEl.seconds" chosenClass="chosen" forceFallback="true"
:group="{name: 'second',pull: true,put: true,index}"
dataIdAttr="data-id"
animation="100"
@start="onStart" @end="onEndResultSubMenu" :move="onMoveResultSubMenu">
<transition-group class="result_submenu_area" :index="index">
<div class="item" v-for="secondEl in firstEl.seconds" :key="secondEl.id+'secondEl'">
<div>{{ secondEl.name }}</div>
</div>
</transition-group>
</draggable>
</div>
</div>
</transition-group>
</draggable>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
//导入draggable组件
import draggable from 'vuedraggable'
export default {
//注册draggable组件
components: {
draggable,
},
data() {
return {
drag: false,
//定义要被拖拽对象的数组
menus: [
{id: 1, name: '一级菜单1', type: 1, seconds: [], disabled: false},
{id: 2, name: '一级菜单2', type: 1, seconds: [], disabled: false},
{id: 3, name: '一级菜单3', type: 1, seconds: [], disabled: false},
{id: 4, name: '一级菜单4', type: 1, seconds: [], disabled: false}
],
submenus: [
{id: 1, name: '二级菜单1', type: 2},
{id: 2, name: '二级菜单2', type: 2},
{id: 3, name: '二级菜单3', type: 2},
{id: 4, name: '二级菜单4', type: 2}], //空数组
resultArr: [],
style: 'min-height:120px;display: block;',
moveItem: ''
}
},
mounted() {
},
methods: {
//开始拖拽事件
onStart() {
this.drag = true;
},
//拖拽结束事件
onEnd() {
this.drag = false;
},
//move回调方法
onMove(e, originalEvent) {
this.moveItem = e.draggedContext.element //拖拽对象
//false表示阻止拖拽
return true;
},
/**
* 拖拽一级菜单回调事件,用来控制那个元素不允许被拖拽和获取当前拖动元素的对象
* @param e //e对象结构
* draggedContext: 被拖拽的元素
* index: 被拖拽的元素的序号
* element: 被拖拽的元素对应的对象
* futureIndex: 预期位置、目标位置
* relatedContext: 将停靠的对象
* index: 目标停靠对象的序号
* element: 目标的元素对应的对象
* list: 目标数组
* component: 将停靠的vue组件对象
* @param originalEvent
* @return {boolean} false表示阻止拖拽
*/
onMoveMenu(e, originalEvent) {
this.moveItem = e.draggedContext //拖拽对象
// false表示阻止拖拽:不允许重复拖拽,不允许拖拽到同级子元素下
return !this.resultArr.some(item => item.id === this.moveItem.element.id) && originalEvent.rootEl._prevClass === 'result_menu_area';
},
//一级菜单拖拽结束事件
onEndMenu(e) {
this.drag = false;
const result = this.resultArr.filter((item) => {
return item.id === this.moveItem.element.id;
})
// 对象不存在, 原来就有一个,在加上新拖拽的就是两个
if (result.length < 2) {
this.$set(this.menus[this.moveItem.index], 'disabled', true)
return;
}
// 对象存在,删除新增对象
this.resultArr.splice(e.newDraggableIndex, 1)
},
//拖拽二级菜单回调事件,用来控制那个元素不允许被拖拽和获取当前拖动元素的对象
onMoveSubmenu(e, originalEvent) {
this.moveItem = e.draggedContext //拖拽对象
// false表示阻止拖拽:不允许重复拖拽,不允许拖拽到同级子元素下
return !(e.relatedContext.list && e.relatedContext.list.some(item => item.id === this.moveItem.element.id)) && originalEvent.rootEl._prevClass === 'result_submenu_area';
},
// 拖拽结果目录一级菜单回调
onMoveResultMenu(e, originalEvent) {
this.moveItem = e.draggedContext //拖拽对象
let list = e.relatedContext.list
// false表示阻止拖拽:不允许重复拖拽,不允许拖拽到同级子元素下
if (e.to._prevClass === 'result_menu_area') {
// 拖动至结果区域
return !(list && list.filter(item => item.id === this.moveItem.element.id).length > 2)
} else {
return e.to._prevClass === 'menu_area'
}
},
// 拖拽结果目录一级菜单结束事件
onEndResultMenu(e) {
this.drag = false;
// 判断目标区域是否为 一级菜单选项
if (e.to._prevClass === 'menu_area') {
let list = [];
// menus 数组去重
this.menus.forEach((item) => {
if (!list.some(el => el.id === item.id)) {
list.push(item)
}
})
this.menus = list;
// 找到原数组的对象下标
this.$nextTick(() => {
this.menus.forEach((item) => {
item.seconds = [];
if (item.id === this.moveItem.element.id) {
item.disabled = false;
}
})
})
}
},
// 拖拽结果目录二级菜单回调
onMoveResultSubMenu(e, originalEvent) {
this.moveItem = e.draggedContext //拖拽对象
let list = e.relatedContext.list
// false表示阻止拖拽:不允许重复拖拽,不允许拖拽到同级子元素下且不能与父级元素同级
if (e.to._prevClass === 'result_submenu_area') {
return !(list && list.filter(item => item.id === this.moveItem.element.id).length > 2)
} else {
return e.to._prevClass === 'submenu_area'
}
},
// 拖拽结果目录二级菜单结束事件,支持排序
onEndResultSubMenu(e) {
this.drag = false;
if (e.to._prevClass === 'result_submenu_area') {
// 获取目标区域所在的下标
let index = e.to.__vue__.$attrs.index
// 判断目标区域的二级菜单是否已经存在拖拽对象
const result = this.resultArr[index].seconds.filter(item => item.id === this.moveItem.element.id)
// 对象不存在, 原来就有一个,在加上新拖拽的就是两个
if (result.length < 2) {
return;
}
// 对象存在,删除新增对象
this.resultArr[index].seconds.splice(e.newDraggableIndex, 1)
} else {
const result = this.submenus.filter((item) => {
return item.id === this.moveItem.element.id;
})
// 对象不存在, 原来就有一个,在加上新拖拽的就是两个
if (result.length < 2) {
return;
}
// 对象存在,删除新增对象
this.submenus.splice(e.newDraggableIndex, 1)
}
},
}
</script>
<style scoped>
/*被拖拽对象的样式*/
.item {
padding: 6px;
background-color: #ffffff;
border: solid 2px #ffffff;
margin-bottom: 10px;
cursor: move;
}
.unmover {
background-color: #C0C4CC;
border: solid 2px #C0C4CC;
margin-bottom: 10px;
cursor: not-allowed;
}
/*选中样式*/
.chosen {
border: solid 2px #3089dc !important;
}
.draggable_box {
overflow: hidden;
}
.area {
height: 76vh;
display: flex;
flex-direction: column;
gap: 15px;
}
.area > .card {
flex: 1;
}
.card {
background-color: #EBEEF5;
border-radius: 4px;
padding: 15px;
font-size: 14px;
box-sizing: border-box;
}
.card_top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
color: #111111;
font-size: 16px;
}
.result_menu_area {
display: block;
height: calc(76vh - 71px);
overflow: auto;
}
.result_menu_area::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.result_menu_area::-webkit-scrollbar-thumb {
border-radius: 6px;
background-color: #e1e1e1;
}
.result_menu_area::-webkit-scrollbar-track {
border-radius: 6px;
background-color: #f1f1f1;
}
.submenu_area, .result_submenu_area, .menu_area {
display: block;
min-height: 120px;
}
</style>
先介绍一波参数参数
var sortable = new Sortable(el, {
//一个网页存在多个分组时设置
//or { name: "...", pull: [true, false, 'clone', array], put: [true, false, array] }
group: "name",
//是否允许列内部排序,如果为false当有多个排序组时,多个组之间可以拖拽,本身不能拖拽
sort: true,
// 按下鼠标后多久可以拖拽 1000表示1秒
delay: 0,
//如果为false按下鼠标不动才计算延时,移动则无效
delayOnTouchOnly: false,
//当按下鼠标移动了n个像素时会取消延迟delay事件,即可超过了这个范围无法拖动元素了
//px, how many pixels the point should move before cancelling a delayed drag event
touchStartThreshold: 0,
//启用禁用拖拽
disabled: false,
//Store
store: null,
//动画效果
animation: 150,
// Easing 动画 默认null. See https://easings.net/ for examples.
easing: "cubic-bezier(1, 0, 0, 1)",
//句柄,点击指定class样式的对象才能拖拽元素
handle: ".my-handle",
//忽略class为ignore-elements的元素不能拖动,或者通过函数来实现过滤不允许拖动的对象
// Selectors that do not lead to dragging (String or Function)
filter: ".ignore-elements",
//触发filter时调用`event.preventDefault()`
// Call `event.preventDefault()` when triggered `filter`
preventOnFilter: true,
//指定那些元素可以被拖拽
// Specifies which items inside the element should be draggable
draggable: ".item",
//指定获取拖动后排序的data属性
dataIdAttr: 'data-id',
//停靠位置的自定义样式
// Class name for the drop placeholder
ghostClass: "sortable-ghost",
//选中元素的自定义样式
// Class name for the chosen item
chosenClass: "sortable-chosen",
//拖拽时的自定义样式
// Class name for the dragging item
dragClass: "sortable-drag",
//交互区大小,A元素到B元素内多深的距离触发替换位置
//Threshold of the swap zone
swapThreshold: 1,
// Will always use inverted swap zone if set to true
invertSwap: false,
// Threshold of the inverted swap zone (will be set to swapThreshold value by default)
invertedSwapThreshold: 1,
//拖拽方向(默认会自动判断方向)
direction: 'horizontal',
//忽略HTML5原生拖拽行为
forceFallback: false,
//拖拽时被克隆元素的样式名称
// Class name for the cloned DOM Element when using forceFallback
fallbackClass: "sortable-fallback",
// Appends the cloned DOM Element into the Document's Body
fallbackOnBody: false,
// Specify in pixels how far the mouse should move before it's considered as a drag.
fallbackTolerance: 0,
dragoverBubble: false,
// Remove the clone element when it is not showing, rather than just hiding it
removeCloneOnHide: true,
// px, distance mouse must be from empty sortable to insert drag element into it
emptyInsertThreshold: 5,
setData: function (/** DataTransfer */dataTransfer, /** HTMLElement*/dragEl) {
dataTransfer.setData('Text', dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent
},
//点击选中元素事件
// Element is chosen
onChoose: function (/**Event*/evt) {
evt.oldIndex; // element index within parent
},
//取消选中事件
// Element is unchosen
onUnchoose: function (/**Event*/evt) {
// same properties as onEnd
},
//开始拖拽事件
// Element dragging started
onStart: function (/**Event*/evt) {
evt.oldIndex; // element index within parent
},
//结束拖拽事件
// Element dragging ended
onEnd: function (/**Event*/evt) {
var itemEl = evt.item; // dragged HTMLElement
evt.to; // target list
evt.from; // previous list
evt.oldIndex; // element's old index within old parent
evt.newIndex; // element's new index within new parent
evt.oldDraggableIndex; // element's old index within old parent, only counting draggable elements
evt.newDraggableIndex; // element's new index within new parent, only counting draggable elements
evt.clone // the clone element
evt.pullMode; // when item is in another sortable: `"clone"` if cloning, `true` if moving
},
// 被拖拽的元素加入到其他列表时的事件
// Element is dropped into the list from another list
onAdd: function (/**Event*/evt) {
// same properties as onEnd
},
//排序发生改变时的事件
// Changed sorting within list
onUpdate: function (/**Event*/evt) {
// same properties as onEnd
},
// Called by any change to the list (add / update / remove)
onSort: function (/**Event*/evt) {
// same properties as onEnd
},
// Element is removed from the list into another list
onRemove: function (/**Event*/evt) {
// same properties as onEnd
},
// Attempt to drag a filtered element
onFilter: function (/**Event*/evt) {
var itemEl = evt.item; // HTMLElement receiving the `mousedown|tapstart` event.
},
// Event when you move an item in the list or between lists
onMove: function (/**Event*/evt, /**Event*/originalEvent) {
/*
evt.dragged; // 被拖拽的对象
evt.draggedRect; // 被拖拽的对象所在区域 {left, top, right, bottom}
evt.related; // 被替换的对象
evt.relatedRect; // DOMRect
evt.willInsertAfter; // 是在被替换对象的前面还是后面
originalEvent.clientY; // 鼠标的位置
*/
evt.dragged; // dragged HTMLElement
evt.draggedRect; // DOMRect {left, top, right, bottom}
evt.related; // HTMLElement on which have guided
evt.relatedRect; // DOMRect
evt.willInsertAfter; // Boolean that is true if Sortable will insert drag element after target by default
originalEvent.clientY; // mouse position
// return false; — for cancel
// return -1; — insert before target
// return 1; — insert after target
},
// Called when creating a clone of element
onClone: function (/**Event*/evt) {
var origEl = evt.item;
var cloneEl = evt.clone;
},
// Called when dragging element changes position
onChange: function (/**Event*/evt) {
evt.newIndex // most likely why this event is used is to get the dragging element's current index
// same properties as onEnd
}
});