Content Table

快捷键复制粘贴组件

访问剪贴板

使用 navigator.clipboard 对象访问系统的剪贴板:

  • readText: 读取剪贴板的内容
  • writeText: 设置剪贴板的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Clipboard</title>
</head>

<body>
<button id="button-copy">Copy</button>
<button id="button-paste">Paste</button>

<script>
// 设置系统剪贴板的内容
document.querySelector('#button-copy').addEventListener('click', () => {
navigator.clipboard.writeText('Hello');
});

// 读取系统剪贴板的内容
document.querySelector('#button-paste').addEventListener('click', () => {
navigator.clipboard.readText().then(text => {
console.log(text);
});
});
</script>
</body>
</html>

提示: IE 不支持 Navigator.clipboard,但 Edge 支持。

绑定快捷键

在 document 上注册键盘事件,按下组合键 ctrl + c 时触发动作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Clipboard</title>
</head>
<body>
<script>
// ctrlPressed 和 cPressed 都同时为 true 时,表示组合快捷键 ctrl + c 触发
let ctrlPressed = false;
let cPressed = false;

// 按下键事件
document.addEventListener('keydown', e => {
switch(e.keyCode) {
case 17: ctrlPressed = true; break; // ctrl 按下
case 67: cPressed = true; break; // c 按下
default: ;
}

// ctrl 和 c 同时按下
if (ctrlPressed && cPressed) {
console.log('ctrl and c are both pressed: ' + Date.now());
}
});

// 松开键事件
document.addEventListener('keyup', e => {
switch(e.keyCode) {
case 17: ctrlPressed = false; break;
case 67: cPressed = false; break;
default: ;
}
});
</script>
</body>
</html>

在指定的元素上触发快捷键,而不是整个 document,如果一个元素可以捕捉键盘事件,其需要能够获得焦点。默认能够获得焦点的 DOM 元素有 input, button 等,div 不能获得焦点。

为了使得一个元素能够获得焦点,设置其属性 tabindex="-1" 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Clipboard</title>

<style>
.container {
display: inline-block;
width: 300px;
height: 300px;
background: #efefef;
outline: none;
border-radius: 4px;
}

.container:focus {
box-shadow: 0 0 5px rgb(50, 123, 218) inset;
}
</style>
</head>

<body>
<!-- 设置 div 的 tabindex="-1" 使其可以获得焦点 -->
<div id="container-1" class="container" tabindex='-1'></div>
<div id="container-2" class="container" tabindex='-1'></div>

<script>
const holder = document.querySelector('#container-2');
// holder.focus(); // 编程使得 div 获得焦点

// ctrlPressed 和 cPressed 都同时为 true 时,表示组合快捷键 ctrl + c 触发
let ctrlPressed = false;
let cPressed = false;

// 按下键事件
holder.addEventListener('keydown', e => {
switch(e.keyCode) {
case 17: ctrlPressed = true; break; // ctrl 按下
case 67: cPressed = true; break; // c 按下
default: ;
}

// ctrl 和 c 同时按下
if (ctrlPressed && cPressed) {
console.log('ctrl and c are both pressed: ' + Date.now());
}
});

// 松开键事件
holder.addEventListener('keyup', e => {
switch(e.keyCode) {
case 17: ctrlPressed = false; break;
case 67: cPressed = false; break;
default: ;
}
});
</script>
</body>
</html>

剪贴板和快捷键插件

已经有比较成熟的操作系统剪贴板和快捷键的插件可以使用,在项目中没必要像上面这样手动原生的实现一遍:

  • 剪贴板插件: clipboard-polyfill
  • 快捷键插件: vue-shortkey:
    • 绑定的是全局快捷键
    • input 和 textarea 中不受快捷键影响: Vue.use(shortkey, { prevent: ['input', 'textarea'] });

参考示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<template>
<div class="problem">
<Button @click="copy">Copy</Button>
<Button @click="paste">Paste</Button>

<!-- 绑定快捷键 -->
<button class="sbtn" v-shortkey="['ctrl', 'c']" @shortkey="theAction">Open</button>
<button class="sbtn" v-shortkey="['ctrl', 'v']" @shortkey="theAction">Open</button>

<Input v-model="name"/>
</div>
</template>

<script>
import * as clipboard from 'clipboard-polyfill/text';

export default {
data() {
return {
name: 'Hi'
};
},
methods: {
copy(e) {
clipboard.writeText('This text is plain.');
},
paste() {
clipboard.readText().then(text => {
this.name = text;
}).catch((err) => {
console.log(err);
});
},
theAction(e) {
console.log(e);
}
}
};
</script>

<style lang="scss">
.sbtn {
display: none;
}
</style>

复制粘贴组件

同一个页面中的组件,数据还可以通过全局变量、Vuex 的 store 等进行传递,但是跨页面的时候,不同页面里的 JS 对象就不能够互相访问了,这个时候需要通过一个中间层来传递数据,例如系统的剪贴板。

跨页面的组件复制粘贴的原理为:

  1. 选择要复制的组件
  2. 按下复制快捷键,把被复制组件的信息保存为 JSON 格式的字符串,然后设置到系统的剪贴板里
  3. 到另一个页面按下粘贴快捷键后,读取系统的剪贴板里的字符串,转换为 JSON 对象,校验得到的对象是否有效
  4. 获取得到的对象信息,创建对应的组件

组件复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<template>
<div class="com-from">
<!-- 按下快捷键 ctrl + c 复制选中的新闻 -->
<div class="news" v-shortkey="['ctrl', 'c']" @shortkey="copySelectedNews">
<Card v-for="news in newses" :key="news.title" dis-hover>
<p slot="title">{{ news.title }}</p>
<p>{{ news.content }}</p>

<Icon slot="extra" :type="newsIconType(news)" @click="selectNews(news)"/>
</Card>
</div>
</div>
</template>

<script>
import * as clipboard from 'clipboard-polyfill/text';

export default {
data() {
return {
// 新闻的数据
newses: [
{ title: '罗永浩承认售假', content: '罗永浩在 “交个朋友直播间” 销售了 “皮尔卡丹” 品牌的羊毛衫,有消费者在收到货后怀疑衣服不是纯羊毛,而是假冒伪劣产品' },
{ title: '羊肉价格持续上涨', content: '羊肉价格持续 8 周上涨,每斤超 40 元,什么原因造成“羊贵妃”' },
],
// 选中的新闻
selectedNews: null,
};
},
methods: {
// 新闻的图标
newsIconType(news) {
return news === this.selectedNews ? 'ios-checkmark-circle' : 'ios-checkmark-circle-outline';
},
// 选中新闻
selectNews(news) {
this.selectedNews = news;
},
// 复制选中的新闻到系统的剪贴板里
copySelectedNews() {
clipboard.writeText(JSON.stringify(this.selectedNews)).then(() => {
this.$Message.success('组件复制成功');
});
}
}
};
</script>

<style lang="scss">
.com-from {
.news {
width: 280px;

.ivu-card {
margin-bottom: 20px;
}
}
}
</style>

组件粘贴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<template>
<div class="com-to">
<!-- 按下快捷键 ctrl + v 粘贴新闻 -->
<div class="news" v-shortkey="['ctrl', 'v']" @shortkey="pasteNews">
<Card v-for="news in newses" :key="news.title" dis-hover>
<p slot="title">{{ news.title }}</p>
<p>{{ news.content }}</p>
</Card>
</div>
</div>
</template>

<script>
import * as clipboard from 'clipboard-polyfill/text';

export default {
data() {
return {
// 新闻的数据
newses: [],
};
},
methods: {
// 复制选中的新闻到系统的剪贴板里
pasteNews() {
clipboard.readText().then(newsJson => {
const news = JSON.parse(newsJson);

// 有效的新闻对象 news 才加入
if (this.validateNews(news)) {
this.newses.push(news);
} else {
throw new Error('无效的新闻对象');
}
}).catch((err) => {
this.$Message.error(`粘贴组件错误: ${err.message}`);
console.log(err);
});
},
// 验证 news 是否有效的新闻对象
validateNews(news) {
// 对象有属性 title 和 content,说明是一个有效的新闻对象
return this.hasProperty(news, 'title') && this.hasProperty(news, 'content');
},
// 判断对象 obj 是否有属性 property
hasProperty(obj, property) {
return Object.prototype.hasOwnProperty.call(obj, property);
}
}
};
</script>

<style lang="scss">
.com-to {
.news {
width: 280px;

.ivu-card {
margin-bottom: 20px;
}
}
}
</style>