From 6beb9c78e1bac9413c42697e5f1c956b3066538d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=95=E5=B1=82=E7=94=A8=E6=88=B7?= Date: Fri, 24 Oct 2025 15:23:51 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?=E6=AF=8F=E6=97=A5=E6=8E=A8=E8=8D=90=20-=20=E4=B8=8D=E6=84=9F?= =?UTF-8?q?=E5=85=B4=E8=B6=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/rec.ts | 10 +++++ src/api/song.ts | 8 ++-- src/assets/icons/HeartBroken.svg | 1 + src/components/Global/Provider.vue | 3 -- src/components/List/SongList.vue | 32 ++++++++++++--- src/components/Menu/SongListMenu.vue | 50 ++++++++++++++++++++++- src/components/Player/PlayerRightMenu.vue | 2 +- src/stores/status.ts | 3 ++ src/utils/lyric.ts | 27 +++++++++--- src/utils/player-utils/lyric.ts | 20 +++++++-- src/views/DailySongs.vue | 8 +++- 11 files changed, 139 insertions(+), 25 deletions(-) create mode 100644 src/assets/icons/HeartBroken.svg diff --git a/src/api/rec.ts b/src/api/rec.ts index 4606392..d8f5d37 100644 --- a/src/api/rec.ts +++ b/src/api/rec.ts @@ -12,6 +12,16 @@ export const dailyRecommend = (type: "songs" | "resource" = "songs") => { }); }; +/** + * 每日推荐 - 不感兴趣 + */ +export const dailyRecommendDislike = (id: number) => { + return request({ + url: "/recommend/songs/dislike", + params: { id, timestamp: Date.now() }, + }); +}; + /** * 推荐内容 * @param {string} [type] - 推荐类型 diff --git a/src/api/song.ts b/src/api/song.ts index aae4810..6b0f74e 100644 --- a/src/api/song.ts +++ b/src/api/song.ts @@ -67,20 +67,18 @@ export const songLyric = (id: number) => { // 获取格式TTML的歌词 export const songLyricTTML = async (id: number) => { -const url = `https://amll-ttml-db.stevexmh.net/ncm/${id}`; + const url = `https://amll-ttml-db.stevexmh.net/ncm/${id}`; try { const response = await fetch(url); if (response === null || response.status !== 200) { - console.error(`TTML API请求失败或TTML仓库没有歌词, 将会使用默认歌词`); return null; } const data = await response.text(); return data; - } catch (error) { - console.error('TTML API请求出错:', error); + } catch { return null; } -} +}; /** * 获取歌曲下载链接 diff --git a/src/assets/icons/HeartBroken.svg b/src/assets/icons/HeartBroken.svg new file mode 100644 index 0000000..7062f08 --- /dev/null +++ b/src/assets/icons/HeartBroken.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Global/Provider.vue b/src/components/Global/Provider.vue index 1d4af0c..7aceb2c 100644 --- a/src/components/Global/Provider.vue +++ b/src/components/Global/Provider.vue @@ -217,9 +217,6 @@ const changeGlobalTheme = () => { railColor: toRGBA(primaryRGB, 0.2), railColorHover: toRGBA(primaryRGB, 0.3), }, - Popover: { - color: `rgb(${surfaceContainerRGB})`, - }, }; } } catch (error) { diff --git a/src/components/List/SongList.vue b/src/components/List/SongList.vue index df0e811..2247579 100644 --- a/src/components/List/SongList.vue +++ b/src/components/List/SongList.vue @@ -15,7 +15,7 @@ @@ -114,7 +122,7 @@ import type { DropdownOption } from "naive-ui"; import type { SongType, SortType } from "@/types/main"; import { useMusicStore, useStatusStore } from "@/stores"; import { VirtList } from "vue-virt-list"; -import { cloneDeep, entries, isEmpty } from "lodash-es"; +import { entries, isEmpty } from "lodash-es"; import { sortOptions } from "@/utils/meta"; import { renderIcon } from "@/utils/helper"; import SongListMenu from "@/components/Menu/SongListMenu.vue"; @@ -143,11 +151,14 @@ const props = withDefaults( disabledSort?: boolean; // 播放歌单 ID playListId?: number; + // 是否为每日推荐 + isDailyRecommend?: boolean; }>(), { type: "song", loadingText: "努力加载中...", playListId: 0, + isDailyRecommend: false, }, ); @@ -179,8 +190,9 @@ const songListMenuRef = ref | null>(null); // 列表数据 const listData = computed(() => { - const data = cloneDeep(props.data); - if (props.disabledSort) return data; + if (props.disabledSort) return props.data; + // 创建副本用于排序(避免修改原数组) + const data = [...props.data]; // 排序 switch (statusStore.listSort) { case "titleAZ": @@ -212,6 +224,16 @@ const listData = computed(() => { } }); +// 虚拟列表 key +const listKey = computed(() => { + // 每日推荐 + if (props.isDailyRecommend) { + return musicStore.dailySongsData.timestamp || 0; + } + // 其他列表长度(检测增删操作) + return listData.value?.length || 0; +}); + // 列表是否具有播放歌曲 const hasPlaySong = computed(() => { return listData.value.findIndex((item) => item.id === musicStore.playSong.id); diff --git a/src/components/Menu/SongListMenu.vue b/src/components/Menu/SongListMenu.vue index 7b940a1..b527600 100644 --- a/src/components/Menu/SongListMenu.vue +++ b/src/components/Menu/SongListMenu.vue @@ -18,7 +18,7 @@ diff --git a/src/components/Player/PlayerRightMenu.vue b/src/components/Player/PlayerRightMenu.vue index f4bc729..f4e5022 100644 --- a/src/components/Player/PlayerRightMenu.vue +++ b/src/components/Player/PlayerRightMenu.vue @@ -37,7 +37,7 @@ :max="1" :step="0.01" vertical - @update:value="(val) => player.setVolume(val)" + @update:value="(val: number) => player.setVolume(val)" /> {{ statusStore.playVolumePercent }}% diff --git a/src/stores/status.ts b/src/stores/status.ts index 5119e41..dca8362 100644 --- a/src/stores/status.ts +++ b/src/stores/status.ts @@ -46,6 +46,8 @@ interface StatusState { spectrumsData: number[]; /** 纯净歌词模式 */ pureLyricMode: boolean; + /** 是否使用 TTML 歌词 */ + usingTTMLLyric: boolean; /** 当前播放索引 */ playIndex: number; /** 歌词播放索引 */ @@ -110,6 +112,7 @@ export const useStatusStore = defineStore("status", { currentTimeOffsetMap: {}, songCoverTheme: {}, pureLyricMode: false, + usingTTMLLyric: false, spectrumsData: [], playIndex: -1, lyricIndex: -1, diff --git a/src/utils/lyric.ts b/src/utils/lyric.ts index 6ff7260..5bee8e2 100644 --- a/src/utils/lyric.ts +++ b/src/utils/lyric.ts @@ -14,12 +14,14 @@ const getExcludeKeywords = () => { // 恢复默认 export const resetSongLyric = () => { const musicStore = useMusicStore(); + const statusStore = useStatusStore(); musicStore.songLyric = { lrcData: [], lrcAMData: [], yrcData: [], yrcAMData: [], }; + statusStore.usingTTMLLyric = false; }; // 解析歌词数据 @@ -172,21 +174,30 @@ export const alignAMLyrics = ( // 处理本地歌词 export const parseLocalLyric = (lyric: string, format: "lrc" | "ttml") => { + const statusStore = useStatusStore(); + if (!lyric) { resetSongLyric(); return; } - const musicStore = useMusicStore(); switch (format) { case "lrc": - parseLocalLyricLrc(lyric, musicStore); + parseLocalLyricLrc(lyric); + statusStore.usingTTMLLyric = false; break; case "ttml": - parseLocalLyricAM(lyric, musicStore); + parseLocalLyricAM(lyric); + statusStore.usingTTMLLyric = true; break; } }; -const parseLocalLyricLrc = (lyric: string, musicStore: any) => { + +/** + * 解析本地LRC歌词 + * @param lyric LRC格式的歌词内容 + */ +const parseLocalLyricLrc = (lyric: string) => { + const musicStore = useMusicStore(); // 解析 const lrc: LyricLine[] = parseLrc(lyric); const lrcData: LyricType[] = parseLrcData(lrc); @@ -220,7 +231,13 @@ const parseLocalLyricLrc = (lyric: string, musicStore: any) => { yrcAMData: [], }; }; -const parseLocalLyricAM = (lyric: string, musicStore: any) => { + +/** + * 解析本地AM歌词 + * @param lyric AM格式的歌词内容 + */ +const parseLocalLyricAM = (lyric: string) => { + const musicStore = useMusicStore(); const ttml = parseTTML(lyric); const yrcAMData = parseTTMLToAMLL(ttml); const yrcData = parseTTMLToYrc(ttml); diff --git a/src/utils/player-utils/lyric.ts b/src/utils/player-utils/lyric.ts index fff296f..8880255 100644 --- a/src/utils/player-utils/lyric.ts +++ b/src/utils/player-utils/lyric.ts @@ -1,4 +1,4 @@ -import { useMusicStore, useSettingStore } from "@/stores"; +import { useMusicStore, useSettingStore, useStatusStore } from "@/stores"; import { parsedLyricsData, parseTTMLToAMLL, parseTTMLToYrc, resetSongLyric } from "../lyric"; import { songLyric, songLyricTTML } from "@/api/song"; import { parseTTML } from "@applemusic-like-lyrics/lyric"; @@ -10,14 +10,17 @@ import { LyricType } from "@/types/main"; * @param id 歌曲id */ export const getLyricData = async (id: number) => { + const musicStore = useMusicStore(); + const settingStore = useSettingStore(); + const statusStore = useStatusStore(); + if (!id) { + statusStore.usingTTMLLyric = false; resetSongLyric(); return; } try { - const musicStore = useMusicStore(); - const settingStore = useSettingStore(); // 检测本地歌词覆盖 const getLyric = getLyricFun(settingStore.localLyricPath, id); const [lyricRes, ttmlContent] = await Promise.all([ @@ -27,7 +30,10 @@ export const getLyricData = async (id: number) => { parsedLyricsData(lyricRes); if (ttmlContent) { const parsedResult = parseTTML(ttmlContent); - if (!parsedResult?.lines?.length) return; + if (!parsedResult?.lines?.length) { + statusStore.usingTTMLLyric = false; + return; + } const ttmlLyric = parseTTMLToAMLL(parsedResult); const ttmlYrcLyric = parseTTMLToYrc(parsedResult); console.log("TTML lyrics:", ttmlLyric, ttmlYrcLyric); @@ -46,10 +52,16 @@ export const getLyricData = async (id: number) => { ...musicStore.songLyric, ...updates, }; + statusStore.usingTTMLLyric = true; + } else { + statusStore.usingTTMLLyric = false; } + } else { + statusStore.usingTTMLLyric = false; } } catch (error) { console.error("❌ Error loading lyrics:", error); + statusStore.usingTTMLLyric = false; resetSongLyric(); } }; diff --git a/src/views/DailySongs.vue b/src/views/DailySongs.vue index 71ae23c..3eb2409 100644 --- a/src/views/DailySongs.vue +++ b/src/views/DailySongs.vue @@ -37,7 +37,13 @@ - +