在这里插入图片描述

一、Vite创建Vue3 项目

这里已经有创建好的模板项目
执行使用即可

二、创建组件相关样式

1、创建data.json

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

{

  "container":{
    "width": "550px",
    "height":"550px",
  },
  "blocks":{

  }

}

2、在Home.vue当中引入组件

在这里插入图片描述

<script setup lang="ts">
import data from '../data.json';
import {ref,reactive} from "vue";
const state = ref(data)
</script>
<template >
  <div class="demo">

  </div>
</template>
<style scoped lang="sass">

</style>

3、创建自定义组件(使用jsx)

在这里插入图片描述

在这里插入图片描述
在vue3的工程中使用jsx

 npm i @vitejs/plugin-vue-jsx -s

在这里插入图片描述

在这里插入图片描述

import vueJsx from '@vitejs/plugin-vue-jsx'
 vueJsx(),

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import {defineComponent} from "vue";
export default defineComponent({
    setup(){
        return ()=> <div>editor</div>
    }
})

Home.vue当中引入组件
在这里插入图片描述

<script setup lang="ts">
import data from '@/data.json';
import Editor from "@/packages/editor";
import {ref,reactive} from "vue";
const state = ref(data)
</script>
<template >
  <div class="demo">
      <Editor></Editor>
  </div>
</template>
<style scoped lang="sass">
</style>

运行测试
在这里插入图片描述
在这里插入图片描述
去掉默认样式
在这里插入图片描述
在这里插入图片描述
在Home.vue当中传递组件对应的值
在这里插入图片描述

<script setup lang="ts">
import data from '@/data.json';
import Editor from "@/packages/editor";
import {ref,reactive} from "vue";
const state = ref(data)
</script>
<template >
  <div class="demo">
      <Editor :data="state"></Editor>
  </div>
</template>
<style scoped lang="sass">
</style>

editor.jsx当中接受对应的值
在这里插入图片描述

import {defineComponent} from "vue";

export default defineComponent({
    props:{
        data:{
            type:Object
        }
    },
    setup(props){
        console.log(props.data)
        return ()=> <div>editor</div>
    }
})

运行测试
在这里插入图片描述

4、完善基本样式

设置页面周围空白间隙
在这里插入图片描述

#app{
    position: fixed;
    top:20px;
    left:20px;
    right:20px;
    bottom:20px;
}

在这里插入图片描述
在这里插入图片描述

import {defineComponent} from "vue";
import './editor.scss'
export default defineComponent({
    props:{
        data:{
            type:Object
        }
    },
    setup(props){
        console.log(props.data)
        return ()=> <div class="editor">
            <div class="editor-left">
                左侧物料区
            </div>
            <div class="editor-top">
                菜单栏
            </div>
            <div class="editor-right">
                属性控制栏目
            </div>
            <div class="editor-container">
                {/*  负责参数滚动条 */}
                <div class="editor-container-canvas">
                    {/* 产生内容区域 */}
                    <div class="editor-container-canvas_content">
                        内容区域
                    </div>
                </div>
            </div>
        </div>
    }
})

在这里插入图片描述
在这里插入图片描述
在editor.jsx当中引入scss、
在这里插入图片描述

import './editor.scss'

在这里插入图片描述

.editor{
  width: 100%;
  height: 100%;
  &-left,
  &-right{
    position: absolute;
    width: 270px;
    background: red;
    top: 0;
    bottom: 0;
  }
  &-left{
    left: 0;
  }
  &-right{
    right: 0;
  }
  &-top{
    position: absolute;
    right: 280px;
    left: 280px;
    height: 80px;
    background: blue;
  }
  &-container{
    padding:80px 270px 0;
    height: 100%;
    box-sizing: border-box;
    &-canvas{
      overflow: scroll;
      height: 86vh;
      &__content{
        margin: 20px auto;
        width:550px;
        height:1550px;
        background: yellow;
      }
    }
  }
}

在这里插入图片描述

三、设置组件动态数据

1、父组件向子组件传递值

在这里插入图片描述

{

  "container":{
    "width": "550",
    "height":"550"
  },
  "blocks":{

  }

}

在这里插入图片描述

<script setup lang="ts">
import data from '@/data.json';
import Editor from "@/packages/editor";
import {ref,reactive} from "vue";
const state = ref(data)
</script>
<template >
  <div class="demo">
      <Editor v-model="state"></Editor>
  </div>
</template>
<style scoped lang="sass">
</style>

在这里插入图片描述

import {defineComponent,computed} from "vue";
import './editor.scss'
export default defineComponent({
    props:{
        modelValue:{
            type:Object
        }
    },
    setup(props){
        const data = computed({
            get(){
                return props.modelValue
            }
        })
        console.log(props.modelValue)
        return ()=> <div class="editor">
            <div class="editor-left"> 左侧物料区 </div>
            <div class="editor-top"> 菜单栏 </div>
            <div class="editor-right"> 属性控制栏目 </div>
            <div class="editor-container">
                {/*  负责参数滚动条 */}
                <div class="editor-container-canvas">
                    {/* 产生内容区域 */}
                    <div class="editor-container-canvas__content">
                        内容区域
                    </div>
                </div>
            </div>
        </div>
    }
})

在这里插入图片描述

2、设置动态数据

删除固定数据
在这里插入图片描述
渲染样式
在这里插入图片描述

import {defineComponent,computed} from "vue";
import './editor.scss'
export default defineComponent({
    props:{
        modelValue:{
            type:Object
        }
    },
    setup(props){
        const data = computed({
            get(){
                return props.modelValue
            }
        })
        console.log(props.modelValue)
        const containerStyles = computed( ()=>(
            {
                width: data.value.container.width+'px',
                height: data.value.container.height+'px'
            }
        ))
        return ()=> <div class="editor">
            <div class="editor-left"> 左侧物料区 </div>
            <div class="editor-top"> 菜单栏 </div>
            <div class="editor-right"> 属性控制栏目 </div>
            <div class="editor-container">
                {/*  负责参数滚动条 */}
                <div class="editor-container-canvas">
                    {/* 产生内容区域 */}
                    <div class="editor-container-canvas__content" style={containerStyles.value}>
                        内容区域
                    </div>
                </div>
            </div>
        </div>
    }
})

在这里插入图片描述
渲染内容
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


import {defineComponent} from "vue";


export default defineComponent({




})

在editor.jsx当中引入EditorBlock
在这里插入图片描述
在这里插入图片描述

import {defineComponent,computed} from "vue";
import './editor.scss'
import EditorBlock from "@/packages/editor-block.jsx";
export default defineComponent({
    props:{
        modelValue:{
            type:Object
        }
    },
    setup(props){
        const data = computed({
            get(){
                return props.modelValue
            }
        })
        console.log(props.modelValue)
        const containerStyles = computed( ()=>(
            {
                width: data.value.container.width+'px',
                height: data.value.container.height+'px'
            }
        ))
        return ()=> <div class="editor">
            <div class="editor-left"> 左侧物料区 </div>
            <div class="editor-top"> 菜单栏 </div>
            <div class="editor-right"> 属性控制栏目 </div>
            <div class="editor-container">
                {/*  负责参数滚动条 */}
                <div class="editor-container-canvas">
                    {/* 产生内容区域 */}
                    <div class="editor-container-canvas__content" style={containerStyles.value}>
                        {
                            (data.value.blocks.map( block=>(
                                <div>
                                    <EditorBlock block={ block }></EditorBlock>
                                </div>
                            )))
                        }
                    </div>
                </div>
            </div>
        </div>
    }
})

完善editor-block.jsx
在这里插入图片描述


import {defineComponent,computed} from "vue";
import editor from "./editor.jsx";


export default defineComponent({
    props:{
        block:{type:Object}
    },
    setup(props){

        const blockStyles = computed(()=>({
            top:`${props.block.top}px`,
            left:`${props.block.left}px`,
            zIndex:`${props.block.zIndex}px`,
        }))

        return ()=>{
            return <div class="editor-block" style={blockStyles.value}>这是一个代码块</div>
        }
    }
})

在这里插入图片描述

.editor-block{
  position: absolute;
}

完善显示数据
在这里插入图片描述

"blocks":[
    {
      "top": 100,
      "left": 100,
      "color": "red",
      "zIndex": 1,
      "key": "text"
    },
    {
      "top": 200,
      "left": 200,
      "color": "red",
      "zIndex": 1,
      "key": "button"
    },
    {
      "top": 300,
      "left": 300,
      "color": "red",
      "zIndex": 1,
      "key": "input"
    }
  ]

在这里插入图片描述

3、使用组件替换

在项目当中已经引入了Element UI Plus
在这里插入图片描述
创建左侧要拖动的组件内容
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import {ElButton,ElInput} from "element-plus";
// 列表区域可以显示所有的物料
// key对应的组件和映射关系
function createEditorConfig() {
    const componentList = [];
    const componentMap  = {};
    return {
        componentList,
        componentMap,
        register:(component)=>{
            componentList.push(component);
            componentMap[component.key] = component
        }
    }
}
export let registerConfig = createEditorConfig()
console.log(registerConfig)
registerConfig.register({
    label:'文本',
    preview:()=>'预览文本',
    render:()=>'渲染文本',
    key:'text'
})
registerConfig.register({
    label: '按钮',
    preview: () => <ElButton>预览按钮</ElButton>,
    render: () => <ElButton>渲染按钮</ElButton>,
    key:'button'
})
registerConfig.register({
    label: '按钮',
    preview: () => <ElInput placeholder="预览输入框">预览按钮</ElInput>,
    render: () => <ElInput placeholder="渲染输入框">渲染按钮</ElInput>,
    key:'input'
})

Home.vueeditor-config注册为全局组件

在这里插入图片描述

<script setup lang="ts">
import {registerConfig as config} from '@/utils/editor-config'

import data from '@/data.json';
import Editor from "@/packages/editor";
import {ref,reactive, provide} from "vue";
const state = ref(data)
provide('config',config);//(Vue3全局组件通信之provide / inject)

</script>
<template >
  <div class="demo">
      <Editor v-model="state"></Editor>
  </div>
</template>
<style scoped lang="sass">
</style>

将组件渲染到对应的页面上
在这里插入图片描述

import {defineComponent,computed,inject} from "vue";
import editor from "./editor.jsx";

export default defineComponent({
    props:{
        block:{type:Object}
    },
    setup(props){
        const blockStyles = computed(()=>({
            top:`${props.block.top}px`,
            left:`${props.block.left}px`,
            zIndex:`${props.block.zIndex}px`,
        }));
        const config = inject('config');
        console.log(config)
        return ()=>{
            //通过block的key属性直接获取对应的组件
            const component = config.componentMap[props.block.key]
            const RenderComponent = component.render();
            return <div class="editor-block" style={blockStyles.value}>
                {RenderComponent}
            </div>
        }
    }
})

在这里插入图片描述

4、在左侧物料区使用该组件

在这里插入图片描述

	    const containerStyles = computed( ()=>(
            {
                width: data.value.container.width+'px',
                height: data.value.container.height+'px'
            }
        ))
        const config = inject('config')
        return ()=> <div class="editor">
            <div class="editor-left">
                {/*  根据注册列表 渲染对应的内容   */}
                {config.componentList.map(component =>(
                        <div className="editor-left-item">
                            <span>{component.label}</span>
                            <span>{component.preview()}</span>
                        </div>
                ))}
            </div>
            <div class="editor-top"> 菜单栏 </div>
            <div class="editor-right"> 属性控制栏目 </div>
            <div class="editor-container">
                {/*  负责参数滚动条 */}
                <div class="editor-container-canvas">
                    {/* 产生内容区域 */}
                    <div class="editor-container-canvas__content" style={containerStyles.value}>
                        {
                            (data.value.blocks.map( block=>(
                                <div>
                                    <EditorBlock block={ block }></EditorBlock>
                                </div>
                            )))
                        }
                    </div>
                </div>
            </div>
        </div>

在这里插入图片描述
完善上述样式
在这里插入图片描述

 &-left{
    left: 0;
    &-item{
      width: 250px;
      margin: 20px auto;
      display: flex;
      justify-content: center;
      align-items: center;
      background: #FFF;
      padding: 20px;
      box-sizing: border-box;
      cursor: move;
      user-select: none;
      min-height: 100px;
      position: relative;
      > span {
        position: absolute;
        top: 0;
        left: 0;
        background: #02d0f5;
        color: white;
        padding: 4px;
      }
      &::after{
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: #888888;
        opacity: 0.2;
      }
    }
  }

在这里插入图片描述

四、实现拖拽功能

deep-copy 是一个深拷贝工具,可对任意数据进行深度拷贝,
包括 函数 function、正则 RegExp、Map、Set、Date、Array、URL 等等;

支持含循环引用关系的对象的拷贝,并且不会丢失成员的引用关系信息 和 类型信息,

支持扩展,可根据数据类型定制拷贝逻辑,也可指定拷贝深度;

所以,通过它可实现对任意类型的数据进行任意想要的拷贝;

解决拷贝(对象的实现序列化和反序列的问题)

在这里插入图片描述

 npm install deepcopy

1、实现拖拽等一系列功能

在这里插入图片描述

import {defineComponent,computed,inject,ref} from "vue";
import './editor.scss'
import EditorBlock from "@/packages/editor-block.jsx";
import deepcopy from "deepcopy";
export default defineComponent({
    props:{
        modelValue:{
            type:Object
        }
    },
    emits:['update:modelValue'], //触发的事件的名称
    setup(props,ctx){
        const data = computed({
            get(){
                return props.modelValue
            },
            set(newValue){
                //emit的主要作用是子组件可以通过使用 emit,让父组件监听到自定义事件 ,以及传递的参数
                ctx.emit('update:modelValue',deepcopy(newValue))
            }
        })
        const containerStyles = computed( ()=>(
            {
                width: data.value.container.width+'px',
                height: data.value.container.height+'px'
            }
        ))
        const config = inject('config'); //获取定义的全局组件

        const containerRef = ref(null);

        let currentComponent = null;

        const dragenter = (e) =>{
            e.dataTransfer.dropEffect = 'move';  //h5 拖动的图标
        }
        const dragover = (e) =>{
            e.preventDefault();
        }
        const dragleave = (e) =>{
            e.dataTransfer.dropEffect = 'none';
        }
        const drop = (e) =>{
            let blocks =  data.value.blocks; //内部已经渲染的组件
            data.value = {
                   ...data.value,blocks:[
                   ...blocks,
                    {
                        top:e.offsetY,
                        left:e.offsetX,
                        zIndex:1,
                        key:currentComponent.key,
                        alignCenter:true //希望松手的时候可以居中
                    }
                ]
            }
            currentComponent = null;
        }
        const dragstart = (e,component) => {
            //dragenter 进入元素中 添加一个移动的标识
            containerRef.value.addEventListener('dragenter',dragenter);
            //dragover 在目标元素当中经过 必须要阻止默认行为 否则不能触发drop
            containerRef.value.addEventListener('dragover',dragover);
            // dragleave 离开元素的时候 需要增加一个禁用标识
            containerRef.value.addEventListener('dragleave',dragleave);
            // drop 松手的时候 根据拖拽的组件 添加一个组件
            containerRef.value.addEventListener('drop',drop);
            currentComponent = component;
        }
        return ()=> <div class="editor">
            <div class="editor-left">
                {/*  根据注册列表 渲染对应的内容  可以实现H5的拖拽  */}
                {config.componentList.map(component =>(
                        <div
                            className="editor-left-item"
                            draggable
                            onDragstart={ e =>dragstart(e,component) }
                        >
                            <span>{component.label}</span>
                            <div>{component.preview()}</div>
                        </div>
                ))}
            </div>
            <div class="editor-top"> 菜单栏 </div>
            <div class="editor-right"> 属性控制栏目 </div>
            <div class="editor-container">
                {/*  负责参数滚动条 */}
                <div class="editor-container-canvas">
                    {/* 产生内容区域 */}
                    <div class="editor-container-canvas__content" style={containerStyles.value} ref={containerRef}>
                        {
                            (data.value.blocks.map( block=>(
                                <div>
                                    <EditorBlock block={ block }></EditorBlock>
                                </div>
                            )))
                        }
                    </div>
                </div>
            </div>
        </div>
    }
})

在这里插入图片描述
上述虽然实现了拖拽,但是在鼠标释放的时候没有对应的组件没有实现居中显示

2、设置鼠标释放居中显示

在这里插入图片描述

import {defineComponent,computed,inject,ref,onMounted} from "vue";
import editor from "./editor.jsx";

export default defineComponent({
    props:{
        //得到父组件传递的参数
        block:{type:Object}
    },
    setup(props){
        const blockStyles = computed(()=>({
            top:`${props.block.top}px`,
            left:`${props.block.left}px`,
            zIndex:`${props.block.zIndex}px`,
        }));
        const config = inject('config');
        const blockRef = ref(null);
        onMounted(()=>{
            let {offsetWidth,offsetHeight} = blockRef.value;
            if(props.block.alignCenter){
                //说明是拖拽松手的时候才渲染生成的页面,其他的默认渲染到页面上的内容不需要居中
                props.block.left = props.block.left - offsetWidth / 2;
                props.block.top = props.block.top - offsetHeight / 2;
                props.block.alignCenter = false;
            }
        })
        return ()=>{
            //通过block的key属性直接获取对应的组件
            const component = config.componentMap[props.block.key];
            const RenderComponent = component.render();
            return <div class="editor-block" style={blockStyles.value} ref={blockRef}>
                {RenderComponent}
            </div>
        }
    }
})

在这里插入图片描述
对上述代码进行重新封装

在这里插入图片描述
在这里插入图片描述

export function useMenuDragger(containerRef,data,currentComponent){
    const dragenter = (e) =>{
        e.dataTransfer.dropEffect = 'move';  //h5 拖动的图标
    }
    const dragover = (e) =>{
        e.preventDefault();
    }
    const dragleave = (e) =>{
        e.dataTransfer.dropEffect = 'none';
    }
    const drop = (e) =>{
        let blocks =  data.value.blocks; //内部已经渲染的组件
        data.value = {
            ...data.value,blocks:[
                ...blocks,
                {
                    top:e.offsetY,
                    left:e.offsetX,
                    zIndex:1,
                    key:currentComponent.key,
                    alignCenter:true //希望松手的时候可以居中
                }
            ]
        }
        currentComponent = null;
    }
    const dragstart = (e,component) => {
        //dragenter 进入元素中 添加一个移动的标识
        containerRef.value.addEventListener('dragenter',dragenter);
        //dragover 在目标元素当中经过 必须要阻止默认行为 否则不能触发drop
        containerRef.value.addEventListener('dragover',dragover);
        // dragleave 离开元素的时候 需要增加一个禁用标识
        containerRef.value.addEventListener('dragleave',dragleave);
        // drop 松手的时候 根据拖拽的组件 添加一个组件
        containerRef.value.addEventListener('drop',drop);
        currentComponent = component;
    }
    const dragend = (e) => {
        //dragenter 进入元素中 添加一个移动的标识
        containerRef.value.removeEventListener('dragenter',dragenter);
        //dragover 在目标元素当中经过 必须要阻止默认行为 否则不能触发drop
        containerRef.value.removeEventListener('dragover',dragover);
        // dragleave 离开元素的时候 需要增加一个禁用标识
        containerRef.value.removeEventListener('dragleave',dragleave);
        // drop 松手的时候 根据拖拽的组件 添加一个组件
        containerRef.value.removeEventListener('drop',drop);
    }
    return{
        dragstart,
        dragend
    }
}

在editor.jsx当中引入并使用
在这里插入图片描述

import {defineComponent,computed,inject,ref} from "vue";
import './editor.scss'
import EditorBlock from "@/packages/editor-block.jsx";
import {useMenuDragger} from './useMenuDragger.js'
import deepcopy from "deepcopy";
export default defineComponent({
    props:{
        modelValue:{
            type:Object
        }
    },
    emits:['update:modelValue'], //触发的事件的名称
    setup(props,ctx){
        const data = computed({
            get(){
                return props.modelValue
            },
            set(newValue){
                //emit的主要作用是子组件可以通过使用 emit,让父组件监听到自定义事件 ,以及传递的参数
                ctx.emit('update:modelValue',deepcopy(newValue))
            }
        })
        const containerStyles = computed( ()=>(
            {
                width: data.value.container.width+'px',
                height: data.value.container.height+'px'
            }
        ))
        const config = inject('config'); //获取定义的全局组件
        const containerRef = ref(null);
        let currentComponent = null;
        const {dragstart,dragend} = useMenuDragger(containerRef,data,currentComponent)

        return ()=> <div class="editor">
            <div class="editor-left">
                {/*  根据注册列表 渲染对应的内容  可以实现H5的拖拽  */}
                {config.componentList.map(component =>(
                        <div
                            className="editor-left-item"
                            draggable
                            onDragstart={ e =>dragstart(e,component) }
                            onDragend = { dragend }
                        >
                            <span>{component.label}</span>
                            <div>{component.preview()}</div>
                        </div>
                ))}
            </div>
            <div class="editor-top"> 菜单栏 </div>
            <div class="editor-right"> 属性控制栏目 </div>
            <div class="editor-container">
                {/*  负责参数滚动条 */}
                <div class="editor-container-canvas">
                    {/* 产生内容区域 */}
                    <div class="editor-container-canvas__content" style={containerStyles.value} ref={containerRef}>
                        {
                            (data.value.blocks.map( block=>(
                                <div>
                                    <EditorBlock block={ block }></EditorBlock>
                                </div>
                            )))
                        }
                    </div>
                </div>
            </div>
        </div>
    }
})

在这里插入图片描述

3、实现焦点聚焦到画布组件当中

在这里插入图片描述

import {defineComponent,computed,inject,ref} from "vue";
import './editor.scss'
import EditorBlock from "@/packages/editor-block.jsx";
import {useMenuDragger} from './useMenuDragger.js'
import deepcopy from "deepcopy";
export default defineComponent({
    props:{
        modelValue:{
            type:Object
        }
    },
    emits:['update:modelValue'], //触发的事件的名称
    setup(props,ctx){
        const data = computed({
            get(){
                return props.modelValue
            },
            set(newValue){
                //emit的主要作用是子组件可以通过使用 emit,让父组件监听到自定义事件 ,以及传递的参数
                ctx.emit('update:modelValue',deepcopy(newValue))
            }
        })
        //设置容器样式  computed设置属性值  根据依赖关系进行缓存的计算  实现动态数据的计算
        const containerStyles = computed( ()=>(
            {
                width: data.value.container.width+'px',
                height: data.value.container.height+'px'
            }
        ))
        const config = inject('config'); //获取定义的全局组件
        const containerRef = ref(null);
        let currentComponent = null;

        //1、实现菜单拖拽功能
        const {dragstart,dragend} = useMenuDragger(containerRef,data,currentComponent)

        //2、实现获取焦点

        //3.实现拖拽多个元素的功能
        const clearBlockFocus = () =>{
            data.value.blocks.forEach(block => block.focus = false);
        }
        const blockMousedown = (e,block)=>{
            e.preventDefault();
            e.stopPropagation();
            //block 上我们规划一个属性 focus 获取焦点后就将 focus 变为true
            if(!block.focus){
                clearBlockFocus();
                block.focus = true; //要清空其他人的 focus 的属性
            }else{
                block.focus = false;
            }
        }
        return ()=> <div class="editor">
            <div class="editor-left">
                {/*  根据注册列表 渲染对应的内容  可以实现H5的拖拽  */}
                {config.componentList.map(component =>(
                        <div className="editor-left-item" draggable onDragstart={ e =>dragstart(e,component) } onDragend = { dragend }
                        >
                            <span>{component.label}</span>
                            <div>{component.preview()}</div>
                        </div>
                ))}
            </div>
            <div class="editor-top"> 菜单栏 </div>
            <div class="editor-right"> 属性控制栏目 </div>
            <div class="editor-container">
                {/*  负责参数滚动条 */}
                <div class="editor-container-canvas">
                    {/* 产生内容区域 */}
                    <div class="editor-container-canvas__content" style={containerStyles.value} ref={containerRef}>
                        {
                            (data.value.blocks.map( block=>(
                                <div>
                                    <EditorBlock
                                        class = { block.focus ? 'editor-block-focus' : '' }
                                        block={ block }
                                        onMousedown = { (e)=>blockMousedown( e,block) }
                                    >
                                    </EditorBlock>
                                </div>
                            )))
                        }
                    </div>
                </div>
            </div>
        </div>
    }
})

在这里插入图片描述

.editor-block{
  position: absolute;
  &::after{
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
  }
}
.editor-block-focus{
  &::after{
    border: 1px dashed red;
  }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import {computed} from "vue";

export function useFocus(data,callback){ //获取那些元素没有选中

    const focusData = computed(()=>{
        let focus = [];
        let unfocused = [];
        data.value.blocks.forEach(block => (block.focus ?  focus:unfocused).push(block))
        return {focus,unfocused}
    });
    const clearBlockFocus = () =>{
        data.value.blocks.forEach(block => block.focus = false);
    }

    const blockMousedown = (e,block)=>{
        e.preventDefault();
        e.stopPropagation();
        //block 上我们规划一个属性 focus 获取焦点后就将 focus 变为true
        if(e.shiftKey){
            block.focus = !block.focus;
        }else{
            if(!block.focus){
                clearBlockFocus();
                block.focus = true; //要清空其他人的 focus 的属性
            }else{
                block.focus = false;
            }
        }
        callback(e);
    }

    return {
        blockMousedown,
        focusData,
    }
}

4、完善拖拽组件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import {defineComponent,computed,inject,ref} from "vue";
import './editor.scss'
import EditorBlock from "@/packages/editor-block.jsx";
import {useMenuDragger} from './useMenuDragger.js'
import {useFocus} from './useFocus.js'
import deepcopy from "deepcopy";
import {useBlockDragger} from "@/packages/useBlockDragger.js";
export default defineComponent({
    props:{
        modelValue:{
            type:Object
        }
    },

    emits:['update:modelValue'], //触发的事件的名称

    setup(props,ctx){
        const data = computed({
            get(){
                return props.modelValue;
            },
            set(newValue){
                //emit的主要作用是子组件可以通过使用 emit,让父组件监听到自定义事件 ,以及传递的参数
                ctx.emit('update:modelValue',deepcopy(newValue));
            }
        })

        //设置容器样式  computed设置属性值  根据依赖关系进行缓存的计算  实现动态数据的计算
        const containerStyles = computed( ()=>(
            {
                width: data.value.container.width+'px',
                height: data.value.container.height+'px'
            }
        ))

        const config = inject('config'); //获取定义的全局组件
        const containerRef = ref(null);
        let currentComponent = null;

        //1、实现菜单拖拽功能
        const {dragstart,dragend} = useMenuDragger(containerRef,data,currentComponent);

        let {blockMousedown,focusData,clearBlockFocus} = useFocus(data,(e)=>{
            //获取焦点后进行拖拽
            mousedown(e)
        });

        //2、实现组件拖拽
        const { mousedown } = useBlockDragger(focusData);

        //3.实现拖拽多个元素的功能
        const containerMousedown = () => {
            clearBlockFocus();  //点击容器让选中的失去焦点
        }


        return ()=> <div class="editor">
            <div class="editor-left">
                {/*  根据注册列表 渲染对应的内容  可以实现H5的拖拽  */}
                {config.componentList.map(component =>(
                        <div className="editor-left-item" draggable onDragstart={ e =>dragstart(e,component) } onDragend = { dragend }
                        >
                            <span>{component.label}</span>
                            <div>{component.preview()}</div>
                        </div>
                ))}
            </div>
            <div class="editor-top"> 菜单栏 </div>
            <div class="editor-right"> 属性控制栏目 </div>
            <div class="editor-container">
                {/*  负责参数滚动条 */}
                <div class="editor-container-canvas">
                    {/* 产生内容区域 */}
                    <div class="editor-container-canvas__content"
                         style={containerStyles.value}
                         ref={containerRef}
                         onMousedown={ containerMousedown }
                    >
                        {
                            (data.value.blocks.map( block=>(
                                <div>
                                    <EditorBlock
                                        class = { block.focus ? 'editor-block-focus' : '' }
                                        block={ block }
                                        onMousedown = { (e)=>blockMousedown( e,block) }
                                    >
                                    </EditorBlock>
                                </div>
                            )))
                        }
                    </div>
                </div>
            </div>
        </div>
    }
})

在这里插入图片描述


        //1、实现菜单拖拽功能
        const {dragstart,dragend} = useMenuDragger(containerRef,data,currentComponent);
        let {blockMousedown,focusData,clearBlockFocus} = useFocus(data,(e)=>{
            //获取焦点后进行拖拽
            mousedown(e)
        });
        //2、实现组件拖拽
        const { mousedown } = useBlockDragger(focusData);
        //3.实现拖拽多个元素的功能
        const containerMousedown = () => {
            clearBlockFocus();  //点击容器让选中的失去焦点
        }
        return ()=> <div class="editor">
            <div class="editor-left">
                {/*  根据注册列表 渲染对应的内容  可以实现H5的拖拽  */}
                {config.componentList.map(component =>(
                        <div className="editor-left-item" draggable onDragstart={ e =>dragstart(e,component) } onDragend = { dragend }
                        >
                            <span>{component.label}</span>
                            <div>{component.preview()}</div>
                        </div>
                ))}
            </div>
            <div class="editor-top"> 菜单栏 </div>
            <div class="editor-right"> 属性控制栏目 </div>
            <div class="editor-container">
                {/*  负责参数滚动条 */}
                <div class="editor-container-canvas">
                    {/* 产生内容区域 */}
                    <div class="editor-container-canvas__content"
                         style={containerStyles.value}
                         ref={containerRef}
                         onMousedown={ containerMousedown }
                    >
                        {
                            (data.value.blocks.map( block=>(
                                <div>
                                    <EditorBlock
                                        class = { block.focus ? 'editor-block-focus' : '' }
                                        block={ block }
                                        onMousedown = { (e)=>blockMousedown( e,block) }
                                    >
                                    </EditorBlock>
                                </div>
                            )))
                        }
                    </div>
                </div>
            </div>
        </div>
    }

在这里插入图片描述

五、源代码地址:https://gitee.com/itbluebox/lowcode-learning-cases-vue3

Logo

低代码爱好者的网上家园

更多推荐