# cv-visual-editor **Repository Path**: zhugqiang/cv-visual-editor ## Basic Information - **Project Name**: cv-visual-editor - **Description**: vue3可视化编辑器 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 9 - **Created**: 2023-10-10 - **Last Updated**: 2023-10-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # vue3 可视化编辑器 [在线预览地址](http://yooyooy.gitee.io/cv-visual-editor/) ## 已完成功能 - [x] 页面结构 - [x] 左侧可选组件列表 - [x] 中间画布内容 - [x] 右侧编辑选中组件属性 - [x] 菜单拖拽组件到容器 - [x] 画布组件选中状态 - [x] 操作命令队列+快捷键 - [x] 编辑组件-单选和多选 - [x] 操作栏功能 - [x] 撤销、重做 - [x] 导入导出json数据 - [x] 组件的置顶和置底 - [x] 组件的删除清空 - [x] 预览和关闭编辑模式 - [x] 拖拽贴边 - [x] 右键菜单操作 - [x] 组件设置预定属性和绑定值 - [x] 编辑组件可调整宽高 - [x] 组件标识、作用域插槽、组件行为事件 - [x] 可选组件列表 - [x] 输入框:双向数据绑定、宽高调整 - [x] 按钮:类型、文字、大小尺寸、宽高调整 - [x] 图片:自定义图片地址、宽高调整 - [x] 下拉框:预定义选项值、双向数据绑定、宽高调整 - [ ] 复选框:预定义选项值 - [ ] 开关按钮:开关布尔值 ## 页面布局 - 左侧组件预览 - 中间 - 操作命令 - 组件编辑区域 - 右侧组件属性 ## 注册组件功能 ![registry](./xmind/registry.png) ### registry 方法 > 通过registry方法注册组件,并将组件push进已注册组件列表 功能: - 注册组件 - 保存组件队列(componentList、componentMap) 参数: - key 组件标识 - component **核心** 注册组件参数对象 方法: ```js export function createVisualEditorConfig() { const componentList: VisualEditorComponent[] = [] const componentMap: Record = {} return { componentList, componentMap, registry: (key: string, component: Omit) => { let comp = { ...component, key } componentList.push(comp) componentMap[key] = comp }, } } ``` #### component参数 - `label` 组件名称 - `preview` 预览方法,返回 *JSX.Element* - `render` 渲染方法,返回 *JSX.Element* - 参数data - props:编辑属性 - model:绑定model - size:组件宽高 - custom:自定义事件或slot - `props` 组件编辑属性 - `model` 组件绑定字段 - `resize` 可调整大小 *宽度*、*高度* 类型定义: ```js interface VisualEditorComponent< Props = Record, Model = Record > { key: string label: string preview: () => JSX.Element render: (data: { props: { [k in keyof Props]: any } model: Partial<{ [k in keyof Model]: any }> size: { width?: number; height?: number } custom: Record }) => JSX.Element props?: Props model?: Model resize?: { width?: boolean; height?: boolean } } ``` ## 创建画布编辑组件 ![createNewBlock](./xmind/createNewBlock.png) ### createNewBlock 方法 功能: 为画布编辑区域创建新组件 参数: - `top` 组件基于画布顶部距离 - `left` 组件基于画布左侧距离 - `component` 注册组件对象 *VisualEditorComponent* 返回值: - `top` 组件基于画布顶部距离 - `left` 组件基于画布左侧距离 - `componentKey` 组件标识 - `adjustPosition` 是否调整位置,判断组件位置在鼠标拖动中心还是左上角 - `focus` 是否选中,选中后右侧编辑栏显示选中组件编辑属性 - `zIndex` z-index样式属性 - `width` 组件宽度,用于调整组件大小 - `height` 组件高度,用于调整组件大小 - `hasResize` 是否允许调整组件大小 - `props` 组件属性参数 - `model` 组件绑定字段 - `slotName` slot插槽名称 类型定义: ```js interface VisualEditorBlockData { top: number left: number componentKey: string adjustPosition?: boolean focus: boolean zIndex: number width: number height: number hasResize: boolean props: Record model: Record slotName?: string } ``` ## 命令队列 ![](./xmind/useCommand.png) 命令队列注册过程: - `name` 注册命令名称 - `keyboard` 键盘事件标识,例如撤销:ctrl+z - `execute` 执行方法,返回*重做(redo)*和*撤销(undo)*两个命令方法 - `init` 初始化方法,返回方法会在命令销毁阶段执行,类似*useEffect* - `data` 辅助对象,可用于缓存命令对象的数据 命令注册进`state.commands`对象。 执行命令`state.commands[name]`,获得`execute`对象,可执行`redo`和`undo`方法。 ### useCommand 方法 功能: - 注册命令(命令名称、撤销、重做) - 生成命令队列 - 监听命令键盘事件 #### commandState 命令对象 接口类型: ```js type CommandExecute = (...args: any[]) => { undo?: () => void redo: () => void } interface CommandState { queue: ReturnType[] current: number commands: Record void> commandArray: Commander[] destroyList: Array<() => void> } ``` ```js const state: CommandState = { queue: [], current: -1, commands: {}, commandArray: [], destroyList: [], } ``` #### register注册方法 ```js interface Commander { name: string keyboard?: string | string[] followQueue?: boolean execute: CommandExecute init?: () => () => void data?: any } const register = (command: Commander) => { state.commandArray.push(command) state.commands[command.name] = (...args: any[]) => { const { undo, redo } = command.execute(...args) redo() // followQueue为false, 不需要将命令执行得到的undo,redo存入命令队列 if (command.followQueue === false) return; let { queue, current } = state if (queue.length) { queue = queue.slice(0, current + 1) state.queue = queue } state.queue.push({ undo, redo }) state.current++ } } ``` 撤销方法注册: ```js register({ name: 'undo', keyboard: 'ctrl+z', followQueue: false, execute: () => { return { redo: () => { const queueItem = state.queue[state.current] if (queueItem && queueItem.undo) { queueItem.undo() state.current-- } }, } }, }) ```