feat(player): 新增桌面端 UI 与响应式双端路由架构
- 将原移动端页面重命名为 Mobile*,新增 22 个 Desktop* 视图与 DesktopShell 布局体系 - 路由入口改为 Wrapper 视图,通过 useViewport(≥1024px)自动切换移动/桌面端 - 新增投注单面板、盘口弹层、联赛侧栏、数据表格等桌面组件及独立样式令牌 - 扩展 betSlip store、串关筛选、冠军盘、Toast/悬浮信箱等交互与三语 i18n - 公告/消息 Hub 拆分移动与桌面实现;API 投注/钱包/充值记录支持 pageSize 分页
This commit is contained in:
131
apps/player/src/components/desktop/DesktopShell.vue
Normal file
131
apps/player/src/components/desktop/DesktopShell.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, watch } from 'vue';
|
||||
import { useRoute, RouterView } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import DesktopTopNav from './DesktopTopNav.vue';
|
||||
import DesktopOddsBetPopover from './DesktopOddsBetPopover.vue';
|
||||
import SportsCategoryBar from './SportsCategoryBar.vue';
|
||||
import LeagueSidebar from './LeagueSidebar.vue';
|
||||
import MatchSidebar from './MatchSidebar.vue';
|
||||
import BetSlipPanel from './BetSlipPanel.vue';
|
||||
import FloatingMailbox from '../FloatingMailbox.vue';
|
||||
import { usePlayerHome } from '../../composables/usePlayerHome';
|
||||
import { useAuthStore } from '../../stores/auth';
|
||||
|
||||
const route = useRoute();
|
||||
const { locale } = useI18n();
|
||||
const auth = useAuthStore();
|
||||
const { load: loadPlayerHome } = usePlayerHome();
|
||||
|
||||
const layoutMode = computed<'betting' | 'account' | 'hub' | 'marketing'>(() => {
|
||||
const p = route.path;
|
||||
if (p === '/bet' || p.startsWith('/match/') || p.startsWith('/outright/')) {
|
||||
return 'betting';
|
||||
}
|
||||
if (
|
||||
p === '/bets' ||
|
||||
p.startsWith('/bets/') ||
|
||||
p.startsWith('/wallet') ||
|
||||
p === '/profile/cashbacks'
|
||||
) {
|
||||
return 'account';
|
||||
}
|
||||
if (p.startsWith('/announcements')) {
|
||||
return 'hub';
|
||||
}
|
||||
if (p.startsWith('/messages')) {
|
||||
return 'hub';
|
||||
}
|
||||
return 'marketing';
|
||||
});
|
||||
|
||||
const isMatchDetail = computed(() => route.path.startsWith('/match/'));
|
||||
const isBettingDetail = computed(
|
||||
() => route.path.startsWith('/match/') || route.path.startsWith('/outright/'),
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
void loadPlayerHome(true);
|
||||
});
|
||||
|
||||
watch(locale, (next, prev) => {
|
||||
if (prev && next !== prev) void loadPlayerHome(true);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => auth.token,
|
||||
() => {
|
||||
void loadPlayerHome(true);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="desktop-shell">
|
||||
<header class="desktop-header-wrap">
|
||||
<DesktopTopNav />
|
||||
</header>
|
||||
|
||||
<div class="desktop-main-container">
|
||||
<!-- Betting Layout -->
|
||||
<div v-if="layoutMode === 'betting'" class="desktop-layout-betting-wrap">
|
||||
<div class="desktop-betting-sports-row">
|
||||
<SportsCategoryBar />
|
||||
</div>
|
||||
<div class="desktop-layout-betting">
|
||||
<aside class="desktop-betting-left">
|
||||
<LeagueSidebar v-if="!isMatchDetail" />
|
||||
<MatchSidebar v-else />
|
||||
</aside>
|
||||
<main class="desktop-betting-center" :class="{ 'is-betting-detail': isBettingDetail }">
|
||||
<RouterView v-slot="{ Component, route: viewRoute }">
|
||||
<KeepAlive v-if="viewRoute.meta.keepAlive" :max="10">
|
||||
<component :is="Component" :key="viewRoute.path" />
|
||||
</KeepAlive>
|
||||
<component v-else :is="Component" :key="viewRoute.fullPath" />
|
||||
</RouterView>
|
||||
</main>
|
||||
<aside class="desktop-betting-right">
|
||||
<BetSlipPanel />
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Account Layout -->
|
||||
<div v-else-if="layoutMode === 'account'" class="desktop-layout-account">
|
||||
<main class="desktop-account-center">
|
||||
<RouterView v-slot="{ Component, route: viewRoute }">
|
||||
<KeepAlive v-if="viewRoute.meta.keepAlive" :max="10">
|
||||
<component :is="Component" :key="viewRoute.path" />
|
||||
</KeepAlive>
|
||||
<component v-else :is="Component" :key="viewRoute.fullPath" />
|
||||
</RouterView>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Hub Layout: Messages / Announcements split pane -->
|
||||
<div v-else-if="layoutMode === 'hub'" class="desktop-layout-hub-outer">
|
||||
<RouterView v-slot="{ Component, route: viewRoute }">
|
||||
<KeepAlive v-if="viewRoute.meta.keepAlive" :max="10">
|
||||
<component :is="Component" :key="viewRoute.path" />
|
||||
</KeepAlive>
|
||||
<component v-else :is="Component" :key="viewRoute.fullPath" />
|
||||
</RouterView>
|
||||
</div>
|
||||
|
||||
<!-- Marketing/Single Column Layout -->
|
||||
<div v-else class="desktop-layout-marketing">
|
||||
<main class="desktop-marketing-content">
|
||||
<RouterView v-slot="{ Component, route: viewRoute }">
|
||||
<KeepAlive v-if="viewRoute.meta.keepAlive" :max="10">
|
||||
<component :is="Component" :key="viewRoute.path" />
|
||||
</KeepAlive>
|
||||
<component v-else :is="Component" :key="viewRoute.fullPath" />
|
||||
</RouterView>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<FloatingMailbox />
|
||||
<DesktopOddsBetPopover />
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user