This commit is contained in:
wchino
2026-06-13 17:38:25 +08:00
parent e7e938f261
commit 7b33d9f9fa
190 changed files with 23222 additions and 4336 deletions

View File

@@ -1,19 +1,21 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { ref, computed, onActivated, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useBetSlipStore } from '../stores/betSlip';
import { usePlayerMatches } from '../composables/usePlayerMatches';
import LeagueAccordionItem from '../components/LeagueAccordionItem.vue';
import OutrightPanel from '../components/outright/OutrightPanel.vue';
import ParlayPanel from '../components/parlay/ParlayPanel.vue';
import emptyMatchesImg from '../assets/images/empty-matches.svg';
import { useOnLocaleChange } from '../composables/useOnLocaleChange';
import GoldSpinner from '../components/GoldSpinner.vue';
import { usePullToRefresh } from '../composables/usePullToRefresh';
import type { MatchPhase } from '../utils/matchPhase';
import {
isAfterLocalToday as isAfterTodayMatchWindow,
isInLocalToday as isInTodayMatchWindow,
} from '@thebet365/shared';
type MainTab = 'matches' | 'outright' | 'parlay';
type MainTab = 'matches' | 'outright';
type TimeTab = 'today' | 'early';
interface Match {
@@ -34,10 +36,18 @@ interface Match {
bettingOpen?: boolean;
matchPhase?: MatchPhase;
score?: {
htHome: number;
htAway: number;
ftHome: number;
ftAway: number;
htHome: number | null;
htAway: number | null;
ftHome: number | null;
ftAway: number | null;
homeCorners?: number | null;
awayCorners?: number | null;
homeYellowCards?: number | null;
awayYellowCards?: number | null;
homeRedCards?: number | null;
awayRedCards?: number | null;
homeCards?: number | null;
awayCards?: number | null;
} | null;
}
@@ -50,17 +60,18 @@ interface LeagueGroup {
const { t } = useI18n();
const router = useRouter();
const slip = useBetSlipStore();
const mainTab = ref<MainTab>('matches');
const timeTab = ref<TimeTab>('early');
const timeTab = ref<TimeTab>('today');
const showAll = ref(false);
const filterNow = ref(new Date());
const { summaryMatches, summaryLoading, loadSummary } = usePlayerMatches();
const matches = summaryMatches;
const loading = summaryLoading;
const expandedLeagues = ref<Set<string>>(new Set());
async function loadMatches() {
filterNow.value = new Date();
await loadSummary(true);
}
@@ -75,26 +86,14 @@ const pullIndicatorStyle = () => ({
opacity: Math.min(pullDistance.value / 48, 1),
});
function dayStart(d: Date) {
const x = new Date(d);
x.setHours(0, 0, 0, 0);
return x;
}
function isKickoffToday(startTime: string) {
const kick = new Date(startTime);
const now = new Date();
const start = dayStart(now);
const end = new Date(start);
end.setDate(end.getDate() + 1);
return kick >= start && kick < end;
}
const filteredMatches = computed(() => {
if (mainTab.value !== 'matches') return [];
const now = filterNow.value;
return matches.value.filter((m) => {
const today = isKickoffToday(m.startTime);
const timeMatch = timeTab.value === 'today' ? today : !today;
const timeMatch =
timeTab.value === 'today'
? isInTodayMatchWindow(m.startTime, now)
: isAfterTodayMatchWindow(m.startTime, now);
if (!timeMatch) return false;
if (!showAll.value && m.matchPhase !== 'open' && m.matchPhase !== undefined) return false;
return true;
@@ -157,6 +156,11 @@ function selectMainTab(tab: MainTab) {
mainTab.value = tab;
}
onActivated(() => {
filterNow.value = new Date();
timeTab.value = 'today';
});
function goMatch(id: string) {
router.push(`/match/${id}`);
}
@@ -190,16 +194,6 @@ function goMatch(id: string) {
<span class="tab-icon">🏆</span>
{{ t('bet.tab_outright') }}
</button>
<button
type="button"
class="main-tab parlay-tab"
:class="{ active: mainTab === 'parlay', 'tab-gold-active': mainTab === 'parlay' }"
@click="selectMainTab('parlay')"
>
<span class="tab-icon">+</span>
{{ t('bet.tab_parlay') }}
<span v-if="slip.count" class="tab-badge">{{ slip.count }}</span>
</button>
</div>
<div v-show="mainTab === 'matches'">
@@ -226,11 +220,12 @@ function goMatch(id: string) {
<button
type="button"
class="phase-toggle"
:class="{ 'phase-toggle--active': showAll }"
:class="{ 'phase-toggle--active': !showAll }"
:aria-pressed="!showAll"
@click="showAll = !showAll"
>
<span class="phase-toggle-dot" />
{{ showAll ? t('bet.show_all_matches') : t('bet.show_open_only') }}
{{ t('bet.show_open_only') }}
</button>
</div>
@@ -259,7 +254,6 @@ function goMatch(id: string) {
<OutrightPanel v-if="mainTab === 'outright'" class="outright-tab" />
<ParlayPanel v-if="mainTab === 'parlay'" />
</div>
</template>
@@ -308,20 +302,6 @@ function goMatch(id: string) {
line-height: 1;
}
.tab-badge {
position: absolute;
top: 4px;
right: 6px;
min-width: 16px;
padding: 0 4px;
border-radius: 8px;
background: #1a1000;
color: var(--primary-light);
font-size: 10px;
font-weight: 800;
line-height: 16px;
}
.time-tabs {
display: flex;
gap: 10px;
@@ -340,7 +320,14 @@ function goMatch(id: string) {
}
.time-tab.active {
background: var(--gradient-gold) !important;
border-color: #fff1a8 !important;
color: #2a1a00 !important;
font-weight: 800;
box-shadow:
0 0 0 1px rgba(255, 244, 200, 0.28) inset,
0 4px 16px rgba(212, 175, 55, 0.28);
text-shadow: 0 1px 0 rgba(255, 252, 235, 0.75);
}
.phase-filter {
@@ -403,10 +390,6 @@ function goMatch(id: string) {
padding: 80px 20px;
}
.parlay-tab.tab-gold-active {
flex: 1.15;
}
.outright-tab {
min-height: 0;
}