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

@@ -9,6 +9,8 @@ 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';
type MainTab = 'matches' | 'outright' | 'parlay';
type TimeTab = 'today' | 'early';
@@ -58,6 +60,15 @@ async function loadMatches() {
useOnLocaleChange(loadMatches);
const { pullDistance, refreshing, spinning, progress } = usePullToRefresh({
onRefresh: async () => { await loadMatches(); },
});
const pullIndicatorStyle = () => ({
height: `${pullDistance.value}px`,
opacity: Math.min(pullDistance.value / 48, 1),
});
function dayStart(d: Date) {
const x = new Date(d);
x.setHours(0, 0, 0, 0);
@@ -140,6 +151,13 @@ function goMatch(id: string) {
<template>
<div class="bet-page">
<div
class="pull-indicator"
:style="pullIndicatorStyle()"
>
<GoldSpinner v-if="spinning" :size="28" :progress="progress" :active="spinning" />
</div>
<div class="main-tabs">
<button
type="button"
@@ -171,7 +189,7 @@ function goMatch(id: string) {
</button>
</div>
<template v-if="mainTab === 'matches'">
<div v-show="mainTab === 'matches'">
<div class="time-tabs">
<button
type="button"
@@ -191,8 +209,9 @@ function goMatch(id: string) {
</button>
</div>
<div v-if="loading" class="state">{{ t('bet.loading') }}</div>
<div v-if="loading" class="state">
<GoldSpinner :size="36" />
</div>
<div v-else-if="leagueGroups.length" class="league-list">
<LeagueAccordionItem
v-for="group in leagueGroups"
@@ -211,17 +230,25 @@ function goMatch(id: string) {
<img :src="emptyMatchesImg" alt="" class="empty-icon" />
<p>{{ t('bet.no_matches') }}</p>
</div>
</template>
</div>
<div v-else-if="mainTab === 'outright'" class="outright-tab">
<div v-show="mainTab === 'outright'" class="outright-tab">
<OutrightPanel />
</div>
<ParlayPanel v-else-if="mainTab === 'parlay'" />
<ParlayPanel v-show="mainTab === 'parlay'" />
</div>
</template>
<style scoped>
.pull-indicator {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
transition: height 0.15s ease;
}
.bet-page {
margin: 0 -16px;
padding-bottom: 8px;