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>
说明:
- jsonData是整个项目的数据中心,是核心数据,后续的操作各种改变数据,就是改的它。我们给他换一个更加语义话的名字
dataModel
。- config是注册的菜单组件。
- 项目中使用了比较多的发布订阅,包括头部功能注册,左侧组件注册都是利用发布订阅功能实现的。
下面正式开始功能思路介绍,详细实现捋一遍代码就都知道了。
拖拽菜单组件到画布区
核心:dragable,dragstart、dragend
(用在当前拖拽组件),dragenter、dragover、dragleave、drop
(用在画布)
思路:
- 按下菜单中组件,给画布addEventListener
dragenter
、dragover
、dragleave
、drop
这几个事件,等到组件拖到画布上时,会触发对应的函数。- 组件拖拽到画布上,调用
createNewBlock
创建一个组件并放到dataModel的blocks中,这时候画布就有一个组件了。
画布组件选中态,拖拽位置及改变宽高
- 画布组件选中态,即
mousedown
时设置组件数据的focus
属性为true,添加选中样式。- 改变拖拽位置,即改变组件数据的left和top值
- 改变宽高,拖拽改变宽高,更新width和height值
标识线贴边检测
思路:
记录画布中所有组件的上下左右中间位置信息并保存,组件拖拽过程中,进行数值比对,小于5则展示标识线。
撤销重做功能
思路:
commands
保存的是自己封装了一层,相当于初始化函数,该函数中再调用对应的注册方法时执行。具体做了:执行redo
方法,将redo,audo
保存到queue
中。- 注册撤销和重做函数,注册的函数都将保存到
queue
中,一遍执行撤销和重做时,调用对应的方法。- 完整的注册对象,包含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();
}
导入导出功能
思路
- 导入,即将导入的json赋值给
jsonData
,数据变动,视图会跟着渲染。- 导出,即将jsonData数据导出。
置顶、置底、删除、清空功能
思路
- 置顶、置底即改变当前组件的权重,提高其优先级,配合样式来渲染。
- 删除、清空功能核心是修改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. 参考资料
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/13977.html