diff --git a/.gitignore b/.gitignore index 4db262f..b46db1c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ dist.zip .vscode bun.lockb +bun.lock .env.*.local @@ -23,4 +24,4 @@ out .cursorrules -.github/deploy_keys \ No newline at end of file +.github/deploy_keys diff --git a/src/i18n/lang/en-US/comp.ts b/src/i18n/lang/en-US/comp.ts index 2d2b179..5b1e574 100644 --- a/src/i18n/lang/en-US/comp.ts +++ b/src/i18n/lang/en-US/comp.ts @@ -87,5 +87,8 @@ export default { exitApp: 'Exit App', rememberChoice: 'Remember my choice', closeApp: 'Close App' + }, + userPlayList: { + title: "{name}'s Playlist" } }; diff --git a/src/i18n/lang/zh-CN/comp.ts b/src/i18n/lang/zh-CN/comp.ts index f0fddcb..5dd6d9f 100644 --- a/src/i18n/lang/zh-CN/comp.ts +++ b/src/i18n/lang/zh-CN/comp.ts @@ -86,5 +86,8 @@ export default { exitApp: '退出应用', rememberChoice: '记住我的选择', closeApp: '关闭应用' + }, + userPlayList: { + title: '{name}的常听' } }; diff --git a/src/main/index.ts b/src/main/index.ts index 0a4d111..371b7aa 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -106,6 +106,7 @@ if (!isSingleInstance) { // 监听语言切换 ipcMain.on('change-language', (_, locale: Language) => { + console.log('locale',locale) // 更新主进程的语言设置 i18n.global.locale = locale; // 更新托盘菜单 diff --git a/src/main/modules/tray.ts b/src/main/modules/tray.ts index d8e4871..b6f5be5 100644 --- a/src/main/modules/tray.ts +++ b/src/main/modules/tray.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, Menu, nativeImage, Tray } from 'electron'; +import { app, BrowserWindow, Menu, nativeImage, Tray, ipcMain } from 'electron'; import { join } from 'path'; import type { Language } from '../../i18n/main'; @@ -35,9 +35,12 @@ export function updateTrayMenu() { i18n.global.locale = value; // 更新托盘菜单 updateTrayMenu(); - // 通知渲染进程 - const win = BrowserWindow.getAllWindows()[0]; - win?.webContents.send('set-language', value); + // 直接通知主窗口 + const windows = BrowserWindow.getAllWindows(); + for (const win of windows) { + win.webContents.send('language-changed', value); + console.log('向窗口发送语言变更事件:', value); + } } })) }, diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 91f1772..93d0300 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -18,6 +18,7 @@ declare global { onDownloadProgress: (callback: (progress: number, status: string) => void) => void; onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => void; removeDownloadListeners: () => void; + onLanguageChanged: (callback: (locale: string) => void) => void; invoke: (channel: string, ...args: any[]) => Promise; }; $message: any; diff --git a/src/preload/index.ts b/src/preload/index.ts index dea198f..92ba3b2 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -24,6 +24,14 @@ const api = { onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => { ipcRenderer.on('download-complete', (_event, success, filePath) => callback(success, filePath)); }, + // 语言相关 + onLanguageChanged: (callback: (locale: string) => void) => { + console.log('注册语言变更监听器'); + ipcRenderer.on('language-changed', (_event, locale) => { + console.log('收到语言变更事件:', locale); + callback(locale); + }); + }, removeDownloadListeners: () => { ipcRenderer.removeAllListeners('download-progress'); ipcRenderer.removeAllListeners('download-complete'); diff --git a/src/renderer/App.vue b/src/renderer/App.vue index 9a1306c..45f1175 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -57,13 +57,15 @@ watch( } ); -const handleSetLanguage = (_: any, value: string) => { - locale.value = value; - // settingsStore.setLanguage(value); +const handleSetLanguage = (value: string) => { + console.log('应用语言变更:', value); + if (value) { + locale.value = value; + } }; settingsStore.initializeSettings(); -handleSetLanguage(null, settingsStore.setData.language); +handleSetLanguage(settingsStore.setData.language); settingsStore.initializeTheme(); settingsStore.initializeSystemFonts(); if (isMobile.value) { @@ -71,13 +73,12 @@ if (isMobile.value) { } if (isElectron) { - window.electron.ipcRenderer.on('language-changed', handleSetLanguage); + window.api.onLanguageChanged(handleSetLanguage); } onMounted(async () => { // 先初始化播放状态 await playerStore.initializePlayState(); - // 如果有正在播放的音乐,则初始化音频监听器 if (playerStore.playMusic && playerStore.playMusic.id) { // 使用 nextTick 确保 DOM 更新后再初始化 diff --git a/src/renderer/components/common/ArtistDrawer.vue b/src/renderer/components/common/ArtistDrawer.vue index 377efb6..fe14ee6 100644 --- a/src/renderer/components/common/ArtistDrawer.vue +++ b/src/renderer/components/common/ArtistDrawer.vue @@ -144,11 +144,14 @@ watch(modelValue, (newVal) => { }); const loading = ref(false); // 加载歌手信息 + +const previousArtistId = ref(); const loadArtistInfo = async (id: number) => { - if (currentArtistId.value === id) return; + // if (currentArtistId.value === id) return; + if (previousArtistId.value === id) return; activeTab.value = 'songs'; loading.value = true; - currentArtistId.value = id; + previousArtistId.value = id; try { const info = await getArtistDetail(id); if (info.data?.data?.artist) { diff --git a/src/renderer/components/common/PlayBottom.vue b/src/renderer/components/common/PlayBottom.vue index ada3112..62536cb 100644 --- a/src/renderer/components/common/PlayBottom.vue +++ b/src/renderer/components/common/PlayBottom.vue @@ -8,7 +8,7 @@ import { computed } from 'vue'; import { usePlayerStore } from '@/store/modules/player'; const playerStore = usePlayerStore(); -const isPlay = computed(() => playerStore.isPlay); +const isPlay = computed(() => playerStore.playMusicUrl); defineProps({ height: { diff --git a/src/renderer/components/common/SongItem.vue b/src/renderer/components/common/SongItem.vue index f175878..9949d16 100644 --- a/src/renderer/components/common/SongItem.vue +++ b/src/renderer/components/common/SongItem.vue @@ -392,7 +392,8 @@ const toggleSelect = () => { }; const handleArtistClick = (id: number) => { - settingsStore.currentArtistId = id; + settingsStore.setCurrentArtistId(id); + settingsStore.setShowArtistDrawer(true); }; // 获取歌手列表(最多显示5个) diff --git a/src/renderer/components/home/TopBanner.vue b/src/renderer/components/home/TopBanner.vue index 75c0f2a..be390fe 100644 --- a/src/renderer/components/home/TopBanner.vue +++ b/src/renderer/components/home/TopBanner.vue @@ -7,12 +7,11 @@ :space-between="20" draggable show-arrow - autoplay + :autoplay="false" >
-
- - {{ userStore.user?.nickname }}的常听 +
+ {{ t('comp.userPlayList.title', { name: userStore.user?.nickname }) }}
-
+
-
-
-
-
{{ item.name }}
-
- {{ t('common.songCount', { count: item.trackCount }) }} +
+
+ +
+
+
+
{{ item.name }}
+
+
+
{{ t('common.songCount', { count: item.trackCount }) }}
@@ -78,13 +80,13 @@ v-for="(item, index) in hotSingerData?.artists" :key="item.id" :class="setAnimationClass('animate__backInRight')" - :style="setAnimationDelay(index + 1, 100)" - style="width: calc((100% / 5) - 16px)" + :style="getCarouselItemStyle(index + 1, 100, 6)" >
-
- -
-
-
{{ item.name }}
+
{{ item.name }}
+
+ +
+
+
@@ -133,8 +136,7 @@ import { getDayRecommend, getHotSinger } from '@/api/home'; import { getListDetail } from '@/api/list'; import { getUserPlaylist } from '@/api/user'; import MusicList from '@/components/MusicList.vue'; -import router from '@/router'; -import { useUserStore } from '@/store'; +import { useSettingsStore, useUserStore } from '@/store'; import { IDayRecommend } from '@/type/day_recommend'; import { Playlist } from '@/type/list'; import type { IListDetail } from '@/type/listDetail'; @@ -157,6 +159,58 @@ const playlistLoading = ref(false); const playlistItem = ref(null); const playlistDetail = ref(null); +/** + * 获取轮播项的样式 + * @param index 项目索引(用于动画延迟) + * @param delayStep 动画延迟的步长(毫秒) + * @param totalItems 总共分成几等分(默认为5) + * @param maxWidth 最大宽度(可选,单位为px) + * @returns 样式字符串 + */ +const getCarouselItemStyle = ( + index: number, + delayStep: number, + totalItems: number = 5, + maxWidth?: number +) => { + const animationDelay = setAnimationDelay(index, delayStep); + const width = `calc((100% / ${totalItems}) - 16px)`; + const maxWidthStyle = maxWidth ? `max-width: ${maxWidth}px;` : ''; + + return `${animationDelay}; width: ${width}; ${maxWidthStyle}`; +}; + +/** + * 根据歌单数量获取轮播项的样式 + * @param playlistCount 歌单数量 + * @returns 样式字符串 + */ +const getCarouselItemStyleForPlaylist = (playlistCount: number) => { + const animationDelay = setAnimationDelay(1, 100); + let width = ''; + let maxWidth = ''; + + switch(playlistCount) { + case 1: + width = 'calc(100% / 4 - 16px)'; + maxWidth = 'max-width: 180px;'; + break; + case 2: + width = 'calc(100% / 3 - 16px)'; + maxWidth = 'max-width: 380px;'; + break; + case 3: + width = 'calc(100% / 2 - 16px)'; + maxWidth = 'max-width: 520px;'; + break; + default: + width = 'calc(100% / 1 - 16px)'; + maxWidth = 'max-width: 656px;'; + } + + return `${animationDelay}; width: ${width}; ${maxWidth}`; +}; + onMounted(async () => { await loadData(); }); @@ -179,22 +233,21 @@ const loadData = async () => { hotSingerData.value = singerData; if (userStore.user) { const { data: playlistData } = await getUserPlaylist(userStore.user?.userId); + // 确保最多只显示4个歌单,并按播放次数排序 userPlaylist.value = (playlistData.playlist as Playlist[]) .sort((a, b) => b.playCount - a.playCount) - .slice(0, 3); + .slice(0, 4); } } catch (error) { console.error('error', error); } }; -const toSearchSinger = (keyword: string) => { - router.push({ - path: '/search', - query: { - keyword - } - }); +const settingsStore = useSettingsStore(); + +const handleOpenSinger = (id: number) => { + settingsStore.setCurrentArtistId(id); + settingsStore.setShowArtistDrawer(true); }; const toPlaylist = async (id: number) => { @@ -226,63 +279,127 @@ watchEffect(() => { loadData(); } }); + +const getPlaylistGridClass = (length: number) => { + switch(length) { + case 1: + return 'one-column'; + case 2: + return 'two-columns'; + case 3: + return 'three-columns'; + default: + return 'four-columns'; + } +};