阅读 5881
低代码可视化编辑平台

低代码可视化编辑平台

1. 背景


公司最近在做能力库,为公司的未来业务赋能,恰好需要一款低代码可视化拖拽平台。所以...

本项目技术栈是:Vue3+TypeScript+Element-plus

本篇文章将介绍平台的主要功能实现思路,详情请移步gitee,平台在线访问地址:Vue3-Visual-Editor,代码地址:gitee

2. 长啥样,有啥功能


目录结构

├─public
└─src
    ├─assets                             静态资源
    │  ├─images
    │  └─styles
    ├─data                               mock数据
    ├─packages
    |    ├─components                     组件列表
    │    |  ├─block-resize                   - 拖拽改变尺寸组件
    │    │  ├─dropdown                       - 右键下拉菜单组件
    │    │  ├─import-export-dialog           - 数据导入导出弹窗组件
    │    │  ├─number-range                   - 数字范围组件
    │    │  ├─table-prop-editor              - 属性添加组件
    │    │  └─visual-editor-block            - 画布中展示的组件
    │    ├─plugins
    │    │  └─command                        - 注册头部操作功能
    │    |     ├─Command.plugin.ts           - 命令注册基础函数
    │    |     └─VisualCommand.ts            - 头部功能命令注册
    │    ├─utils                             - 工具函数
    |    |   └─event.ts                      - 发布订阅工具函数
    |    ├─layout                            - 工具函数
    |    |  ├─visual-editor-content          - 画布容器
    │    │  ├─visual-editor-header           - 头部操作区容器
    │    │  ├─visual-editor-menu             - 左侧组件列表容器
    │    │  └─visual-editor-props            - 右侧属性管理容器
    |    └─Index.tsx                         - 容器包裹层
    ├─main.ts                                - 入口
    └─App.vue
复制代码

主要功能

  • 拖拽组件到画布区
  • 画布组件选中态,拖拽位置及改变宽高
  • 标识线贴边检测
  • 撤销重做功能
  • 导入导出功能
  • 置顶、置底、删除、清空功能
  • 功能命令快捷键,支持单选多选
  • 画布中组件右键展示操作项
  • 画布中组件添加属性、绑定字段
  • 根据组件标识,通过作用域插槽,自定义组件行为

3. 设计理念及数据交互


基于JSON Schema,简单易用。中心思想就是数据控制视图显示,所有的操作功能本质上都是在操作数据。

4. 功能拆解,分析实现


准备工作

  • App.vue
export default defineComponent({
  name: "App",
  data() {
    return {
      // 数据中心,展示就靠它
      jsonData: {
        container: {
          width: 1000,
          height: 1000
        },
        blocks: []
      },
      // 配置数据
      config: VisualConfig,
      // 绑定的数据
      formData: {},
      // 自定义属性
      customProps: {}
    };
  }
});
</script>
复制代码

说明:

  1. jsonData是整个项目的数据中心,是核心数据,后续的操作各种改变数据,就是改的它。我们给他换一个更加语义话的名字dataModel
  2. config是注册的菜单组件。
  3. 项目中使用了比较多的发布订阅,包括头部功能注册,左侧组件注册都是利用发布订阅功能实现的。

下面正式开始功能思路介绍,详细实现捋一遍代码就都知道了。

拖拽菜单组件到画布区

核心:dragable,dragstart、dragend(用在当前拖拽组件),dragenter、dragover、dragleave、drop(用在画布)

思路:

  1. 按下菜单中组件,给画布addEventListener dragenterdragoverdragleavedrop 这几个事件,等到组件拖到画布上时,会触发对应的函数。
  2. 组件拖拽到画布上,调用createNewBlock创建一个组件并放到dataModel的blocks中,这时候画布就有一个组件了。

画布组件选中态,拖拽位置及改变宽高

  1. 画布组件选中态,即mousedown时设置组件数据的focus属性为true,添加选中样式。
  2. 改变拖拽位置,即改变组件数据的left和top值
  3. 改变宽高,拖拽改变宽高,更新width和height值

标识线贴边检测

思路:
记录画布中所有组件的上下左右中间位置信息并保存,组件拖拽过程中,进行数值比对,小于5则展示标识线。

撤销重做功能

思路:

  1. commands保存的是自己封装了一层,相当于初始化函数,该函数中再调用对应的注册方法时执行。具体做了:执行redo方法,将redo,audo保存到queue中。
  2. 注册撤销和重做函数,注册的函数都将保存到queue中,一遍执行撤销和重做时,调用对应的方法。
  3. 完整的注册对象,包含name,都保存在commandArray中。
const state = reactive({
    // 游标,控制执行queue中的哪一项
    current: -1, 
    // 撤回重做的方法都存在queue中
    queue: [] as CommandExecute[], // [{undo, redo}, {deleteUndo, deleteRedo}, {dragUndo, dragRedo} ...]
    // 注册的命令对应的撤回重做方法都存在这里
    commands: {} as Record<string, (...args: any[]) => void>, // {undo:()=>{redo()}, redo:()=>{redo()}, delete:()=> {redo()}, drag:()=> {redo()}, ...}
    // 注册的完整对象集合
    commandArray: [] as Command[],  [{name: 'xx', keywords: "", followQueue: false, execute: () => {redo, undo}}]
    // 移除事件
    destroyList: [] as ((() => void | undefined))[],  removeEventListener
})
// 注册函数
const register = (command: Command) => {
    // 保存注册的对象
    state.commandArray.push(command);
    // 根据名称构建注册对象,并保存,等到调用的时候执行内部代码
    state.commands[command.name] = (...args) => {
        // 执行redo
        const { undo, redo } = command.execute(...args);
        redo();
        // 如果需要保存到queue中,则进行保存,游标加1
        if (command.followQueue) {
            let { queue, current } = state;
            if (queue.length > 0) {
                queue = queue.slice(0, current + 1); // 1 => 2 => 3 => 4 => 3 => 5, 4不会保留
                state.queue = queue;
            }
            queue.push({ undo, redo })
            state.current = current + 1;
        }
    }
    // 调用注册对象的init方法
    command.init && command.init();
}
复制代码

导入导出功能

思路

  1. 导入,即将导入的json赋值给jsonData,数据变动,视图会跟着渲染。
  2. 导出,即将jsonData数据导出。

置顶、置底、删除、清空功能

思路

  1. 置顶、置底即改变当前组件的权重,提高其优先级,配合样式来渲染。
  2. 删除、清空功能核心是修改jsonData数据,删除功能就是遍历jsonData将对应的组件数据对象删除,清空则直接将画布中数据清除即可。

功能命令快捷键,支持单选多选

思路:
功能命令快捷键是在注册命令时(如:注册撤销、重做、拖拽、删除等功能),给window绑定keydown事件,并给命令定义好快捷键名称。当用户出发keydown事件时,遍历commonArray,匹配对应的键盘命令,匹配到则执行更新方法redo函数。 多选是监听shiftKey,判断shift按键是否被按下,如果按下则认为用户要多选。

画布中组件右键展示操作项

思路:
右键,触发系统方法contextMenu,此时弹窗自定义选项组件即可。

画布中组件添加属性、绑定字段

思路:
画布中选中一个组件,右侧属性列表会动态渲染出来当前组件需要配置哪些属性。 组件对应的属性配置,是在注册组件初始化的时候定义好的,详情见VisualEditorConfig.tsx

根据组件标识,通过作用域插槽,自定义组件行为

思路:
slot的应用。 定义#myBtn,Vue会在context上下文添加一个myBtn方法,此方法的作用是执行渲染函数renderFnWithContext,得到Vitual-DOM。 定义一个组件标识,即增加一个slotName。 自定义属性将全部被绑定到组件上,那么当用户执行操作时,就会在自定义属性中找到slotName对应的方法并执行,组件行为触发了。

<template #myBtn>
  <el-button v-if="formData.food === 'dangao'">自定义按钮</el-button>
  <el-tag v-else>自定义标签</el-tag>
</template>
复制代码
data() {
  return {
    // 绑定的数据
    formData: {},
    // 自定义属性
    customProps: {
      myBtn: { // myBtn是组件标识,即slotName
        onClick: () => {
          this.$notify({ message: "执行动作" });
        }
      },
      mySelect: {
        onChange: (val: any) => {
          this.$notify({ message: `下拉框发生变化,${val}` });
        }
      }
    }
  };
}
复制代码

5. 小结


以上实现的是常用功能,还有很多优秀的功能可以添加进来,比如:

  • 增加事件配置
  • 拖拽框选多个组件,进行组合和拆分
  • 画布中选中组件复制粘贴一份
  • 插入图片
  • ...

后续有时间我将继续完善相关功能,让他变的更好用更通用一些。

6. 参考资料


文章分类
前端
文章标签