可视化拖拽页面编辑器 一__Vue.js
发布于 3 年前 作者 banyungong 1871 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black, awesome-green, qklhk-chocolate

贡献主题: https://github.com/xitu/juejin-markdown-themes

theme: juejin highlight:

前端技术日益发展,组件化日益成熟,作为一个前端,每天的工作就是用组件堆砌页面,有没有一种方式可以像CocosCreator,通过组件+脚本绑定的方式来实现我们的页面和功能,今天我们就来实现一个提高生产力的工具 可视化拖拽页面编辑器, 让产品和UI通过拖拽编辑页面,生产自己想要的页面。

技术框架采用Vue3 + Typescript + ElementPlus

每个章节下边都会贴出对应commit代码,方便大家对比学习

最终效果

实现功能:

  • 主页面结构:左侧可选组件列表、中间容器画布、右侧编辑组件定义好的属性
  • 从菜单拖拽组件到容器;
  • 单选、多选;
  • 容器内的组件可以拖拽移动位置;
  • 组件拖拽调整宽高;
  • 组件拖拽贴边,显示辅助线;
  • 操作栏按钮与命令
    • 撤销、重做;
    • 导入、导出;
    • 置顶、置底;
    • 删除、清空;
  • 组件绑定值;
  • 根据组件标识,通过作用域插槽自定义某个组件的行为 预览地址

一、项目搭建与页面布局

通过vue-cli生成项目

vue create visual-editor-vue
  • 选择手动配置 选择配置如下:

  • 选择vue3.x版本
  • 这一步选y,使用jsx写组件,需要添加对应的babel插件

接下来我们来实现基本的左中右布局

  • 左侧菜单栏放置组件列表
  • 中间是画布和工具栏,用来编辑预览页面
  • 右侧是我们选中某个组件后,显示的该组件的属性

第一部分代码: 基本布局

二、数据结构设计与双向绑定实现

数据结构设计

  • 定义数据结构如下
    • container 表示画布容器
    • blocks 表示放置在容器中的组件
    • 每个block表示一个组件,包含了组件的类型位置、宽高、选中状态等信息
  • 画布采用绝对定位,里面的元素通过top、left来确定位置
{
  "container": { 
    "height": 500,
    "width": 800
  },
  "blocks": [
    {
      "componentKey": "button",
      "top": 102,
      "left": 136,
      "adjustPosition": false,
      "focus": false,
      "zIndex": 0,
      "width": 0,
      "height": 0
    },
    {
      "componentKey": "input",
      "top": 148,
      "left": 358,
      "adjustPosition": false,
      "focus": false,
      "zIndex": 0,
      "width": 244,
      "height": 0
    }
   ]
 }

数据双向绑定实现

  • 组件采用vue3中的jsx语法编写,需要实现数据双向绑定机制,useModel就是用来处理数据双向绑定的
import { computed, defineComponent, ref, watch } from "vue";

// 用jsx封装组件的时候,实现双向数据绑定
export function useModel<T>(getter: () => T, emitter: (val: T) => void) {
  const state = ref(getter()) as { value: T };

  watch(getter, (val) => {
    if (val !== state.value) {
      state.value = val;
    }
  });

  return {
    get value() {
      return state.value;
    },
    set value(val: T) {
      if (state.value !== val) {
        state.value = val;
        emitter(val);
      }
    },
  };
}

useModel用法

// modelValue 外部可以用v-model绑定
export const TestUseModel = defineComponent({
  props: {
    modelValue: { type: String },
  },
  emits: {
    "update:modelValue": (val?: string) => true,
  },
  setup(props, ctx) {
    const model = useModel(
      () => props.modelValue,
      (val) => ctx.emit("update:modelValue", val)
    );
    return () => (
      <div>
        自定义输入框
        <input type="text" v-model={model.value} />
      </div>
    );
  },
});

第二部分代码

三、Block渲染

  • 新建visual-editor-block的组件
  • block来表示在画布显示的组件元素
  • block先用文本来显示
import { computed, defineComponent, PropType } from "vue";
import { VisualEditorBlockData } from "./visual-editor.utils";

export const VisualEditorBlock = defineComponent({
  props: {
    block: {
      type: Object as PropType<VisualEditorBlockData>,
    },
  },
  setup(props) {
    const styles = computed(() => ({
      top: `${props.block?.top}px`,
      left: `${props.block?.left}px`,
    }));
    return () => (
      <div class="visual-editor-block" style={styles.value}>
        这是一条block
      </div>
    );
  },
});

  • 将定义的数据用v-model传入editor App.vue文件
<template>
  <div class="app">
    <visual-editor v-model="editorData" />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import { VisualEditor } from "../src/packages/visual-editor";

export default defineComponent({
  name: "App",
  components: { VisualEditor },
  data() {
    return {
      editorData: {
        container: {
          height: 500,
          width: 800,
        },
        blocks: [
          { top: 100, left: 100 },
          { top: 200, left: 200 },
        ],
      },
    };
  },
});
</script>
  • 引入block组件,并进行渲染 visual-editor.tsx文件
import { computed, defineComponent, PropType } from "vue";
import { useModel } from "./utils/useModel";
import { VisualEditorBlock } from "./visual-editor-block";
import "./visual-editor.scss";
import { VisualEditorModelValue } from "./visual-editor.utils";

export const VisualEditor = defineComponent({
  props: {
    modelValue: {
      type: Object as PropType<VisualEditorModelValue>,
    },
  },
  emits: {
    "update:modelValue": (val?: VisualEditorModelValue) => 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`,
    }));

    return () => (
      <div class="visual-editor">
        <div class="menu">menu</div>
        <div class="head">head</div>
        <div class="operator">operator</div>
        <div class="body">
          <div class="content">
            <div class="container" style={containerStyles.value}>
              {(dataModel.value?.blocks || []).map((block, index: number) => (
                <VisualEditorBlock block={block} key={index} />
              ))}
            </div>
          </div>
        </div>
      </div>
    );
  },
});

  • 最终效果

  • 画布会根据我们定义的editorData对象,来进行展示,container来描述画布的大小,block来描述在画布上的每个组件

第三部分代码

完整代码 GitHub

下一节:左侧组件菜单、组件拖拽渲染和组件的选中与移动

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: Miller 原文链接: https://juejin.im/post/6925976869334417421

回到顶部

PHP网站源码观澜设计公司网站吉祥网站搭建福田SEO按效果付费龙华SEO按效果付费宝安网站关键词优化横岗标王宝安网站改版南澳高端网站设计丹竹头网站优化按天收费惠州设计公司网站光明设计网站宝安SEO按天收费罗湖网站开发荷坳网站推广方案盐田百度seo木棉湾网站制作丹竹头百度竞价宝安建网站双龙seo网站优化塘坑网站推广福田网站seo优化荷坳阿里店铺运营吉祥百搜词包荷坳外贸网站制作东莞优化深圳建网站吉祥SEO按天收费大运英文网站建设木棉湾英文网站建设坪山SEO按天计费歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

PHP网站源码 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化