diff --git a/frontend/index.html b/frontend/index.html index ca8701b..700f3a5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + Game Group V2 diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 7c7181a..7965628 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -3,7 +3,10 @@ import { createApp } from 'vue' import { createPinia } from 'pinia' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' +import Vant from 'vant' +import 'vant/lib/index.css' import './assets/design.css' +import './assets/mobile.css' import * as ElementPlusIconsVue from '@element-plus/icons-vue' import App from './App.vue' import router from './router' @@ -19,5 +22,6 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.use(pinia) app.use(router) app.use(ElementPlus) +app.use(Vant) app.mount('#app') diff --git a/frontend/src/mobile/MobileLayout.vue b/frontend/src/mobile/MobileLayout.vue new file mode 100644 index 0000000..2af1971 --- /dev/null +++ b/frontend/src/mobile/MobileLayout.vue @@ -0,0 +1,157 @@ + + + + + + + diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 082a076..4bd0a4e 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -2,84 +2,144 @@ import { createRouter, createWebHistory } from 'vue-router' import type { RouteRecordRaw } from 'vue-router' import { isAuthenticated } from '@/api/pocketbase' +import { isMobile } from '@/mobile/useDevice' + +// 动态选择布局:手机端用 MobileLayout,桌面端用 Layout +const LayoutComponent = () => + isMobile() + ? import('@/mobile/MobileLayout.vue') + : import('@/views/Layout.vue') + +// 动态选择视图:同一路由名,根据设备加载桌面/手机视图 +function view(desktop: () => Promise, mobile: () => Promise) { + return isMobile() ? mobile : desktop +} // 路由配置 const routes: RouteRecordRaw[] = [ { path: '/login', name: 'Login', - component: () => import('@/views/Login.vue'), + component: view( + () => import('@/views/Login.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 2 替换为 LoginMobile + ), meta: { requiresGuest: true } }, { path: '/register', name: 'Register', - component: () => import('@/views/Register.vue'), + component: view( + () => import('@/views/Register.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 2 替换为 RegisterMobile + ), meta: { requiresGuest: true } }, { path: '/', - component: () => import('@/views/Layout.vue'), + component: LayoutComponent, meta: { requiresAuth: true }, children: [ { path: '', name: 'Home', - component: () => import('@/views/Home.vue') + component: view( + () => import('@/views/Home.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 3 替换为 HomeMobile + ) + }, + { + path: 'mobile-groups', + name: 'MobileGroups', + component: view( + () => import('@/views/Home.vue'), // 桌面端无此路由,回退首页 + () => import('@/views-mobile/Placeholder.vue') // 阶段 3 替换为 GroupsMobile + ) + }, + { + path: 'mobile-notifications', + name: 'MobileNotifications', + component: view( + () => import('@/views/Home.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 3 替换为 NotificationsMobile + ) }, { path: 'group/:id', name: 'GroupView', - component: () => import('@/views/GroupView.vue'), + component: view( + () => import('@/views/GroupView.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 4 替换为 GroupViewMobile + ), props: true }, { path: 'group/:groupId/ledger', name: 'LedgerView', - component: () => import('@/views/LedgerView.vue'), - props: true, - meta: { requiresAuth: true } + component: view( + () => import('@/views/LedgerView.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 8 替换为 LedgerMobile + ), + props: true }, { path: 'group/:groupId/assets', name: 'AssetView', - component: () => import('@/views/AssetView.vue'), - props: true, - meta: { requiresAuth: true } + component: view( + () => import('@/views/AssetView.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 8 替换为 AssetMobile + ), + props: true }, { path: 'group/:groupId/blacklist', name: 'BlacklistView', - component: () => import('@/views/BlacklistView.vue'), - props: true, - meta: { requiresAuth: true } + component: view( + () => import('@/views/BlacklistView.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 8 替换为 BlacklistMobile + ), + props: true }, { path: 'group/:groupId/voice/:sessionId', name: 'VoiceRoom', - component: () => import('@/views/VoiceRoom.vue'), - props: true, - meta: { requiresAuth: true } + component: view( + () => import('@/views/VoiceRoom.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 5 替换为 VoiceRoomMobile + ), + props: true }, { path: 'games', name: 'GamesLibrary', - component: () => import('@/views/GamesLibrary.vue') + component: view( + () => import('@/views/GamesLibrary.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 7 替换为 GamesLibraryMobile + ) }, { path: 'profile', name: 'Profile', - component: () => import('@/views/Profile.vue') + component: view( + () => import('@/views/Profile.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 9 替换为 ProfileMobile + ) }, { path: 'settings', name: 'Settings', - component: () => import('@/views/Settings.vue') + component: view( + () => import('@/views/Settings.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 9 替换为 SettingsMobile + ) }, { path: 'changelog', name: 'Changelog', - component: () => import('@/views/Changelog.vue') + component: view( + () => import('@/views/Changelog.vue'), + () => import('@/views-mobile/Placeholder.vue') // 阶段 9 替换为 ChangelogMobile + ) } ] }, @@ -96,17 +156,15 @@ const router = createRouter({ routes }) -// 路由守卫 +// 路由守卫(与原逻辑一致) router.beforeEach((to, _from, next) => { const authenticated = isAuthenticated() - // 需要登录的页面 if (to.meta.requiresAuth && !authenticated) { next({ name: 'Login', query: { redirect: to.fullPath } }) return } - // 已登录用户访问登录/注册页 if (to.meta.requiresGuest && authenticated) { next({ name: 'Home' }) return