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

@@ -13,6 +13,7 @@ const props = defineProps<{
oddsVersion: string;
}>;
stakes: Record<string, number>;
locked?: boolean;
}>();
const emit = defineEmits<{
@@ -26,6 +27,7 @@ const columns = computed(() =>
);
function setStake(sel: CsSelection, raw: string) {
if (props.locked) return;
const n = Math.max(0, Number(raw) || 0);
emit('update:stakes', { ...props.stakes, [sel.id]: n });
}
@@ -37,7 +39,7 @@ function formatOdds(odds: string) {
</script>
<template>
<div class="cs-panel">
<div class="cs-panel" :class="{ 'cs-panel--locked': locked }">
<div class="cols-head">
<span>{{ t('bet.col_home') }}</span>
<span>{{ t('bet.col_draw') }}</span>
@@ -57,6 +59,7 @@ function formatOdds(odds: string) {
inputmode="decimal"
:placeholder="t('bet.stake_placeholder')"
:value="stakes[sel.id] ?? ''"
:disabled="locked"
@input="setStake(sel, ($event.target as HTMLInputElement).value)"
/>
</div>
@@ -70,6 +73,14 @@ function formatOdds(odds: string) {
padding: 6px 8px 0;
}
.cs-panel--locked {
opacity: 0.72;
}
.cs-panel--locked .stake-input {
cursor: not-allowed;
}
.cols-head {
display: grid;
grid-template-columns: repeat(3, 1fr);

View File

@@ -11,6 +11,7 @@ const props = defineProps<{
}[];
isSelected: (id: string) => boolean;
compact?: boolean;
locked?: boolean;
}>();
const emit = defineEmits<{ pick: [id: string] }>();
@@ -22,18 +23,24 @@ function label(sel: (typeof props.selections)[number]) {
}
return sel.selectionName;
}
function onPick(id: string) {
if (props.locked) return;
emit('pick', id);
}
</script>
<template>
<div class="wrap" :class="{ compact }">
<div class="wrap" :class="{ compact, locked }">
<div class="panel">
<button
v-for="sel in selections"
:key="sel.id"
type="button"
class="odds-btn"
:class="{ selected: isSelected(sel.id) }"
@click="emit('pick', sel.id)"
:class="{ selected: isSelected(sel.id), 'odds-btn--locked': locked }"
:disabled="locked"
@click="onPick(sel.id)"
>
<span class="label">{{ label(sel) }}</span>
<span class="odds">{{ sel.odds }}</span>
@@ -48,6 +55,10 @@ function label(sel: (typeof props.selections)[number]) {
background: #0c0c0c;
}
.wrap.locked {
opacity: 0.78;
}
.panel {
display: grid;
grid-template-columns: repeat(3, 1fr);
@@ -73,6 +84,17 @@ function label(sel: (typeof props.selections)[number]) {
background: rgba(212, 175, 55, 0.1);
}
.odds-btn--locked {
background: #111;
border-color: #333;
cursor: not-allowed;
}
.odds-btn--locked .label,
.odds-btn--locked .odds {
color: #666;
}
.label {
font-size: 9px;
color: var(--text-muted);