feat(admin,api,player): 优胜赛配置、赛事管理重构与玩家端投注体验优化
管理端拆分赛事/优胜赛 Tab,新增联赛优胜赔率面板(批量、排序、外侧删除);统一 list-chrome 工具栏对齐与列表页布局;Dashboard 失败重试、Users 操作下拉、小屏侧栏等体验修复。 API 扩展优胜赛与赛事目录接口,完善投注与钱包查询;玩家端重构赛事卡片、串关面板、注单/钱包页,新增注单详情、下注成功动画与下拉刷新。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user