feat: 托盘菜单完善

This commit is contained in:
imsyy
2024-09-26 15:24:25 +08:00
parent 2b6d68ecbd
commit 62c9dc33db
13 changed files with 133 additions and 50 deletions

View File

@@ -33,6 +33,7 @@
- ✨ 支持扫码登录
- 📱 支持手机号登录
- 📅 自动进行每日签到及云贝签到
- 💻 支持桌面歌词
- 💻 支持切换为本地播放器,此模式将不会连接网络
- 🎨 封面主题色自适应,支持全站着色
- 🌚 Light / Dark / Auto 模式自动切换

View File

@@ -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);

View File

@@ -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;

View File

@@ -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>

View File

@@ -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;

View File

@@ -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);
}
}
}
}

View File

@@ -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>

View File

@@ -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", // 播放设备

View File

@@ -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;

View File

@@ -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
? {

View File

@@ -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());

View File

@@ -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;
// 标题

View File

@@ -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;