Skip to content
4 changes: 3 additions & 1 deletion packages/devui-vue/devui/dragdrop/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { App } from 'vue'
import DraggableDirective from './src/draggable-directive'
import DroppableDirective from './src/droppable-directive'
import SortableDirective from './src/sortable-directive'

export { DraggableDirective, DroppableDirective }
export { DraggableDirective, DroppableDirective, SortableDirective }

export default {
title: 'Dragdrop 拖拽',
Expand All @@ -11,5 +12,6 @@ export default {
install(app: App): void {
app.directive('DDraggable', DraggableDirective)
app.directive('DDroppable', DroppableDirective)
app.directive('DSortable', SortableDirective)
}
}
1 change: 1 addition & 0 deletions packages/devui-vue/devui/dragdrop/src/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const shadowId = 'shadow0611'
40 changes: 35 additions & 5 deletions packages/devui-vue/devui/dragdrop/src/draggable-directive.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
import { changeDragState, deleteInsertedSortableShadow } from './utils'
import { shadowId } from './constant'

export default {
mounted(el: HTMLElement): void {
el.setAttribute('draggable', 'true')
/**
*
* @param el
* @description
* 1、绑定该指令的element将会具备拖拽能力
* 2、为各元素进行初始化配置
* 2.1、dragFlag: 是否处于拖拽中
* 2.2、dragOverFlag: 是否处于可放置区域
*
* 1、整体思路
* 1.1、为每个绑定drag指令的元素维护状态
* 1.1.1、状态集合:dragStart、drag、dragover、drop、shouldCreateShadow
*
* 1.2、进入drop区域后,确保drop区域能够获取正在进行drag的元素
*/
mounted(el: HTMLElement, binding: unknown): void {
el.setAttribute('draggable', 'true');
el.style.cursor = 'grab'

// dragstart/drag/dragend
el.addEventListener('dragstart', (event: DragEvent) => {
event.dataTransfer.setData('originId', el.id)
})
el.addEventListener('drag', () => {
changeDragState(el, el.id, 'true', 'true', 'false', 'false', 'false', 'true')
if (binding.instance.$root.dropElement && document.getElementById(shadowId)){
deleteInsertedSortableShadow(binding.instance.$root.dropElement) // 如何让它仅执行1次?
binding.instance.$root.dropElement = null
}
}, false)

// dragStart事件为每个绑定元素进行初始化
el.addEventListener('dragstart', ()=>{
// el or binding.instance or vnode.context
changeDragState(el, el.id, 'true', 'true', 'false', 'false', 'false', 'false')
binding.instance.$root.identity = el.id
el.dataset.dragArea = el.parentNode.className
}, false)
},
}
48 changes: 41 additions & 7 deletions packages/devui-vue/devui/dragdrop/src/droppable-directive.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,49 @@
import { changeDragState } from './utils'

export default {
mounted(el: HTMLElement): void {
// dragenter/dragover/dragend/drop
/**
*
* @param el
* @description
* dragOver:
* 1、生成与清除阴影的时机
* 1.1、生成时机(只生成一次): dragFlag === true && dragOverFlag === true
* drop:
* 1、完成放的操作
* 1.1、清除相应的阴影
*/
mounted(el: HTMLElement, binding:unknown): void {
// dragenter/dragover/dragend/drop
el.addEventListener('dragover', (event: DragEvent) => {
event.preventDefault()
})
const dragId = binding.instance.$root.identity
changeDragState(document.getElementById(dragId), dragId, 'true', 'false', 'true', 'false', 'false', 'false')
document.getElementById(dragId).dataset.dropArea = [...el.childNodes][1].className
}, false)

// 新增两个标识解决战斗,即dragStart区域、drop区域、sortableDrop区域
el.addEventListener('drop', (event: DragEvent) => {
const originId = event.dataTransfer.getData('originId')
const originNodeCopy = document.getElementById(originId).cloneNode(true)
const targetNode: any = event.target
targetNode.append(originNodeCopy)
event.preventDefault()
const dragId = binding.instance.$root.identity
if (document.getElementById(dragId).dataset.dropArea == document.getElementById(dragId).dataset.dragArea){
return
}
// 如何定义可放置区域这个问题得商榷一下
const childrenArr = [...Array.from(el.children)[1].children]
if (childrenArr.length > 0){
for (let index = 0; index < childrenArr.length; index++){
const childrenYRange = childrenArr[index].getBoundingClientRect().top + childrenArr[index].offsetHeight / 2
if (parseFloat(event.clientY) < parseFloat(childrenYRange)){
el.children[1].insertBefore(document.getElementById(dragId), childrenArr[index])
break
}
if (index === childrenArr.length-1){
el.children[1].appendChild(document.getElementById(dragId))
}
}
}else {
el.childNodes[1].appendChild(document.getElementById(dragId))
}
})
},
}
48 changes: 48 additions & 0 deletions packages/devui-vue/devui/dragdrop/src/sortable-directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { shadowId } from './constant'
import { changeDragState, createInsertSortableShadow, insertDragElement } from './utils'


export default {
/**
*
* @param el
* @description
* 此命令用于将元素变为可放置的元素并且支持排序
* dragover:
* 1、说明此时进入可排序放置的区域
* 2、此时应该生成相应的可排序的shadow
* drop:
* 1、可放置区域里如果没有拖拽元素,直接放置
* 2、可放置区域里如果有其他的可拖拽元素,需要对比放置到正确的位置上
*/
mounted(el: HTMLElement, binding:unknown):void {
el.addEventListener('dragover', function (event: DragEvent){
event.preventDefault()
const targetNode: any = event.target;
const dragId = binding.instance.$root.identity
if (!binding.instance.$root.dropElement){
binding.instance.$root.dropElement = [...el.childNodes][1]
}
changeDragState(document.getElementById(binding.instance.$root.identity), binding.instance.$root.identity, 'true', 'false', 'true', 'false', 'true', 'false')
const { dragover, shouldCreateShadow } = document.getElementById(dragId).dataset
if (dragover == 'true'){
if (shouldCreateShadow == 'true'){
createInsertSortableShadow([...targetNode.children][1], event, dragId)
}
}

})
el.addEventListener('drop', function (event: DragEvent){
// 获取可放置区域
const dropArea = [...el.childNodes][1]
const dragId = binding.instance.$root.identity
dropArea.removeChild(document.getElementById(shadowId))
if ([...dropArea.childNodes].length == 0){
dropArea.appendChild(document.getElementById(dragId))
}else {
insertDragElement(dropArea, dragId, event)
}
changeDragState(document.getElementById(dragId), dragId, 'false', 'false', 'false', 'true', 'false', 'false')
})
}
}
143 changes: 143 additions & 0 deletions packages/devui-vue/devui/dragdrop/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { shadowId } from './constant'

/**
*
* @param id
* @descriprion
* 根据id获取非内联样式元素的样式
*/
function getElementStyle (id: string, styleName: string):string {
return document.getElementById(id).currentStyle ? document.getElementById(id).currentStyle[styleName] : window.getComputedStyle(
document.getElementById(id),
styleName
)
}

/**
*
* @param originId
* @description
* 根据拖拽的id生成相应的阴影
* 如何生成shadow?
* 情况一: dragable -> drop without sortable
* 情况二: anything -> drop without anything
*/
function createShadow (originId:string):HTMLElement {
const shadow = document.createElement('div');
shadow.id = shadowId
shadow.style.background = 'rgb(206, 215, 255)'
shadow.style.width = getElementStyle(originId, 'width')
shadow.style.height = '20px'
return shadow
}

/**
*
* @param el
* @param originId
* @param dragStart
* @param drag
* @param dragover
* @param drop
* @param shouldCreateShadow
* @param dragFlag
* @description
* 改变拖拽元素相应的状态
*/
function changeDragState (el:string, originId:string, dragStart:string, drag:string, dragover:string, drop:string, shouldCreateShadow:string, dragFlag: string): void{
el.dataset.originId = originId
el.dataset.dragStart = dragStart
el.dataset.dragover = dragover
el.dataset.drop = drop
el.dataset.shouldCreateShadow = shouldCreateShadow
el.dataset.dragFlag = dragFlag
}

/**
*
* @param compareElement
* @returns
* @description
* 计算可对比元素的高度
*/
function computeCompareElementHeight (compareElement: HTMLCollection): unknown{
return compareElement.getBoundingClientRect().top + Math.floor(compareElement.offsetHeight / 2)
}

/**
*
* @param sortDropArea
* @param mouseObject
* 1、首先确认可放置区域
* 2、确保每个元素只生成一次shadow
* 3、
*/
function createInsertSortableShadow (sortDropArea: unknown, mouseObject: unknown, originId: string):void {
const sortDropAreaArr: Array = [...sortDropArea.children]
if (sortDropAreaArr.length == 0){
if (!document.getElementById(shadowId)){
const shadowElement = createShadow(originId)
sortDropArea.appendChild(shadowElement)
}
}else {
for (let index = 0; index < sortDropAreaArr.length; index++){
const compareHeight = computeCompareElementHeight(sortDropAreaArr[index])
document.getElementById(shadowId) ? sortDropArea.removeChild(document.getElementById(shadowId)) : null
if (index == sortDropAreaArr.length-1){
sortDropArea.appendChild(createShadow(originId))
break
}
if (Math.floor(mouseObject.clientY)<= compareHeight){
sortDropArea.insertBefore(createShadow(originId), sortDropAreaArr[index])
break
}
}
}
}

/**
*
* @param dropAreaContainer
* @param dragId
* @param mouseObject
* @description
* 向sortable区域插入拖拽元素
*/
function insertDragElement (dropAreaContainer: HTMLCollection, dragId: string, mouseObject: MouseEvent): void {
for (let index = 0; index < [...dropAreaContainer.children].length; index++){
if (index == [...dropAreaContainer.children].length-1){
dropAreaContainer.appendChild(document.getElementById(dragId))
break
}
if (Math.floor(mouseObject.clientY) <= computeCompareElementHeight([...dropAreaContainer.children][index])){
dropAreaContainer.insertBefore(document.getElementById(dragId), [...dropAreaContainer.children][index])
break
}
}
}

/**
*
* @param dropSortArea
* @description
* 删除可排序区域中的shadow
*/
function deleteInsertedSortableShadow (dropSortArea: unknown):void{
if (dropSortArea){
if (document.getElementById(shadowId)){
if (dropSortArea.contains(document.getElementById(shadowId))){
dropSortArea.removeChild(document.getElementById(shadowId))
}
}
}
}


export {
createShadow,
changeDragState,
createInsertSortableShadow,
deleteInsertedSortableShadow,
computeCompareElementHeight,
insertDragElement
}
12 changes: 10 additions & 2 deletions packages/devui-vue/docs/components/dragdrop/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,27 @@
<div class="dragdrop-card-container">
<div class="dragdrop-card">
<div class="dragdrop-card-header">Draggable Item</div>
<div class="dragdrop-card-block">
<div class="dragdrop-card-block drag">
<div id="draggable-item" class="draggable-item" v-d-draggable="{
dragScope: 'default-css',
dragData: { item: 'item', parent: 'list1' },
}">VSCode</div>
<div id="draggable-item2" class="draggable-item" v-d-draggable="{
dragScope: 'default-css',
dragData: { item: 'item', parent: 'list1' },
}">Sublime</div>
</div>
</div>
<div class="dragdrop-card" v-d-droppable>
<div class="dragdrop-card-header">Drop Area</div>
<div class="dragdrop-card-block">
<div class="dragdrop-card-block droppable">

</div>
</div>
<div class="dragdrop-card" v-d-sortable>
<div class="dragdrop-card-header">Drop Area With Sortable</div>
<div class="dragdrop-card-block"></div>
</div>
</div>
</template>

Expand Down