feat: 快捷键整体重构优化

This commit is contained in:
alger
2026-03-04 20:28:38 +08:00
parent 36917a979d
commit 19092647d1
16 changed files with 2168 additions and 663 deletions
File diff suppressed because it is too large Load Diff
-4
View File
@@ -11,7 +11,6 @@ import pinia from '@/store';
import App from './App.vue';
import directives from './directive';
import { initAppShortcuts } from './utils/appShortcuts';
const app = createApp(App);
@@ -23,6 +22,3 @@ app.use(pinia);
app.use(router);
app.use(i18n as any);
app.mount('#app');
// 初始化应用内快捷键
initAppShortcuts();
+108 -142
View File
@@ -3,68 +3,61 @@ import { onMounted, onUnmounted } from 'vue';
import i18n from '@/../i18n/renderer';
import { usePlayerStore, useSettingsStore } from '@/store';
import {
hasShortcutAction,
normalizeShortcutAccelerator,
normalizeShortcutsConfig,
type ShortcutAction,
shortcutActionOrder,
type ShortcutsConfig
} from '../../shared/shortcuts';
import { isElectron } from '.';
import { isEditableTarget, keyboardEventToAccelerator } from './shortcutKeyboard';
import { showShortcutToast } from './shortcutToast';
// 添加一个简单的防抖机制
let actionTimeout: NodeJS.Timeout | null = null;
const ACTION_DELAY = 300; // 毫秒
const ACTION_DELAY = 260;
// 添加一个操作锁,记录最后一次操作的时间和动作
let lastActionInfo = {
action: '',
timestamp: 0
};
interface ShortcutConfig {
key: string;
enabled: boolean;
scope: 'global' | 'app';
}
interface ShortcutsConfig {
[key: string]: ShortcutConfig;
}
const actionTimestamps = new Map<ShortcutAction, number>();
const { t } = i18n.global;
// 全局存储快捷键配置
let appShortcuts: ShortcutsConfig = {};
let appShortcuts: ShortcutsConfig = normalizeShortcutsConfig(null);
let appShortcutsSuspended = false;
let appShortcutsInitialized = false;
const onGlobalShortcut = (_event: unknown, action: string) => {
if (!hasShortcutAction(action)) {
return;
}
void handleShortcutAction(action);
};
const onUpdateAppShortcuts = (_event: unknown, shortcuts: unknown) => {
updateAppShortcuts(shortcuts);
};
function shouldSkipAction(action: ShortcutAction): boolean {
const now = Date.now();
const lastTimestamp = actionTimestamps.get(action) ?? 0;
if (now - lastTimestamp < ACTION_DELAY) {
return true;
}
actionTimestamps.set(action, now);
return false;
}
/**
* 处理快捷键动作
* @param action 快捷键动作
*/
export async function handleShortcutAction(action: string) {
const now = Date.now();
// 如果存在未完成的动作,则忽略当前请求
if (actionTimeout) {
console.log('[AppShortcuts] 忽略快速连续的动作请求:', action);
export async function handleShortcutAction(action: ShortcutAction) {
if (shouldSkipAction(action)) {
return;
}
// 检查是否是同一个动作的重复触发(300ms内)
if (lastActionInfo.action === action && now - lastActionInfo.timestamp < ACTION_DELAY) {
console.log(
`[AppShortcuts] 忽略重复的 ${action} 动作,距上次仅 ${now - lastActionInfo.timestamp}ms`
);
return;
}
// 更新最后一次操作信息
lastActionInfo = {
action,
timestamp: now
};
// 设置防抖锁
actionTimeout = setTimeout(() => {
actionTimeout = null;
}, ACTION_DELAY);
console.log(`[AppShortcuts] 执行动作: ${action}, 时间戳: ${now}`);
const playerStore = usePlayerStore();
const settingsStore = useSettingsStore();
@@ -81,11 +74,9 @@ export async function handleShortcutAction(action: string) {
if (playerStore.play) {
await playerStore.handlePause();
showToast(t('player.playBar.pause'), 'ri-pause-circle-line');
} else {
if (playerStore.playMusic?.id) {
await playerStore.setPlay({ ...playerStore.playMusic });
showToast(t('player.playBar.play'), 'ri-play-circle-line');
}
} else if (playerStore.playMusic?.id) {
await playerStore.setPlay({ ...playerStore.playMusic });
showToast(t('player.playBar.play'), 'ri-play-circle-line');
}
break;
case 'prevPlay':
@@ -115,16 +106,19 @@ export async function handleShortcutAction(action: string) {
}
break;
case 'toggleFavorite': {
const isFavorite = playerStore.favoriteList.includes(Number(playerStore.playMusic.id));
const numericId = Number(playerStore.playMusic.id);
console.log(`[AppShortcuts] toggleFavorite 当前状态: ${isFavorite}, ID: ${numericId}`);
if (isFavorite) {
playerStore.removeFromFavorite(numericId);
console.log(`[AppShortcuts] 已从收藏中移除: ${numericId}`);
} else {
playerStore.addToFavorite(numericId);
console.log(`[AppShortcuts] 已添加到收藏: ${numericId}`);
if (!playerStore.playMusic?.id) {
return;
}
const currentSongId = Number(playerStore.playMusic.id);
const isFavorite = playerStore.favoriteList.includes(currentSongId);
if (isFavorite) {
playerStore.removeFromFavorite(currentSongId);
} else {
playerStore.addToFavorite(currentSongId);
}
showToast(
isFavorite
? t('player.playBar.unFavorite', { name: playerStore.playMusic.name })
@@ -134,119 +128,91 @@ export async function handleShortcutAction(action: string) {
break;
}
default:
console.log('未知的快捷键动作:', action);
break;
}
} catch (error) {
console.error(`执行快捷键动作 ${action} 时出错:`, error);
} finally {
// 确保在出错时也能清除超时
clearTimeout(actionTimeout);
actionTimeout = null;
console.log(
`[AppShortcuts] 动作完成: ${action}, 时间戳: ${Date.now()}, 耗时: ${Date.now() - now}ms`
);
console.error(`[AppShortcuts] 执行动作失败: ${action}`, error);
}
}
/**
* 检查按键是否匹配快捷键
* @param e KeyboardEvent
* @param shortcutKey 快捷键字符串
* @returns 是否匹配
*/
function matchShortcut(e: KeyboardEvent, shortcutKey: string): boolean {
const keys = shortcutKey.split('+');
const pressedKey = e.key.length === 1 ? e.key.toUpperCase() : e.key;
// 检查修饰键
const hasCommandOrControl = keys.includes('CommandOrControl');
const hasAlt = keys.includes('Alt');
const hasShift = keys.includes('Shift');
// 检查主键
let mainKey = keys.find((k) => !['CommandOrControl', 'Alt', 'Shift'].includes(k));
if (!mainKey) return false;
// 处理特殊键
if (mainKey === 'Left' && pressedKey === 'ArrowLeft') mainKey = 'ArrowLeft';
if (mainKey === 'Right' && pressedKey === 'ArrowRight') mainKey = 'ArrowRight';
if (mainKey === 'Up' && pressedKey === 'ArrowUp') mainKey = 'ArrowUp';
if (mainKey === 'Down' && pressedKey === 'ArrowDown') mainKey = 'ArrowDown';
// 检查是否所有条件都匹配
return (
hasCommandOrControl === (e.ctrlKey || e.metaKey) &&
hasAlt === e.altKey &&
hasShift === e.shiftKey &&
mainKey === pressedKey
);
}
/**
* 全局键盘事件处理函数
* @param e KeyboardEvent
*/
function handleKeyDown(e: KeyboardEvent) {
// 如果在输入框中则不处理快捷键
if (['INPUT', 'TEXTAREA'].includes((e.target as HTMLElement).tagName)) {
function handleKeyDown(event: KeyboardEvent) {
if (appShortcutsSuspended) {
return;
}
Object.entries(appShortcuts).forEach(([action, config]) => {
if (config.enabled && config.scope === 'app' && matchShortcut(e, config.key)) {
e.preventDefault();
handleShortcutAction(action);
if (isEditableTarget(event.target)) {
return;
}
const accelerator = keyboardEventToAccelerator(event);
if (!accelerator) {
return;
}
for (const action of shortcutActionOrder) {
const config = appShortcuts[action];
if (!config.enabled || config.scope !== 'app') {
continue;
}
});
const shortcutKey = normalizeShortcutAccelerator(config.key);
if (!shortcutKey || shortcutKey !== accelerator) {
continue;
}
event.preventDefault();
void handleShortcutAction(action);
break;
}
}
/**
* 更新应用内快捷键
* @param shortcuts 快捷键配置
*/
export function updateAppShortcuts(shortcuts: ShortcutsConfig) {
appShortcuts = shortcuts;
export function updateAppShortcuts(shortcuts: unknown) {
appShortcuts = normalizeShortcutsConfig(shortcuts);
}
export function setAppShortcutsSuspended(suspended: boolean) {
appShortcutsSuspended = suspended;
}
/**
* 初始化应用内快捷键
*/
export function initAppShortcuts() {
if (isElectron) {
// 监听全局快捷键事件
window.electron.ipcRenderer.on('global-shortcut', async (_, action: string) => {
handleShortcutAction(action);
});
// 监听应用内快捷键更新
window.electron.ipcRenderer.on('update-app-shortcuts', (_, shortcuts: ShortcutsConfig) => {
updateAppShortcuts(shortcuts);
});
// 获取初始快捷键配置
const storedShortcuts = window.electron.ipcRenderer.sendSync('get-store-value', 'shortcuts');
if (storedShortcuts) {
updateAppShortcuts(storedShortcuts);
}
// 添加键盘事件监听
document.addEventListener('keydown', handleKeyDown);
if (!isElectron || appShortcutsInitialized) {
return;
}
appShortcutsInitialized = true;
window.electron.ipcRenderer.on('global-shortcut', onGlobalShortcut);
window.electron.ipcRenderer.on('update-app-shortcuts', onUpdateAppShortcuts);
const storedShortcuts = window.electron.ipcRenderer.sendSync('get-store-value', 'shortcuts');
updateAppShortcuts(storedShortcuts);
document.addEventListener('keydown', handleKeyDown);
}
/**
* 清理应用内快捷键
*/
export function cleanupAppShortcuts() {
if (isElectron) {
// 移除全局事件监听
window.electron.ipcRenderer.removeAllListeners('global-shortcut');
window.electron.ipcRenderer.removeAllListeners('update-app-shortcuts');
// 移除键盘事件监听
document.removeEventListener('keydown', handleKeyDown);
if (!isElectron || !appShortcutsInitialized) {
return;
}
appShortcutsInitialized = false;
window.electron.ipcRenderer.removeListener('global-shortcut', onGlobalShortcut);
window.electron.ipcRenderer.removeListener('update-app-shortcuts', onUpdateAppShortcuts);
document.removeEventListener('keydown', handleKeyDown);
}
/**
+118
View File
@@ -0,0 +1,118 @@
import { normalizeShortcutAccelerator } from '../../shared/shortcuts';
const modifierOnlyKeys = new Set(['Control', 'Meta', 'Alt', 'Shift']);
const keyAliases: Record<string, string> = {
ArrowLeft: 'Left',
ArrowRight: 'Right',
ArrowUp: 'Up',
ArrowDown: 'Down',
Escape: 'Escape',
Enter: 'Enter',
Tab: 'Tab',
Backspace: 'Backspace',
Delete: 'Delete',
Insert: 'Insert',
Home: 'Home',
End: 'End',
PageUp: 'PageUp',
PageDown: 'PageDown',
' ': 'Space',
Spacebar: 'Space',
MediaPlayPause: 'MediaPlayPause',
MediaNextTrack: 'MediaNextTrack',
MediaPreviousTrack: 'MediaPreviousTrack',
MediaStop: 'MediaStop'
};
function resolveMainKey(event: KeyboardEvent): string | null {
if (event.isComposing || modifierOnlyKeys.has(event.key)) {
return null;
}
const { code, key } = event;
if (/^Key[A-Z]$/.test(code)) {
return code.slice(3);
}
if (/^Digit[0-9]$/.test(code)) {
return code.slice(5);
}
if (/^Numpad[0-9]$/.test(code)) {
return code.slice(6);
}
if (/^F([1-9]|1\d|2[0-4])$/.test(code)) {
return code;
}
if (code === 'NumpadAdd') {
return 'Plus';
}
if (code === 'NumpadSubtract' || code === 'Minus') {
return 'Minus';
}
if (code === 'Equal' && event.shiftKey) {
return 'Plus';
}
if (key === '+' || key === '=') {
return 'Plus';
}
if (key === '-' || key === '_') {
return 'Minus';
}
if (keyAliases[key]) {
return keyAliases[key];
}
if (/^[A-Za-z0-9]$/.test(key)) {
return key.toUpperCase();
}
return null;
}
export function keyboardEventToAccelerator(event: KeyboardEvent): string | null {
const mainKey = resolveMainKey(event);
if (!mainKey) {
return null;
}
const parts: string[] = [];
if (event.ctrlKey || event.metaKey) {
parts.push('CommandOrControl');
}
if (event.altKey) {
parts.push('Alt');
}
if (event.shiftKey) {
parts.push('Shift');
}
return normalizeShortcutAccelerator([...parts, mainKey].join('+'));
}
export function isEditableTarget(target: EventTarget | null): boolean {
if (!(target instanceof HTMLElement)) {
return false;
}
if (target.isContentEditable) {
return true;
}
const tagName = target.tagName;
if (tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT') {
return true;
}
return Boolean(target.closest('[contenteditable="true"]'));
}