feat: 完善部分配置及页面

This commit is contained in:
imsyy
2024-12-11 17:12:59 +08:00
parent a4d4cd5f70
commit 191ab29a44
29 changed files with 1123 additions and 641 deletions

2
components.d.ts vendored
View File

@@ -17,6 +17,7 @@ declare module 'vue' {
CoverMenu: typeof import('./src/components/Menu/CoverMenu.vue')['default']
CreatePlaylist: typeof import('./src/components/Modal/CreatePlaylist.vue')['default']
DownloadSong: typeof import('./src/components/Modal/DownloadSong.vue')['default']
ExcludeKeywords: typeof import('./src/components/Modal/ExcludeKeywords.vue')['default']
FullPlayer: typeof import('./src/components/Player/FullPlayer.vue')['default']
GeneralSetting: typeof import('./src/components/Setting/GeneralSetting.vue')['default']
JumpArtist: typeof import('./src/components/Modal/JumpArtist.vue')['default']
@@ -54,6 +55,7 @@ declare module 'vue' {
NDrawer: typeof import('naive-ui')['NDrawer']
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
NDropdown: typeof import('naive-ui')['NDropdown']
NDynamicTags: typeof import('naive-ui')['NDynamicTags']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NEmpty: typeof import('naive-ui')['NEmpty']
NFlex: typeof import('naive-ui')['NFlex']

View File

@@ -47,12 +47,12 @@
"@pixi/filter-bulge-pinch": "^5.1.1",
"@pixi/filter-color-matrix": "^7.4.2",
"@pixi/sprite": "^7.4.2",
"@vueuse/core": "^10.11.1",
"@vueuse/core": "^12.0.0",
"NeteaseCloudMusicApi": "^4.25.0",
"axios": "^1.7.8",
"axios": "^1.7.9",
"change-case": "^5.4.4",
"dayjs": "^1.11.13",
"electron-dl": "^3.5.2",
"electron-dl": "^4.0.0",
"electron-store": "^8.2.0",
"electron-updater": "^6.3.9",
"file-saver": "^2.0.5",
@@ -67,8 +67,8 @@
"lodash-es": "^4.17.21",
"marked": "^14.1.4",
"music-metadata": "7.14.0",
"pinia": "^2.2.8",
"pinia-plugin-persistedstate": "^3.2.3",
"pinia": "^2.3.0",
"pinia-plugin-persistedstate": "^4.1.3",
"plyr": "^3.7.8",
"vue-virt-list": "^1.5.5"
},
@@ -84,24 +84,24 @@
"@types/howler": "^2.2.12",
"@types/js-cookie": "^3.0.6",
"@types/node": "^22.10.1",
"@typescript-eslint/eslint-plugin": "^8.16.0",
"@typescript-eslint/parser": "^8.16.0",
"@typescript-eslint/eslint-plugin": "^8.18.0",
"@typescript-eslint/parser": "^8.18.0",
"@vitejs/plugin-vue": "^5.2.1",
"ajv": "^8.17.1",
"crypto-js": "^4.2.0",
"electron": "^30.5.1",
"electron-builder": "^25.1.8",
"electron-log": "^5.2.3",
"electron-log": "^5.2.4",
"electron-vite": "^2.3.0",
"eslint": "^9.16.0",
"eslint-plugin-vue": "^9.32.0",
"fast-glob": "^3.3.2",
"fastify": "^4.28.1",
"fastify": "^4.29.0",
"naive-ui": "^2.40.3",
"node-taglib-sharp": "^5.2.3",
"prettier": "^3.4.1",
"sass": "^1.81.0",
"terser": "^5.36.0",
"prettier": "^3.4.2",
"sass": "^1.82.0",
"terser": "^5.37.0",
"typescript": "5.6.2",
"unplugin-auto-import": "^0.18.6",
"unplugin-vue-components": "^0.27.5",

1360
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -55,9 +55,10 @@
<!-- 路由页面 -->
<RouterView v-slot="{ Component }">
<Transition :name="`router-${settingStore.routeAnimation}`" mode="out-in">
<KeepAlive :max="20" :exclude="['layout']">
<KeepAlive v-if="settingStore.useKeepAlive" :max="20" :exclude="['layout']">
<component :is="Component" class="router-view" />
</KeepAlive>
<component v-else :is="Component" class="router-view" />
</Transition>
</RouterView>
<!-- 回顶 -->

View File

@@ -0,0 +1,20 @@
<template>
<div class="exclude">
<n-alert :show-icon="false">请勿添加过多以免影响歌词的正常显示</n-alert>
<n-dynamic-tags v-model:value="settingStore.excludeKeywords" />
</div>
</template>
<script setup lang="ts">
import { useSettingStore } from "@/stores";
const settingStore = useSettingStore();
</script>
<style lang="scss" scoped>
.exclude {
.n-alert {
margin-bottom: 20px;
}
}
</style>

View File

@@ -44,6 +44,11 @@ const login = async () => {
window.$message.warning("请输入 Cookie");
return;
}
// 是否为有效 Cookie
if (!cookie.value.includes("MUSIC_U") || cookie.value.endsWith(";")) {
window.$message.warning("请输入有效的 Cookie");
return;
}
// 写入 Cookie
try {
window.$message.success("登录成功");
@@ -66,8 +71,6 @@ const login = async () => {
onMounted(() => {
if (isElectron) {
window.electron.ipcRenderer.on("send-cookies", (_, value) => {
console.log(typeof value);
if (!value) return;
cookie.value = value;
login();

View File

@@ -2,7 +2,7 @@
<div
v-show="statusStore.showFullPlayer"
:style="{
'--main-color': mainColor,
'--main-color': statusStore.mainColor,
cursor: statusStore.playerMetaShow ? 'auto' : 'none',
}"
class="full-player"
@@ -66,7 +66,7 @@
<!-- 封面 -->
<PlayerCover />
<!-- 数据 -->
<PlayerData :center="playerDataCenter" :theme="mainColor" />
<PlayerData :center="playerDataCenter" :theme="statusStore.mainColor" />
</div>
<Transition name="fade" mode="out-in">
<!-- 评论 -->
@@ -94,7 +94,7 @@
<!-- 音乐频谱 -->
<PlayerSpectrum
v-if="settingStore.showSpectrums"
:color="mainColor ? `rgb(${mainColor})` : 'rgb(239 239 239)'"
:color="statusStore.mainColor ? `rgb(${statusStore.mainColor})` : 'rgb(239 239 239)'"
:show="!statusStore.playerMetaShow"
:height="60"
/>
@@ -144,13 +144,6 @@ const instantLyrics = computed(() => {
return { content: content?.content, tran: settingStore.showTran && content?.tran };
});
// 播放器主色
const mainColor = computed(() => {
const mainColor = statusStore.songCoverTheme?.main;
if (!mainColor) return "239, 239, 239";
return `${mainColor.r}, ${mainColor.g}, ${mainColor.b}`;
});
// 隐藏播放元素
const {
isPending,

View File

@@ -50,9 +50,8 @@ const { pause: pauseSeek, resume: resumeSeek } = useRafFn(() => {
// 歌词主色
const mainColor = computed(() => {
const mainColor = statusStore.songCoverTheme?.main;
if (!mainColor) return "rgb(239, 239, 239)";
return `rgb(${mainColor.r}, ${mainColor.g}, ${mainColor.b})`;
if (!statusStore.mainColor) return "rgb(239, 239, 239)";
return `rgb(${statusStore.mainColor})`;
});
// 当前歌词

View File

@@ -3,7 +3,7 @@
<n-drawer
v-model:show="statusStore.playListShow"
:class="{ 'full-player': statusStore.showFullPlayer }"
:style="{ '--main-color': mainColor }"
:style="{ '--main-color': statusStore.mainColor }"
:auto-focus="false"
id="main-playlist"
style="width: 400px"
@@ -124,13 +124,6 @@ const statusStore = useStatusStore();
const playListRef = ref<VirtualListInst | null>(null);
// 列表主色
const mainColor = computed(() => {
const mainColor = statusStore.songCoverTheme?.main;
if (!mainColor) return "239, 239, 239";
return `${mainColor.r}, ${mainColor.g}, ${mainColor.b}`;
});
// 播放列表数据
const playListData = computed(() => {
return dataStore.playList.map((item, index) => {
@@ -290,6 +283,9 @@ const scrollToItem = (index: number, behavior: "smooth" | "auto" = "smooth") =>
&.on {
border-color: rgb(var(--main-color));
}
&:hover {
border-color: rgb(var(--main-color));
}
.num {
color: rgba(var(--main-color), 0.52);
}

View File

@@ -173,17 +173,17 @@
@select="(mode) => player.togglePlayMode(mode)"
>
<div class="menu-icon" @click.stop="player.togglePlayMode(false)">
<SvgIcon :name="playModeIcon" />
<SvgIcon :name="statusStore.playModeIcon" />
</div>
</n-dropdown>
<!-- 音量调节 -->
<n-popover :show-arrow="false" :style="{ padding: 0 }">
<template #trigger>
<div class="menu-icon" @click.stop="player.toggleMute" @wheel="changeVolume">
<SvgIcon :name="playVolumeIcon" />
<div class="menu-icon" @click.stop="player.toggleMute" @wheel="player.setVolume">
<SvgIcon :name="statusStore.playVolumeIcon" />
</div>
</template>
<div class="volume-change" @wheel="changeVolume">
<div class="volume-change" @wheel="player.setVolume">
<n-slider
v-model:value="statusStore.playVolume"
:tooltip="false"
@@ -193,7 +193,7 @@
vertical
@update:value="(val) => player.setVolume(val)"
/>
<n-text class="slider-num">{{ playVolumePercentage }}%</n-text>
<n-text class="slider-num">{{ statusStore.playVolumePercent }}%</n-text>
</div>
</n-popover>
<!-- 播放列表 -->
@@ -219,7 +219,7 @@
import type { DropdownOption } from "naive-ui";
import { useMusicStore, useStatusStore, useDataStore, useSettingStore } from "@/stores";
import { secondsToTime, calculateCurrentTime } from "@/utils/time";
import { renderIcon, isElectron } from "@/utils/helper";
import { renderIcon, isElectron, coverLoaded } from "@/utils/helper";
import { toLikeSong } from "@/utils/auth";
import { openDownloadSong, openJumpArtist, openPlaylistAdd } from "@/utils/modal";
import player from "@/utils/player";
@@ -282,6 +282,20 @@ const songMoreOptions = computed<DropdownOption[]>(() => {
props: { onClick: () => openDownloadSong(musicStore.playSong) },
icon: renderIcon("Download"),
},
{
key: "comment",
label: "查看评论",
show: !isLocal,
props: {
onClick: () => {
statusStore.$patch({
showFullPlayer: true,
showPlayerComment: true,
});
},
},
icon: renderIcon("Message"),
},
];
});
@@ -294,43 +308,6 @@ const sliderDragend = () => {
player.play();
};
// 封面加载完成
const coverLoaded = (e: Event) => {
const target = e.target as HTMLElement | null;
if (target && target.nodeType === Node.ELEMENT_NODE) {
target.style.opacity = "1";
}
};
// 当前音量百分比
const playVolumePercentage = computed(() => {
return Math.round(statusStore.playVolume * 100);
});
// 当前音量图标
const playVolumeIcon = computed(() => {
const volume = statusStore.playVolume;
return volume === 0
? "VolumeOff"
: volume < 0.4
? "VolumeMute"
: volume < 0.7
? "VolumeDown"
: "VolumeUp";
});
// 当前播放模式图标
const playModeIcon = computed(() => {
const mode = statusStore.playSongMode;
return statusStore.playHeartbeatMode
? "HeartBit"
: mode === "repeat"
? "Repeat"
: mode === "repeat-once"
? "RepeatSong"
: "Shuffle";
});
// 是否展示歌词
const isShowLyrics = computed(() => {
const isHasLrc = musicStore.isHasLrc;
@@ -353,12 +330,6 @@ const instantLyrics = computed(() => {
? `${content?.content} ${content?.tran} `
: content?.content;
});
// 音量条鼠标滚动
const changeVolume = (e: WheelEvent) => {
const deltaY = e.deltaY;
player.setVolume(deltaY > 0 ? "down" : "up");
};
</script>
<style lang="scss" scoped>

View File

@@ -129,6 +129,9 @@ onMounted(() => {
:deep(.n-scrollbar-content) {
padding-right: 60px;
}
:deep(.n-skeleton) {
background-color: rgba(var(--main-color), 0.08);
}
.comment-list {
margin: 0 auto;
}

View File

@@ -94,8 +94,28 @@
</div>
<!-- 播放模式 -->
<div class="menu-icon" @click.stop="player.togglePlayMode(false)">
<SvgIcon :name="playModeIcon" />
<SvgIcon :name="statusStore.playModeIcon" />
</div>
<!-- 音量调节 -->
<n-popover :show-arrow="false" :style="{ '--main-color': statusStore.mainColor }" raw>
<template #trigger>
<div class="menu-icon" @click.stop="player.toggleMute" @wheel="player.setVolume">
<SvgIcon :name="statusStore.playVolumeIcon" />
</div>
</template>
<div class="volume-change" @wheel="player.setVolume">
<n-slider
v-model:value="statusStore.playVolume"
:tooltip="false"
:min="0"
:max="1"
:step="0.01"
vertical
@update:value="(val) => player.setVolume(val)"
/>
<n-text class="slider-num">{{ statusStore.playVolumePercent }}%</n-text>
</div>
</n-popover>
<!-- 播放列表 -->
<div
v-if="!statusStore.personalFmMode"
@@ -121,18 +141,6 @@ const dataStore = useDataStore();
const musicStore = useMusicStore();
const statusStore = useStatusStore();
// 当前播放模式图标
const playModeIcon = computed(() => {
const mode = statusStore.playSongMode;
return statusStore.playHeartbeatMode
? "HeartBit"
: mode === "repeat"
? "Repeat"
: mode === "repeat-once"
? "RepeatSong"
: "Shuffle";
});
// 进度条拖拽结束
const sliderDragend = () => {
const seek = calculateCurrentTime(statusStore.progress, statusStore.duration);
@@ -259,11 +267,6 @@ const sliderDragend = () => {
margin: 6px 8px;
--n-handle-size: 12px;
--n-rail-height: 4px;
--n-rail-color: rgba(var(--main-color), 0.14);
--n-rail-color-hover: rgba(var(--main-color), 0.3);
--n-fill-color: rgb(var(--main-color));
--n-handle-color: rgb(var(--main-color));
--n-fill-color-hover: rgb(var(--main-color));
}
span {
opacity: 0.6;
@@ -277,4 +280,28 @@ const sliderDragend = () => {
}
}
}
// volume
.volume-change {
display: flex;
flex-direction: column;
align-items: center;
width: 64px;
height: 200px;
padding: 12px 16px;
backdrop-filter: blur(10px);
background-color: rgba(var(--main-color), 0.14);
.slider-num {
margin-top: 4px;
font-size: 12px;
color: rgb(var(--main-color));
}
}
// slider
.n-slider {
--n-rail-color: rgba(var(--main-color), 0.14);
--n-rail-color-hover: rgba(var(--main-color), 0.3);
--n-fill-color: rgb(var(--main-color));
--n-handle-color: rgb(var(--main-color));
--n-fill-color-hover: rgb(var(--main-color));
}
</style>

View File

@@ -95,6 +95,13 @@
</div>
<n-switch class="set" v-model:value="settingStore.menuShowCover" :round="false" />
</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 class="set" v-model:value="settingStore.useKeepAlive" :round="false" />
</n-card>
<n-card class="set-item">
<div class="label">
<n-text class="name">页面切换动画</n-text>

View File

@@ -232,6 +232,13 @@
</div>
<n-switch v-model:value="settingStore.lyricsBlur" class="set" :round="false" />
</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-button type="primary" strong secondary @click="openLyricExclude">配置</n-button>
</n-card>
</div>
<div class="set-list">
<n-h3 prefix="bar"> Apple Music-like Lyrics </n-h3>
@@ -328,6 +335,7 @@ import { useSettingStore, useStatusStore } from "@/stores";
import { cloneDeep, isEqual } from "lodash-es";
import { isElectron } from "@/utils/helper";
import player from "@/utils/player";
import { openLyricExclude } from "@/utils/modal";
const statusStore = useStatusStore();
const settingStore = useSettingStore();

View File

@@ -50,8 +50,7 @@ const userDB = localforage.createInstance({
storeName: "user",
});
export const useDataStore = defineStore({
id: "data",
export const useDataStore = defineStore("data", {
state: (): ListState => ({
// 播放列表
playList: [],
@@ -168,15 +167,15 @@ export const useDataStore = defineStore({
}
// 在当前播放位置之后插入歌曲
const indexAdd = index + 1
this.playList.splice(indexAdd, 0, song)
const indexAdd = index + 1;
this.playList.splice(indexAdd, 0, song);
// 移除重复的歌曲(如果存在)
const playList = this.playList.filter((item,idx) => idx === indexAdd || item.id !== song.id);
const playList = this.playList.filter((item, idx) => idx === indexAdd || item.id !== song.id);
// 更新本地存储
this.playList = playList;
await musicDB.setItem("playList", cloneDeep(playList));
// 返回刚刚插入的歌曲索引
return playList.findIndex(item => item.id === song.id);
return playList.findIndex((item) => item.id === song.id);
},
// 更改播放历史
async setHistory(song: SongType) {
@@ -304,6 +303,6 @@ export const useDataStore = defineStore({
persist: {
key: "data-store",
storage: localStorage,
paths: ["userLoginStatus", "loginType", "userData", "searchHistory", "catData"],
pick: ["userLoginStatus", "loginType", "userData", "searchHistory", "catData"],
},
});

View File

@@ -34,8 +34,7 @@ const defaultMusicData: SongType = {
type: "song",
};
export const useMusicStore = defineStore({
id: "music",
export const useMusicStore = defineStore("music", {
state: (): MusicState => ({
// 当前播放歌曲
playSong: { ...defaultMusicData },
@@ -86,7 +85,7 @@ export const useMusicStore = defineStore({
actions: {
// 恢复默认音乐数据
resetMusicData() {
this.playSong = {...defaultMusicData};
this.playSong = { ...defaultMusicData };
this.songLyric = {
lrcData: [],
yrcData: [],

View File

@@ -1,4 +1,5 @@
import { defineStore } from "pinia";
import { keywords } from "@/assets/data/exclude";
interface SettingState {
themeMode: "light" | "dark" | "auto";
@@ -83,10 +84,11 @@ interface SettingState {
fullPlayerCache: boolean;
scrobbleSong: boolean;
dynamicCover: boolean;
useKeepAlive: boolean;
excludeKeywords: string[];
}
export const useSettingStore = defineStore({
id: "setting",
export const useSettingStore = defineStore("setting", {
state: (): SettingState => ({
// 个性化
themeMode: "auto", // 明暗模式
@@ -108,6 +110,7 @@ export const useSettingStore = defineStore({
checkUpdateOnStart: true, // 启动时检查更新
preventSleep: false, // 是否禁止休眠
fullPlayerCache: false, // 全屏播放器缓存
useKeepAlive: true, // 使用 keep-alive
// 播放
songLevel: "exhigh", // 音质
playDevice: "default", // 播放设备
@@ -142,6 +145,7 @@ export const useSettingStore = defineStore({
lyricsBlur: false, // 歌词模糊
lyricsScrollPosition: "start", // 歌词滚动位置
lrcMousePause: false, // 鼠标悬停暂停
excludeKeywords: keywords, // 排除歌词关键字
// 本地
localFilesPath: [],
localSeparators: ["/", "&"],

View File

@@ -21,8 +21,7 @@ interface ShortcutStore {
};
}
export const useShortcutStore = defineStore({
id: "shortcut",
export const useShortcutStore = defineStore("shortcut", {
state: (): ShortcutStore => ({
// 全局快捷键开启
globalOpen: true,

View File

@@ -40,8 +40,7 @@ interface StatusState {
personalFmMode: boolean;
}
export const useStatusStore = defineStore({
id: "status",
export const useStatusStore = defineStore("status", {
state: (): StatusState => ({
// 菜单折叠状态
menuCollapsed: false,
@@ -101,13 +100,46 @@ export const useStatusStore = defineStore({
// 播放器评论
showPlayerComment: false,
}),
getters: {},
getters: {
// 播放音量图标
playVolumeIcon(state) {
const volume = state.playVolume;
return volume === 0
? "VolumeOff"
: volume < 0.4
? "VolumeMute"
: volume < 0.7
? "VolumeDown"
: "VolumeUp";
},
// 播放模式图标
playModeIcon(state) {
const mode = state.playSongMode;
return state.playHeartbeatMode
? "HeartBit"
: mode === "repeat"
? "Repeat"
: mode === "repeat-once"
? "RepeatSong"
: "Shuffle";
},
// 音量百分比
playVolumePercent(state) {
return Math.round(state.playVolume * 100);
},
// 播放器主色
mainColor(state) {
const mainColor = state.songCoverTheme?.main;
if (!mainColor) return "239, 239, 239";
return `${mainColor.r}, ${mainColor.g}, ${mainColor.b}`;
},
},
actions: {},
// 持久化
persist: {
key: "status-store",
storage: localStorage,
paths: [
pick: [
"menuCollapsed",
"currentTime",
"duration",

View File

@@ -1,9 +1,14 @@
import { LyricLine, parseLrc, parseYrc } from "@applemusic-like-lyrics/lyric";
import { keywords } from "@/assets/data/exclude";
import type { LyricType } from "@/types/main";
import { useMusicStore } from "@/stores";
import { useMusicStore, useSettingStore } from "@/stores";
import { msToS } from "./time";
// 歌词排除内容
const getExcludeKeywords = () => {
const settingStore = useSettingStore();
return settingStore.excludeKeywords;
};
// 恢复默认
export const resetSongLyric = () => {
const musicStore = useMusicStore();
@@ -77,7 +82,7 @@ export const parseLrcData = (lrcData: LyricLine[]): LyricType[] => {
const time = msToS(words[0].startTime);
const content = words[0].word.trim();
// 排除内容
if (!content || keywords.some((keyword) => content.includes(keyword))) {
if (!content || getExcludeKeywords().some((keyword) => content.includes(keyword))) {
return null;
}
return {
@@ -113,7 +118,7 @@ export const parseYrcData = (yrcData: LyricLine[]): LyricType[] => {
.map((word) => word.content + (word.endsWithSpace ? " " : ""))
.join("");
// 排除内容
if (!contentStr || keywords.some((keyword) => contentStr.includes(keyword))) {
if (!contentStr || getExcludeKeywords().some((keyword) => contentStr.includes(keyword))) {
return null;
}
return {

View File

@@ -16,6 +16,7 @@ import UpdatePlaylist from "@/components/Modal/UpdatePlaylist.vue";
import DownloadSong from "@/components/Modal/DownloadSong.vue";
import MainSetting from "@/components/Setting/MainSetting.vue";
import UpdateApp from "@/components/Modal/UpdateApp.vue";
import ExcludeKeywords from "@/components/Modal/ExcludeKeywords.vue";
// 用户协议
export const openUserAgreement = () => {
@@ -233,3 +234,17 @@ export const openUpdateApp = (data: UpdateInfoType) => {
},
});
};
// 歌词排除内容
export const openLyricExclude = () => {
window.$modal.create({
preset: "card",
transformOrigin: "center",
autoFocus: false,
style: { width: "600px" },
title: "歌词排除内容",
content: () => {
return h(ExcludeKeywords);
},
});
};

View File

@@ -758,15 +758,17 @@ class Player {
}
/**
* 设置播放音量
* @param volume 音量
* @param actions 音量
*/
setVolume(volume: number | "up" | "down") {
setVolume(actions: number | WheelEvent) {
const statusStore = useStatusStore();
// 直接设置
if (typeof volume === "number") {
volume = Math.max(0, Math.min(volume, 1));
if (typeof actions === "number") {
actions = Math.max(0, Math.min(actions, 1));
} else {
const increment = 0.05;
const deltaY = actions.deltaY;
const volume = deltaY > 0 ? "down" : "up";
statusStore.playVolume = Math.max(
0,
Math.min(statusStore.playVolume + (volume === "up" ? increment : -increment), 1),

View File

@@ -129,7 +129,7 @@
<!-- 路由 -->
<RouterView v-slot="{ Component }">
<Transition :name="`router-${settingStore.routeAnimation}`" mode="out-in">
<KeepAlive>
<KeepAlive v-if="settingStore.useKeepAlive">
<component
ref="componentRef"
:is="Component"
@@ -138,6 +138,14 @@
@scroll="listScroll"
/>
</KeepAlive>
<component
v-else
ref="componentRef"
:is="Component"
:id="artistId"
class="router-view"
@scroll="listScroll"
/>
</Transition>
</RouterView>
</div>

View File

@@ -18,9 +18,10 @@
<!-- 路由 -->
<RouterView v-slot="{ Component }">
<Transition :name="`router-${settingStore.routeAnimation}`" mode="out-in">
<KeepAlive>
<KeepAlive v-if="settingStore.useKeepAlive">
<component :is="Component" class="router-view" />
</KeepAlive>
<component v-else :is="Component" class="router-view" />
</Transition>
</RouterView>
</div>

View File

@@ -31,7 +31,12 @@
</n-grid>
<!-- 公共推荐 -->
<div v-for="(item, index) in recData" :key="index" class="rec-public">
<n-flex class="title" align="center" justify="space-between">
<n-flex
class="title"
align="center"
justify="space-between"
@click="router.push({ path: item.path ?? undefined })"
>
<n-h3 prefix="bar">
<n-text>{{ item.name }}</n-text>
<SvgIcon v-if="item.path" :size="26" name="Right" />
@@ -194,7 +199,10 @@ const getAllRecData = async () => {
}
};
onActivated(getAllRecData);
onMounted(() => {
getAllRecData();
onActivated(getAllRecData);
});
</script>
<style lang="scss" scoped>

View File

@@ -31,9 +31,10 @@
<!-- 路由 -->
<RouterView v-slot="{ Component }">
<Transition :name="`router-${settingStore.routeAnimation}`" mode="out-in">
<KeepAlive>
<KeepAlive v-if="settingStore.useKeepAlive">
<component :is="Component" class="router-view" />
</KeepAlive>
<component v-else :is="Component" class="router-view" />
</Transition>
</RouterView>
</div>

View File

@@ -215,7 +215,12 @@
import type { CoverType, SongType } from "@/types/main";
import type { DropdownOption, MessageReactive } from "naive-ui";
import { songDetail } from "@/api/song";
import { playlistDetail, playlistAllSongs, deletePlaylist } from "@/api/playlist";
import {
playlistDetail,
playlistAllSongs,
deletePlaylist,
updatePlaylistPrivacy,
} from "@/api/playlist";
import { formatCoverList, formatSongsList } from "@/utils/format";
import { coverLoaded, formatNumber, fuzzySearch, renderIcon } from "@/utils/helper";
import { renderToolbar } from "@/utils/meta";
@@ -283,6 +288,7 @@ const moreOptions = computed<DropdownOption[]>(() => [
label: "公开隐私歌单",
key: "privacy",
show: playlistDetailData.value?.privacy === 10,
props: { onClick: openPrivacy },
icon: renderIcon("ListLockOpen"),
},
{
@@ -483,6 +489,23 @@ const updatePlaylist = () => {
);
};
// 公开隐私歌单
const openPrivacy = async () => {
if (playlistDetailData.value?.privacy !== 10) return;
window.$dialog.warning({
title: "公开隐私歌单",
content: "确认公开这个歌单?该操作无法撤销!",
positiveText: "公开",
negativeText: "取消",
onPositiveClick: async () => {
const result = await updatePlaylistPrivacy(playlistId.value);
if (result.code !== 200) return;
if (playlistDetailData.value) playlistDetailData.value.privacy = 0;
window.$message.success("歌单公开成功");
},
});
};
onBeforeRouteUpdate((to) => {
const id = Number(to.query.id as string);
if (id) {

View File

@@ -82,9 +82,10 @@
<!-- 路由 -->
<RouterView v-slot="{ Component }">
<Transition :name="`router-${settingStore.routeAnimation}`" mode="out-in">
<KeepAlive>
<KeepAlive v-if="settingStore.useKeepAlive">
<component :is="Component" :data="listData" :loading="loading" class="router-view" />
</KeepAlive>
<component v-else :is="Component" :data="listData" :loading="loading" class="router-view" />
</Transition>
</RouterView>
<!-- 目录管理 -->

View File

@@ -16,16 +16,17 @@
<!-- 路由 -->
<RouterView v-slot="{ Component }">
<Transition :name="`router-${settingStore.routeAnimation}`" mode="out-in">
<KeepAlive>
<KeepAlive v-if="settingStore.useKeepAlive">
<component :is="Component" :keyword="searchKeyword" class="router-view" />
</KeepAlive>
<component v-else :is="Component" :keyword="searchKeyword" class="router-view" />
</Transition>
</RouterView>
</div>
</template>
<script setup lang="ts">
import { useSettingStore } from '@/stores';
import { useSettingStore } from "@/stores";
const router = useRouter();
const settingStore = useSettingStore();