元素列表,多选后可以成组,支持最多3层的嵌套组。
现要求可以拖拽元素,实现顺序的交换、组间的移动;移动时,显示位置提示的样式,放进组内与并列的样式不同;支持多选拖拽。
使用的事件:
未使用的事件:
每个item绑定以上使用的5个事件,item将自己的信息emit到容器内,由容器统一处理。
元素列表的数据结构是tree,每个item除了自己的属性外,有一个数组类型的elements属性,保存的是子元素列表。
在容器内,实现了一些操作tree的工具方法,例如删除节点、移动节点到某个节点后、移动节点到某个节点下等。
1.handleDragStart
记录被拖动的元素信息,修改被拖动的元素样式
handleDragStart: function(element, ref) {
this.isDragging = true
if(element === 'multi'){
this.isMultiDragging = true
this.dragElement = undefined
this.dragElementIdList = Array.from(this.selectedItemList)
for(let id of this.dragElementIdList){
document.getElementById(id).style.opacity = 0.6
}
} else {
this.isMultiDragging = false
this.dragElement = element
this.dragElementIdList = []
ref.style.opacity = 0.6
}
},
2. handleDragEnter
dragenter & dragover 的处理,元素会根据鼠标位置的细微差别,区分目标位置是在自己后、前或是与1、2、3级父节点并列(逻辑细碎不提),函数根据这些信息记录当前目标位置,并显示位置提示样式。
handleDragEnter: function(element, targetPosition, ref, that) {
if(!this.isDragging) return;
this.clearStyle('borderBottom')
this.clearStyle('border')
let nodeList = this.isMultiDragging ? this.dragElementIdList.map(id => this.getTreeNode(this.elementsTree, id)) : [this.dragElement]
this.isTargetItemValid = this.isTargetValid(nodeList, element, targetPosition)
this.targetElementId = this.isTargetItemValid ? element.uuid : undefined
this.targetPosition = this.isTargetItemValid ? targetPosition : undefined
let styleColor = this.isTargetItemValid ? '#007FFF' : '#404551'
//0 后面
if(targetPosition===0){
ref.style.borderBottom = "2px " + styleColor + " inset"
let pid = this.findDirectParentNode(this.elementsTree, element.uuid)
if (document.getElementById(pid)) document.getElementById(pid).firstChild.style.border = "1px " + styleColor + " inset"
}
// 1 底层的第一个子节点 放到最前面
else if(targetPosition===1){
ref.style.borderTop = "2px " + styleColor + " inset"
}
// 2 第一个子元素
else if(targetPosition===2){
ref.style.border = "1px " + styleColor + " inset"
}
// -1 与一级父节点并列
else if(targetPosition===-1){
let pid = this.findDirectParentNode(this.elementsTree, element.uuid)
let realPreNode = this.getTreeNode(this.elementsTree, pid)
let ppid = this.findDirectParentNode(this.elementsTree, pid)
let realParentNode = this.getTreeNode(this.elementsTree, ppid)
if(realParentNode){
this.targetElementId = realPreNode.uuid
document.getElementById(realPreNode.uuid).style.borderBottom = "2px " + styleColor + " inset"
document.getElementById(realParentNode.uuid).firstChild.style.border = "1px " + styleColor + " inset"
} else { // 最外层的最后一个
let realPreNode = this.elementsTree[this.elementsTree.length - 1]
this.targetElementId = realPreNode.uuid
document.getElementById(realPreNode.uuid).style.borderBottom = "2px " + styleColor + " inset"
}
}
// -2 与二级父节点并列
else if(targetPosition===-2){
...
}
// -3 与三级父节点并列
else if(targetPosition===-3){
...
}
if(element.type==='GROUP' && targetPosition===0 && nodeList.findIndex(item => item.uuid === element.uuid) < 0){ // 没展开的组,停留一段时间后要展开
clearTimeout(this.dragTimeout)
this.dragTimeout = setTimeout(() => {
if(this.targetElementId===element.uuid){
that.toggleWrap(null, 1)
}
}, 400)
}
},
3.handleDragEnd
判断移动的合法性、修改tree数据结构、整理数据调用接口通知后端、清楚数据状态与样式。
handleDragEnd: function() {
// console.log('from: ', this.dragElement.uuid, 'to: ', this.targetElementId)
let directParentId = this.findDirectParentNode(this.elementsTree, this.targetElementId)
if(!this.isTargetItemValid || !this.targetElementId || ( this.dragElement && this.targetElementId === this.dragElement.uuid) || this.dragElementIdList.includes( this.targetElementId)){
this.clearStyle('border')
this.clearStyle('borderBottom')
this.clearStyle('opacity')
return
}
if(this.isDragging){
let nodeList = this.isMultiDragging ? this.dragElementIdList.map(id => this.getTreeNode(this.elementsTree, id)) : [this.dragElement]
let nodeIdList = this.isMultiDragging ? this.dragElementIdList : [this.dragElement.uuid]
// 0 后面
if(this.targetPosition === 0){
for(let id of nodeIdList){
this.deleteTreeNode(this.elementsTree, id)
}
this.insertTreeNodeAfter(this.elementsTree, this.targetElementId, nodeList)
this.dragItemRequest(nodeIdList, '', this.targetElementId) // 需要联调
}
// 1 底层的第一个子节点 放到最前面
else if(this.targetPosition===1){
for(let id of nodeIdList){
this.deleteTreeNode(this.elementsTree, id)
}
this.insertTreeNodeUnder(this.elementsTree, undefined, nodeList)
this.dragItemRequest(nodeIdList, '', '') // 需要联调
}
// 2 第一个子元素
else if(this.targetPosition === 2){
for(let id of nodeIdList){
this.deleteTreeNode(this.elementsTree, id)
}
this.insertTreeNodeUnder(this.elementsTree, this.targetElementId, nodeList)
this.dragItemRequest(nodeIdList, this.targetElementId, '') // 需要联调
}
// -1 -2 -3 此时的targetElementId是preElement的Id
else if(this.targetPosition < 0){
for(let id of nodeIdList){
this.deleteTreeNode(this.elementsTree, id)
}
this.insertTreeNodeAfter(this.elementsTree, this.targetElementId, nodeList)
this.dragItemRequest(nodeIdList, '', this.targetElementId) // 需要联调
}
this.updateTreeLevel(this.elementsTree)
}
this.dragElement = undefined
this.dragElementIdList = []
this.isDragging = false
this.isMultiDragging = false
this.targetElementId = ''
this.targetPosition = undefined
this.clearStyle('border')
this.clearStyle('borderBottom')
this.clearStyle('opacity')
},
1. drop事件不触发。(在dragover里 preventDefault也不能触发,原因未明,因此用了dragend事件,同样可以实现需求)
2.合法性判断里,要保证移动后嵌套也不能超过3层,因此需要计算depth和level相加,较为麻烦。
3.根据鼠标在元素上dragover时左右位置、上下位置的不同,判断不同的位置,使用event.clientX 与 event.offsetY 参与判断。
handleDragOver: function(event) {
event.preventDefault();
if(this.isGroupLastElement){
let targetPosition = 0
// -1 - 与一级父节点并列 -2 - 与二级父节点并列 -3 - 与三级父节点并列
if(this.element.type==='GROUP' && !this.isElementsWrap) { // 对于打开的组,只能放进去
targetPosition = 2 // 第一个子元素
}
else if(event.clientX<=60) targetPosition = this.level >= 3 ? -3 : -1 * this.level;
else if(event.clientX<=90) targetPosition = this.level >= 2 ? -2 : -1 * this.level;
else if(event.clientX<=120) targetPosition = this.level >= 1 ? -1 : -1 * this.level;
this.$emit('handle-drag-enter', this.element, targetPosition, this.$refs['self'], this)
}
else if(this.level===0 && this.index===0 && (this.element.type !== 'GROUP' || this.isElementsWrap) ){ //底层的第一个子节点 放到最前面
let targetPosition = 0
if(event.offsetY <= 12){
targetPosition = 1
}
this.$emit('handle-drag-enter', this.element, targetPosition, this.$refs['self'], this)
}
},