diff --git a/packages/devui-vue/devui/dragdrop/index.ts b/packages/devui-vue/devui/dragdrop/index.ts index b203021a07..d1953c4993 100644 --- a/packages/devui-vue/devui/dragdrop/index.ts +++ b/packages/devui-vue/devui/dragdrop/index.ts @@ -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 拖拽', @@ -11,5 +12,6 @@ export default { install(app: App): void { app.directive('DDraggable', DraggableDirective) app.directive('DDroppable', DroppableDirective) + app.directive('DSortable', SortableDirective) } } diff --git a/packages/devui-vue/devui/dragdrop/src/constant.ts b/packages/devui-vue/devui/dragdrop/src/constant.ts new file mode 100644 index 0000000000..98c9224ec4 --- /dev/null +++ b/packages/devui-vue/devui/dragdrop/src/constant.ts @@ -0,0 +1 @@ +export const shadowId = 'shadow0611' \ No newline at end of file diff --git a/packages/devui-vue/devui/dragdrop/src/draggable-directive.ts b/packages/devui-vue/devui/dragdrop/src/draggable-directive.ts index c53e3342c1..fa333e2bdf 100644 --- a/packages/devui-vue/devui/dragdrop/src/draggable-directive.ts +++ b/packages/devui-vue/devui/dragdrop/src/draggable-directive.ts @@ -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) }, } diff --git a/packages/devui-vue/devui/dragdrop/src/droppable-directive.ts b/packages/devui-vue/devui/dragdrop/src/droppable-directive.ts index d60618868e..289ad8ef7e 100644 --- a/packages/devui-vue/devui/dragdrop/src/droppable-directive.ts +++ b/packages/devui-vue/devui/dragdrop/src/droppable-directive.ts @@ -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)) + } }) }, } diff --git a/packages/devui-vue/devui/dragdrop/src/sortable-directive.ts b/packages/devui-vue/devui/dragdrop/src/sortable-directive.ts new file mode 100644 index 0000000000..2066b831d4 --- /dev/null +++ b/packages/devui-vue/devui/dragdrop/src/sortable-directive.ts @@ -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') + }) + } +} \ No newline at end of file diff --git a/packages/devui-vue/devui/dragdrop/src/utils.ts b/packages/devui-vue/devui/dragdrop/src/utils.ts new file mode 100644 index 0000000000..0b07447eba --- /dev/null +++ b/packages/devui-vue/devui/dragdrop/src/utils.ts @@ -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 +} \ No newline at end of file diff --git a/packages/devui-vue/docs/components/dragdrop/index.md b/packages/devui-vue/docs/components/dragdrop/index.md index f37a25bf6a..35f67ed833 100644 --- a/packages/devui-vue/docs/components/dragdrop/index.md +++ b/packages/devui-vue/docs/components/dragdrop/index.md @@ -15,19 +15,27 @@