type KeyboardCallback = (e: KeyboardEvent) => void export class KeyboardManager { private shortcuts: Map = new Map() private pressedKeys: Set = new Set() private enabled: boolean = false constructor() { this.handleKeyDown = this.handleKeyDown.bind(this) this.handleKeyUp = this.handleKeyUp.bind(this) } /** * 启用快捷键监听 */ enable(): void { if (!this.enabled) { window.addEventListener('keydown', this.handleKeyDown) window.addEventListener('keyup', this.handleKeyUp) this.enabled = true } } /** * 禁用快捷键监听 */ disable(): void { if (this.enabled) { window.removeEventListener('keydown', this.handleKeyDown) window.removeEventListener('keyup', this.handleKeyUp) this.enabled = false this.pressedKeys.clear() } } /** * 绑定快捷键 */ bind(shortcut: string, callback: KeyboardCallback): void { this.shortcuts.set(this.normalizeShortcut(shortcut), callback) } /** * 解绑快捷键 */ unbind(shortcut: string): void { this.shortcuts.delete(this.normalizeShortcut(shortcut)) } /** * 清除所有快捷键绑定 */ clear(): void { this.shortcuts.clear() this.pressedKeys.clear() } private handleKeyDown(e: KeyboardEvent): void { // 忽略在输入框中的按键 if (this.shouldIgnoreInput(e)) { return } const key = this.normalizeKey(e.key.toLowerCase()) this.pressedKeys.add(key) const currentShortcut = Array.from(this.pressedKeys).sort().join('+') const callback = this.shortcuts.get(currentShortcut) if (callback) { e.preventDefault() e.stopPropagation() callback(e) } } private handleKeyUp(e: KeyboardEvent): void { const key = this.normalizeKey(e.key.toLowerCase()) this.pressedKeys.delete(key) } private normalizeKey(key: string): string { const keyMap: Record = { 'control': 'ctrl', 'command': 'cmd', 'meta': 'cmd', 'escape': 'esc', ' ': 'space', 'arrowup': 'up', 'arrowdown': 'down', 'arrowleft': 'left', 'arrowright': 'right', } return keyMap[key] || key } private normalizeShortcut(shortcut: string): string { return shortcut .toLowerCase() .split('+') .map(key => this.normalizeKey(key.trim())) .sort() .join('+') } private shouldIgnoreInput(e: KeyboardEvent): boolean { const element = e.target as HTMLElement return element.tagName === 'INPUT' || element.tagName === 'TEXTAREA' || element.tagName === 'SELECT' || element.isContentEditable } } // 导出单例实例 export const keyboardManager = new KeyboardManager()