feat: split admin dashboard, improve match ops, and player closed-match UX

Admin: add match/player overview sub-nav; refine settlement flow and league
match management UI; improve action button enabled/disabled styles; enhance
logo upload and outright odds sync.

API: expose matchPhase/bettingOpen for closed matches; league publish guards;
settlement preview with auto score save; outright team auto-sync.

Player: watermark for closed/settled states; keep match and bet details visible;
remove default login credentials.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-10 13:00:14 +08:00
parent 6124313369
commit 03f54ca689
43 changed files with 2787 additions and 519 deletions

View File

@@ -3,6 +3,8 @@ import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { formatMoney } from '../utils/localeDisplay';
import StatusWatermark from './StatusWatermark.vue';
import { matchPhaseLabel, matchPhaseVariant, type MatchPhase } from '../utils/matchPhase';
export interface BetScore {
ht: string | null;
@@ -24,6 +26,7 @@ export interface BetHistoryItem {
isParlay?: boolean;
legCount?: number;
matchScore?: BetScore | null;
matchPhase?: MatchPhase | null;
legs?: Array<{
marketLabel: string;
selectionName: string;
@@ -48,6 +51,22 @@ const statusKey = computed(() => {
const statusLabel = computed(() => t(`history.status_${statusKey.value}`));
const watermarkLabel = computed(() => {
if (statusKey.value === 'pending' && props.bet.matchPhase === 'closed_pending') {
return matchPhaseLabel(t, 'closed_pending');
}
if (statusKey.value === 'pending' && props.bet.matchPhase === 'settled') {
return matchPhaseLabel(t, 'settled');
}
return statusLabel.value;
});
const watermarkVariant = computed(() => {
if (statusKey.value === 'pending' && props.bet.matchPhase === 'closed_pending') return 'closed';
if (statusKey.value === 'pending' && props.bet.matchPhase === 'settled') return 'settled';
return statusKey.value;
});
const placedDate = computed(() =>
new Date(props.bet.placedAt).toLocaleDateString(locale.value, {
month: 'short', day: 'numeric',
@@ -87,10 +106,7 @@ function goDetail() {
<template>
<article class="bet-card" @click="goDetail">
<span
class="watermark"
:class="statusKey"
>{{ statusLabel }}</span>
<StatusWatermark :label="watermarkLabel" :variant="watermarkVariant" />
<div class="card-left">
<span class="title">{{ title }}</span>
<span class="subtitle">{{ subtitle }} · {{ placedDate }}</span>
@@ -125,28 +141,6 @@ function goDetail() {
background: rgba(255, 255, 255, 0.02);
}
.watermark {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-35deg);
font-size: 28px;
font-weight: 900;
letter-spacing: 0.06em;
white-space: nowrap;
pointer-events: none;
text-transform: uppercase;
opacity: 0.18;
max-width: 90%;
overflow: hidden;
text-overflow: ellipsis;
}
.watermark.won { color: #3db865; }
.watermark.lost { color: #e05050; }
.watermark.push { color: #888; }
.watermark.pending { color: #e8c84a; }
.card-left {
flex: 1;
min-width: 0;