import { ref, onMounted, onUnmounted } from 'vue' export interface KeyboardShortcut { key: string code: string description: string action: () => void preventDefault?: boolean ctrlKey?: boolean shiftKey?: boolean altKey?: boolean metaKey?: boolean } export interface KeyboardOptions { ignoreInputs?: boolean ignoreContentEditable?: boolean } export function useKeyboard(options: KeyboardOptions = {}) { const shortcuts = ref>(new Map()) const isEnabled = ref(true) const lastKeyPressed = ref('') const keySequence = ref([]) const defaultOptions: Required = { ignoreInputs: true, ignoreContentEditable: true, ...options } // 檢查是否應該忽略按鍵事件 const shouldIgnoreEvent = (event: KeyboardEvent): boolean => { if (!isEnabled.value) return true const target = event.target as HTMLElement // 檢查是否在輸入框中 if (defaultOptions.ignoreInputs) { if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) { return true } } // 檢查是否在可編輯元素中 if (defaultOptions.ignoreContentEditable) { if (target.contentEditable === 'true') { return true } } return false } // 生成快捷鍵的唯一標識符 const generateShortcutKey = (shortcut: Omit): string => { const modifiers = [] if (shortcut.ctrlKey) modifiers.push('ctrl') if (shortcut.shiftKey) modifiers.push('shift') if (shortcut.altKey) modifiers.push('alt') if (shortcut.metaKey) modifiers.push('meta') return [...modifiers, shortcut.code.toLowerCase()].join('+') } // 檢查事件是否匹配快捷鍵 const matchesShortcut = (event: KeyboardEvent, shortcut: KeyboardShortcut): boolean => { return ( event.code === shortcut.code && !!event.ctrlKey === !!shortcut.ctrlKey && !!event.shiftKey === !!shortcut.shiftKey && !!event.altKey === !!shortcut.altKey && !!event.metaKey === !!shortcut.metaKey ) } // 註冊快捷鍵 const register = (shortcut: KeyboardShortcut) => { const key = generateShortcutKey(shortcut) shortcuts.value.set(key, shortcut) } // 批量註冊快捷鍵 const registerMultiple = (shortcutList: KeyboardShortcut[]) => { shortcutList.forEach(shortcut => register(shortcut)) } // 取消註冊快捷鍵 const unregister = (code: string, modifiers?: { ctrlKey?: boolean shiftKey?: boolean altKey?: boolean metaKey?: boolean }) => { const key = generateShortcutKey({ code, key: '', ...modifiers }) shortcuts.value.delete(key) } // 清空所有快捷鍵 const clear = () => { shortcuts.value.clear() } // 啟用/禁用快捷鍵 const enable = () => { isEnabled.value = true } const disable = () => { isEnabled.value = false } const toggle = () => { isEnabled.value = !isEnabled.value } // 獲取所有已註冊的快捷鍵 const getShortcuts = () => { return Array.from(shortcuts.value.values()) } // 按鍵事件處理器 const handleKeydown = (event: KeyboardEvent) => { if (shouldIgnoreEvent(event)) return lastKeyPressed.value = event.code keySequence.value.push(event.code) // 限制序列長度 if (keySequence.value.length > 5) { keySequence.value.shift() } // 查找匹配的快捷鍵 for (const shortcut of shortcuts.value.values()) { if (matchesShortcut(event, shortcut)) { if (shortcut.preventDefault !== false) { event.preventDefault() } try { shortcut.action() } catch (error) { console.error('快捷鍵執行錯誤:', error) } break } } } // 常用快捷鍵預設集 const presets = { // 詞彙學習相關 vocabulary: [ { key: 'Space', code: 'Space', description: '播放/暫停音頻', action: () => {} }, { key: 'ArrowRight', code: 'ArrowRight', description: '下一個詞彙', action: () => {} }, { key: 'ArrowLeft', code: 'ArrowLeft', description: '上一個詞彙', action: () => {} }, { key: 'h', code: 'KeyH', description: '顯示/隱藏幫助', action: () => {} }, { key: 'a', code: 'KeyA', description: '切換自動播放', action: () => {} }, { key: 'r', code: 'KeyR', description: '重播音頻', action: () => {} } ] as KeyboardShortcut[], // 練習模式相關 practice: [ { key: 'Enter', code: 'Enter', description: '提交答案', action: () => {} }, { key: 'n', code: 'KeyN', description: '下一題', action: () => {} }, { key: 's', code: 'KeyS', description: '跳過題目', action: () => {} }, { key: 'Escape', code: 'Escape', description: '退出練習', action: () => {} } ] as KeyboardShortcut[], // 通用導航 navigation: [ { key: 'Escape', code: 'Escape', description: '返回上一頁', action: () => {} }, { key: 'f', code: 'KeyF', description: '全螢幕模式', action: () => {} }, { key: '/', code: 'Slash', description: '搜索', action: () => {} } ] as KeyboardShortcut[] } // 生命週期 onMounted(() => { document.addEventListener('keydown', handleKeydown) }) onUnmounted(() => { document.removeEventListener('keydown', handleKeydown) }) return { // 狀態 shortcuts, isEnabled, lastKeyPressed, keySequence, // 方法 register, registerMultiple, unregister, clear, enable, disable, toggle, getShortcuts, // 預設集 presets } }