From 8e1259d2aa9dd1a3ecd2d551538e6a9306de887b Mon Sep 17 00:00:00 2001 From: alger Date: Fri, 19 Dec 2025 00:23:24 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E9=92=88=E5=AF=B9=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E7=AB=AF=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/common/DisclaimerModal.vue | 359 ++++++++++++++ .../components/common/MobileUpdateModal.vue | 280 +++++++++++ .../components/lyric/MusicFullMobile.vue | 83 +++- .../components/player/MobilePlayBar.vue | 208 ++------ .../player/MobilePlayerSettings.vue | 363 ++++++++++++++ src/renderer/index.css | 4 + src/renderer/layout/AppLayout.vue | 23 +- src/renderer/layout/MobileLayout.vue | 133 +++++ src/renderer/layout/components/AppMenu.vue | 4 +- .../layout/components/MobileHeader.vue | 113 +++++ src/renderer/layout/components/SearchBar.vue | 11 +- src/renderer/router/home.ts | 6 +- src/renderer/router/other.ts | 22 + src/renderer/views/history/index.vue | 18 +- .../views/historyAndFavorite/index.vue | 3 +- src/renderer/views/home/index.vue | 2 +- .../views/mobile-search-result/index.vue | 464 ++++++++++++++++++ src/renderer/views/mobile-search/index.vue | 392 +++++++++++++++ 18 files changed, 2299 insertions(+), 189 deletions(-) create mode 100644 src/renderer/components/common/DisclaimerModal.vue create mode 100644 src/renderer/components/common/MobileUpdateModal.vue create mode 100644 src/renderer/components/player/MobilePlayerSettings.vue create mode 100644 src/renderer/layout/MobileLayout.vue create mode 100644 src/renderer/layout/components/MobileHeader.vue create mode 100644 src/renderer/views/mobile-search-result/index.vue create mode 100644 src/renderer/views/mobile-search/index.vue diff --git a/src/renderer/components/common/DisclaimerModal.vue b/src/renderer/components/common/DisclaimerModal.vue new file mode 100644 index 0000000..56b39d0 --- /dev/null +++ b/src/renderer/components/common/DisclaimerModal.vue @@ -0,0 +1,359 @@ + + + + + diff --git a/src/renderer/components/common/MobileUpdateModal.vue b/src/renderer/components/common/MobileUpdateModal.vue new file mode 100644 index 0000000..ca689b4 --- /dev/null +++ b/src/renderer/components/common/MobileUpdateModal.vue @@ -0,0 +1,280 @@ + + + + + diff --git a/src/renderer/components/lyric/MusicFullMobile.vue b/src/renderer/components/lyric/MusicFullMobile.vue index e4d47f7..9068c46 100644 --- a/src/renderer/components/lyric/MusicFullMobile.vue +++ b/src/renderer/components/lyric/MusicFullMobile.vue @@ -21,13 +21,38 @@
+ +
+ +
+ + {{ sleepTimerDisplayText }} +
+
+ +
+
+ + + +
@@ -368,6 +393,7 @@ import { useWindowSize } from '@vueuse/core'; import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'; import { useI18n } from 'vue-i18n'; +import MobilePlayerSettings from '@/components/player/MobilePlayerSettings.vue'; import { allTime, artistList, @@ -396,6 +422,55 @@ const playerStore = usePlayerStore(); const play = computed(() => playerStore.isPlay); const playIcon = computed(() => (play.value ? 'ri-pause-fill' : 'ri-play-fill')); +// 播放设置弹窗 +const showPlayerSettings = ref(false); + +// 定时器相关 +const sleepTimerRefresh = ref(0); +let sleepTimerInterval: ReturnType | null = null; + +const hasSleepTimerActive = computed(() => playerStore.hasSleepTimerActive); + +const sleepTimerDisplayText = computed(() => { + void sleepTimerRefresh.value; // 触发响应式更新 + + const timer = playerStore.sleepTimer; + if (timer.type === 'time' && timer.endTime) { + const remaining = Math.max(0, timer.endTime - Date.now()); + const totalSeconds = Math.floor(remaining / 1000); + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + return `${minutes}:${seconds.toString().padStart(2, '0')}`; + } + if (timer.type === 'songs' && timer.remainingSongs) { + return `${timer.remainingSongs}首`; + } + if (timer.type === 'end') { + return '列表结束'; + } + return ''; +}); + +// 启动/停止定时器刷新 +watch( + hasSleepTimerActive, + (active) => { + if (active && playerStore.sleepTimer.type === 'time') { + if (!sleepTimerInterval) { + sleepTimerInterval = setInterval(() => { + sleepTimerRefresh.value = Date.now(); + }, 1000); + } + } else { + if (sleepTimerInterval) { + clearInterval(sleepTimerInterval); + sleepTimerInterval = null; + } + } + }, + { immediate: true } +); + // 播放模式 const { playMode, playModeIcon, playModeText, togglePlayMode: togglePlayModeBase } = usePlayMode(); // 打开播放列表 @@ -443,9 +518,11 @@ watch(isLandscape, (newVal) => { } }); +// 显示全屏歌词 // 显示全屏歌词 const showFullLyricScreen = () => { showFullLyrics.value = true; + // 使用多次延迟尝试滚动,确保能够滚动到当前歌词 nextTick(() => { scrollToCurrentLyric(true); @@ -1754,9 +1831,8 @@ const getWordStyle = (lineIndex: number, _wordIndex: number, word: any) => { } .fullscreen-header { - @apply pt-8 pb-4 px-6 flex flex-col items-center fixed top-0 left-0 w-full z-10; + @apply pt-16 pb-4 px-6 flex flex-col items-center fixed top-0 left-0 w-full z-10; background: linear-gradient(to bottom, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0) 100%); - height: 100px; pointer-events: auto; .song-title { @@ -1817,6 +1893,7 @@ const getWordStyle = (lineIndex: number, _wordIndex: number, word: any) => { @apply w-9 h-9 flex items-center justify-center rounded cursor-pointer transition-all duration-300 z-[9999]; background: rgba(142, 142, 142, 0.192); backdrop-filter: blur(12px); + top: calc(var(--safe-area-inset-top, 0) + 20px); i { @apply text-xl; diff --git a/src/renderer/components/player/MobilePlayBar.vue b/src/renderer/components/player/MobilePlayBar.vue index dcacebe..9fbcc0a 100644 --- a/src/renderer/components/player/MobilePlayBar.vue +++ b/src/renderer/components/player/MobilePlayBar.vue @@ -1,13 +1,14 @@ + + diff --git a/src/renderer/index.css b/src/renderer/index.css index 0555b7e..db9cf78 100644 --- a/src/renderer/index.css +++ b/src/renderer/index.css @@ -64,6 +64,10 @@ :root { --text-color: #000000dd; + --safe-area-inset-top: 0px; + --safe-area-inset-right: 0px; + --safe-area-inset-bottom: 10px; + --safe-area-inset-left: 0px; } :root[class='dark'] { diff --git a/src/renderer/layout/AppLayout.vue b/src/renderer/layout/AppLayout.vue index fb6f302..5d0057c 100644 --- a/src/renderer/layout/AppLayout.vue +++ b/src/renderer/layout/AppLayout.vue @@ -1,5 +1,9 @@
- @@ -65,7 +69,6 @@ import { computed, defineAsyncComponent, onMounted, provide, ref } from 'vue'; import { useRoute } from 'vue-router'; import DownloadDrawer from '@/components/common/DownloadDrawer.vue'; -import InstallAppModal from '@/components/common/InstallAppModal.vue'; import PlayBottom from '@/components/common/PlayBottom.vue'; import UpdateModal from '@/components/common/UpdateModal.vue'; import SleepTimerTop from '@/components/player/SleepTimerTop.vue'; @@ -76,6 +79,9 @@ import { usePlayerStore } from '@/store/modules/player'; import { useSettingsStore } from '@/store/modules/settings'; import { isElectron } from '@/utils'; +// 移动端专用布局 +import MobileLayout from './MobileLayout.vue'; + const keepAliveInclude = computed(() => { const allRoutes = [...homeRouter, ...otherRouter]; @@ -118,6 +124,9 @@ const shouldShowMobileMenu = computed(() => { provide('shouldShowMobileMenu', shouldShowMobileMenu); +// 使用 settingsStore.isMobile 进行移动端检测而不是 Capacitor 设备检测 +const isPhone = computed(() => settingsStore.isMobile); + onMounted(() => { settingsStore.initializeSettings(); settingsStore.initializeTheme(); @@ -144,7 +153,7 @@ provide('openPlaylistDrawer', openPlaylistDrawer); } .layout-main { - @apply w-full h-full relative text-gray-900 dark:text-white; + @apply w-full h-full relative text-gray-900 dark:text-white; } .layout-main-page { @@ -173,10 +182,12 @@ provide('openPlaylistDrawer', openPlaylistDrawer); overflow: auto; display: block; flex: none; + position: relative; } .mobile-content { height: calc(100vh - 75px); + position: relative; } } diff --git a/src/renderer/layout/MobileLayout.vue b/src/renderer/layout/MobileLayout.vue new file mode 100644 index 0000000..ea503f2 --- /dev/null +++ b/src/renderer/layout/MobileLayout.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/src/renderer/layout/components/AppMenu.vue b/src/renderer/layout/components/AppMenu.vue index 7cc7af8..0ba4b05 100644 --- a/src/renderer/layout/components/AppMenu.vue +++ b/src/renderer/layout/components/AppMenu.vue @@ -172,9 +172,11 @@ const toggleMenu = () => { .app-menu { max-width: 100%; width: 100vw; - position: fixed; + position: relative; bottom: 0; left: 0; + z-index: 99; + @apply bg-light dark:bg-black border-t border-gray-200 dark:border-gray-700; z-index: 99999; @apply bg-light dark:bg-black border-none border-gray-200 dark:border-gray-700; diff --git a/src/renderer/layout/components/MobileHeader.vue b/src/renderer/layout/components/MobileHeader.vue new file mode 100644 index 0000000..894fc47 --- /dev/null +++ b/src/renderer/layout/components/MobileHeader.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/renderer/layout/components/SearchBar.vue b/src/renderer/layout/components/SearchBar.vue index a1aa6b0..ba51d96 100644 --- a/src/renderer/layout/components/SearchBar.vue +++ b/src/renderer/layout/components/SearchBar.vue @@ -1,5 +1,5 @@