mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-28 19:07:23 +08:00
@@ -197,6 +197,8 @@ export default {
|
|||||||
shortcutConflict: 'Shortcut Conflict',
|
shortcutConflict: 'Shortcut Conflict',
|
||||||
inputPlaceholder: 'Click to input shortcut',
|
inputPlaceholder: 'Click to input shortcut',
|
||||||
resetShortcuts: 'Reset',
|
resetShortcuts: 'Reset',
|
||||||
|
disableAll: 'Disable All',
|
||||||
|
enableAll: 'Enable All',
|
||||||
togglePlay: 'Play/Pause',
|
togglePlay: 'Play/Pause',
|
||||||
prevPlay: 'Previous',
|
prevPlay: 'Previous',
|
||||||
nextPlay: 'Next',
|
nextPlay: 'Next',
|
||||||
@@ -204,12 +206,18 @@ export default {
|
|||||||
volumeDown: 'Volume Down',
|
volumeDown: 'Volume Down',
|
||||||
toggleFavorite: 'Favorite/Unfavorite',
|
toggleFavorite: 'Favorite/Unfavorite',
|
||||||
toggleWindow: 'Show/Hide Window',
|
toggleWindow: 'Show/Hide Window',
|
||||||
|
scopeGlobal: 'Global',
|
||||||
|
scopeApp: 'App Only',
|
||||||
|
enabled: 'Enabled',
|
||||||
|
disabled: 'Disabled',
|
||||||
messages: {
|
messages: {
|
||||||
resetSuccess: 'Shortcuts reset successfully, please save',
|
resetSuccess: 'Shortcuts reset successfully, please save',
|
||||||
conflict: 'Shortcut conflict, please reset',
|
conflict: 'Shortcut conflict, please reset',
|
||||||
saveSuccess: 'Shortcuts saved successfully',
|
saveSuccess: 'Shortcuts saved successfully',
|
||||||
saveError: 'Failed to save shortcuts',
|
saveError: 'Failed to save shortcuts',
|
||||||
cancelEdit: 'Edit cancelled'
|
cancelEdit: 'Edit cancelled',
|
||||||
|
disableAll: 'All shortcuts disabled, please save to apply',
|
||||||
|
enableAll: 'All shortcuts enabled, please save to apply'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -197,6 +197,8 @@ export default {
|
|||||||
shortcutConflict: '快捷键冲突',
|
shortcutConflict: '快捷键冲突',
|
||||||
inputPlaceholder: '点击输入快捷键',
|
inputPlaceholder: '点击输入快捷键',
|
||||||
resetShortcuts: '恢复默认',
|
resetShortcuts: '恢复默认',
|
||||||
|
disableAll: '全部禁用',
|
||||||
|
enableAll: '全部启用',
|
||||||
togglePlay: '播放/暂停',
|
togglePlay: '播放/暂停',
|
||||||
prevPlay: '上一首',
|
prevPlay: '上一首',
|
||||||
nextPlay: '下一首',
|
nextPlay: '下一首',
|
||||||
@@ -204,12 +206,18 @@ export default {
|
|||||||
volumeDown: '音量减少',
|
volumeDown: '音量减少',
|
||||||
toggleFavorite: '收藏/取消收藏',
|
toggleFavorite: '收藏/取消收藏',
|
||||||
toggleWindow: '显示/隐藏窗口',
|
toggleWindow: '显示/隐藏窗口',
|
||||||
|
scopeGlobal: '全局',
|
||||||
|
scopeApp: '应用内',
|
||||||
|
enabled: '启用',
|
||||||
|
disabled: '禁用',
|
||||||
messages: {
|
messages: {
|
||||||
resetSuccess: '已恢复默认快捷键,请记得保存',
|
resetSuccess: '已恢复默认快捷键,请记得保存',
|
||||||
conflict: '存在冲突的快捷键,请重新设置',
|
conflict: '存在冲突的快捷键,请重新设置',
|
||||||
saveSuccess: '快捷键设置已保存',
|
saveSuccess: '快捷键设置已保存',
|
||||||
saveError: '保存快捷键失败,请重试',
|
saveError: '保存快捷键失败,请重试',
|
||||||
cancelEdit: '已取消修改'
|
cancelEdit: '已取消修改',
|
||||||
|
disableAll: '已禁用所有快捷键,请记得保存',
|
||||||
|
enableAll: '已启用所有快捷键,请记得保存'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,66 +7,93 @@ ipcMain.on('get-platform', (event) => {
|
|||||||
event.returnValue = process.platform;
|
event.returnValue = process.platform;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 定义快捷键配置接口
|
||||||
|
export interface ShortcutConfig {
|
||||||
|
key: string;
|
||||||
|
enabled: boolean;
|
||||||
|
scope: 'global' | 'app';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShortcutsConfig {
|
||||||
|
[key: string]: ShortcutConfig;
|
||||||
|
}
|
||||||
|
|
||||||
// 定义默认快捷键
|
// 定义默认快捷键
|
||||||
export const defaultShortcuts = {
|
export const defaultShortcuts: ShortcutsConfig = {
|
||||||
togglePlay: 'CommandOrControl+Alt+P',
|
togglePlay: { key: 'CommandOrControl+Alt+P', enabled: true, scope: 'global' },
|
||||||
prevPlay: 'CommandOrControl+Alt+Left',
|
prevPlay: { key: 'Alt+Left', enabled: true, scope: 'global' },
|
||||||
nextPlay: 'CommandOrControl+Alt+Right',
|
nextPlay: { key: 'Alt+Right', enabled: true, scope: 'global' },
|
||||||
volumeUp: 'CommandOrControl+Alt+Up',
|
volumeUp: { key: 'Alt+Up', enabled: true, scope: 'app' },
|
||||||
volumeDown: 'CommandOrControl+Alt+Down',
|
volumeDown: { key: 'Alt+Down', enabled: true, scope: 'app' },
|
||||||
toggleFavorite: 'CommandOrControl+Alt+L',
|
toggleFavorite: { key: 'CommandOrControl+Alt+L', enabled: true, scope: 'app' },
|
||||||
toggleWindow: 'CommandOrControl+Alt+Shift+M'
|
toggleWindow: { key: 'CommandOrControl+Alt+Shift+M', enabled: true, scope: 'global' }
|
||||||
};
|
};
|
||||||
|
|
||||||
let mainWindowRef: Electron.BrowserWindow | null = null;
|
let mainWindowRef: Electron.BrowserWindow | null = null;
|
||||||
|
|
||||||
// 注册快捷键
|
// 注册快捷键
|
||||||
export function registerShortcuts(mainWindow: Electron.BrowserWindow) {
|
export function registerShortcuts(
|
||||||
|
mainWindow: Electron.BrowserWindow,
|
||||||
|
shortcutsConfig?: ShortcutsConfig
|
||||||
|
) {
|
||||||
mainWindowRef = mainWindow;
|
mainWindowRef = mainWindow;
|
||||||
const store = getStore();
|
const store = getStore();
|
||||||
const shortcuts = store.get('shortcuts');
|
const shortcuts =
|
||||||
|
shortcutsConfig || (store.get('shortcuts') as ShortcutsConfig) || defaultShortcuts;
|
||||||
|
|
||||||
// 注销所有已注册的快捷键
|
// 注销所有已注册的快捷键
|
||||||
globalShortcut.unregisterAll();
|
globalShortcut.unregisterAll();
|
||||||
|
|
||||||
// 显示/隐藏主窗口
|
// 对旧格式数据进行兼容处理
|
||||||
globalShortcut.register(shortcuts.toggleWindow, () => {
|
if (shortcuts && typeof shortcuts.togglePlay === 'string') {
|
||||||
if (mainWindow.isVisible()) {
|
// 将 shortcuts 强制转换为 unknown,再转为 Record<string, string>
|
||||||
mainWindow.hide();
|
const oldShortcuts = { ...shortcuts } as unknown as Record<string, string>;
|
||||||
} else {
|
const newShortcuts: ShortcutsConfig = {};
|
||||||
mainWindow.show();
|
|
||||||
|
Object.entries(oldShortcuts).forEach(([key, value]) => {
|
||||||
|
newShortcuts[key] = {
|
||||||
|
key: value,
|
||||||
|
enabled: true,
|
||||||
|
scope: ['volumeUp', 'volumeDown', 'toggleFavorite'].includes(key) ? 'app' : 'global'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
store.set('shortcuts', newShortcuts);
|
||||||
|
registerShortcuts(mainWindow, newShortcuts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册全局快捷键
|
||||||
|
Object.entries(shortcuts).forEach(([action, config]) => {
|
||||||
|
const { key, enabled, scope } = config as ShortcutConfig;
|
||||||
|
|
||||||
|
// 只注册启用且作用域为全局的快捷键
|
||||||
|
if (!enabled || scope !== 'global') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (action) {
|
||||||
|
case 'toggleWindow':
|
||||||
|
globalShortcut.register(key, () => {
|
||||||
|
if (mainWindow.isVisible()) {
|
||||||
|
mainWindow.hide();
|
||||||
|
} else {
|
||||||
|
mainWindow.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
globalShortcut.register(key, () => {
|
||||||
|
mainWindow.webContents.send('global-shortcut', action);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`注册快捷键 ${key} 失败:`, error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 播放/暂停
|
// 通知渲染进程更新应用内快捷键
|
||||||
globalShortcut.register(shortcuts.togglePlay, () => {
|
mainWindow.webContents.send('update-app-shortcuts', shortcuts);
|
||||||
mainWindow.webContents.send('global-shortcut', 'togglePlay');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 上一首
|
|
||||||
globalShortcut.register(shortcuts.prevPlay, () => {
|
|
||||||
mainWindow.webContents.send('global-shortcut', 'prevPlay');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 下一首
|
|
||||||
globalShortcut.register(shortcuts.nextPlay, () => {
|
|
||||||
mainWindow.webContents.send('global-shortcut', 'nextPlay');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 音量增加
|
|
||||||
globalShortcut.register(shortcuts.volumeUp, () => {
|
|
||||||
mainWindow.webContents.send('global-shortcut', 'volumeUp');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 音量减少
|
|
||||||
globalShortcut.register(shortcuts.volumeDown, () => {
|
|
||||||
mainWindow.webContents.send('global-shortcut', 'volumeDown');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 收藏当前歌曲
|
|
||||||
globalShortcut.register(shortcuts.toggleFavorite, () => {
|
|
||||||
mainWindow.webContents.send('global-shortcut', 'toggleFavorite');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化快捷键
|
// 初始化快捷键
|
||||||
@@ -85,4 +112,11 @@ export function initializeShortcuts(mainWindow: Electron.BrowserWindow) {
|
|||||||
registerShortcuts(mainWindowRef);
|
registerShortcuts(mainWindowRef);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 监听快捷键更新事件
|
||||||
|
ipcMain.on('update-shortcuts', (_, shortcutsConfig: ShortcutsConfig) => {
|
||||||
|
if (mainWindowRef) {
|
||||||
|
registerShortcuts(mainWindowRef, shortcutsConfig);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { isElectron, isLyricWindow } from '@/utils';
|
|||||||
|
|
||||||
import { initAudioListeners } from './hooks/MusicHook';
|
import { initAudioListeners } from './hooks/MusicHook';
|
||||||
import { isMobile } from './utils';
|
import { isMobile } from './utils';
|
||||||
|
import { useAppShortcuts } from './utils/appShortcuts';
|
||||||
import { initShortcut } from './utils/shortcut';
|
import { initShortcut } from './utils/shortcut';
|
||||||
|
|
||||||
const { locale } = useI18n();
|
const { locale } = useI18n();
|
||||||
@@ -101,6 +102,9 @@ if (isElectron) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用应用内快捷键
|
||||||
|
useAppShortcuts();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (isLyricWindow.value) {
|
if (isLyricWindow.value) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -16,24 +16,54 @@
|
|||||||
<div class="shortcut-info">
|
<div class="shortcut-info">
|
||||||
<span class="shortcut-label">{{ getShortcutLabel(key) }}</span>
|
<span class="shortcut-label">{{ getShortcutLabel(key) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="shortcut-input">
|
<div class="shortcut-controls">
|
||||||
<n-input
|
<div class="shortcut-input">
|
||||||
:value="formatShortcut(shortcut)"
|
<n-input
|
||||||
:status="duplicateKeys[key] ? 'error' : undefined"
|
:value="formatShortcut(shortcut.key)"
|
||||||
:placeholder="t('settings.shortcutSettings.inputPlaceholder')"
|
:status="duplicateKeys[key] ? 'error' : undefined"
|
||||||
readonly
|
:placeholder="t('settings.shortcutSettings.inputPlaceholder')"
|
||||||
@keydown="(e) => handleKeyDown(e, key)"
|
:disabled="!shortcut.enabled"
|
||||||
@focus="() => startRecording(key)"
|
readonly
|
||||||
@blur="stopRecording"
|
@keydown="(e) => handleKeyDown(e, key)"
|
||||||
/>
|
@focus="() => startRecording(key)"
|
||||||
<n-tooltip v-if="duplicateKeys[key]" trigger="hover">
|
@blur="stopRecording"
|
||||||
<template #trigger>
|
/>
|
||||||
<n-icon class="error-icon" size="18">
|
<n-tooltip v-if="duplicateKeys[key]" trigger="hover">
|
||||||
<i class="ri-error-warning-line"></i>
|
<template #trigger>
|
||||||
</n-icon>
|
<n-icon class="error-icon" size="18">
|
||||||
</template>
|
<i class="ri-error-warning-line"></i>
|
||||||
{{ t('settings.shortcutSettings.shortcutConflict') }}
|
</n-icon>
|
||||||
</n-tooltip>
|
</template>
|
||||||
|
{{ t('settings.shortcutSettings.shortcutConflict') }}
|
||||||
|
</n-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="shortcut-options">
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<n-switch v-model:value="shortcut.enabled" size="small" />
|
||||||
|
</template>
|
||||||
|
{{
|
||||||
|
shortcut.enabled
|
||||||
|
? t('settings.shortcutSettings.enabled')
|
||||||
|
: t('settings.shortcutSettings.disabled')
|
||||||
|
}}
|
||||||
|
</n-tooltip>
|
||||||
|
<n-tooltip v-if="shortcut.enabled" trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<n-select
|
||||||
|
v-model:value="shortcut.scope"
|
||||||
|
:options="scopeOptions"
|
||||||
|
size="small"
|
||||||
|
style="width: 100px"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
{{
|
||||||
|
shortcut.scope === 'global'
|
||||||
|
? t('settings.shortcutSettings.scopeGlobal')
|
||||||
|
: t('settings.shortcutSettings.scopeApp')
|
||||||
|
}}
|
||||||
|
</n-tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-space>
|
</n-space>
|
||||||
@@ -46,6 +76,12 @@
|
|||||||
<n-button size="small" @click="resetShortcuts">{{
|
<n-button size="small" @click="resetShortcuts">{{
|
||||||
t('settings.shortcutSettings.resetShortcuts')
|
t('settings.shortcutSettings.resetShortcuts')
|
||||||
}}</n-button>
|
}}</n-button>
|
||||||
|
<n-button size="small" type="warning" @click="disableAllShortcuts">{{
|
||||||
|
t('settings.shortcutSettings.disableAll')
|
||||||
|
}}</n-button>
|
||||||
|
<n-button size="small" type="success" @click="enableAllShortcuts">{{
|
||||||
|
t('settings.shortcutSettings.enableAll')
|
||||||
|
}}</n-button>
|
||||||
<n-button type="primary" size="small" :disabled="hasConflict" @click="handleSave">
|
<n-button type="primary" size="small" :disabled="hasConflict" @click="handleSave">
|
||||||
{{ t('common.save') }}
|
{{ t('common.save') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
@@ -66,26 +102,37 @@ import { isElectron } from '@/utils';
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
interface ShortcutConfig {
|
||||||
|
key: string;
|
||||||
|
enabled: boolean;
|
||||||
|
scope: 'global' | 'app';
|
||||||
|
}
|
||||||
|
|
||||||
interface Shortcuts {
|
interface Shortcuts {
|
||||||
togglePlay: string;
|
togglePlay: ShortcutConfig;
|
||||||
prevPlay: string;
|
prevPlay: ShortcutConfig;
|
||||||
nextPlay: string;
|
nextPlay: ShortcutConfig;
|
||||||
volumeUp: string;
|
volumeUp: ShortcutConfig;
|
||||||
volumeDown: string;
|
volumeDown: ShortcutConfig;
|
||||||
toggleFavorite: string;
|
toggleFavorite: ShortcutConfig;
|
||||||
toggleWindow: string;
|
toggleWindow: ShortcutConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultShortcuts: Shortcuts = {
|
const defaultShortcuts: Shortcuts = {
|
||||||
togglePlay: 'CommandOrControl+Alt+P',
|
togglePlay: { key: 'CommandOrControl+Alt+P', enabled: true, scope: 'global' },
|
||||||
prevPlay: 'Alt+Left',
|
prevPlay: { key: 'Alt+Left', enabled: true, scope: 'global' },
|
||||||
nextPlay: 'Alt+Right',
|
nextPlay: { key: 'Alt+Right', enabled: true, scope: 'global' },
|
||||||
volumeUp: 'Alt+Up',
|
volumeUp: { key: 'Alt+Up', enabled: true, scope: 'app' },
|
||||||
volumeDown: 'Alt+Down',
|
volumeDown: { key: 'Alt+Down', enabled: true, scope: 'app' },
|
||||||
toggleFavorite: 'CommandOrControl+Alt+L',
|
toggleFavorite: { key: 'CommandOrControl+Alt+L', enabled: true, scope: 'app' },
|
||||||
toggleWindow: 'CommandOrControl+Alt+Shift+M'
|
toggleWindow: { key: 'CommandOrControl+Alt+Shift+M', enabled: true, scope: 'global' }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scopeOptions = [
|
||||||
|
{ label: t('settings.shortcutSettings.scopeGlobal'), value: 'global' },
|
||||||
|
{ label: t('settings.shortcutSettings.scopeApp'), value: 'app' }
|
||||||
|
];
|
||||||
|
|
||||||
const shortcuts = ref<Shortcuts>(
|
const shortcuts = ref<Shortcuts>(
|
||||||
isElectron
|
isElectron
|
||||||
? window.electron.ipcRenderer.sendSync('get-store-value', 'shortcuts') || defaultShortcuts
|
? window.electron.ipcRenderer.sendSync('get-store-value', 'shortcuts') || defaultShortcuts
|
||||||
@@ -93,7 +140,7 @@ const shortcuts = ref<Shortcuts>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 临时存储编辑中的快捷键
|
// 临时存储编辑中的快捷键
|
||||||
const tempShortcuts = ref<Shortcuts>({ ...shortcuts.value });
|
const tempShortcuts = ref<Shortcuts>(cloneDeep(shortcuts.value));
|
||||||
|
|
||||||
// 监听快捷键更新
|
// 监听快捷键更新
|
||||||
if (isElectron) {
|
if (isElectron) {
|
||||||
@@ -101,7 +148,7 @@ if (isElectron) {
|
|||||||
const newShortcuts = window.electron.ipcRenderer.sendSync('get-store-value', 'shortcuts');
|
const newShortcuts = window.electron.ipcRenderer.sendSync('get-store-value', 'shortcuts');
|
||||||
if (newShortcuts) {
|
if (newShortcuts) {
|
||||||
shortcuts.value = newShortcuts;
|
shortcuts.value = newShortcuts;
|
||||||
tempShortcuts.value = { ...newShortcuts };
|
tempShortcuts.value = cloneDeep(newShortcuts);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -116,12 +163,27 @@ onMounted(() => {
|
|||||||
console.log('storedShortcuts', storedShortcuts);
|
console.log('storedShortcuts', storedShortcuts);
|
||||||
if (storedShortcuts) {
|
if (storedShortcuts) {
|
||||||
shortcuts.value = storedShortcuts;
|
shortcuts.value = storedShortcuts;
|
||||||
tempShortcuts.value = { ...storedShortcuts };
|
tempShortcuts.value = cloneDeep(storedShortcuts);
|
||||||
} else {
|
} else {
|
||||||
shortcuts.value = { ...defaultShortcuts };
|
shortcuts.value = { ...defaultShortcuts };
|
||||||
tempShortcuts.value = { ...defaultShortcuts };
|
tempShortcuts.value = cloneDeep(defaultShortcuts);
|
||||||
window.electron.ipcRenderer.send('set-store-value', 'shortcuts', defaultShortcuts);
|
window.electron.ipcRenderer.send('set-store-value', 'shortcuts', defaultShortcuts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 转换旧格式的快捷键数据到新格式
|
||||||
|
if (storedShortcuts && typeof storedShortcuts.togglePlay === 'string') {
|
||||||
|
const convertedShortcuts = {} as Shortcuts;
|
||||||
|
Object.entries(storedShortcuts).forEach(([key, value]) => {
|
||||||
|
convertedShortcuts[key as keyof Shortcuts] = {
|
||||||
|
key: value as string,
|
||||||
|
enabled: true,
|
||||||
|
scope: ['volumeUp', 'volumeDown', 'toggleFavorite'].includes(key) ? 'app' : 'global'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
shortcuts.value = convertedShortcuts;
|
||||||
|
tempShortcuts.value = cloneDeep(convertedShortcuts);
|
||||||
|
window.electron.ipcRenderer.send('set-store-value', 'shortcuts', convertedShortcuts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -144,13 +206,21 @@ const message = useMessage();
|
|||||||
// 检查快捷键冲突
|
// 检查快捷键冲突
|
||||||
const duplicateKeys = computed(() => {
|
const duplicateKeys = computed(() => {
|
||||||
const result: Record<string, boolean> = {};
|
const result: Record<string, boolean> = {};
|
||||||
const usedShortcuts = new Set<string>();
|
const usedShortcuts = new Map<string, string>();
|
||||||
|
|
||||||
Object.entries(tempShortcuts.value).forEach(([key, shortcut]) => {
|
Object.entries(tempShortcuts.value).forEach(([key, shortcut]) => {
|
||||||
if (usedShortcuts.has(shortcut)) {
|
// 只检查启用的快捷键
|
||||||
result[key] = true;
|
if (!shortcut.enabled) return;
|
||||||
|
|
||||||
|
const conflictKey = usedShortcuts.get(shortcut.key);
|
||||||
|
if (conflictKey) {
|
||||||
|
// 只有相同作用域的快捷键才会被认为冲突
|
||||||
|
const conflictScope = tempShortcuts.value[conflictKey as keyof Shortcuts].scope;
|
||||||
|
if (shortcut.scope === conflictScope) {
|
||||||
|
result[key] = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
usedShortcuts.add(shortcut);
|
usedShortcuts.set(shortcut.key, key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -161,6 +231,8 @@ const duplicateKeys = computed(() => {
|
|||||||
const hasConflict = computed(() => Object.keys(duplicateKeys.value).length > 0);
|
const hasConflict = computed(() => Object.keys(duplicateKeys.value).length > 0);
|
||||||
|
|
||||||
const startRecording = (key: keyof Shortcuts) => {
|
const startRecording = (key: keyof Shortcuts) => {
|
||||||
|
if (!tempShortcuts.value[key].enabled) return;
|
||||||
|
|
||||||
isRecording.value = true;
|
isRecording.value = true;
|
||||||
currentKey.value = key;
|
currentKey.value = key;
|
||||||
// 禁用全局快捷键
|
// 禁用全局快捷键
|
||||||
@@ -220,12 +292,12 @@ const handleKeyDown = (e: KeyboardEvent, key: keyof Shortcuts) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!['Control', 'Alt', 'Shift', 'Meta', 'Command'].includes(keyName)) {
|
if (!['Control', 'Alt', 'Shift', 'Meta', 'Command'].includes(keyName)) {
|
||||||
tempShortcuts.value[key] = [...modifiers, keyName].join('+');
|
tempShortcuts.value[key].key = [...modifiers, keyName].join('+');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetShortcuts = () => {
|
const resetShortcuts = () => {
|
||||||
tempShortcuts.value = { ...defaultShortcuts };
|
tempShortcuts.value = cloneDeep(defaultShortcuts);
|
||||||
message.success(t('settings.shortcutSettings.messages.resetSuccess'));
|
message.success(t('settings.shortcutSettings.messages.resetSuccess'));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -245,7 +317,7 @@ const saveShortcuts = () => {
|
|||||||
// 先保存到 store
|
// 先保存到 store
|
||||||
window.electron.ipcRenderer.send('set-store-value', 'shortcuts', shortcutsToSave);
|
window.electron.ipcRenderer.send('set-store-value', 'shortcuts', shortcutsToSave);
|
||||||
// 然后更新快捷键
|
// 然后更新快捷键
|
||||||
window.electron.ipcRenderer.send('update-shortcuts');
|
window.electron.ipcRenderer.send('update-shortcuts', shortcutsToSave);
|
||||||
message.success(t('settings.shortcutSettings.messages.saveSuccess'));
|
message.success(t('settings.shortcutSettings.messages.saveSuccess'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存快捷键失败:', error);
|
console.error('保存快捷键失败:', error);
|
||||||
@@ -255,7 +327,7 @@ const saveShortcuts = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const cancelEdit = () => {
|
const cancelEdit = () => {
|
||||||
tempShortcuts.value = { ...shortcuts.value };
|
tempShortcuts.value = cloneDeep(shortcuts.value);
|
||||||
message.info(t('settings.shortcutSettings.messages.cancelEdit'));
|
message.info(t('settings.shortcutSettings.messages.cancelEdit'));
|
||||||
emit('update:show', false);
|
emit('update:show', false);
|
||||||
};
|
};
|
||||||
@@ -309,7 +381,7 @@ watch(visible, (newVal) => {
|
|||||||
// 处理弹窗关闭后的事件
|
// 处理弹窗关闭后的事件
|
||||||
const handleAfterLeave = () => {
|
const handleAfterLeave = () => {
|
||||||
// 重置临时数据
|
// 重置临时数据
|
||||||
tempShortcuts.value = { ...shortcuts.value };
|
tempShortcuts.value = cloneDeep(shortcuts.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理取消按钮点击
|
// 处理取消按钮点击
|
||||||
@@ -324,6 +396,22 @@ const handleSave = () => {
|
|||||||
visible.value = false;
|
visible.value = false;
|
||||||
emit('change', shortcuts.value);
|
emit('change', shortcuts.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 全部禁用快捷键
|
||||||
|
const disableAllShortcuts = () => {
|
||||||
|
Object.keys(tempShortcuts.value).forEach((key) => {
|
||||||
|
tempShortcuts.value[key as keyof Shortcuts].enabled = false;
|
||||||
|
});
|
||||||
|
message.info(t('settings.shortcutSettings.messages.disableAll'));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 全部启用快捷键
|
||||||
|
const enableAllShortcuts = () => {
|
||||||
|
Object.keys(tempShortcuts.value).forEach((key) => {
|
||||||
|
tempShortcuts.value[key as keyof Shortcuts].enabled = true;
|
||||||
|
});
|
||||||
|
message.info(t('settings.shortcutSettings.messages.enableAll'));
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -359,25 +447,32 @@ const handleSave = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.shortcut-info {
|
.shortcut-info {
|
||||||
@apply flex flex-col;
|
@apply flex flex-col min-w-[150px];
|
||||||
|
|
||||||
.shortcut-label {
|
.shortcut-label {
|
||||||
@apply text-base font-medium;
|
@apply text-base font-medium;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shortcut-input {
|
.shortcut-controls {
|
||||||
@apply flex items-center gap-2;
|
@apply flex items-center gap-3 flex-1;
|
||||||
min-width: 200px;
|
|
||||||
|
|
||||||
:deep(.n-input) {
|
.shortcut-input {
|
||||||
.n-input__input-el {
|
@apply flex items-center gap-2 flex-1;
|
||||||
@apply text-center font-mono;
|
|
||||||
|
:deep(.n-input) {
|
||||||
|
.n-input__input-el {
|
||||||
|
@apply text-center font-mono;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon {
|
||||||
|
@apply text-red-500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-icon {
|
.shortcut-options {
|
||||||
@apply text-red-500;
|
@apply flex items-center gap-2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import pinia from '@/store';
|
|||||||
|
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import directives from './directive';
|
import directives from './directive';
|
||||||
|
import { initAppShortcuts } from './utils/appShortcuts';
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
@@ -21,3 +22,6 @@ app.use(pinia);
|
|||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|
||||||
|
// 初始化应用内快捷键
|
||||||
|
initAppShortcuts();
|
||||||
|
|||||||
@@ -0,0 +1,210 @@
|
|||||||
|
import { onMounted, onUnmounted } from 'vue';
|
||||||
|
|
||||||
|
import i18n from '@/../i18n/renderer';
|
||||||
|
import { audioService } from '@/services/audioService';
|
||||||
|
import { usePlayerStore, useSettingsStore } from '@/store';
|
||||||
|
|
||||||
|
import { isElectron } from '.';
|
||||||
|
import { showShortcutToast } from './shortcutToast';
|
||||||
|
|
||||||
|
interface ShortcutConfig {
|
||||||
|
key: string;
|
||||||
|
enabled: boolean;
|
||||||
|
scope: 'global' | 'app';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShortcutsConfig {
|
||||||
|
[key: string]: ShortcutConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t } = i18n.global;
|
||||||
|
|
||||||
|
// 全局存储快捷键配置
|
||||||
|
let appShortcuts: ShortcutsConfig = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理快捷键动作
|
||||||
|
* @param action 快捷键动作
|
||||||
|
*/
|
||||||
|
export async function handleShortcutAction(action: string) {
|
||||||
|
const playerStore = usePlayerStore();
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
const currentSound = audioService.getCurrentSound();
|
||||||
|
const showToast = (message: string, iconName: string) => {
|
||||||
|
if (settingsStore.isMiniMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showShortcutToast(message, iconName);
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'togglePlay':
|
||||||
|
if (playerStore.play) {
|
||||||
|
await audioService.pause();
|
||||||
|
showToast(t('player.playBar.pause'), 'ri-pause-circle-line');
|
||||||
|
} else {
|
||||||
|
await audioService.play();
|
||||||
|
showToast(t('player.playBar.play'), 'ri-play-circle-line');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'prevPlay':
|
||||||
|
playerStore.prevPlay();
|
||||||
|
showToast(t('player.playBar.prev'), 'ri-skip-back-line');
|
||||||
|
break;
|
||||||
|
case 'nextPlay':
|
||||||
|
playerStore.nextPlay();
|
||||||
|
showToast(t('player.playBar.next'), 'ri-skip-forward-line');
|
||||||
|
break;
|
||||||
|
case 'volumeUp':
|
||||||
|
if (currentSound && currentSound?.volume() < 1) {
|
||||||
|
currentSound?.volume((currentSound?.volume() || 0) + 0.1);
|
||||||
|
showToast(
|
||||||
|
`${t('player.playBar.volume')}${Math.round((currentSound?.volume() || 0) * 100)}%`,
|
||||||
|
'ri-volume-up-line'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'volumeDown':
|
||||||
|
if (currentSound && currentSound?.volume() > 0) {
|
||||||
|
currentSound?.volume((currentSound?.volume() || 0) - 0.1);
|
||||||
|
showToast(
|
||||||
|
`${t('player.playBar.volume')}${Math.round((currentSound?.volume() || 0) * 100)}%`,
|
||||||
|
'ri-volume-down-line'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'toggleFavorite': {
|
||||||
|
const isFavorite = playerStore.favoriteList.includes(Number(playerStore.playMusic.id));
|
||||||
|
const numericId = Number(playerStore.playMusic.id);
|
||||||
|
if (isFavorite) {
|
||||||
|
playerStore.removeFromFavorite(numericId);
|
||||||
|
} else {
|
||||||
|
playerStore.addToFavorite(numericId);
|
||||||
|
}
|
||||||
|
showToast(
|
||||||
|
isFavorite
|
||||||
|
? t('player.playBar.favorite', { name: playerStore.playMusic.name })
|
||||||
|
: t('player.playBar.unFavorite', { name: playerStore.playMusic.name }),
|
||||||
|
isFavorite ? 'ri-heart-fill' : 'ri-heart-line'
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
console.log('未知的快捷键动作:', action);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查按键是否匹配快捷键
|
||||||
|
* @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)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(appShortcuts).forEach(([action, config]) => {
|
||||||
|
if (config.enabled && config.scope === 'app' && matchShortcut(e, config.key)) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleShortcutAction(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新应用内快捷键
|
||||||
|
* @param shortcuts 快捷键配置
|
||||||
|
*/
|
||||||
|
export function updateAppShortcuts(shortcuts: ShortcutsConfig) {
|
||||||
|
appShortcuts = shortcuts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化应用内快捷键
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理应用内快捷键
|
||||||
|
*/
|
||||||
|
export function cleanupAppShortcuts() {
|
||||||
|
if (isElectron) {
|
||||||
|
// 移除全局事件监听
|
||||||
|
window.electron.ipcRenderer.removeAllListeners('global-shortcut');
|
||||||
|
window.electron.ipcRenderer.removeAllListeners('update-app-shortcuts');
|
||||||
|
|
||||||
|
// 移除键盘事件监听
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用应用内快捷键的组合函数
|
||||||
|
*/
|
||||||
|
export function useAppShortcuts() {
|
||||||
|
onMounted(() => {
|
||||||
|
initAppShortcuts();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
cleanupAppShortcuts();
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,81 +1,10 @@
|
|||||||
import i18n from '@/../i18n/renderer';
|
|
||||||
import { audioService } from '@/services/audioService';
|
|
||||||
import { usePlayerStore, useSettingsStore } from '@/store';
|
|
||||||
|
|
||||||
import { isElectron } from '.';
|
import { isElectron } from '.';
|
||||||
import { showShortcutToast } from './shortcutToast';
|
import { handleShortcutAction } from './appShortcuts';
|
||||||
|
|
||||||
const { t } = i18n.global;
|
|
||||||
|
|
||||||
export function initShortcut() {
|
export function initShortcut() {
|
||||||
if (isElectron) {
|
if (isElectron) {
|
||||||
window.electron.ipcRenderer.on('global-shortcut', async (_, action: string) => {
|
window.electron.ipcRenderer.on('global-shortcut', async (_, action: string) => {
|
||||||
const playerStore = usePlayerStore();
|
handleShortcutAction(action);
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
const currentSound = audioService.getCurrentSound();
|
|
||||||
const showToast = (message: string, iconName: string) => {
|
|
||||||
if (settingsStore.isMiniMode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showShortcutToast(message, iconName);
|
|
||||||
};
|
|
||||||
switch (action) {
|
|
||||||
case 'togglePlay':
|
|
||||||
if (playerStore.play) {
|
|
||||||
await audioService.pause();
|
|
||||||
showToast(t('player.playBar.pause'), 'ri-pause-circle-line');
|
|
||||||
} else {
|
|
||||||
await audioService.play();
|
|
||||||
showToast(t('player.playBar.play'), 'ri-play-circle-line');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'prevPlay':
|
|
||||||
playerStore.prevPlay();
|
|
||||||
showToast(t('player.playBar.prev'), 'ri-skip-back-line');
|
|
||||||
break;
|
|
||||||
case 'nextPlay':
|
|
||||||
playerStore.nextPlay();
|
|
||||||
showToast(t('player.playBar.next'), 'ri-skip-forward-line');
|
|
||||||
break;
|
|
||||||
case 'volumeUp':
|
|
||||||
if (currentSound && currentSound?.volume() < 1) {
|
|
||||||
currentSound?.volume((currentSound?.volume() || 0) + 0.1);
|
|
||||||
showToast(
|
|
||||||
`${t('player.playBar.volume')}${Math.round((currentSound?.volume() || 0) * 100)}%`,
|
|
||||||
'ri-volume-up-line'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'volumeDown':
|
|
||||||
if (currentSound && currentSound?.volume() > 0) {
|
|
||||||
currentSound?.volume((currentSound?.volume() || 0) - 0.1);
|
|
||||||
showToast(
|
|
||||||
`${t('player.playBar.volume')}${Math.round((currentSound?.volume() || 0) * 100)}%`,
|
|
||||||
'ri-volume-down-line'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'toggleFavorite': {
|
|
||||||
const isFavorite = playerStore.favoriteList.includes(Number(playerStore.playMusic.id));
|
|
||||||
const numericId = Number(playerStore.playMusic.id);
|
|
||||||
if (isFavorite) {
|
|
||||||
playerStore.removeFromFavorite(numericId);
|
|
||||||
} else {
|
|
||||||
playerStore.addToFavorite(numericId);
|
|
||||||
}
|
|
||||||
showToast(
|
|
||||||
isFavorite
|
|
||||||
? t('player.playBar.favorite', { name: playerStore.playMusic.name })
|
|
||||||
: t('player.playBar.unFavorite', { name: playerStore.playMusic.name }),
|
|
||||||
isFavorite ? 'ri-heart-fill' : 'ri-heart-line'
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
console.log('未知的快捷键动作:', action);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* 快捷键配置
|
||||||
|
*/
|
||||||
|
export interface ShortcutConfig {
|
||||||
|
/** 快捷键字符串 */
|
||||||
|
key: string;
|
||||||
|
/** 是否启用 */
|
||||||
|
enabled: boolean;
|
||||||
|
/** 作用范围: global(全局) 或 app(仅应用内) */
|
||||||
|
scope: 'global' | 'app';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快捷键配置集合
|
||||||
|
*/
|
||||||
|
export interface ShortcutsConfig {
|
||||||
|
[key: string]: ShortcutConfig;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user