背景
最近在实习过程中,mentor安排了一个小任务,分享一下自己是如何一错再错的。首先是基于一个二次封装的Element-ui的el-tree组件,里面的树形结构的每一项都带有name、auth、desc等字段,总的来说就是关于一个系统的侧边栏的显示内容和权限配置都在里面可以同步修改。OK,现在看看我的任务是什么,在该组件基础上添加复制粘贴功能,即是可以通过复制原有的某项结构,然后复制到树形的任意节点上。
思路
要实现这一功能,四个步骤:
一、复制的时候拿到该该节点的配置实例对象
二、将对象复制到剪切板
三、从剪切板拿到对象
四、将对象存进目标节点
节点搜索
由于该组件可以拿到当前节点对象,在根据拿到的节点在dataSource就可以锁定其位置了。简单一个递归就可以实现,这里就不说了。

剪切方案选择
自己去查看了不同的博客总结了以下三种方案:
原生方式document.execCommand
先看一下代码
js
function copy(content) {
const dom = document.createElement("input");
dom.value = content;
document.body.appendChild(dom);
dom.select();
document.execCommand("copy");
document.body.removeChild(dom);
}
// 剪切功能
function cut(){
document.execCommand("cut")
}
// 粘贴功能:注意,只会在光标所在位置进行粘贴
function paste(){
const pasteText = document.querySelector("#output")
pasteText.focus() // 让光标聚焦到output标识的输入框上
document.execCommand("paste"); // 截至目前该方法涉及到安全问题已失效
}
其实就是doucument.execCommand这个api可以对选择的内容进行copy、paste、cut操作,所以前提是要对内容进行选择,因此dom.select(); pasteText.focus();就很好理解了。
浏览器自带cliboard API
先说一下cliboard,ClipboardAPI是下一代的剪贴板操作方法
(1)支持异步 它的所有操作都是异步进行的,返回promise对象,不会造成页面卡顿
(2)内容丰富 它可以将任何内容(例如图片)放入到剪贴板。
(3)安全通过navigator.clipboard属性返回Clipboard对象,所有操作都通过这个对象进行,如果navigator.clipboard属性返回undefined,就说明当前浏览器不支持这个API。
Clipboard.writeText()
方法用于将文本内容写入剪贴板(也就是复制功能) ,该方法同样返回一个Promise对象
js
if (navigator.clipboard && window.isSecureContext) {
// navigator clipboard 向剪贴板写文本
navigator.clipboard.writeText(this.ToJSON(newData))
}
Clipboard.write()
方法用于将任意数据写入剪贴板,可以是文本数据,也可以是二进制数据。
js
async function copy1() {
try {
// 图片地址
const imgURL = "https://dummyimage.com/300.png";
// 获取图片
const data = await fetch(imgURL);
// 将图片转为blob类型
const blob = await data.blob();
// 将数据写入(复制)到剪贴板
await navigator.clipboard.write([
// 创建Clipboard实例对象,对象的键名是数据的 MIME 类型,键值就是数据本身
new ClipboardItem({
[blob.type]: blob,
}),
]);
console.log("Image copied.");
} catch (err) {
console.error(err.name, err.message);
}
}
Clipboard.read()
方法用于复制剪贴板 (也就是粘贴功能 )里面的数据,可以是文本数据,也可以是二进制数据(比如图片)。该方法需要用户明确给予许可。
js
// async 函数
async function paste(){
try{
// 创建clipboard对象并调用readText()方法读取剪贴板上的内容进行返回
const text = await navigator.clipboard.readText()
console.log("要粘贴的内容为:",text); // 具体粘贴到哪你自己决定
}catch(err){
// catch捕获处理报错
console.log("粘贴失败!",err);
}
}
Cliboard.js插件
附上链接Cliboardjs
Clipboard.js 是一个用于复制文本到剪贴板轻量级的 JavaScript 库。它提供了一个简单的接口,使开发人员可以通过点击按钮或其他交互方式将文本复制到剪贴板中。Clipboard.js 支持跨浏览器,并且不需要 Flash 或其他插件。它是一种方便的工具,可以简化复制文本的过程,特别适用于网站或应用程序中需要提供复制功能的场景。
安装
js
# node安装
npm install clipboard --save
# CDN引入
<src="https://cdn.jsdelivr.net/npm/[email protected]/dist/clipboard.min.js">
使用
js
import Clipboard from 'clipboard' //引入cliboard
import { ElMessage } from 'element-plus' //消息提示
function clipboardSuccess(msg) {
ElMessage.success(msg || '复制成功')
}
function clipboardError(msg) {
ElMessage.error(msg || '复制失败')
}
export default function handleClipboard(text, event, msg) {
const clipboard = new Clipboard(event.target, {
text: () => text,
})
clipboard.on('success', () => {
clipboardSuccess(msg)
clipboard.destroy()
})
clipboard.on('error', () => {
clipboardError(msg)
clipboard.destroy()
})
clipboard.onClick(event)
}
以上是一个简单的对复制事件的拦截弹窗提示demo
选择
首先因为document.execCommand('paste')
涉及到安全问题,考虑到安全原因, document.execCommand('paste')操作已经被禁止了,且兼容性不好,排除该方案。然后的话通过阅读文档可以发现Cliboardjs没有提供粘贴相应的api,虽然它轻量且兼容性好,但是不符合业务需求。所以最后选择了浏览器自带cliboard API
事件监听拦截
首先因为我需要考虑键盘的Ctrl + CV和按钮实现拷贝复制即可,按钮监听点击事件不用说。然后键盘的cv我第一时间想到以下方式
js
document.addEventListener('keydown', keydownEventHandler)
// ctrl/meta + c/v 快捷键回调
keydownEventHandler(event: KeyboardEvent) {
if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
// 在这里编写按下Ctrl+C后的逻辑
if (this.curretnNode) this.handleCopy(this.curretnNode)
}
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
// 在这里编写按下Ctrl+V后的逻辑
if (this.curretnNode) this.handlePaste(this.curretnNode)
}
}
然后问题来了mac和windows系统的快捷键不一样,不可能两个快捷键都可以,这样不符合实际。然后我就用以下方式来判断系统选择不同的键盘监听事件
js
let isMac = /macintosh|mac os x/i.test(navigator.userAgent); //苹果
// isMac 返回true是苹果,false不是
let isWindows = /windows|win32/i.test(navigator.userAgent); //Windows
// isMac2 返回true是Windows,false不是
let isLinux = /linux/i.test(navigator.userAgent); //Linux
// isMac3 返回true是Linux,false不是
反思
OK,反思来了,虽然功能的需要的流程全部完成了,但是代码整体显得臃肿,为什么说臃肿,因为拷贝粘贴的复制可以有多种多样,如果每个都要写一个特定的方式来监听,遗漏出错的概率大,而且还要考虑兼容性。经过mentor提醒,我想起来浏览器的自带监听器
这种方式,代码简洁,兼容性好。
额额额,其实还有一个地方,就是插入节点的那里。后面其实发现拿到节点实例可以直接调用对象的insert方法插入,不用自己去写递归搜索。所以一定要注意阅读文档!!但是多了解几种方法百利而无一害,但是最终上线版本中,当然还是选择最优方案!