前端快捷键插件封装
数据库表
SQL
CREATE TABLE IF NOT EXISTS short_command (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
key TEXT NOT NULL,
description TEXT NULL,
command TEXT NULL,
default_command TEXT NOT NULL,
deleted INTEGER NOT NULL DEFAULT 0,
other TEXT NULL
)
models
Rust
#[derive(Queryable, Selectable, Serialize, Deserialize, Clone, Debug)]
#[diesel(table_name = short_command)]
pub struct ShortCommand {
pub id: i32,
pub name: String,
pub key: String,
pub description: Option<String>,
pub command: Option<String>,
pub default_command: String,
pub deleted: i32,
pub other: Option<String>,
}
#[derive(Insertable, AsChangeset, Serialize, Deserialize, Clone, Debug)]
#[diesel(table_name = short_command)]
pub struct NewShortCommand {
pub name: String,
pub key: String,
pub description: Option<String>,
pub command: Option<String>,
pub default_command: String,
pub deleted: i32,
pub other: Option<String>,
}
快捷键插件封装
该插件通过 pathname区分指令,一般来说一个页面只能存在一个快捷键指令
后期可以通过 sqlite 缓存快捷键指令,允许用户自定义快捷键
插件封装
TypeScript
import { Message } from "@arco-design/web-vue";
import { isRegistered, register, unregister } from "@tauri-apps/api/globalShortcut";
import { useShortCmdStore } from "../store/shortCmd.store";
import { computed } from "vue";
import { ProvideKeys } from "../const/provide.connst";
class ShortRegister {
shortMap = new Map();
shortCommand = 'Command+Shift+S';
constructor(command) {
this.shortCommand = command;
this.init();
}
/**
* 初始化
*/
async init() {
try {
console.log("注册快捷键", this.shortCommand);
const isRegisteredSave = await isRegistered(this.shortCommand);
if (!isRegisteredSave) {
await register(this.shortCommand, () => {
this.invoke();
});
} else {
console.error('快捷键已存在', this.shortCommand);
}
} catch (error) {
console.log(this.shortCommand, error);
}
window.onbeforeunload = ()=>{
// 移除快捷键
unregister(this.shortCommand);
}
window.onunload = ()=>{
// 移除快捷键
unregister(this.shortCommand);
}
}
/**
* 执行快捷键
*
*/
invoke() {
const pathname = window.location.pathname;
const methods = Object.values(this.shortMap.get(pathname) || {});
if (methods.length) {
methods.forEach((method) => {
if (typeof method === "function") {
(method as Function)();
}
});
}
}
/**
* 注册快捷键监听钩子
* @param key
* @param method
* @param pathname
*/
register(key: string, method: () => void, pathname: string = window.location.pathname) {
const methods = this.shortMap.get(pathname) || {};
methods[key] = method;
this.shortMap.set(pathname, methods);
}
/**
* 取消快捷键监听钩子
* @param key
* @param pathname
*/
unregister(key: string, pathname: string = window.location.pathname) {
const methods = this.shortMap.get(pathname) || {};
delete methods[key];
this.shortMap.set(pathname, methods);
}
/**
* 更新快捷键 command
*/
updateShortCommand(command: string) {
unregister(this.shortCommand);
this.shortCommand = command;
register(this.shortCommand, () => {
this.invoke();
});
}
/**
* 移除快捷键
*/
removeShortCommand() {
unregister(this.shortCommand);
}
}
export interface ShortCommandRegister {
command_s: ShortRegister;
command_z: ShortRegister,
[key: string]: ShortRegister
}
export const ShortCommand = {
install(app, options) {
const shortCommandStore = useShortCmdStore()
const commandList = computed(()=> shortCommandStore.commandList);
const registerCmd = {};
commandList.value.forEach((item) => {
if(item.command || item.defaultCommand){
const command = !item.command ? item.defaultCommand : item.command;
const shortRegister = new ShortRegister(command);
registerCmd[item.key] = shortRegister;
}
})
shortCommandStore.setShortCmdInstanceDispatch(registerCmd as ShortCommandRegister);
// 注册快捷键
app.provide(ProvideKeys.shortCommand, registerCmd);
},
};
使用插件
TypeScript
const { command_s } = inject(ProvideKeys.shortCommand) as ShortCommand;
onMounted(()=>{
command_s.register("saveYapi",()=>{
saveYapi()
})
})
onUnmounted(()=>{
command_s.unregister("saveYapi")
})
快捷键录入
Store存储
TypeScript
import { cloneDeep } from 'loadsh';
import { defineStore } from "pinia";
import { computed, inject, ref } from "vue";
import { ShortCommand } from "../types/shortCmd";
import { ProvideKeys } from '../const/provide.connst';
import { ShortCommandRegister } from '../plugins/shortCommand.plugin';
export const useShortCmdStore = defineStore("shortCommand", () => {
const commandList = ref<ShortCommand[]>([
{
id: 1,
name: "保存",
command: '',
key: "command_s",
defaultCommand: 'Command+Shift+S',
deleted: 0,
},{
id: 2,
name: "撤销",
command: 'Command+Shift+Z',
key: "command_z",
defaultCommand: 'Command+Shift+S',
deleted: 1,
},{
id: 3,
name: "刷新",
command: 'Command+Shift+R',
key: "command_r",
defaultCommand: 'Command+Shift+R',
deleted: 0,
}
])
const shortCommandRegister = ref<ShortCommandRegister| null>(null);
const setShortCmdInstanceDispatch = (shortCmd:ShortCommandRegister) => {
shortCommandRegister.value = shortCmd;
}
const getShortCmdInstanceDispatch = () => {
return shortCommandRegister.value;
}
const currentActiveShortCmd = ref<Partial<ShortCommand>>({});
const commandKeyMap = computed(() => {
const map = new Map<string, string>()
commandList.value.forEach(item => {
map.set(item.key, item.command)
})
return map
})
const setCurrentActiveShortCmdDispatch = (cmd: ShortCommand) => {
currentActiveShortCmd.value = cloneDeep(cmd);
// 移除快捷键
if(cmd.key){
console.log(shortCommandRegister);
console.log(shortCommandRegister)
shortCommandRegister.value[cmd.key].removeShortCommand()
}
currentActiveShortCmd.value.command = '';
}
const restoreShortCmdDispatch = (cmd)=>{
currentActiveShortCmd.value = {};
const shortcmd = commandList.value.find(item => item.id === cmd.id);
// 恢复默认快捷键
if(shortcmd){
shortcmd.command = cmd.defaultCommand;
}
}
const updateCurrentActiveShortCmdValueDispatch = (cmd)=>{
currentActiveShortCmd.value.command = cmd;
}
const saveShortCmdDispatch = (cmd: ShortCommand)=>{
const shortcmd = commandList.value.find(item => item.id === cmd.id);
if(shortcmd){
shortcmd.command = cmd.command;
// 如果是已经删除的恢复
shortcmd.deleted = 0;
shortCommandRegister.value[shortcmd.key].updateShortCommand(cmd.command)
}
setCurrentActiveShortCmdDispatch({
name: '',
key: '',
command: '',
deleted: 0
});
// TODO: 发送请求 保存
}
/**
* 删除快捷键
* @param cmd
*/
const deleteShortCmdDispatch = (cmd)=>{
const shortcmd = commandList.value.find(item => item.id === cmd.id);
if(shortcmd){
shortcmd.deleted = 1;
// shortCommandRegister.value[shortcmd.key].removeShortCommand()
}
setCurrentActiveShortCmdDispatch({
name: '',
key: '',
command: '',
deleted: 0
})
// TODO: 保存
}
return {
commandList,
commandKeyMap,
currentActiveShortCmd,
shortCommandRegister,
setCurrentActiveShortCmdDispatch,
restoreShortCmdDispatch,
saveShortCmdDispatch,
updateCurrentActiveShortCmdValueDispatch,
deleteShortCmdDispatch,
setShortCmdInstanceDispatch,
getShortCmdInstanceDispatch
}
})
<template>
<div class="short-cmd">
<div class="short-cmd-list">
<div v-for="(item, index) in shortCmdList" :key="index" class="short-cmd-item">
<div class="short-cmd-item-name">{{ item.name }}</div>
<div class="short-cmd-item-command">
<div class="short-cmd-item-command-text" @click="handleModifyShortCmd(item)">
<template v-if="currentActiveShortCmd?.id === item.id">
<span v-if="!currentActiveShortCmd.command">
键入快捷键
</span>
<span v-else-if="currentActiveShortCmd.command">
{{ currentActiveShortCmd.command }}
</span>
</template>
<span v-else>
{{ item.deleted ? "记录快捷键" : (item.command ? item.command : item.defaultCommand)}}
</span>
</div>
<template v-if="currentActiveShortCmd?.id === item.id">
<ParkIcon class="park-icon" icon="park-refresh"
@click="handleCancelModify(currentActiveShortCmd)"></ParkIcon>
<ParkIcon class="park-icon park-icon-icon" icon="park-check"
v-if="currentActiveShortCmd.command" @click="handeSaveShortCmd()"></ParkIcon>
</template>
<ParkIcon class="park-icon" icon="park-close-small"
v-if="currentActiveShortCmd?.id !== item.id && !item.deleted"
@click="handleDeleteShortCmd(item)"></ParkIcon>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { storeToRefs } from 'pinia';
import { useShortCmdStore } from '../../store/shortCmd.store';
import { computed, inject, onMounted, onUnmounted, ref } from 'vue';
import ParkIcon from '../ParkIcon.vue';
import { ShortCommand } from '../../types/shortCmd';
import { ProvideKeys } from '../../const/provide.connst';
defineOptions({
name: 'ShortCmd'
})
const shortStore = useShortCmdStore()
const shortCmdList = computed(() => shortStore.commandList);
const currentActiveShortCmd = computed(() => shortStore.currentActiveShortCmd);
const {
setCurrentActiveShortCmdDispatch,
restoreShortCmdDispatch,
saveShortCmdDispatch,
updateCurrentActiveShortCmdValueDispatch,
deleteShortCmdDispatch,
setShortCmdInstanceDispatch
} = shortStore;
setShortCmdInstanceDispatch(inject(ProvideKeys.shortCommand))
const handleModifyShortCmd = (item) => {
console.log(item)
setCurrentActiveShortCmdDispatch(item);
isListening.value = true;
}
const handleDeleteShortCmd = (item) => {
deleteShortCmdDispatch(item)
}
const isListening = ref(false);
const handleCancelModify = (item) => {
restoreShortCmdDispatch(item)
isListening.value = false;
}
const handleKeydown = (event) => {
if (isListening.value && currentActiveShortCmd.value) {
let command = [];
if (event.metaKey) command.push('Command');
if (event.ctrlKey) command.push('Ctrl');
if (event.altKey) command.push('Alt');
if (event.shiftKey) command.push('Shift');
let key = event.key.toUpperCase();
if (!['SHIFT', 'CONTROL', 'ALT', 'META'].includes(key)) {
command.push(key);
}
const commandString = command.join('+');
updateCurrentActiveShortCmdValueDispatch(commandString);
// isListening.value = false;
}
}
const handeSaveShortCmd = () => {
saveShortCmdDispatch(currentActiveShortCmd.value as ShortCommand)
}
onMounted(() => {
window.addEventListener('keydown', handleKeydown);
});
onUnmounted(() => {
window.removeEventListener('keydown', handleKeydown);
});
</script>
<style lang="less" scoped>
.short-cmd {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.short-cmd-list {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.short-cmd-item {
padding: 10px 0;
display: flex;
align-items: center;
&-name {
font-size: 14px;
font-weight: 600;
color: #333;
width: 60px;
text-align: right;
margin-right: 10px;
}
&-command {
font-size: 12px;
color: #666;
display: flex;
align-items: center;
width: 340px;
&-text {
width: 260px;
border: 1px solid #ccc;
padding: 2px 5px;
border-radius: 5px;
margin-right: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.park-icon {
margin-left: 5px;
cursor: pointer;
margin-right: 10px;
&-icon {
color: rgb(var(--primary-6));
}
}
}
}
}
</style>