如何封装一个后台管理系统快捷键功能

1. 背景

常用的后台管理,需要快捷键进行快捷操作,提高用户的使用便利。接下来说明如何实现快捷键功能,需要支持的功能有:

  • 支持单个和批量注册或者取消快捷键
  • 支持组合键(如 Ctrl+S, Ctrl+Shift+S 等)
  • 支持 Mac 和 Windows 按键映射
  • 支持事件阻止(preventDefault)和事件冒泡阻止(stopPropagation)
  • 支持通配符删除所有快捷键

2. 整体实现

2.1 实现原理

通过监听 keydown 事件,与注册的事件进行对比,如果存在则执行。

完整流程:

2.2 代码实现

2.2.1 注册

js 复制代码
const shortcuts = []
register(key, command, options = { prevent: true, stop: true }) {
    const shortcut = { ...this.parseKey(key), command, ...options };
    this.shortcuts.push(shortcut);
    return shortcut;
}

// 解析修饰键
parseKey(keyString) {
    const keys = keyString.toLowerCase().split("+");
    return {
        key: keys[keys.length - 1],
        ctrlKey: keys.includes("ctrl"),
        metaKey: keys.includes("meta"),
        shiftKey: keys.includes("shift"),
        altKey: keys.includes("alt"),
    };
}

注意: 这样定义是为了识别组合键,方便后期进行对比,例如:ctrl+s,在键盘事件event对象中是key为's',修饰键盘ctrl为true,

json 复制代码
ctrlKey: true
altKey: false
metaKey: false
shiftKey: false

code: "KeyS"
key: "s"
keyCode: 83

对象数据结构如下:

js 复制代码
{
    key: string,          // 主键
    ctrlKey: boolean,     // 是否按下 Ctrl
    metaKey: boolean,     // 是否按下 Command
    shiftKey: boolean,    // 是否按下 Shift
    altKey: boolean,      // 是否按下 Alt
    command: Function,    // 回调函数
    prevent: boolean,     // 是否阻止默认行为
    stop: boolean        // 是否阻止事件冒泡
}

2.2.2 监听事件

使用keydown事件进行监听按键,将按键与已注册进行对比,如果有则进行执行

js 复制代码
window.addEventListener("keydown", (e) => {
    const matchingShortcuts = this.findMatchingShortcut(e);
    matchingShortcuts.forEach((shortcut) => {
        shortcut.prevent && e.preventDefault();
        shortcut.stop && e.stopPropagation();
        shortcut.command(e);
    });
});

this.modifierKeys = ['ctrlKey', 'metaKey', 'shiftKey', 'altKey'];

// 匹配按键
findMatchingShortcut(event) {
    return this.shortcuts.filter(shortcut => {
        const preciseMatching = this.modifierKeys.every(key => shortcut[key] === event[key]) &&
            shortcut.key === this.convertMacKey(event.key).toLowerCase();

        const anyMatching = shortcut.key === '*';

        return preciseMatching || anyMatching;
    });
}
// mac中按键进行转换
convertMacKey(key) {
    return (
        {
            [this.KEYBOARD.CONTROL]: this.KEYBOARD.CTRL,
            [this.KEYBOARD.META]: this.KEYBOARD.COMMAND,
        }[key] || key
    );
}

2.2.3 取消

  • 如果通配符删除所有
  • 如果只有key则只匹配key
  • 有key和command则两者进行匹配
js 复制代码
cancel(key, command) {
  if (key === '*') {
      this.shortcuts.length = 0;
      console.log('打印***shortcuts', this.shortcuts);
      return;
  }

  const normalShortcut = this.parseKey(key);
  const matchingShortcuts = this.findMatchingShortcut(normalShortcut);

  for (let i = this.shortcuts.length - 1; i >= 0; i--) {
      if (matchingShortcuts.includes(this.shortcuts[i]) &&
          (!command || this.shortcuts[i].command === command)) {
          this.shortcuts.splice(i, 1);
      }
  }

}

2.2.4 添加一个批量注册和取消

批量注册需要以对象形式进行注册或者取消

javascript 复制代码
batchCancel(array) {
  if (Array.isArray(array)) {
      array.forEach(shortcut => this.cancel(shortcut.key, shortcut.command));
  }
}

batchRegister(array) {
  if (Array.isArray(array)) {
      array.forEach(shortcut => this.register(shortcut.key, shortcut.command, shortcut.options));
  }
}

3. 使用案例

3.1 初始化

ini 复制代码
const shortcutManager = new ShortcutManager();

3.2 注册快捷键

3.2.1 单个注册

javascript 复制代码
// 方式一:使用参数形式
shortcutManager.register(
    "ctrl+s", 
    (e) => {
        console.log('保存操作');
    },
    { prevent: true, stop: true }  // 可选参数
);

3.2.2 批量注册

javascript 复制代码
shortcutManager.batchRegister([
    {
        key: "ctrl+shift+s",
        command: (e) => {
            console.log('另存为操作');
        }
    },
    {
        key: "esc",
        command: (e) => {
            console.log('退出操作');
        }
    }
]);

3.3 取消快捷键

3.3.1 取消单个快捷键

arduino 复制代码
// 取消特定快捷键
shortcutManager.cancel('ctrl+s');

// 取消所有快捷键
shortcutManager.cancel('*');

3.3.2 批量取消快捷键

php 复制代码
shortcutManager.batchCancel([
    { key: 'ctrl+s' },
    { key: 'esc' }
]);

3.4. 获取当前注册的所有快捷键

ini 复制代码
const shortcuts = shortcutManager.getShortcuts();

4. 配置选项

4.1 register 方法参数

参数 类型 默认值 说明
key string - 快捷键组合,如 "ctrl+s"
command function - 快捷键触发时执行的函数
prevent boolean true 是否阻止默认行为
stop boolean true 是否阻止事件冒泡

4.2 注意事项

  1. 快捷键字符串不区分大小写,如 "ctrl+s" 和 "CTRL+S" 等效
  2. 组合键使用 "+" 连接,如 "ctrl+shift+s"
  3. 注册快捷键时,建议使用箭头函数来避免立即执行:
c 复制代码
// 正确方式
shortcutManager.register('ctrl+s', () => saveFunction('param'));

// 错误方式(会立即执行)
shortcutManager.register('ctrl+s', saveFunction('param'));

5. 完整代码

kotlin 复制代码
class ShortcutManager {
  constructor() {
      this.shortcuts = [];
      this.modifierKeys = ['ctrlKey', 'metaKey', 'shiftKey', 'altKey'];
      this.isInitialized = false;
      this.KEYBOARD = {
          CTRL: "Ctrl",
          CONTROL: "Control",
          META: "Meta",
          COMMAND: "Command",
      };
      this.initialize();
  }

  initialize() {
      if (this.isInitialized) return;
      window.addEventListener("keydown", (e) => {
          const matchingShortcuts = this.findMatchingShortcut(e);
          matchingShortcuts.forEach((shortcut) => {
              shortcut.prevent && e.preventDefault();
              shortcut.stop && e.stopPropagation();
              shortcut.command(e);
          });
      });
      this.isInitialized = true;
      console.log('快捷键管理器已初始化');
  }

  findMatchingShortcut(event) {
      return this.shortcuts.filter(shortcut => {
          const preciseMatching = this.modifierKeys.every(key => shortcut[key] === event[key]) &&
              shortcut.key === this.convertMacKey(event.key).toLowerCase();
          const anyMatching = shortcut.key === '*';
          return preciseMatching || anyMatching;
      });
  }

  convertMacKey(key) {
      return (
          {
              [this.KEYBOARD.CONTROL]: this.KEYBOARD.CTRL,
              [this.KEYBOARD.META]: this.KEYBOARD.COMMAND,
          }[key] || key
      );
  }

  parseKey(keyString) {
      const keys = keyString.toLowerCase().split("+");
      return {
          key: keys[keys.length - 1],
          ctrlKey: keys.includes("ctrl"),
          metaKey: keys.includes("meta"),
          shiftKey: keys.includes("shift"),
          altKey: keys.includes("alt"),
      };
  }

  register(key, command, options = { prevent: true, stop: true }) {
      const shortcut = { ...this.parseKey(key), command, ...options };
      this.shortcuts.push(shortcut);
      return shortcut;
  }

  batchRegister(array) {
      if (Array.isArray(array)) {
          array.forEach(shortcut => this.register(shortcut.key, shortcut.command, shortcut.options));
      }
  }

  cancel(key, command) {
      if (key === '*') {
          this.shortcuts.length = 0;
          return;
      }
      const normalShortcut = this.parseKey(key);
      const matchingShortcuts = this.findMatchingShortcut(normalShortcut);
      for (let i = this.shortcuts.length - 1; i >= 0; i--) {
          if (matchingShortcuts.includes(this.shortcuts[i]) &&
              (!command || this.shortcuts[i].command === command)) {
              this.shortcuts.splice(i, 1);
          }
      }
  }

  batchCancel(array) {
      if (Array.isArray(array)) {
          array.forEach(shortcut => this.cancel(shortcut.key, shortcut.command));
      }
  }

  getShortcuts() {
      return [...this.shortcuts];
  }
}

6. 总结

最后总结一下整体流程:先注册存储:解析组合键(如ctrl+s)为结构体存入数组,监听keydown事件,捕获按键组合,对比事件对象与注册项的修饰键/主键,根据配置阻止默认行为/冒泡,触发回调 。 如有错误,请指正!

相关推荐
键指江湖22 分钟前
React 在组件间共享状态
前端·javascript·react.js
诸葛亮的芭蕉扇40 分钟前
D3路网图技术文档
前端·javascript·vue.js·microsoft
小离a_a42 分钟前
小程序css实现容器内 数据滚动 无缝衔接 点击暂停
前端·css·小程序
徐小夕1 小时前
花了2个月时间研究了市面上的4款开源表格组件,崩溃了,决定自己写一款
前端·javascript·react.js
by————组态2 小时前
低代码 Web 组态
前端·人工智能·物联网·低代码·数学建模·组态
拉不动的猪2 小时前
UniApp金融理财产品项目简单介绍
前端·javascript·面试
菜冬眠。2 小时前
uni-app/微信小程序接入腾讯位置服务地图选点插件
前端·微信小程序·uni-app
jayson.h2 小时前
pdf解密程序
java·前端·pdf
萌萌哒草头将军2 小时前
😡😡😡早知道有这两个 VueRouter 增强插件,我还加什么班!🚀🚀🚀
前端·vue.js·vue-router
苏卫苏卫苏卫2 小时前
【Vue】案例——To do list:
开发语言·前端·javascript·vue.js·笔记·list