import { computed , defineComponent , PropType , ref , reactive } from "vue" ; import { useModel } from "./utils/useModel" ; import { VisualEditorBlock } from "./visual-editor-block" ; import "./visual-editor.scss" ; import { createNewBlock , VisualEditorBlockData , VisualEditorComponent , VisualEditorConfig , VisualEditorMarkLine , VisualEditorModelValue , } from "./visual-editor.utils" ; import { VisualOperatorEditor } from "./visual-editor-operator" ; import deepcopy from "deepcopy" ; export const VisualEditor = defineComponent ( { props : { modelValue : { type : Object as PropType < VisualEditorModelValue > , require : true , } , config : { type : Object as PropType < VisualEditorConfig > , require : true , } , } , emits : { "update:modelValue" : ( val ? ) => true , } , setup ( props , ctx ) { const dataModel = useModel ( ( ) => props . modelValue , ( val ) => ctx . emit ( "update:modelValue" , val ) ) ; const containerStyles = computed ( ( ) => ( { width : `${ props . modelValue ?. container . width }px` , height : `${ props . modelValue ?. container . height }px` , } ) ) ; const containerRef = ref ( { } as HTMLElement ) ; const focusData = computed ( ( ) => { const focus = dataModel . value ?. blocks . filter ( ( v ) => v . focus ) || [ ] ; const unfocus = dataModel . value ?. blocks . filter ( ( v ) => ! v . focus ) || [ ] ; return { focus , unfocus , } ; } ) ; const methods = { clearFocus : ( block ? ) => { let blocks = dataModel . value ?. blocks || [ ] ; if ( blocks . length === 0 ) return ; if ( block ) { blocks = blocks . filter ( ( v ) => v !== block ) ; } blocks . forEach ( ( block ) => ( block . focus = false ) ) ; } , updateBlocks : ( blocks ) => { dataModel . value ! . blocks = blocks ; } , } ; const menuDragger = ( ( ) => { let component = null as null | VisualEditorComponent ; const containerHandler = { /**
                 * 拖拽组件进入容器，设置鼠标可放置状态
                 */ dragenter : ( e ) => { e . dataTransfer ! . dropEffect = "move" ; } , dragover : ( e ) => { e . preventDefault ( ) ; } , /**
                 * 拖拽组件离开容器，设置鼠标禁用状态
                 */ dragleave : ( e ) => { e . dataTransfer ! . dropEffect = "none" ; } , /**
                 * 在容器中放置组件
                 */ drop : ( e ) => { console . log ( "drop" , component ) ; const blocks = dataModel . value ?. blocks || [ ] ; blocks . push ( createNewBlock ( { component : component ! , top : e . offsetY , left : e . offsetX , } ) ) ; console . log ( "x" , e . offsetX ) ; console . log ( "y" , e . offsetY ) ; dataModel . value = { ... dataModel . value , blocks , } as VisualEditorModelValue ; } , } ; const blockHandler = { dragstart : ( e , current ) => { containerRef . value . addEventListener ( "dragenter" , containerHandler . dragenter ) ; containerRef . value . addEventListener ( "dragover" , containerHandler . dragover ) ; containerRef . value . addEventListener ( "dragleave" , containerHandler . dragleave ) ; containerRef . value . addEventListener ( "drop" , containerHandler . drop ) ; component = current ; } , dragend : ( e ) => { containerRef . value . removeEventListener ( "dragenter" , containerHandler . dragenter ) ; containerRef . value . removeEventListener ( "dragover" , containerHandler . dragover ) ; containerRef . value . removeEventListener ( "dragleave" , containerHandler . dragleave ) ; containerRef . value . removeEventListener ( "drop" , containerHandler . drop ) ; component = null ; } , } ; return blockHandler ; } ) ( ) ; const state = reactive ( { selectBlock : null as null | VisualEditorBlockData , } ) ; const blockDragger = ( ( ) => { let dragState = { startX : 0 , startY : 0 , startPos : [ ] as { left : number ; top : number ; } [ ] , startLeft : 0 , startTop : 0 , markLines : { } as VisualEditorMarkLine , } ; const mark = reactive ( { x : null as null | number , y : null as null | number , } ) ; const mousemove = ( e ) => { let { clientX : moveX , clientY : moveY } = e ; const { startX , startY } = dragState ; if ( e . shiftKey ) { if ( Math . abs ( e . clientX - startX ) > Math . abs ( e . clientY - startY ) ) { moveY = startY ; } else { moveX = startX ; } } const currentLeft = dragState . startLeft + moveX - startX ; const currentTop = dragState . startTop + moveY - startY ; const currentMark = { x : null as null | number , y : null as null | number , } ; for ( let i = 0 ; i < dragState . markLines . y . length ; i ++ ) { const { top , showTop } = dragState . markLines . y [ i ] ; if ( Math . abs ( top - currentTop ) < 5 ) { moveY = top + startY - dragState . startTop ; currentMark . y = showTop ; break ; } } for ( let i = 0 ; i < dragState . markLines . x . length ; i ++ ) { const { left , showLeft } = dragState . markLines . x [ i ] ; if ( Math . abs ( left - currentLeft ) < 5 ) { moveX = left + startX - dragState . startLeft ; currentMark . x = showLeft ; break ; } } const durY = moveY - startY ; const durX = moveX - startX ; focusData . value . focus . forEach ( ( block , i ) => { block . top = dragState . startPos [ i ] . top + durY ; block . left = dragState . startPos [ i ] . left + durX ; } ) ; mark . x = currentMark . x ; mark . y = currentMark . y ; } ; const mouseup = ( e ) => { document . removeEventListener ( "mousemove" , mousemove ) ; document . removeEventListener ( "mouseup" , mouseup ) ; } ; const mousedown = ( e ) => { dragState = { startX : e . clientX , startY : e . clientY , startPos : focusData . value . focus . map ( ( { top , left } ) => ( { top , left , } ) ) , startTop : state . selectBlock ! . top , startLeft : state . selectBlock ! . left , markLines : ( ( ) => { const { focus , unfocus } = focusData . value ; const { top , left , width , height } = state . selectBlock ! ; let lines = { x : [ ] , y : [ ] } as VisualEditorMarkLine ; unfocus . forEach ( ( block ) => { const { top : t , left : l , width : w , height : h } = block ; lines . y . push ( { top : t , showTop : t } ) ; lines . y . push ( { top : t + h , showTop : t + h } ) ; lines . y . push ( { top : t + h / 2 - height / 2 , showTop : t + h / 2 } ) ; lines . y . push ( { top : t - height , showTop : t } ) ; lines . y . push ( { top : t + h - height , showTop : t + h } ) ; lines . x . push ( { left : l , showLeft : l } ) ; lines . x . push ( { left : l + w , showLeft : l + w } ) ; lines . x . push ( { left : l + w / 2 - width / 2 , showLeft : l + w / 2 , } ) ; lines . x . push ( { left : l - width , showLeft : l } ) ; lines . x . push ( { left : l + w - width , showLeft : l + w } ) ; } ) ; return lines ; } ) ( ) , } ; document . addEventListener ( "mousemove" , mousemove ) ; document . addEventListener ( "mouseup" , mouseup ) ; } ; return { mousedown , mark } ; } ) ( ) ; const focusHandler = ( ( ) => { return { container : { onMousedown : ( e ) => { e . stopPropagation ( ) ; methods . clearFocus ( ) ; state . selectBlock = null ; } , } , block : { onMousedown : ( e , block ) => { e . stopPropagation ( ) ; if ( ! block . focus ) { if ( ! e . shiftKey ) { block . focus = ! block . focus ; methods . clearFocus ( block ) ; } else { block . focus = true ; } } state . selectBlock = block ; blockDragger . mousedown ( e ) ; } , } , } ; } ) ( ) ; const toolButtons = [ { label : "撤销" , icon : "icon-back" , tip : "ctrl+z" , } , { label : "重做" , icon : "icon-forward" , tip : "ctrl+y, ctrl+shift+z" , } , { label : "删除" , icon : "icon-delete" , handler : ( ) => { dataModel . value ! . blocks = [ ... focusData . value . unfocus , ] as VisualEditorBlockData [ ] ; } , tip : "ctrl+d, backspance, delete," , } , ] ; const updateBlockProps = ( newBlock , oldBlock ) => { const blocks = [ ... dataModel . value ! . blocks ] ; const index = dataModel . value ! . blocks . indexOf ( state . selectBlock ! ) ; if ( index > - 1 ) { blocks . splice ( index , 1 , newBlock ) ; dataModel . value ! . blocks = deepcopy ( blocks ) ; state . selectBlock = dataModel . value ! . blocks [ index ] ; } } ; const updateModelValue = ( newVal ) => { props . modelValue ! . container = { ... newVal . container } ; } ; return ( ) => ( < div class = "visual-editor" > < div class = "menu" > { props . config ?. componentList . map ( ( component ) => ( < div class = "menu-item" draggable onDragend = { menuDragger . dragend } onDragstart = { ( e ) => menuDragger . dragstart ( e , component ) } > < span class = "menu-item-label" > { component . label } < /span> { component . preview ( ) } < /div>))} < /div> < div class = "head" > { toolButtons . map ( ( btn , index ) => ( < div key = { index } class = "head-btn" onClick = { btn . handler } > < i class = { `iconfont ${ btn . icon }` } > < /i> < span > { btn . label } < /span> < /div>))} < /div> < div class = "body" > < div class = "content" > < div class = "container" ref = { containerRef } style = { containerStyles . value } { ... focusHandler . container } > { ( dataModel . value ?. blocks || [ ] ) . map ( ( block , index ) => ( < VisualEditorBlock block = { block } key = { index } config = { props . config } { ... { onMousedown : ( e ) => focusHandler . block . onMousedown ( e , block ) , } } / > ) ) } { blockDragger . mark . x && ( < div class = "mark-line-x" style = { { left : `${ blockDragger . mark . x }px` } } > < /div>)} { blockDragger . mark . y && ( < div class = "mark-line-y" style = { { top : `${ blockDragger . mark . y }px` } } > < /div>)} < /div> < /div> < /div> < VisualOperatorEditor block = { state . selectBlock ! } config = { props . config } dataModel = { dataModel as any } updateBlock = { updateBlockProps } updateModelValue = { updateModelValue } / > < /div>); } , } ) ;