feat(player-desktop): 优化注单侧栏面板折叠、隐藏首页滚动条、默认选中联赛、主页添加近期热门快速下注

This commit is contained in:
2026-06-30 17:42:50 +08:00
parent 8a1ba0fd95
commit 178787e89a
9 changed files with 583 additions and 90 deletions

View File

@@ -857,6 +857,16 @@ onUnmounted(() => {
<div class="betslip-panel">
<div class="panel-head">
<h3>{{ t('bet.bet_slip') || '投注单' }}</h3>
<button
type="button"
class="panel-close-btn"
@click="slip.collapsePanel()"
:aria-label="t('common.collapse') || '折叠'"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
<path d="M13 17l5-5-5-5M6 17l5-5-5-5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<div class="panel-tabs">
@@ -1173,6 +1183,24 @@ onUnmounted(() => {
color: var(--primary-light);
}
.panel-close-btn {
border: none;
background: transparent;
color: var(--text-muted);
cursor: pointer;
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: background 0.2s, color 0.2s;
}
.panel-close-btn:hover {
background: var(--bg-hover);
color: var(--primary);
}
.clear-slip-btn {
background: transparent;
border: 1px solid var(--border);

View File

@@ -0,0 +1,163 @@
<script setup lang="ts">
import { watch } from 'vue';
import { useI18n } from 'vue-i18n';
import BetSlipPanel from './BetSlipPanel.vue';
import { useBetSlipStore } from '../../stores/betSlip';
const { t } = useI18n();
const slip = useBetSlipStore();
watch(
() => slip.totalCount,
(next, prev) => {
if (next > prev) slip.expandPanel();
},
);
</script>
<template>
<aside
class="desktop-betslip-rail"
:class="{ 'is-collapsed': !slip.panelExpanded }"
:aria-expanded="slip.panelExpanded"
>
<button
v-if="!slip.panelExpanded"
type="button"
class="rail-collapsed-hint"
:aria-label="t('bet.bet_slip')"
@click="slip.expandPanel()"
>
<span v-if="slip.totalCount" class="rail-badge">{{ slip.totalCount > 99 ? '99+' : slip.totalCount }}</span>
<span class="rail-label">{{ t('bet.bet_slip') }}</span>
</button>
<div class="rail-content" :hidden="!slip.panelExpanded">
<BetSlipPanel />
</div>
</aside>
</template>
<style scoped>
.desktop-betslip-rail {
position: relative;
flex-shrink: 0;
width: var(--desktop-right-betslip-w);
min-width: 0;
border-left: 1px solid var(--border);
background: var(--bg-card);
display: flex;
flex-direction: column;
min-height: 0;
transition: width 0.28s cubic-bezier(0.4, 0, 0.2, 1);
overflow: visible;
}
.rail-toggle {
position: absolute;
left: -18px;
top: 50%;
z-index: 30;
width: 36px;
height: 36px;
padding: 0;
border: 1px solid var(--border);
border-radius: 50%;
background: var(--bg-card);
color: var(--primary-light);
cursor: pointer;
display: grid;
place-items: center;
transform: translateY(-50%);
box-shadow:
0 2px 10px rgba(0, 0, 0, 0.5),
0 0 0 3px rgba(20, 20, 20, 0.92);
transition:
transform 0.22s cubic-bezier(0.4, 0, 0.2, 1),
background 0.2s,
color 0.2s,
border-color 0.2s,
box-shadow 0.2s;
}
.rail-toggle-icon {
width: 16px;
height: 16px;
transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}
.rail-toggle.is-collapsed .rail-toggle-icon {
transform: rotate(180deg);
}
.rail-toggle:hover {
border-color: var(--border-gold);
background: var(--primary);
color: #111;
box-shadow:
0 4px 14px rgba(0, 0, 0, 0.6),
0 0 0 3px rgba(212, 175, 55, 0.15);
}
.rail-toggle:active {
transform: translateY(-50%) scale(0.94);
}
.rail-toggle:focus-visible {
outline: 2px solid var(--border-gold);
outline-offset: 2px;
}
.rail-content {
flex: 1;
min-height: 0;
width: var(--desktop-right-betslip-expanded-w, 288px);
overflow: hidden;
}
.desktop-betslip-rail.is-collapsed .rail-content {
visibility: hidden;
}
.rail-collapsed-hint {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
padding: 16px 0;
border: none;
background: transparent;
cursor: pointer;
transition: background 0.2s;
}
.rail-collapsed-hint:hover {
background: var(--bg-hover);
}
.rail-label {
writing-mode: vertical-rl;
text-orientation: mixed;
font-size: 12px;
font-weight: 800;
color: var(--primary);
letter-spacing: 0.12em;
}
.rail-badge {
min-width: 20px;
height: 20px;
padding: 0 5px;
border-radius: 10px;
background: var(--primary, #D4AF37);
color: #111;
font-size: 10px;
font-weight: 800;
line-height: 20px;
text-align: center;
}
</style>

View File

@@ -7,16 +7,28 @@ 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 DesktopBetSlipRail from './DesktopBetSlipRail.vue';
import FloatingMailbox from '../FloatingMailbox.vue';
import { usePlayerHome } from '../../composables/usePlayerHome';
import { useAuthStore } from '../../stores/auth';
import { useBetSlipStore } from '../../stores/betSlip';
const route = useRoute();
const { locale } = useI18n();
const auth = useAuthStore();
const slip = useBetSlipStore();
const { load: loadPlayerHome } = usePlayerHome();
const showBetSlipRail = computed(() => {
const p = route.path;
if (p === '/bets' || p.startsWith('/bets/')) return false;
if (p.startsWith('/wallet') || p.startsWith('/profile')) return false;
if (route.params.id && (p.startsWith('/announcements/') || p.startsWith('/messages/'))) {
return false;
}
return true;
});
const layoutMode = computed<'betting' | 'account' | 'hub' | 'marketing'>(() => {
const p = route.path;
if (p === '/bet' || p.startsWith('/match/') || p.startsWith('/outright/')) {
@@ -61,11 +73,18 @@ watch(
</script>
<template>
<div class="desktop-shell">
<div
class="desktop-shell"
:class="{
'betslip-collapsed': showBetSlipRail && !slip.panelExpanded,
'betslip-hidden': !showBetSlipRail,
}"
>
<header class="desktop-header-wrap">
<DesktopTopNav />
</header>
<div class="desktop-body">
<div class="desktop-main-container">
<!-- Betting Layout -->
<div v-if="layoutMode === 'betting'" class="desktop-layout-betting-wrap">
@@ -85,9 +104,6 @@ watch(
<component v-else :is="Component" :key="viewRoute.fullPath" />
</RouterView>
</main>
<aside class="desktop-betting-right">
<BetSlipPanel />
</aside>
</div>
</div>
@@ -114,7 +130,7 @@ watch(
</div>
<!-- Marketing/Single Column Layout -->
<div v-else class="desktop-layout-marketing">
<div v-else class="desktop-layout-marketing no-scrollbar">
<main class="desktop-marketing-content">
<RouterView v-slot="{ Component, route: viewRoute }">
<KeepAlive v-if="viewRoute.meta.keepAlive" :max="10">
@@ -125,6 +141,8 @@ watch(
</main>
</div>
</div>
<DesktopBetSlipRail v-if="showBetSlipRail" />
</div>
<FloatingMailbox />
<DesktopOddsBetPopover />
</div>

View File

@@ -62,7 +62,9 @@ const availableLeagues = computed(() => {
return Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name));
});
const isSelected = (leagueId: string) => filterState.value.leagueIds.includes(leagueId);
const isSelected = (leagueId: string) => {
return filterState.value.leagueIds.length === 0 || filterState.value.leagueIds.includes(leagueId);
};
</script>
<template>