在开发 Vue 3 应用时,经常需要为用户提供快捷键支持,以提高用户体验。快捷键不仅能够提升效率,而且可以让用户更好地与应用交互。在本篇文章中,我们将探讨如何在 Vue 3 中实现对 单个按键快捷键组合快捷键 的支持,并通过 自定义指令服务函数 进行管理。

1. 快捷键功能的需求

在很多应用中,我们希望支持两种类型的快捷键:

  • 单个按键快捷键:比如 Esc 键用于关闭弹窗,Enter 键用于提交表单。

  • 组合按键快捷键:如 Ctrl+S 用于保存,Ctrl+P 用于打印。

这两种快捷键的使用场景和处理方式有所不同,因此我们需要分别进行处理,但又要保证它们的实现足够灵活和可扩展。

2. 快捷键处理逻辑

我们将在 Vue 3 中实现一个 shortcutService,这个服务负责管理快捷键的注册与注销,确保键盘事件能够被正确捕获并执行相应的动作。

2.1 处理按键事件

我们将监听 keydown 事件,区分单个按键和组合按键,并执行对应的操作。

// /src/services/shortcutService.ts
import { ElMessage } from 'element-plus'

export interface ShortcutConfig {
  shortcut: string | string[]  // 可以是单个快捷键字符串,或快捷键数组
  disabled?: boolean
  run: () => void
}

const shortcuts = new Map<string, ShortcutConfig[]>() // 存储快捷键及其处理函数

// 处理按下的快捷键
function handleKeydown(e: KeyboardEvent): void {
  const keys: string[] = []

  if (e.ctrlKey || e.metaKey) keys.push('Ctrl')
  if (e.altKey) keys.push('Alt')
  if (e.shiftKey) keys.push('Shift')

  if (e.key.length === 1) {
    keys.push(e.key.toUpperCase())
  } else if (!['Control', 'Alt', 'Shift', 'Meta'].includes(e.key)) {
    keys.push(e.key)
  }

  const shortcutKey = keys.join('+')

  // 处理单个按键快捷键
  if (e.key.length === 1 && shortcuts.has(e.key)) {
    const handlers = shortcuts.get(e.key)
    handlers?.forEach((handler) => {
      if (handler.disabled) {
        ElMessage.warning('当前操作不可用')
        return
      }
      handler.run()
    })
  }

  // 处理组合快捷键
  if (shortcuts.has(shortcutKey)) {
    const handlers = shortcuts.get(shortcutKey)
    handlers?.forEach((handler) => {
      if (handler.disabled) {
        ElMessage.warning('当前操作不可用')
        return
      }
      handler.run()
    })
  }
}

// 注册快捷键
export function registerShortcut(shortcutsArray: (string | string[])[], config: Omit<ShortcutConfig, 'shortcut'>): void {
  shortcutsArray.forEach((shortcut) => {
    const shortcutKeys = Array.isArray(shortcut) ? shortcut : [shortcut]
    shortcutKeys.forEach((shortcutKey) => {
      const currentHandlers = shortcuts.get(shortcutKey) || []
      currentHandlers.push({ ...config, shortcut: shortcutKey })
      shortcuts.set(shortcutKey, currentHandlers)
    })
  })
}

export function unregisterShortcut(shortcut: string): void {
  shortcuts.delete(shortcut)
}

document.addEventListener('keydown', handleKeydown)

在上面的代码中,我们定义了一个 shortcuts Map 来存储快捷键及其相应的处理函数。通过 handleKeydown 方法来监听和处理按下的键盘事件,并通过 registerShortcut 函数来注册快捷键。

2.2 注册与注销快捷键

我们提供了 registerShortcutunregisterShortcut 方法,用于注册和注销快捷键。

  • registerShortcut 支持注册单个按键或多个按键的快捷键。

  • unregisterShortcut 用于注销某个快捷键。

3. Vue 3 自定义指令

接下来,我们创建一个自定义指令 v-shortcut,让用户可以在模板中直接绑定快捷键,并关联事件。

3.1 自定义指令的实现

我们在 v-shortcut 指令中解析快捷键配置,并通过 registerShortcut 注册快捷键。

// /src/directives/shortcut.ts
import { Directive, DirectiveBinding } from 'vue'
import { registerShortcut, unregisterShortcut } from '../services/shortcutService'

function parseBinding(binding: DirectiveBinding) {
  if (Array.isArray(binding.value)) {
    return binding.value
  }
  return binding.value ? [binding.value] : []
}

export const vShortcut: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const configs = parseBinding(binding)
    
    configs.forEach(config => {
      if (!config?.shortcut) return
      const shortcutsArray = Array.isArray(config.shortcut) ? config.shortcut : [config.shortcut]
      registerShortcut(shortcutsArray, {
        run: () => el.click(),
        disabled: config.disabled
      })
    })
  },

  updated(el: HTMLElement, binding: DirectiveBinding) {
    const configs = parseBinding(binding)
    
    configs.forEach(config => {
      if (!config?.shortcut) return
      // Handle updates to shortcuts if necessary
    })
  },

  unmounted(el: HTMLElement, binding: DirectiveBinding) {
    const configs = parseBinding(binding)
    configs.forEach(config => {
      if (config?.shortcut) {
        const shortcutsArray = Array.isArray(config.shortcut) ? config.shortcut : [config.shortcut]
        shortcutsArray.forEach(shortcut => unregisterShortcut(shortcut))
      }
    })
  }
}

通过 v-shortcut 指令,用户可以在模板中将快捷键绑定到特定的 DOM 元素上。

3.2 使用指令

<template>
  <el-button
    v-shortcut="[{ shortcut: 'Escape', disabled: false }]"
    @click="handleSave"
  >
    按下 Esc 保存
  </el-button>

  <el-button
    v-shortcut="[{ shortcut: ['Ctrl+S', 'Ctrl+P'], disabled: false }]"
    @click="handleSave"
  >
    保存 (Ctrl+S 或 Ctrl+P)
  </el-button>
</template>

在上面的模板中,我们使用 v-shortcut 指令绑定了单个快捷键 Esc 和多个快捷键 Ctrl+SCtrl+P,并通过 @click 绑定了对应的事件处理。

4. 在组件中注册快捷键

除了通过指令,我们还可以在组件的 setup 函数中直接使用 registerShortcut 来注册快捷键。

<script setup>
import { registerShortcut, unregisterShortcut } from '@/services/shortcutService'

const shortcutEsc = 'Escape'
const shortcutEnter = 'Enter'

// 注册单个按键快捷键
registerShortcut([shortcutEsc], {
  run: () => {
    console.log('Esc 按下了!')
  }
})

registerShortcut([shortcutEnter], {
  run: () => {
    console.log('Enter 按下了!')
  }
})
</script>

5、常见按键的字符表示:

按键

e.key 返回值

描述

字母

A, B, C, ... Z

字母按键(区分大小写)

数字

0, 1, 2, ... 9

数字按键

功能键

F1, F2, ..., F12

功能键(F1 至 F12)

回车

Enter

回车键

空格

Space

空格键

方向键

ArrowUp, ArrowDown, ArrowLeft, ArrowRight

方向箭头键(上、下、左、右)

Esc

Escape

Esc

删除

Delete

删除键

控制键

Control, Shift, Alt, Meta

控制键(Ctrl、Shift、Alt、Meta)

插入

Insert

插入键

页面上/下

PageUp, PageDown

页面上/下键

结束

End

结束键

Home

Home

Home 键

Tab

Tab

Tab 键

Caps Lock

CapsLock

大写锁定键

Shift 键

Shift

Shift 键

Ctrl 键

Control

Ctrl 键(Windows 和 Linux 系统)

Alt 键

Alt

Alt 键(Windows 和 Linux 系统)

Meta 键

Meta

Meta 键(通常是 macOS 上的 Cmd 键)

上下文菜单

ContextMenu

上下文菜单键(通常是右键)

数字键盘

Numpad0, Numpad1, ..., Numpad9

数字键盘的数字按键

Num Lock

NumLock

数字锁定键(数字键盘的开关)

乘号

NumpadMultiply

数字键盘上的乘号键

加号

NumpadAdd

数字键盘上的加号键

减号

NumpadSubtract

数字键盘上的减号键

NumpadDecimal

数字键盘上的小数点键

除号

NumpadDivide

数字键盘上的除号键

回车(数字键盘)

NumpadEnter

数字键盘上的回车键

6. 总结

通过上述实现,我们成功地在 Vue 3 中支持了 单个按键快捷键多个按键组合快捷键 的处理。无论是通过自定义指令 v-shortcut 还是直接在组件中注册快捷键,我们都能灵活地管理快捷键,并根据需要启用或禁用快捷键。

这种方式不仅支持简单的按键事件,还能应对复杂的组合快捷键,为 Vue 应用提供了更加直观且高效的快捷键管理方案。


这篇文章将帮助你更好地理解如何在 Vue 3 中实现快捷键支持,并提供了灵活的实现方式以适应不同的需求。