feat(admin,api,player): 优胜赛配置、赛事管理重构与玩家端投注体验优化

管理端拆分赛事/优胜赛 Tab,新增联赛优胜赔率面板(批量、排序、外侧删除);统一 list-chrome 工具栏对齐与列表页布局;Dashboard 失败重试、Users 操作下拉、小屏侧栏等体验修复。

API 扩展优胜赛与赛事目录接口,完善投注与钱包查询;玩家端重构赛事卡片、串关面板、注单/钱包页,新增注单详情、下注成功动画与下拉刷新。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-08 09:55:56 +08:00
parent efff7c27e6
commit 24fa1b275c
66 changed files with 6289 additions and 1426 deletions

View File

@@ -16,6 +16,8 @@ import CorrectScoreConfirmModal, {
import { isCorrectScoreMarket, parseScoreCode } from '../utils/correctScoreLayout';
import { useOnLocaleChange } from '../composables/useOnLocaleChange';
import vsImg from '../assets/images/vs.png';
import GoldSpinner from '../components/GoldSpinner.vue';
import BetSuccessOverlay from '../components/BetSuccessOverlay.vue';
import cardBg from '../assets/images/卡片.png';
const heroCardBg = `url(${cardBg})`;
@@ -65,6 +67,7 @@ const expandedKey = ref<string | null>(null);
const correctScoreStakes = ref<Record<string, number>>({});
const placingCs = ref(false);
const csMessage = ref('');
const showCsSuccess = ref(false);
const csConfirmOpen = ref(false);
const csConfirmMarketType = ref<string | null>(null);
const marketsByType = computed(() => {
@@ -188,6 +191,7 @@ async function placeCorrectScoreBets(marketType: string) {
const next = { ...correctScoreStakes.value };
for (const sel of entries) delete next[sel.id];
correctScoreStakes.value = next;
showCsSuccess.value = true;
} catch (e: unknown) {
csMessage.value =
(e as { response?: { data?: { error?: string } } })?.response?.data?.error ||
@@ -270,11 +274,11 @@ function hasSlipPickForMarket(marketType: string) {
</div>
</header>
<div v-if="loading" class="state">{{ t('bet.loading') }}</div>
<div v-if="loading" class="state">
<GoldSpinner :size="36" />
</div>
<template v-else-if="match">
<section class="match-hero">
<p class="kickoff">{{ kickoff }}</p>
<div class="hero-teams">
<!-- home -->
<div class="hero-team">
@@ -325,6 +329,8 @@ function hasSlipPickForMarket(marketType: string) {
<span class="hero-name">{{ match.awayTeamName }}</span>
</div>
</div>
<p class="kickoff">{{ t('bet.kickoff_time') }}{{ kickoff }}</p>
</section>
<section class="markets-section">
@@ -391,6 +397,8 @@ function hasSlipPickForMarket(marketType: string) {
</section>
</template>
</div>
<BetSuccessOverlay :show="showCsSuccess" @done="showCsSuccess = false" />
</template>
<style scoped>
@@ -405,6 +413,12 @@ function hasSlipPickForMarket(marketType: string) {
justify-content: space-between;
padding: 4px 12px 8px;
gap: 8px;
position: sticky;
top: -12px;
z-index: 50;
margin-top: -12px;
background: rgba(0, 0, 0, 0.55);
backdrop-filter: blur(4px);
}
.toolbar-title {
@@ -467,8 +481,9 @@ function hasSlipPickForMarket(marketType: string) {
z-index: 1;
font-size: 11px;
color: var(--text-muted);
text-align: center;
margin-bottom: 14px;
text-align: left;
margin-top: 10px;
padding-left: 2px;
}
.hero-teams {
@@ -615,13 +630,32 @@ function hasSlipPickForMarket(marketType: string) {
}
.market-list {
border-radius: 6px;
overflow: hidden;
background: #111;
display: flex;
flex-direction: column;
gap: 6px;
padding: 4px 0;
}
.market-group + .market-group {
border-top: 1px solid #252525;
.market-group {
border-radius: 8px;
border: 1px solid #252525;
background: linear-gradient(180deg, #1a1a1a 0%, #151515 100%);
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.35);
transition: border-color 0.2s, box-shadow 0.2s;
}
.market-group.open {
border-color: rgba(212, 175, 55, 0.25);
box-shadow: 0 4px 16px rgba(212, 175, 55, 0.08);
}
.market-group :deep(.row) {
border-radius: 8px;
}
.market-group.open :deep(.row) {
border-radius: 0;
}
.market-foot-btn {