mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-24 08:07:23 +08:00
feat:针对移动端优化
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div class="layout-page">
|
||||
<!-- 移动端使用专用布局(平板模式下使用 PC 布局) -->
|
||||
<mobile-layout v-if="isPhone && !settingsStore.setData?.tabletMode" :is-phone="isPhone" />
|
||||
|
||||
<!-- PC 端 / 浏览器移动端 / 平板模式 保持原有布局 -->
|
||||
<div v-else class="layout-page" :class="{ mobile: settingsStore.isMobile }">
|
||||
<div id="layout-main" class="layout-main">
|
||||
<title-bar />
|
||||
<div class="layout-main-page">
|
||||
@@ -7,7 +11,7 @@
|
||||
<app-menu v-if="!settingsStore.isMobile" class="menu" :menus="menuStore.menus" />
|
||||
<div class="main">
|
||||
<!-- 搜索栏 -->
|
||||
<search-bar />
|
||||
<search-bar class="search-bar" />
|
||||
<!-- 主页面路由 -->
|
||||
<div
|
||||
class="main-content"
|
||||
@@ -25,7 +29,8 @@
|
||||
</router-view>
|
||||
</div>
|
||||
<play-bottom />
|
||||
<app-menu v-if="shouldShowMobileMenu" class="menu" :menus="menuStore.menus" />
|
||||
<!-- 移动端底部菜单(浏览器模拟移动端时使用) -->
|
||||
<app-menu v-if="shouldShowMobileMenu" class="menu mobile-menu" :menus="menuStore.menus" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部音乐播放 -->
|
||||
@@ -42,7 +47,6 @@
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<install-app-modal v-if="!isElectron"></install-app-modal>
|
||||
<update-modal v-if="isElectron" />
|
||||
<playlist-drawer v-model="showPlaylistDrawer" :song-id="currentSongId" />
|
||||
<sleep-timer-top v-if="!settingsStore.isMobile" />
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div id="layout-main" class="mobile-layout mobile" :class="{ 'has-safe-area': isPhone }">
|
||||
<!-- 顶部头部 -->
|
||||
<mobile-header />
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<div
|
||||
class="mobile-content"
|
||||
:class="{ 'has-bottom-menu': shouldShowBottomMenu, 'has-player': isPlay }"
|
||||
>
|
||||
<router-view v-slot="{ Component }" class="mobile-page">
|
||||
<keep-alive :include="keepAliveInclude">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</div>
|
||||
|
||||
<!-- 底部播放条 -->
|
||||
<mobile-play-bar v-if="isPlay" />
|
||||
|
||||
<!-- 底部导航菜单 -->
|
||||
<div v-if="shouldShowBottomMenu" class="mobile-bottom-menu">
|
||||
<app-menu class="mobile-menu" :menus="menuStore.menus" />
|
||||
</div>
|
||||
<!-- 其他弹窗/抽屉 -->
|
||||
<playlist-drawer v-model="showPlaylistDrawer" :song-id="currentSongId" />
|
||||
<playing-list-drawer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, defineAsyncComponent, provide, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import homeRouter from '@/router/home';
|
||||
import otherRouter from '@/router/other';
|
||||
import { useMenuStore } from '@/store/modules/menu';
|
||||
import { usePlayerStore } from '@/store/modules/player';
|
||||
|
||||
import MobileHeader from './components/MobileHeader.vue';
|
||||
|
||||
const AppMenu = defineAsyncComponent(() => import('./components/AppMenu.vue'));
|
||||
const MobilePlayBar = defineAsyncComponent(() => import('@/components/player/MobilePlayBar.vue'));
|
||||
const PlayingListDrawer = defineAsyncComponent(
|
||||
() => import('@/components/player/PlayingListDrawer.vue')
|
||||
);
|
||||
const PlaylistDrawer = defineAsyncComponent(() => import('@/components/common/PlaylistDrawer.vue'));
|
||||
|
||||
const props = defineProps<{
|
||||
isPhone: boolean;
|
||||
}>();
|
||||
|
||||
const route = useRoute();
|
||||
const playerStore = usePlayerStore();
|
||||
const menuStore = useMenuStore();
|
||||
|
||||
// 提供是否有安全区域
|
||||
provide('hasSafeArea', props.isPhone);
|
||||
|
||||
// 是否有播放的歌曲
|
||||
const isPlay = computed(() => playerStore.playMusic && playerStore.playMusic.id);
|
||||
|
||||
// 是否显示底部菜单
|
||||
const shouldShowBottomMenu = computed(() => {
|
||||
const menuPaths = menuStore.menus.map((item: any) => item.path);
|
||||
return menuPaths.includes(route.path) && !playerStore.musicFull;
|
||||
});
|
||||
|
||||
// 提供给 MobilePlayBar 使用,用于调整播放栏位置
|
||||
provide('shouldShowMobileMenu', shouldShowBottomMenu);
|
||||
|
||||
// Keep-alive 配置
|
||||
const keepAliveInclude = computed(() => {
|
||||
const allRoutes = [...homeRouter, ...otherRouter];
|
||||
return allRoutes
|
||||
.filter((item) => item.meta?.keepAlive)
|
||||
.map((item) =>
|
||||
typeof item.name === 'string' ? item.name.charAt(0).toUpperCase() + item.name.slice(1) : ''
|
||||
)
|
||||
.filter(Boolean);
|
||||
});
|
||||
|
||||
// 歌单抽屉
|
||||
const showPlaylistDrawer = ref(false);
|
||||
const currentSongId = ref<number | undefined>();
|
||||
|
||||
// 提供打开歌单抽屉的方法
|
||||
const openPlaylistDrawer = (songId: number, isOpen: boolean = true) => {
|
||||
currentSongId.value = songId;
|
||||
showPlaylistDrawer.value = isOpen;
|
||||
playerStore.setMusicFull(false);
|
||||
playerStore.setPlayListDrawerVisible(!isOpen);
|
||||
};
|
||||
|
||||
provide('openPlaylistDrawer', openPlaylistDrawer);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mobile-layout {
|
||||
@apply w-screen h-screen flex flex-col;
|
||||
@apply bg-light dark:bg-black;
|
||||
@apply overflow-hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mobile-content {
|
||||
@apply flex-1 overflow-auto;
|
||||
|
||||
// // 只有底部菜单
|
||||
// &.has-bottom-menu:not(.has-player) {
|
||||
// padding-bottom: calc(60px + var(--safe-area-inset-bottom, 0px));
|
||||
// }
|
||||
|
||||
// // 只有播放栏
|
||||
// &.has-player:not(.has-bottom-menu) {
|
||||
// padding-bottom: calc(70px + var(--safe-area-inset-bottom, 0px));
|
||||
// }
|
||||
}
|
||||
|
||||
.mobile-page {
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
// 底部菜单固定在底部
|
||||
.mobile-bottom-menu {
|
||||
@apply bg-light dark:bg-black;
|
||||
@apply border-t border-gray-200 dark:border-gray-800;
|
||||
}
|
||||
|
||||
.mobile-menu {
|
||||
@apply w-full;
|
||||
}
|
||||
</style>
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div class="mobile-header" :class="{ 'safe-area-top': hasSafeArea }">
|
||||
<!-- 左侧区域 -->
|
||||
<div class="header-left">
|
||||
<div v-if="showBack" class="header-btn" @click="goBack">
|
||||
<i class="ri-arrow-left-s-line"></i>
|
||||
</div>
|
||||
<div v-else class="header-logo">
|
||||
<span class="logo-text">Alger</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间标题 -->
|
||||
<div class="header-title">
|
||||
<span v-if="title">{{ t(title) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 右侧区域 -->
|
||||
<div class="header-right">
|
||||
<div class="header-btn" @click="openSearch">
|
||||
<i class="ri-search-line"></i>
|
||||
</div>
|
||||
<div class="header-btn" @click="openSettings">
|
||||
<i class="ri-settings-3-line"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
|
||||
// 注入是否有安全区域
|
||||
const hasSafeArea = inject('hasSafeArea', false);
|
||||
|
||||
// 是否显示返回按钮
|
||||
const showBack = computed(() => {
|
||||
return route.meta.back === true;
|
||||
});
|
||||
|
||||
// 页面标题
|
||||
const title = computed(() => {
|
||||
return (route.meta.title as string) || '';
|
||||
});
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
// 打开搜索
|
||||
const openSearch = () => {
|
||||
router.push('/mobile-search');
|
||||
};
|
||||
|
||||
const openSettings = () => {
|
||||
router.push('/set');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mobile-header {
|
||||
@apply flex items-center justify-between px-4 py-3;
|
||||
@apply bg-light dark:bg-black;
|
||||
@apply border-b border-gray-100 dark:border-gray-800;
|
||||
min-height: 56px;
|
||||
|
||||
&.safe-area-top {
|
||||
padding-top: calc(var(--safe-area-inset-top, 0px) + 16px);
|
||||
}
|
||||
}
|
||||
|
||||
.header-left {
|
||||
@apply flex items-center;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
@apply flex items-center;
|
||||
|
||||
.logo-text {
|
||||
@apply text-lg font-bold text-green-500;
|
||||
}
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@apply flex-1 text-center;
|
||||
|
||||
span {
|
||||
@apply text-base font-medium text-gray-900 dark:text-white;
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
@apply flex items-center gap-2;
|
||||
min-width: 80px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
@apply flex items-center justify-center;
|
||||
@apply w-10 h-10 rounded-full;
|
||||
@apply text-xl text-gray-600 dark:text-gray-300;
|
||||
@apply active:bg-gray-100 dark:active:bg-gray-800;
|
||||
@apply transition-colors duration-150;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="search-box flex">
|
||||
<div class="search-box flex search-bar">
|
||||
<div v-if="showBackButton" class="back-button" @click="goBack">
|
||||
<i class="ri-arrow-left-line"></i>
|
||||
</div>
|
||||
@@ -364,14 +364,9 @@ const checkForUpdates = async () => {
|
||||
};
|
||||
|
||||
const toGithubRelease = () => {
|
||||
if (updateInfo.value.hasUpdate) {
|
||||
settingsStore.showUpdateModal = true;
|
||||
} else {
|
||||
window.open('https://github.com/algerkong/AlgerMusicPlayer/releases', '_blank');
|
||||
}
|
||||
window.location.href = 'https://donate.alger.fun/download';
|
||||
};
|
||||
|
||||
// ==================== 搜索建议相关的状态和方法 ====================
|
||||
const suggestions = ref<string[]>([]);
|
||||
const showSuggestions = ref(false);
|
||||
const suggestionsLoading = ref(false);
|
||||
@@ -446,8 +441,6 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// ================================================================
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Reference in New Issue
Block a user