web架构师编辑器内容-快捷键操作的实现

快捷键操作的需求

元素选择 前提都是在元素被选中的情况下

  • 拷贝图层 - ⌘C / Ctrl+C : 新建当前选择的元素的一个数据结构
  • 粘贴图层 - ⌘V / Ctrl+V : 将新建的元素添加到 components 数组中
  • 删除图层 - Backspace / Delete : 在 components 数组中删除选择的元素
  • 取消选中 - ESC : currentElement 设置为空

元素移动

  • 上下左右移动一像素 - ↑ ↓ → ← : 更新选中元素 props 的 top/left 值
  • 上下左右移动十像素 - Shift + ↑ ↓ → ←:更新选中元素 props 的 top/left 值

撤销/重做

  • 撤销 - ⌘Z / Ctrl+Z
  • 重做 - ⌘⇧Z / Ctrl+Shift+Z

好用的按键响应库 - HotKeys.js

之前我们在InlineEdit.vue组件中,使用到了useKeyPress帮助了我们对esc和enter进行了绑定:

const useKeyPress = (key: string, cb: () => any) => {const trigger = (event: KeyboardEvent) => {if (event.key === key) {cb()}}onMounted(() => {document.addEventListener('keydown', trigger)})onUnmounted(() => {document.removeEventListener('keydown', trigger)})
}

但是现在我们的需求复杂了很多,因为我们有很多的组合键,所以我们需要好用的第三方库来完成对应的工作:
项目地址:https://github.com/jaywcjlove/hotkeys
演示地址:https://wangchujiang.com/hotkeys/

完成对应的编码
// useHotKey.ts
import hotkeys, { KeyHandler } from 'hotkeys-js'
import { onMounted, onUnmounted } from 'vue'const useHotKey = (keys: string, callback: KeyHandler) => {onMounted(() => {hotkeys(keys, callback)})onUnmounted(() => {hotkeys.unbind(keys, callback)})
}export default useHotKey

对于快捷键的操作,是独立于整个应用,为了更好的交互,而单独添加的,看起来相对独立的功能,所以可以把整个模块称之为系统的插件。
创建plugins文件夹:

// plugins/hotKey.ts
import useHotKey from '../hooks/useHotKey'
export default function initHotKeys() {useHotKey('ctrl+c, command+c', () => {alert('ctrl+c, command+c')})useHotKey('ctrl+v, command+v', () => {alert('ctrl+v, command+v')})
}// 在Editor.vue中的setup函数中进行调用,测试效果
setup(){initHotKeys()
}

完成ctrl+c快捷键的操作:

// plugins/hotKey.ts
import { useStore } from "vuex";
import { computed } from "vue";
import { GlobalDataProps } from "../store";useHotKey("ctrl+c, command+c", () => {const store = useStore<GlobalDataProps>();const currentId = computed(() => store.state.editor.currentElement);store.commit("copyComponent", currentId.value);
}// store/editor.ts mutations中的
copyComponent(state, id) {// 这里是在geeters定义了一个getElement,引入store,就可以在mutations中拿到getters中的属性了const currentComponent = store.getters.getElement(id)if (currentComponent) {state.copiedComponent = currentComponent;message.success('已拷贝当前图层', 1);}
},// 在editoreProps中添加copiedComponent
interface EditorProps {// 供中间编辑器渲染的数组components: ComponentData[];// 当前编辑的是哪个元素,uuidcurrentElement: string;// 当然最后保存的时候还有有一些项目信息,这里并没有写出,等做到的时候再补充page: PageData;// 当前被复制的组件copiedComponent?: ComponentData;
}

完成ctrl+v快捷键的操作:

// plugins/hotKey.ts
useHotKey('ctrl+v, command+v', () => {store.commit('pasteCopiedComponent')
})// store/editors.ts中mutations中的
pasteCopiedComponent(){if (state.copiedComponent) {state.components.push(state.copiedComponent)message.success('已黏贴当前图层', 1);}}

这里如果直接进行push操作,就会发现复制出来的元素也粘贴出来的元素是一摸一样的,最终就会导致在移动复制的元素的时候,被复制的元素也会被同样移动。所以我们要对pasteCopiedComponent进行稍微的改造,拷贝一下数据:

// store/editors.ts中mutations中的
pasteCopiedComponent: setDirtyWrapper((state) => {if (state.copiedComponent) {// 使用lodash中的cloneDeep进行拷贝,可以丧失响应式const clone = cloneDeep(state.copiedComponent);clone.id = uuidv4();clone.layerName = clone.layerName + '副本';state.components.push(clone);message.success('已黏贴当前图层', 1);}
}),// 删除图层:
deleteComponent: setDirtyWrapper((state, id) => {const currentComponent = state.components.find((component) => component.id === id);if (currentComponent) {state.components = state.components.filter((component) => component.id !== id);message.success("删除当前图层成功", 1);}
}),

添加移动元素的快捷键:

// store/editors.ts
moveComponent(state,data: { direction: MoveDirection; amount: number; id: string }
) {const currentComponent = state.components.find((component) => component.id === data.id);if (currentComponent) {// 获取旧的值const oldTop = parseInt(currentComponent.props.top || "0");const oldLeft = parseInt(currentComponent.props.left || "0");const { direction, amount } = data;switch (direction) {case "Up": {const newValue = oldTop - amount + "px";store.commit("updateComponent", {key: "top",value: newValue,id: data.id,});break;}case "Down": {const newValue = oldTop + amount + "px";store.commit("updateComponent", {key: "top",value: newValue,id: data.id,});break;}case "Left": {const newValue = oldLeft - amount + "px";store.commit("updateComponent", {key: "left",value: newValue,id: data.id,});break;}case "Right": {const newValue = oldLeft + amount + "px";store.commit("updateComponent", {key: "left",value: newValue,id: data.id,});break;}default:break;}}
},// hotKey.ts
function initHotKeys(){...useHotKey('down', () => {store.commit('moveComponent', { direction: 'Down', amount: 1, id: currentId.value})
})useHotKey('left', () => {store.commit('moveComponent', { direction: 'Left', amount: 1, id: currentId.value})
})useHotKey('right', () => {store.commit('moveComponent', { direction: 'Right', amount: 1, id: currentId.value})
})useHotKey('shift+up', () => {store.commit('moveComponent', { direction: 'Up', amount: 10, id: currentId.value})
})useHotKey('shift+down', () => {store.commit('moveComponent', { direction: 'Down', amount: 10, id: currentId.value})
})useHotKey('shift+left', () => {store.commit('moveComponent', { direction: 'Left', amount: 10, id: currentId.value})
})useHotKey('shift+right', () => {store.commit('moveComponent', { direction: 'Right', amount: 10, id: currentId.value})
})
}

实现之后发现会有一个问题:如果滚动条在下面的话,滚动条会跟着一起移动。
滚动条跟随元素一起移动,这个行为是浏览器默认的一个行为,所以我们可以使用e.preventDefault来阻止浏览器默认行为,在useKotKey回调函数里面,第一个参数就是事件对象,我们可以在这里进行添加,其他事件也是如此。但是如果要是都是写同样的逻辑,是比较繁琐的,有什么办法能够完成无数个这样的逻辑呢?我们可以给回调函数在来一套包装,返回一个新的function,这就是高阶函数,高阶函数在处理多次逻辑重复的回调中比较适用:

const wrap = (callback: KeyHandler) => {const wrapperFn = (e: KeyboardEvent, event: HotkeysEvent) => {e.preventDefault()callback(e, event)}return wrapperFn
}useHotKey('up', wrap(() => {store.commit('moveComponent', { direction: 'Up', amount: 1, id: currentId.value })
}))
useHotKey('down', wrap(() => {store.commit('moveComponent', { direction: 'Down', amount: 1, id: currentId.value})
}))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/641564.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

接口测试之webservice

什么是Webservice Web service是一个平台独立的&#xff0c;低耦合的&#xff0c;自包含的、基于可编程的web的应用程序&#xff0c;可使用开放的XML&#xff08;标准通用标记语言下的一个子集&#xff09;标准来描述、发布、发现、协调和配置这些应用程序&#xff0c;用于开发…

STL之map【有序哈希表】使用方法

这里写目录标题 map【有序哈希表】使用方法1.头文件:2.创建map:3.添加键值对:4.查找键值对&#xff1a;5.遍历键-值对&#xff1a;5.综合示例&#xff1a;班级学生 map【有序哈希表】使用方法 话不多说&#xff0c;接着讲map用法&#xff1a; map&#xff1a;映射&#xff0c…

spyder 对 lambda 函数的调试

如何进入lambda匿名函数进行调试&#xff1a; import pandas as pddef func(a, b):return a bseries pd.Series([1,2,3,4]) a series.apply(lambda x: func(x, 2)) print(a) 在调用匿名函数的地方打上断点&#xff0c;也就是这一行&#xff1a; a series.apply(lambda x…

总结Symbol、Set、WeakSet、Map、WeakMap

前言 这几个es6新增的数据结构和变量类型&#xff0c;不经常用&#xff0c;好容易忘记啊。在此记录一下&#xff0c;方便复习。 Symbol Symbol是es6新增的基本数据类型&#xff0c;用于生成独一无二的值。 基本使用 1、创建两个描述相同的值&#xff0c;也不会相等。 let s1 …

R303 指纹识别模块功能实现流程

1 基本通信流程 1.1 UART 命令包的处理过程 1.2 UART 数据包的发送过程 UART 传输数据包前&#xff0c;首先要接收到传输数据包的指令包&#xff0c;做好传输准备后发送成功应答包&#xff0c;最后才开始传输数据包。数据包主要包括&#xff1a;包头、设备地址、包标识、包长…

基于jQuery与Spring MVC实现用户密码异步修改的实战演示

文章目录 一、实战概述二、实战步骤&#xff08;一&#xff09;创建表单1、表单界面2、表单代码3、脚本代码 &#xff08;二&#xff09;后端控制器&#xff08;三&#xff09;测试代码&#xff0c;查看效果1、弹出更改密码表单2、演示更改密码操作 三、实战总结 一、实战概述 …

【MySQL】最左匹配原则

最左匹配原则 0x1 简单说下什么是最左匹配原则 顾名思义&#xff1a;最左优先&#xff0c;以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like&#xff09;就会停止匹配。 例如&#xff1a;b 2 如果建立(a&#xff0c;b&#xff09;顺序…

苏州渭塘镇应用无人机“智慧执法”

苏州渭塘镇应用无人机“智慧执法” 在今年以来&#xff0c;渭塘镇综合行政执法局采用了“空中地面”的立体监督模式&#xff0c;以实现对“互联网执法”工作的深入推进。在这一模式下&#xff0c;无人机巡查作为技术手段得到广泛应用&#xff0c;而安全生产监管信息系统和综合…

自然语言处理--双向匹配算法

自然语言处理作业1--双向匹配算法 一、概述 双向匹配算法是一种用于自然语言处理的算法&#xff0c;用于确定两个文本之间的相似度或匹配程度。该算法通常使用在文本对齐、翻译、语义匹配等任务中。 在双向匹配算法中&#xff0c;首先将两个文本分别进行处理&#xff0c;然后…

设计模式学习总结

责任链模式 使用方法&#xff1a; 1.创建接口 2.定义实现类&#xff0c;每个实现类实现接口&#xff0c;并拥有一个ArchiveHandle的成员&#xff0c;用作责任链的链接 public interface ArchiveHandle {void handle(ArchiveVO archiveVO); } public class ArchivePreHandle i…

企业使用CRM系统有哪些好处?使用CRM应该注意什么?

近年来&#xff0c;企业竞争日趋激烈&#xff0c;为推动企业业绩增长&#xff0c;赢得市场的一席之地&#xff0c;CRM成为企业争相布局的管理工具。那么CRM是什么&#xff0c;到底有什么魔力能让企业管理者着迷&#xff0c;本文我们将为大家进行深入剖析。 什么是CRM&#xff…

龙芯3A6000_通过xrdp远程访问统信UOS

原文链接&#xff1a;龙芯3A6000|通过xrdp远程访问统信UOS hello&#xff0c;大家好&#xff01;今天我带给大家的是一篇实用性极强的技术文章——通过xrdp远程访问装载在龙芯3A6000上的统信UOS操作系统。这意味着&#xff0c;无论您使用的是Windows、MACOS还是Linux操作系统&a…

[视频处理]关于视频处理的多画面样式

在开发视频系统时&#xff0c;经常会遇到多画面的需求&#xff0c;这里收集一些多画面的素材&#xff0c;共大家参考。 图片来源于网络&#xff0c;仅供参考。 后续补充文章 【图像处理】使用FPGA实现视频多画面的方案 多画面样式

NLP深入学习(七):词向量

文章目录 0. 引言1. 什么是词向量2. Word2Vec2.1 介绍2.2 例子 3. 参考 0. 引言 前情提要&#xff1a; 《NLP深入学习&#xff08;一&#xff09;&#xff1a;jieba 工具包介绍》 《NLP深入学习&#xff08;二&#xff09;&#xff1a;nltk 工具包介绍》 《NLP深入学习&#x…

常见域控开放端口及其作用

TCP/UDP 53&#xff08;域名系统&#xff0c;DNS&#xff09;&#xff1a;域控制器使用DNS端口来提供域名解析服务&#xff0c;将域名映射到IP地址&#xff0c;以便客户端能够找到域控制器。 TCP/UDP 88&#xff08;Kerberos&#xff09;&#xff1a;Kerberos是Windows中用于身…

【博士每天一篇论文-综述】Deep Echo State Network (DeepESN)_ A Brief Survey

阅读时间&#xff1a;2023-11-22 1 介绍 年份&#xff1a;2017 作者&#xff1a;C. Gallicchio 比萨大学计算机科学系终身教授助理教授&#xff0c;A. Micheli&#xff0c;比萨大学计算机科学系 期刊&#xff1a; ArXiv 引用量&#xff1a;68 这是两个大牛的论文&#xff0c;…

Nginx 平滑升级原理分析和实验

Nginx平滑升级 1. 为什么要对 Nginx 平滑升级2. 平滑升级的原理3. Nginx 信号简介3.1. master 主进程支持的信号3.2. worker 工作进程支持的信号 4. Nginx 平滑升级实战5. 平滑升级总结6. 升级实验6.1. 部署一台新的 Nginx 服务器6.2. 查看版本和模块6.3. 访问验证6.4. 升级 Ng…

autosar学习笔记 之SecOC

SecOC 接下来SecOC标准就更复杂一点,它不单单是做了通讯校验。 SecOC是基于对称密钥加密的一套机制,需要对ECU间的通讯作身份认证处理,来更好的防止伪装攻击,谈起对称或非对称加密,就会涉及到密钥的存储和Mac值的计算。 因此SECOC机制对于密钥的硬件存储,也有一定的要求…

matlab appdesigner系列-常用15-滑块、微调器

滑块&#xff0c;以左右拖动的方式在一定范围内改变数值 此示例&#xff0c;滑块显示微调器的数值&#xff0c;微调器也可以显示滑块的数值 操作步骤为&#xff1a; 1&#xff09;将滑块和微调器拖拽到画布上 2&#xff09;分别设置这两个组件的回调函数 回调函数有两个选项…

c语言-常见的动态内存错误

文章目录 前言一、常见的动态内存错误1.1 对空指针进行解引用操作1.2 对动态开辟的空间进行越界访问1.3 对非动态开辟的空间使用free()1.4 使用free()释放一块动态开辟的空间时&#xff0c;释放不完全1.5 对同一块动态开辟的空间进行多次释放1.6 动态开辟的空间使用后&#xff…