mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-25 11:29:26 +08:00
✨ feat: 托盘菜单完善
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
- ✨ 支持扫码登录
|
||||
- 📱 支持手机号登录
|
||||
- 📅 自动进行每日签到及云贝签到
|
||||
- 💻 支持桌面歌词
|
||||
- 💻 支持切换为本地播放器,此模式将不会连接网络
|
||||
- 🎨 封面主题色自适应,支持全站着色
|
||||
- 🌚 Light / Dark / Auto 模式自动切换
|
||||
|
||||
@@ -645,6 +645,11 @@ const initTrayIpcMain = (
|
||||
tray?.setPlayMode(mode);
|
||||
});
|
||||
|
||||
// 喜欢状态切换
|
||||
ipcMain.on("like-status-change", (_, likeStatus: boolean) => {
|
||||
tray?.setLikeState(likeStatus);
|
||||
});
|
||||
|
||||
// 桌面歌词开关
|
||||
ipcMain.on("change-desktop-lyric", (_, val: boolean) => {
|
||||
tray?.setDesktopLyricShow(val);
|
||||
|
||||
@@ -19,12 +19,14 @@ type PlayState = "play" | "pause" | "loading";
|
||||
let playMode: PlayMode = "repeat";
|
||||
let playState: PlayState = "pause";
|
||||
let playName: string = "未播放歌曲";
|
||||
let likeSong: boolean = false;
|
||||
let desktopLyricShow: boolean = false;
|
||||
let desktopLyricLock: boolean = false;
|
||||
|
||||
export interface MainTray {
|
||||
setTitle(title: string): void;
|
||||
setPlayMode(mode: PlayMode): void;
|
||||
setLikeState(like: boolean): void;
|
||||
setPlayState(state: PlayState): void;
|
||||
setPlayName(name: string): void;
|
||||
setDesktopLyricShow(show: boolean): void;
|
||||
@@ -34,11 +36,11 @@ export interface MainTray {
|
||||
|
||||
// 托盘图标
|
||||
const trayIcon = (filename: string) => {
|
||||
// const rootPath = isDev
|
||||
// ? join(__dirname, "../../public/icons/tray")
|
||||
// : join(app.getAppPath(), "../../public/icons/tray");
|
||||
// return nativeImage.createFromPath(join(rootPath, filename));
|
||||
return nativeImage.createFromPath(join(__dirname, `../../public/icons/tray/${filename}`));
|
||||
const rootPath = isDev
|
||||
? join(__dirname, "../../public/icons/tray")
|
||||
: join(app.getAppPath(), "../../public/icons/tray");
|
||||
return nativeImage.createFromPath(join(rootPath, filename));
|
||||
// return nativeImage.createFromPath(join(__dirname, `../../public/icons/tray/${filename}`));
|
||||
};
|
||||
|
||||
// 托盘菜单
|
||||
@@ -60,7 +62,6 @@ const createTrayMenu = (
|
||||
id: "name",
|
||||
label: playName,
|
||||
icon: showIcon("music"),
|
||||
accelerator: "CmdOrCtrl+Alt+S",
|
||||
click: () => {
|
||||
win.show();
|
||||
win.focus();
|
||||
@@ -71,19 +72,10 @@ const createTrayMenu = (
|
||||
},
|
||||
{
|
||||
id: "toogleLikeSong",
|
||||
label: "添加到我喜欢",
|
||||
icon: showIcon("unlike"),
|
||||
accelerator: "CmdOrCtrl+Alt+L",
|
||||
label: likeSong ? "从我喜欢中移除" : "添加到我喜欢",
|
||||
icon: showIcon(likeSong ? "like" : "unlike"),
|
||||
click: () => win.webContents.send("toogleLikeSong"),
|
||||
},
|
||||
{
|
||||
id: "unLike",
|
||||
label: "从我喜欢中移除",
|
||||
icon: showIcon("like"),
|
||||
visible: false,
|
||||
accelerator: "CmdOrCtrl+Alt+L",
|
||||
click: () => win.webContents.send("unlike-song"),
|
||||
},
|
||||
{
|
||||
id: "changeMode",
|
||||
label:
|
||||
@@ -123,21 +115,18 @@ const createTrayMenu = (
|
||||
id: "playNext",
|
||||
label: "上一曲",
|
||||
icon: showIcon("prev"),
|
||||
accelerator: "CmdOrCtrl+Left",
|
||||
click: () => win.webContents.send("playPrev"),
|
||||
},
|
||||
{
|
||||
id: "playOrPause",
|
||||
label: playState === "pause" ? "播放" : "暂停",
|
||||
icon: showIcon(playState === "pause" ? "play" : "pause"),
|
||||
accelerator: "CmdOrCtrl+Space",
|
||||
click: () => win.webContents.send(playState === "pause" ? "play" : "pause"),
|
||||
},
|
||||
{
|
||||
id: "playNext",
|
||||
label: "下一曲",
|
||||
icon: showIcon("next"),
|
||||
accelerator: "CmdOrCtrl+Right",
|
||||
click: () => win.webContents.send("playNext"),
|
||||
},
|
||||
{
|
||||
@@ -176,7 +165,6 @@ const createTrayMenu = (
|
||||
id: "exit",
|
||||
label: "退出",
|
||||
icon: showIcon("power"),
|
||||
accelerator: "CmdOrCtrl+Alt+Q",
|
||||
click: () => {
|
||||
win.close();
|
||||
// app.exit(0);
|
||||
@@ -255,6 +243,12 @@ class CreateTray implements MainTray {
|
||||
// 更新菜单
|
||||
this.initTrayMenu();
|
||||
}
|
||||
// 设置喜欢状态
|
||||
setLikeState(like: boolean) {
|
||||
likeSong = like;
|
||||
// 更新菜单
|
||||
this.initTrayMenu();
|
||||
}
|
||||
// 桌面歌词开关
|
||||
setDesktopLyricShow(show: boolean) {
|
||||
desktopLyricShow = show;
|
||||
|
||||
@@ -74,7 +74,12 @@
|
||||
<!-- 全屏播放器 -->
|
||||
<Teleport to="body">
|
||||
<Transition name="up" mode="out-in">
|
||||
<FullPlayer v-if="statusStore.showFullPlayer || statusStore.fullPlayerActive" />
|
||||
<FullPlayer
|
||||
v-if="
|
||||
statusStore.showFullPlayer ||
|
||||
(statusStore.fullPlayerActive && settingStore.fullPlayerCache)
|
||||
"
|
||||
/>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</Provider>
|
||||
|
||||
@@ -192,9 +192,9 @@
|
||||
<div v-if="type !== 'radio'" class="actions" @click.stop @dblclick.stop>
|
||||
<!-- 喜欢歌曲 -->
|
||||
<SvgIcon
|
||||
:name="isLikeSong(item.id) ? 'Favorite' : 'FavoriteBorder'"
|
||||
:name="dataStore.isLikeSong(item.id) ? 'Favorite' : 'FavoriteBorder'"
|
||||
:size="20"
|
||||
@click.stop="toLikeSong(item, !isLikeSong(item.id))"
|
||||
@click.stop="toLikeSong(item, !dataStore.isLikeSong(item.id))"
|
||||
@delclick.stop
|
||||
/>
|
||||
</div>
|
||||
@@ -381,11 +381,6 @@ const sortSelect = (key: SortType) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 是否为喜欢歌曲
|
||||
const isLikeSong = (id: number) => {
|
||||
return dataStore.userLikeData.songs.includes(id);
|
||||
};
|
||||
|
||||
// 滚动至播放歌曲
|
||||
const scrollTo = (index: number) => {
|
||||
if (index === 0) songListScrollTop.value = 0;
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
<template>
|
||||
<div class="login-qrcode">
|
||||
<div class="qr-img">
|
||||
<n-qr-code
|
||||
<div
|
||||
v-if="qrImg"
|
||||
:value="qrImg"
|
||||
:class="['qr', { success: qrStatusCode === 802, error: qrStatusCode === 800 }]"
|
||||
:size="160"
|
||||
:icon-size="30"
|
||||
icon-src="/icons/favicon.png?assest"
|
||||
error-correction-level="H"
|
||||
/>
|
||||
>
|
||||
<n-qr-code
|
||||
:value="qrImg"
|
||||
:size="160"
|
||||
:icon-size="30"
|
||||
icon-src="/icons/favicon.png?assest"
|
||||
error-correction-level="H"
|
||||
/>
|
||||
<!-- 待确认 -->
|
||||
<Transition name="fade" mode="out-in">
|
||||
<div v-if="loginName" class="login-data">
|
||||
<n-image
|
||||
:src="loginAvatar.replace(/^http:/, 'https:') + '?param=100y100'"
|
||||
class="cover"
|
||||
preview-disabled
|
||||
@load="coverLoaded"
|
||||
>
|
||||
<template #placeholder>
|
||||
<div class="cover-loading">
|
||||
<img src="/images/avatar.jpg?assest" class="loading-img" alt="loading-img" />
|
||||
</div>
|
||||
</template>
|
||||
</n-image>
|
||||
<n-text>{{ loginName }}</n-text>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
<n-skeleton v-else class="qr" />
|
||||
</div>
|
||||
<n-text class="tip" depth="3">{{ qrTipText }}</n-text>
|
||||
@@ -18,6 +39,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { qrKey, checkQr } from "@/api/login";
|
||||
import { coverLoaded } from "@/utils/helper";
|
||||
|
||||
const emit = defineEmits<{
|
||||
saveLogin: [any];
|
||||
@@ -41,11 +63,17 @@ const qrTipText = computed(() => {
|
||||
return qrCodeTip[qrStatusCode.value] || "遇到未知状态,请重试";
|
||||
});
|
||||
|
||||
// 待确认数据
|
||||
const loginName = ref<string>("");
|
||||
const loginAvatar = ref<string>("");
|
||||
|
||||
// 获取二维码
|
||||
const getQrData = async () => {
|
||||
try {
|
||||
pauseCheck();
|
||||
qrStatusCode.value = 801;
|
||||
loginName.value = "";
|
||||
loginAvatar.value = "";
|
||||
// 获取 key
|
||||
const res = await qrKey();
|
||||
qrImg.value = `https://music.163.com/login?codekey=${res.data.unikey}`;
|
||||
@@ -63,7 +91,7 @@ const getQrData = async () => {
|
||||
const checkQrStatus = async () => {
|
||||
if (!qrUnikey.value) return;
|
||||
// 检查状态
|
||||
const { code, cookie } = await checkQr(qrUnikey.value);
|
||||
const { code, cookie, nickname, avatarUrl } = await checkQr(qrUnikey.value);
|
||||
switch (code) {
|
||||
// 二维码过期
|
||||
case 800:
|
||||
@@ -77,6 +105,8 @@ const checkQrStatus = async () => {
|
||||
// 待确认
|
||||
case 802:
|
||||
qrStatusCode.value = 802;
|
||||
loginName.value = nickname;
|
||||
loginAvatar.value = avatarUrl;
|
||||
break;
|
||||
// 登录成功
|
||||
case 803:
|
||||
@@ -115,15 +145,43 @@ onBeforeUnmount(pauseCheck);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
.qr {
|
||||
padding: 0;
|
||||
height: 180px;
|
||||
width: 180px;
|
||||
min-height: 180px;
|
||||
min-width: 180px;
|
||||
transition: opacity 0.3s;
|
||||
:deep(canvas) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.n-qr-code {
|
||||
padding: 0;
|
||||
height: 180px;
|
||||
width: 180px;
|
||||
min-height: 180px;
|
||||
min-width: 180px;
|
||||
transition:
|
||||
opacity 0.3s,
|
||||
filter 0.3s;
|
||||
:deep(canvas) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
.login-data {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
.cover {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
&.success {
|
||||
.n-qr-code {
|
||||
opacity: 0.5;
|
||||
filter: blur(4px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,13 @@
|
||||
class="set"
|
||||
/>
|
||||
</n-card>
|
||||
<n-card class="set-item">
|
||||
<div class="label">
|
||||
<n-text class="name">全屏播放器留存</n-text>
|
||||
<n-text class="tip" :depth="3">在播放器收起时是否销毁,开启将会增大内存占用</n-text>
|
||||
</div>
|
||||
<n-switch v-model:value="settingStore.fullPlayerCache" class="set" :round="false" />
|
||||
</n-card>
|
||||
<n-card class="set-item">
|
||||
<div class="label">
|
||||
<n-text class="name">显示前奏倒计时</n-text>
|
||||
|
||||
@@ -80,6 +80,7 @@ interface SettingState {
|
||||
routeAnimation: "none" | "fade" | "zoom" | "slide" | "up";
|
||||
useRealIP: boolean;
|
||||
realIP: string;
|
||||
fullPlayerCache: boolean;
|
||||
}
|
||||
|
||||
export const useSettingStore = defineStore({
|
||||
@@ -104,6 +105,7 @@ export const useSettingStore = defineStore({
|
||||
showTaskbarProgress: false, // 显示任务栏进度
|
||||
checkUpdateOnStart: true, // 启动时检查更新
|
||||
preventSleep: false, // 是否禁止休眠
|
||||
fullPlayerCache: false, // 全屏播放器缓存
|
||||
// 播放
|
||||
songLevel: "exhigh", // 音质
|
||||
playDevice: "default", // 播放设备
|
||||
|
||||
@@ -19,6 +19,7 @@ import { openUserLogin } from "./modal";
|
||||
import { debounce } from "lodash-es";
|
||||
import { isBeforeSixAM } from "./time";
|
||||
import { dailyRecommend } from "@/api/rec";
|
||||
import { isElectron } from "./helper";
|
||||
|
||||
// 是否登录
|
||||
export const isLogin = () => !!getCookie("MUSIC_U");
|
||||
@@ -182,6 +183,8 @@ export const toLikeSong = debounce(
|
||||
}
|
||||
// 更新
|
||||
dataStore.setUserLikeData("songs", likeList);
|
||||
// ipc
|
||||
if (isElectron) window.electron.ipcRenderer.send("like-status-change", like);
|
||||
} else {
|
||||
window.$message.error(`${like ? "喜欢" : "取消"}音乐时发生错误`);
|
||||
return;
|
||||
|
||||
@@ -45,8 +45,8 @@ export const formatSongsList = (data: any[]): SongType[] => {
|
||||
name: (item.album || item.al)?.name,
|
||||
cover: (item.album || item.al)?.picUrl,
|
||||
},
|
||||
alia: isArray(item.alia || item.alias || item.transNames)
|
||||
? item.alia?.[0] || item.alias?.[0] || item.transNames?.[0]
|
||||
alia: isArray(item.alia || item.alias || item.transNames || item.tns)
|
||||
? item.alia?.[0] || item.alias?.[0] || item.transNames?.[0] || item.tns?.[0]
|
||||
: item.alia,
|
||||
dj: item.dj
|
||||
? {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { isElectron } from "./helper";
|
||||
import { openUpdateApp } from "./modal";
|
||||
import { useMusicStore, useDataStore } from "@/stores";
|
||||
import player from "./player";
|
||||
import { toLikeSong } from "./auth";
|
||||
|
||||
// 全局 IPC 事件
|
||||
const initIpc = () => {
|
||||
@@ -22,6 +24,12 @@ const initIpc = () => {
|
||||
window.electron.ipcRenderer.on("volumeDown", () => player.setVolume("down"));
|
||||
// 播放模式切换
|
||||
window.electron.ipcRenderer.on("changeMode", (_, mode) => player.togglePlayMode(mode));
|
||||
// 喜欢歌曲
|
||||
window.electron.ipcRenderer.on("toogleLikeSong", async () => {
|
||||
const dataStore = useDataStore();
|
||||
const musicStore = useMusicStore();
|
||||
await toLikeSong(musicStore.playSong, !dataStore.isLikeSong(musicStore.playSong.id));
|
||||
});
|
||||
// 桌面歌词开关
|
||||
window.electron.ipcRenderer.on("toogleDesktopLyric", () => player.toggleDesktopLyric());
|
||||
window.electron.ipcRenderer.on("closeDesktopLyric", () => player.toggleDesktopLyric());
|
||||
|
||||
@@ -249,6 +249,7 @@ class Player {
|
||||
*/
|
||||
private playerEvent() {
|
||||
// 获取数据
|
||||
const dataStore = useDataStore();
|
||||
const statusStore = useStatusStore();
|
||||
const settingStore = useSettingStore();
|
||||
const playSongData = this.getPlaySongData();
|
||||
@@ -268,6 +269,10 @@ class Player {
|
||||
// ipc
|
||||
if (isElectron) {
|
||||
window.electron.ipcRenderer.send("play-song-change", this.getPlayerInfo());
|
||||
window.electron.ipcRenderer.send(
|
||||
"like-status-change",
|
||||
dataStore.isLikeSong(playSongData?.id || 0),
|
||||
);
|
||||
}
|
||||
});
|
||||
// 播放
|
||||
@@ -488,7 +493,7 @@ class Player {
|
||||
* @param sep 分隔符
|
||||
* @returns 播放信息
|
||||
*/
|
||||
getPlayerInfo(song?: SongType, sep: string = "&"): string | null {
|
||||
getPlayerInfo(song?: SongType, sep: string = "/"): string | null {
|
||||
const playSongData = song || this.getPlaySongData();
|
||||
if (!playSongData) return null;
|
||||
// 标题
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
z-index: 0;
|
||||
opacity: 0;
|
||||
|
||||
Reference in New Issue
Block a user