From 6d7ba6dbaef0781ec65398bc00f9d53b55f1a6da Mon Sep 17 00:00:00 2001 From: alger Date: Wed, 22 Oct 2025 21:51:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=20=E6=B7=BB=E5=8A=A0=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E5=92=8C=E4=BA=91=E7=AB=AF=E4=B8=A4=E7=A7=8D=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E6=94=AF=E6=8C=81=E6=AD=8C=E6=9B=B2?= =?UTF-8?q?=E3=80=81=E6=AD=8C=E5=8D=95=E3=80=81=E4=B8=93=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron.vite.config.ts | 3 +- src/i18n/lang/en-US/history.ts | 18 +- src/i18n/lang/ja-JP/history.ts | 10 +- src/i18n/lang/ko-KR/history.ts | 10 +- src/i18n/lang/zh-CN/history.ts | 17 +- src/i18n/lang/zh-Hant/history.ts | 17 +- src/renderer/App.vue | 14 + src/renderer/api/user.ts | 27 + src/renderer/components/common/AlbumItem.vue | 112 ++++ .../components/common/PlaylistItem.vue | 112 ++++ src/renderer/hooks/AlbumHistoryHook.ts | 63 +++ src/renderer/hooks/PlaylistHistoryHook.ts | 65 +++ src/renderer/store/modules/user.ts | 62 +- src/renderer/views/history/index.vue | 529 +++++++++++++++--- src/renderer/views/music/MusicListPage.vue | 51 +- src/renderer/views/user/index.vue | 72 ++- 16 files changed, 1045 insertions(+), 137 deletions(-) create mode 100644 src/renderer/components/common/AlbumItem.vue create mode 100644 src/renderer/components/common/PlaylistItem.vue create mode 100644 src/renderer/hooks/AlbumHistoryHook.ts create mode 100644 src/renderer/hooks/PlaylistHistoryHook.ts diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 82aa7d7..c7d889b 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -40,7 +40,8 @@ export default defineConfig({ ], publicDir: resolve('resources'), server: { - host: '0.0.0.0' + host: '0.0.0.0', + port: 2389 } } }); diff --git a/src/i18n/lang/en-US/history.ts b/src/i18n/lang/en-US/history.ts index 95c9f06..3e5f33a 100644 --- a/src/i18n/lang/en-US/history.ts +++ b/src/i18n/lang/en-US/history.ts @@ -1,5 +1,21 @@ export default { title: 'Play History', playCount: '{count}', - getHistoryFailed: 'Failed to get play history' + getHistoryFailed: 'Failed to get play history', + categoryTabs: { + songs: 'Songs', + playlists: 'Playlists', + albums: 'Albums' + }, + tabs: { + all: 'All Records', + local: 'Local Records', + cloud: 'Cloud Records' + }, + getCloudRecordFailed: 'Failed to get cloud records', + needLogin: 'Please login with cookie to view cloud records', + merging: 'Merging records...', + noDescription: 'No description', + noData: 'No records', + newKey: 'New translation' }; diff --git a/src/i18n/lang/ja-JP/history.ts b/src/i18n/lang/ja-JP/history.ts index db14769..20518a5 100644 --- a/src/i18n/lang/ja-JP/history.ts +++ b/src/i18n/lang/ja-JP/history.ts @@ -1,5 +1,13 @@ export default { title: '再生履歴', playCount: '{count}', - getHistoryFailed: '履歴の取得に失敗しました' + getHistoryFailed: '履歴の取得に失敗しました', + tabs: { + all: 'すべての記録', + local: 'ローカル記録', + cloud: 'クラウド記録' + }, + getCloudRecordFailed: 'クラウド記録の取得に失敗しました', + needLogin: 'cookieを使用してログインしてクラウド記録を表示できます', + merging: '記録を統合中...' }; diff --git a/src/i18n/lang/ko-KR/history.ts b/src/i18n/lang/ko-KR/history.ts index 1449ae5..a44506f 100644 --- a/src/i18n/lang/ko-KR/history.ts +++ b/src/i18n/lang/ko-KR/history.ts @@ -1,5 +1,13 @@ export default { title: '재생 기록', playCount: '{count}', - getHistoryFailed: '기록 가져오기 실패' + getHistoryFailed: '기록 가져오기 실패', + tabs: { + all: '전체 기록', + local: '로컬 기록', + cloud: '클라우드 기록' + }, + getCloudRecordFailed: '클라우드 기록 가져오기 실패', + needLogin: 'cookie를 사용하여 로그인하여 클라우드 기록을 볼 수 있습니다', + merging: '기록 병합 중...' }; diff --git a/src/i18n/lang/zh-CN/history.ts b/src/i18n/lang/zh-CN/history.ts index 78641fa..bbc6c59 100644 --- a/src/i18n/lang/zh-CN/history.ts +++ b/src/i18n/lang/zh-CN/history.ts @@ -1,5 +1,20 @@ export default { title: '播放历史', playCount: '{count}', - getHistoryFailed: '获取历史记录失败' + getHistoryFailed: '获取历史记录失败', + categoryTabs: { + songs: '歌曲', + playlists: '歌单', + albums: '专辑' + }, + tabs: { + all: '全部记录', + local: '本地记录', + cloud: '云端记录' + }, + getCloudRecordFailed: '获取云端记录失败', + needLogin: '请使用cookie登录以查看云端记录', + merging: '正在合并记录...', + noDescription: '暂无描述', + noData: '暂无记录' }; diff --git a/src/i18n/lang/zh-Hant/history.ts b/src/i18n/lang/zh-Hant/history.ts index 5904ca1..8f13d55 100644 --- a/src/i18n/lang/zh-Hant/history.ts +++ b/src/i18n/lang/zh-Hant/history.ts @@ -1,5 +1,20 @@ export default { title: '播放歷史', playCount: '{count}', - getHistoryFailed: '取得歷史記錄失敗' + getHistoryFailed: '取得歷史記錄失敗', + categoryTabs: { + songs: '歌曲', + playlists: '歌單', + albums: '專輯' + }, + tabs: { + all: '全部記錄', + local: '本地記錄', + cloud: '雲端記錄' + }, + getCloudRecordFailed: '取得雲端記錄失敗', + needLogin: '請使用cookie登入以查看雲端記錄', + merging: '正在合併記錄...', + noDescription: '暫無描述', + noData: '暫無記錄' }; diff --git a/src/renderer/App.vue b/src/renderer/App.vue index 9fd344f..4e057e7 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -21,7 +21,9 @@ import { useRouter } from 'vue-router'; import TrafficWarningDrawer from '@/components/TrafficWarningDrawer.vue'; import { usePlayerStore } from '@/store/modules/player'; import { useSettingsStore } from '@/store/modules/settings'; +import { useUserStore } from '@/store/modules/user'; import { isElectron, isLyricWindow } from '@/utils'; +import { checkLoginStatus } from '@/utils/auth'; import { initAudioListeners, initMusicHook } from './hooks/MusicHook'; import { audioService } from './services/audioService'; @@ -31,6 +33,7 @@ import { useAppShortcuts } from './utils/appShortcuts'; const { locale } = useI18n(); const settingsStore = useSettingsStore(); const playerStore = usePlayerStore(); +const userStore = useUserStore(); const router = useRouter(); // 监听语言变化 @@ -72,6 +75,17 @@ if (!isLyricWindow.value) { settingsStore.initializeSettings(); settingsStore.initializeTheme(); settingsStore.initializeSystemFonts(); + + // 初始化登录状态 - 从 localStorage 恢复用户信息和登录类型 + const loginInfo = checkLoginStatus(); + if (loginInfo.isLoggedIn) { + if (loginInfo.user && !userStore.user) { + userStore.setUser(loginInfo.user); + } + if (loginInfo.loginType && !userStore.loginType) { + userStore.setLoginType(loginInfo.loginType); + } + } } handleSetLanguage(settingsStore.setData.language); diff --git a/src/renderer/api/user.ts b/src/renderer/api/user.ts index 89896e3..2776a68 100644 --- a/src/renderer/api/user.ts +++ b/src/renderer/api/user.ts @@ -20,6 +20,33 @@ export function getUserRecord(uid: number, type: number = 0) { } as any); } +// 最近播放-歌曲 +// /record/recent/song +export function getRecentSongs(limit: number = 100) { + return request.get('/record/recent/song', { + params: { limit }, + noRetry: true + } as any); +} + +// 最近播放-歌单 +// /record/recent/playlist +export function getRecentPlaylists(limit: number = 100) { + return request.get('/record/recent/playlist', { + params: { limit }, + noRetry: true + } as any); +} + +// 最近播放-专辑 +// /record/recent/album +export function getRecentAlbums(limit: number = 100) { + return request.get('/record/recent/album', { + params: { limit }, + noRetry: true + } as any); +} + // 获取用户关注列表 // /user/follows?uid=32953014 export function getUserFollows(uid: number, limit: number = 30, offset: number = 0) { diff --git a/src/renderer/components/common/AlbumItem.vue b/src/renderer/components/common/AlbumItem.vue new file mode 100644 index 0000000..92189b4 --- /dev/null +++ b/src/renderer/components/common/AlbumItem.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/src/renderer/components/common/PlaylistItem.vue b/src/renderer/components/common/PlaylistItem.vue new file mode 100644 index 0000000..f754f25 --- /dev/null +++ b/src/renderer/components/common/PlaylistItem.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/src/renderer/hooks/AlbumHistoryHook.ts b/src/renderer/hooks/AlbumHistoryHook.ts new file mode 100644 index 0000000..8e99092 --- /dev/null +++ b/src/renderer/hooks/AlbumHistoryHook.ts @@ -0,0 +1,63 @@ +import { useLocalStorage } from '@vueuse/core'; +import { ref, watch } from 'vue'; + +// 专辑历史记录类型 +export interface AlbumHistoryItem { + id: number; + name: string; + picUrl?: string; + size?: number; // 歌曲数量 + artist?: { + name: string; + id: number; + }; + count?: number; // 播放次数 + lastPlayTime?: number; // 最后播放时间 +} + +export const useAlbumHistory = () => { + const albumHistory = useLocalStorage('albumHistory', []); + + const addAlbum = (album: AlbumHistoryItem) => { + const index = albumHistory.value.findIndex((item) => item.id === album.id); + const now = Date.now(); + + if (index !== -1) { + // 如果已存在,更新播放次数和时间,并移到最前面 + albumHistory.value[index].count = (albumHistory.value[index].count || 0) + 1; + albumHistory.value[index].lastPlayTime = now; + albumHistory.value.unshift(albumHistory.value.splice(index, 1)[0]); + } else { + // 如果不存在,添加新记录 + albumHistory.value.unshift({ + ...album, + count: 1, + lastPlayTime: now + }); + } + }; + + const delAlbum = (album: AlbumHistoryItem) => { + const index = albumHistory.value.findIndex((item) => item.id === album.id); + if (index !== -1) { + albumHistory.value.splice(index, 1); + } + }; + + const albumList = ref(albumHistory.value); + + watch( + () => albumHistory.value, + () => { + albumList.value = albumHistory.value; + }, + { deep: true } + ); + + return { + albumHistory, + albumList, + addAlbum, + delAlbum + }; +}; diff --git a/src/renderer/hooks/PlaylistHistoryHook.ts b/src/renderer/hooks/PlaylistHistoryHook.ts new file mode 100644 index 0000000..260516f --- /dev/null +++ b/src/renderer/hooks/PlaylistHistoryHook.ts @@ -0,0 +1,65 @@ +import { useLocalStorage } from '@vueuse/core'; +import { ref, watch } from 'vue'; + +// 歌单历史记录类型 +export interface PlaylistHistoryItem { + id: number; + name: string; + coverImgUrl?: string; + picUrl?: string; // 兼容字段 + trackCount?: number; + playCount?: number; + creator?: { + nickname: string; + userId: number; + }; + count?: number; // 播放次数 + lastPlayTime?: number; // 最后播放时间 +} + +export const usePlaylistHistory = () => { + const playlistHistory = useLocalStorage('playlistHistory', []); + + const addPlaylist = (playlist: PlaylistHistoryItem) => { + const index = playlistHistory.value.findIndex((item) => item.id === playlist.id); + const now = Date.now(); + + if (index !== -1) { + // 如果已存在,更新播放次数和时间,并移到最前面 + playlistHistory.value[index].count = (playlistHistory.value[index].count || 0) + 1; + playlistHistory.value[index].lastPlayTime = now; + playlistHistory.value.unshift(playlistHistory.value.splice(index, 1)[0]); + } else { + // 如果不存在,添加新记录 + playlistHistory.value.unshift({ + ...playlist, + count: 1, + lastPlayTime: now + }); + } + }; + + const delPlaylist = (playlist: PlaylistHistoryItem) => { + const index = playlistHistory.value.findIndex((item) => item.id === playlist.id); + if (index !== -1) { + playlistHistory.value.splice(index, 1); + } + }; + + const playlistList = ref(playlistHistory.value); + + watch( + () => playlistHistory.value, + () => { + playlistList.value = playlistHistory.value; + }, + { deep: true } + ); + + return { + playlistHistory, + playlistList, + addPlaylist, + delPlaylist + }; +}; diff --git a/src/renderer/store/modules/user.ts b/src/renderer/store/modules/user.ts index 23b3bad..a9a0966 100644 --- a/src/renderer/store/modules/user.ts +++ b/src/renderer/store/modules/user.ts @@ -3,7 +3,7 @@ import { ref } from 'vue'; import { logout } from '@/api/login'; import { getLikedList } from '@/api/music'; -import { getUserAlbumSublist } from '@/api/user'; +import { getUserAlbumSublist, getUserPlaylist } from '@/api/user'; import { clearLoginStatus } from '@/utils/auth'; interface UserData { @@ -30,6 +30,10 @@ export const useUserStore = defineStore('user', () => { const searchType = ref(1); // 收藏的专辑 ID 列表 const collectedAlbumIds = ref>(new Set()); + // 用户的歌单列表 + const playList = ref([]); + // 用户的专辑列表 + const albumList = ref([]); // 方法 const setUser = (userData: UserData) => { @@ -51,6 +55,9 @@ export const useUserStore = defineStore('user', () => { await logout(); user.value = null; loginType.value = null; + collectedAlbumIds.value.clear(); + playList.value = []; + albumList.value = []; clearLoginStatus(); // 刷新 window.location.reload(); @@ -59,6 +66,9 @@ export const useUserStore = defineStore('user', () => { // 即使API调用失败,也要清除本地状态 user.value = null; loginType.value = null; + collectedAlbumIds.value.clear(); + playList.value = []; + albumList.value = []; clearLoginStatus(); window.location.reload(); } @@ -72,7 +82,41 @@ export const useUserStore = defineStore('user', () => { searchType.value = type; }; - // 初始化收藏的专辑列表 + // 初始化歌单列表 + const initializePlaylist = async () => { + if (!user.value) { + playList.value = []; + return; + } + + try { + const { data } = await getUserPlaylist(user.value.userId, 1000, 0); + playList.value = data?.playlist || []; + console.log(`已加载 ${playList.value.length} 个歌单`); + } catch (error) { + console.error('获取歌单列表失败:', error); + playList.value = []; + } + }; + + // 初始化专辑列表 + const initializeAlbumList = async () => { + if (!user.value || !localStorage.getItem('token')) { + albumList.value = []; + return; + } + + try { + const { data } = await getUserAlbumSublist({ limit: 1000, offset: 0 }); + albumList.value = data?.data || []; + console.log(`已加载 ${albumList.value.length} 个收藏专辑`); + } catch (error) { + console.error('获取专辑列表失败:', error); + albumList.value = []; + } + }; + + // 初始化收藏的专辑ID列表 const initializeCollectedAlbums = async () => { if (!user.value || !localStorage.getItem('token')) { collectedAlbumIds.value.clear(); @@ -83,7 +127,7 @@ export const useUserStore = defineStore('user', () => { const { data } = await getUserAlbumSublist({ limit: 1000, offset: 0 }); const albumIds = (data?.data || []).map((album: any) => album.id); collectedAlbumIds.value = new Set(albumIds); - console.log(`已加载 ${albumIds.length} 个收藏专辑`); + console.log(`已加载 ${albumIds.length} 个收藏专辑ID`); } catch (error) { console.error('获取收藏专辑列表失败:', error); collectedAlbumIds.value.clear(); @@ -113,8 +157,12 @@ export const useUserStore = defineStore('user', () => { // 如果用户已登录,获取收藏列表 if (localStorage.getItem('token')) { try { - // 同时初始化收藏专辑列表 - await initializeCollectedAlbums(); + // 并行加载歌单、专辑和收藏ID列表 + await Promise.all([ + initializePlaylist(), + initializeAlbumList(), + initializeCollectedAlbums() + ]); const { data } = await getLikedList(savedUser.userId); return data?.ids || []; @@ -134,6 +182,8 @@ export const useUserStore = defineStore('user', () => { searchValue, searchType, collectedAlbumIds, + playList, + albumList, // 方法 setUser, @@ -142,6 +192,8 @@ export const useUserStore = defineStore('user', () => { setSearchValue, setSearchType, initializeUser, + initializePlaylist, + initializeAlbumList, initializeCollectedAlbums, addCollectedAlbum, removeCollectedAlbum, diff --git a/src/renderer/views/history/index.vue b/src/renderer/views/history/index.vue index bd7935c..b998168 100644 --- a/src/renderer/views/history/index.vue +++ b/src/renderer/views/history/index.vue @@ -3,61 +3,361 @@
{{ t('history.title') }}
+ +
+ + + + + +
+ +
+ + + + +
-
- -
- {{ t('history.playCount', { count: item.count }) }} -
-
- + + + + + + + + + +
+ {{ t('history.noData') }}
-
{{ t('common.noMore') }}
+
+ {{ t('common.noMore') }} +