Skip to content

Commit 113d39a

Browse files
committed
✨ feat: 新增应用内快捷键 #560
1 parent 0f0730d commit 113d39a

10 files changed

Lines changed: 196 additions & 31 deletions

File tree

components.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ declare module 'vue' {
136136
PlayerSlider: typeof import('./src/components/Player/PlayerSlider.vue')['default']
137137
PlayerSpectrum: typeof import('./src/components/Player/PlayerSpectrum.vue')['default']
138138
PlaylistAdd: typeof import('./src/components/Modal/PlaylistAdd.vue')['default']
139-
PlaylistComment: typeof import('./src/components/List/PlaylistComment.vue')['default']
140139
PlaySetting: typeof import('./src/components/Setting/PlaySetting.vue')['default']
141140
Provider: typeof import('./src/components/Global/Provider.vue')['default']
142141
RouterLink: typeof import('vue-router')['RouterLink']

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "splayer",
33
"productName": "SPlayer",
4-
"version": "3.0.0-beta.6",
4+
"version": "3.0.0",
55
"description": "A minimalist music player",
66
"main": "./out/main/index.js",
77
"author": "imsyy",

src/components/Setting/KeyboardSetting.vue

Lines changed: 126 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@
1818
</n-card>
1919
</div>
2020
<div class="set-list">
21-
<n-h3 prefix="bar"> 快捷键更改 </n-h3>
21+
<n-h3 prefix="bar"> 全局快捷键更改 </n-h3>
2222
<n-card id="shortcut-list" class="set-item">
23-
<n-list v-for="(item, key, index) in shortcutList" :key="index" class="shortcut" hoverable>
23+
<n-list
24+
v-for="(item, key, index) in globalShortcutList"
25+
:key="index"
26+
class="shortcut"
27+
hoverable
28+
>
2429
<n-list-item>
2530
<template #prefix>
2631
<n-text class="name">{{ item.name }}</n-text>
@@ -39,7 +44,7 @@
3944
<n-input
4045
:value="item.globalShortcut"
4146
:disabled="!shortcutStore.globalOpen"
42-
:status="item?.isRegistered ? 'error' : 'success'"
47+
:status="item.globalShortcut && item?.isRegistered ? 'error' : undefined"
4348
placeholder="快捷键为空"
4449
readonly
4550
@focus="inputFocus(key, true)"
@@ -58,11 +63,39 @@
5863
</n-card>
5964
<n-card class="set-item">
6065
<div class="label">
61-
<n-text class="name">恢复默认</n-text>
66+
<n-text class="name">恢复全局默认</n-text>
6267
</div>
6368
<n-button type="primary" strong secondary @click="resetShortcut"> 恢复默认 </n-button>
6469
</n-card>
6570
</div>
71+
<div class="set-list">
72+
<n-h3 prefix="bar"> 页面内快捷键 </n-h3>
73+
<n-card id="page-shortcut-list" class="set-item">
74+
<n-list
75+
v-for="(item, key, index) in pageShortcutList"
76+
:key="index"
77+
class="shortcut"
78+
hoverable
79+
>
80+
<n-list-item>
81+
<template #prefix>
82+
<n-text class="name">{{ item.name }}</n-text>
83+
</template>
84+
<n-thing>
85+
<n-input
86+
:value="item.shortcut"
87+
placeholder="快捷键为空"
88+
readonly
89+
@focus="inputFocus(key)"
90+
@blur="inputBlur"
91+
@keydown.stop="inputKeyDown"
92+
@keyup="keyHandled = ''"
93+
/>
94+
</n-thing>
95+
</n-list-item>
96+
</n-list>
97+
</n-card>
98+
</div>
6699
</div>
67100
</template>
68101

@@ -83,6 +116,34 @@ const keyHandled = ref<string>("");
83116
// 快捷键列表
84117
const shortcutList = ref(cloneDeep(shortcutStore.shortcutList));
85118
119+
// 全局快捷键列表(排除页面内快捷键)
120+
const globalShortcutList = computed(() => {
121+
const pageShortcutKeys = ["openPlayer", "openPlayList", "closePlayer"];
122+
return Object.entries(shortcutList.value)
123+
.filter(([key]) => !pageShortcutKeys.includes(key))
124+
.reduce(
125+
(acc, [key, value]) => {
126+
acc[key] = value;
127+
return acc;
128+
},
129+
{} as Record<string, (typeof shortcutList.value)[keyof typeof shortcutList.value]>,
130+
);
131+
});
132+
133+
// 页面内快捷键列表
134+
const pageShortcutList = computed(() => {
135+
const pageShortcutKeys = ["openPlayer", "openPlayList", "closePlayer"];
136+
return Object.entries(shortcutList.value)
137+
.filter(([key]) => pageShortcutKeys.includes(key))
138+
.reduce(
139+
(acc, [key, value]) => {
140+
acc[key] = value;
141+
return acc;
142+
},
143+
{} as Record<string, (typeof shortcutList.value)[keyof typeof shortcutList.value]>,
144+
);
145+
});
146+
86147
// 获取按下的快捷键
87148
const getShortcut = (e: KeyboardEvent): string => {
88149
// 允许输入
@@ -142,6 +203,8 @@ const getShortcut = (e: KeyboardEvent): string => {
142203
"ArrowUp",
143204
"ArrowRight",
144205
"ArrowDown",
206+
// Escape 键
207+
"Escape",
145208
];
146209
if (!allowedCodes.includes(e.code)) return "";
147210
return e.code;
@@ -157,8 +220,12 @@ const inputFocus = (type: string, isGlobal: boolean = false) => {
157220
};
158221
159222
// 失去焦点
160-
const inputBlur = () => {
161-
if (selectShortcut.value) shortcutStore.registerAllShortcuts();
223+
const inputBlur = async () => {
224+
if (selectShortcut.value) {
225+
await shortcutStore.registerAllShortcuts();
226+
// 重新检查所有全局快捷键占用状态
227+
await checkAllGlobalShortcuts();
228+
}
162229
selectShortcut.value = null;
163230
selectGlobal.value = false;
164231
};
@@ -199,9 +266,15 @@ const inputKeyDown = async (e: KeyboardEvent) => {
199266
// 先更改
200267
shortcutList.value[selectShortcut.value].globalShortcut = globalShortcut;
201268
// 是否被占用
202-
await checkRegistered(globalShortcut);
269+
const isRegistered = await checkRegistered(globalShortcut);
270+
if (isRegistered) {
271+
window.$message.warning("快捷键已被占用");
272+
} else {
273+
window.$message.success("快捷键设置成功");
274+
}
203275
changeShortcut(globalShortcut);
204276
} else {
277+
// 页面内快捷键或全局快捷键的页面内部分
205278
changeShortcut(shortcut);
206279
window.$message.success("快捷键设置成功");
207280
}
@@ -226,28 +299,53 @@ const isRepeat = (shortcut: string): boolean => {
226299
};
227300
228301
// 是否被占用
229-
const checkRegistered = debounce(async (shortcut: string) => {
302+
const checkRegistered = debounce(async (shortcut: string, shortcutKey?: string) => {
230303
try {
231-
if (!shortcut || !selectShortcut.value) return;
304+
if (!shortcut) return false;
305+
const targetKey = shortcutKey || selectShortcut.value;
306+
if (!targetKey) return false;
232307
const isRegistered = await window.electron.ipcRenderer.invoke(
233308
"is-shortcut-registered",
234309
formatForGlobalShortcut(shortcut),
235310
);
236-
if (isRegistered) window.$message.warning("快捷键已被占用");
237311
// 更新状态
238-
shortcutList.value[selectShortcut.value].isRegistered = isRegistered;
312+
shortcutList.value[targetKey].isRegistered = isRegistered;
313+
return isRegistered;
239314
} catch (error) {
240315
console.error("Error checking shortcut registration:", error);
241316
window.$message.error("快捷键检查出现错误");
242-
changeShortcut("");
317+
if (selectShortcut.value) {
318+
changeShortcut("");
319+
}
320+
return false;
243321
}
244322
}, 500);
245323
324+
// 检查所有全局快捷键占用状态
325+
const checkAllGlobalShortcuts = async () => {
326+
if (!shortcutStore.globalOpen) return;
327+
for (const key in shortcutList.value) {
328+
const item = shortcutList.value[key];
329+
if (item.globalShortcut) {
330+
await checkRegistered(item.globalShortcut, key);
331+
}
332+
}
333+
};
334+
246335
// 开关全局快捷键
247-
const updateGlobalOpen = (val: boolean) => {
248-
if(val) shortcutStore.registerAllShortcuts();
249-
else window.electron.ipcRenderer.send("unregister-all-shortcut");
250-
}
336+
const updateGlobalOpen = async (val: boolean) => {
337+
if (val) {
338+
await shortcutStore.registerAllShortcuts();
339+
// 重新检查所有全局快捷键占用状态
340+
await checkAllGlobalShortcuts();
341+
} else {
342+
window.electron.ipcRenderer.send("unregister-all-shortcut");
343+
// 清除所有占用状态
344+
for (const key in shortcutList.value) {
345+
shortcutList.value[key].isRegistered = false;
346+
}
347+
}
348+
};
251349
252350
// 重置快捷键
253351
const resetShortcut = () => {
@@ -264,13 +362,16 @@ const resetShortcut = () => {
264362
});
265363
};
266364
267-
onMounted(() => {
365+
onMounted(async () => {
268366
shortcutStore.registerAllShortcuts();
367+
// 检查所有全局快捷键占用状态
368+
await checkAllGlobalShortcuts();
269369
});
270370
</script>
271371

272372
<style lang="scss" scoped>
273-
#shortcut-list {
373+
#shortcut-list,
374+
#page-shortcut-list {
274375
overflow: hidden;
275376
:deep(.n-card__content) {
276377
padding: 0;
@@ -289,4 +390,11 @@ onMounted(() => {
289390
}
290391
}
291392
}
393+
#page-shortcut-list {
394+
.shortcut {
395+
.name {
396+
min-width: 120px;
397+
}
398+
}
399+
}
292400
</style>

src/stores/shortcut.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ interface ShortcutStore {
1818
volumeUp: ShortcutType;
1919
volumeDown: ShortcutType;
2020
toogleDesktopLyric: ShortcutType;
21+
openPlayer: ShortcutType;
22+
openPlayList: ShortcutType;
23+
closePlayer: ShortcutType;
2124
};
2225
}
2326

@@ -61,6 +64,24 @@ export const useShortcutStore = defineStore("shortcut", {
6164
shortcut: "CmdOrCtrl+KeyD",
6265
globalShortcut: "CmdOrCtrl+Shift+D",
6366
},
67+
// 打开播放界面
68+
openPlayer: {
69+
name: "打开播放界面",
70+
shortcut: "KeyP",
71+
globalShortcut: "",
72+
},
73+
// 打开播放列表
74+
openPlayList: {
75+
name: "打开播放列表",
76+
shortcut: "KeyL",
77+
globalShortcut: "",
78+
},
79+
// 关闭播放界面
80+
closePlayer: {
81+
name: "关闭播放界面",
82+
shortcut: "Escape",
83+
globalShortcut: "",
84+
},
6485
},
6586
}),
6687
getters: {},

src/utils/init.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const initEventListener = () => {
6161
const keyDownEvent = debounce((event: KeyboardEvent) => {
6262
const player = usePlayer();
6363
const shortcutStore = useShortcutStore();
64+
const statusStore = useStatusStore();
6465
const target = event.target as HTMLElement;
6566
// 排除元素
6667
const extendsDom = ["input", "textarea"];
@@ -78,10 +79,18 @@ const keyDownEvent = debounce((event: KeyboardEvent) => {
7879
const shortcutParts = shortcut.shortcut.split("+");
7980
// 标志位
8081
let match = true;
81-
// 检查修饰键
82-
if (shortcutParts.includes("CmdOrCtrl") && !isCtrl) match = false;
83-
if (shortcutParts.includes("Shift") && !isShift) match = false;
84-
if (shortcutParts.includes("Alt") && !isAlt) match = false;
82+
// 检查是否包含修饰键
83+
const hasCmdOrCtrl = shortcutParts.includes("CmdOrCtrl");
84+
const hasShift = shortcutParts.includes("Shift");
85+
const hasAlt = shortcutParts.includes("Alt");
86+
// 检查修饰键匹配
87+
if (hasCmdOrCtrl && !isCtrl) match = false;
88+
if (hasShift && !isShift) match = false;
89+
if (hasAlt && !isAlt) match = false;
90+
// 如果快捷键定义中没有修饰键,确保没有按下任何修饰键
91+
if (!hasCmdOrCtrl && !hasShift && !hasAlt) {
92+
if (isCtrl || isShift || isAlt) match = false;
93+
}
8594
// 检查实际按键
8695
const mainKey = shortcutParts.find(
8796
(part: string) => part !== "CmdOrCtrl" && part !== "Shift" && part !== "Alt",
@@ -108,6 +117,20 @@ const keyDownEvent = debounce((event: KeyboardEvent) => {
108117
case "toogleDesktopLyric":
109118
player.toggleDesktopLyric();
110119
break;
120+
case "openPlayer":
121+
// 打开播放界面(任意界面)
122+
statusStore.showFullPlayer = true;
123+
break;
124+
case "closePlayer":
125+
// 关闭播放界面(仅在播放界面时)
126+
if (statusStore.showFullPlayer) {
127+
statusStore.showFullPlayer = false;
128+
}
129+
break;
130+
case "openPlayList":
131+
// 打开播放列表(任意界面)
132+
statusStore.playListShow = !statusStore.playListShow;
133+
break;
111134
default:
112135
break;
113136
}

src/utils/player.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,12 @@ class Player {
742742
data: SongType[],
743743
song?: SongType,
744744
pid?: number,
745-
options: { showTip?: boolean; scrobble?: boolean; play?: boolean } = {
745+
options: {
746+
showTip?: boolean;
747+
scrobble?: boolean;
748+
play?: boolean;
749+
keepHeartbeatMode?: boolean;
750+
} = {
746751
showTip: true,
747752
scrobble: true,
748753
play: true,
@@ -766,7 +771,9 @@ class Player {
766771
// 更新列表
767772
await dataStore.setPlayList(processedData);
768773
// 关闭特殊模式
769-
if (statusStore.playHeartbeatMode) this.toggleHeartMode(false);
774+
if (statusStore.playHeartbeatMode && !options.keepHeartbeatMode) {
775+
this.toggleHeartMode(false);
776+
}
770777
if (statusStore.personalFmMode) statusStore.personalFmMode = false;
771778
// 是否直接播放
772779
if (song && typeof song === "object" && "id" in song) {
@@ -1020,9 +1027,15 @@ class Player {
10201027
this.message?.destroy();
10211028
const heartRatelists = formatSongsList(result.data);
10221029
this.nextPrefetch = null;
1023-
statusStore.playHeartbeatMode = true;
10241030
statusStore.playIndex = 0;
1025-
await this.updatePlayList(heartRatelists, heartRatelists[0]);
1031+
// 先更新播放列表,再设置心动模式标志
1032+
await this.updatePlayList(heartRatelists, heartRatelists[0], undefined, {
1033+
showTip: true,
1034+
scrobble: true,
1035+
play: true,
1036+
keepHeartbeatMode: true,
1037+
});
1038+
statusStore.playHeartbeatMode = true;
10261039
} else {
10271040
this.message?.destroy();
10281041
window.$message.error(result.message || "心动模式开启出错,请重试");

src/views/Home/HomeOnline.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ onMounted(() => {
250250
.title {
251251
margin-top: 28px;
252252
padding: 0 4px;
253+
width: max-content;
253254
.n-h {
254255
margin: 0;
255256
display: flex;

0 commit comments

Comments
 (0)