feat(admin,api,player): 优胜赛配置、赛事管理重构与玩家端投注体验优化
管理端拆分赛事/优胜赛 Tab,新增联赛优胜赔率面板(批量、排序、外侧删除);统一 list-chrome 工具栏对齐与列表页布局;Dashboard 失败重试、Users 操作下拉、小屏侧栏等体验修复。 API 扩展优胜赛与赛事目录接口,完善投注与钱包查询;玩家端重构赛事卡片、串关面板、注单/钱包页,新增注单详情、下注成功动画与下拉刷新。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
useBetFilterOptions,
|
||||
} from '../utils/bet-labels';
|
||||
import { useAdminLocale } from '../composables/useAdminLocale';
|
||||
import AdminTableEmpty from '../components/AdminTableEmpty.vue';
|
||||
|
||||
const { t, localeTag } = useAdminLocale();
|
||||
const { statusOptions, typeOptions } = useBetFilterOptions();
|
||||
@@ -33,6 +34,12 @@ const detailLoading = ref(false);
|
||||
|
||||
onMounted(load);
|
||||
|
||||
function betContentCounts(row: BetListRow) {
|
||||
const singles = row.betType === 'SINGLE' ? 1 : 0;
|
||||
const parlays = row.betType === 'PARLAY' ? 1 : 0;
|
||||
return t('bet.content.bet_counts', { singles, parlays });
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const { data } = await api.get('/admin/bets', {
|
||||
params: {
|
||||
@@ -164,6 +171,9 @@ async function openDetail(row: BetListRow) {
|
||||
<el-card class="data-card" shadow="never">
|
||||
<div class="table-wrap">
|
||||
<el-table :data="bets" stripe class="bets-table">
|
||||
<template #empty>
|
||||
<AdminTableEmpty />
|
||||
</template>
|
||||
<el-table-column prop="id" :label="t('bet.col.serial')" width="64" align="center" />
|
||||
<el-table-column prop="betNo" :label="t('bet.col.bet_no')" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
@@ -179,28 +189,15 @@ async function openDetail(row: BetListRow) {
|
||||
<el-tag type="info" size="small" effect="plain">{{ betTypeLabel(row.betType) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('bet.col.content')" min-width="260">
|
||||
<el-table-column :label="t('bet.col.content')" width="108" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip
|
||||
v-if="row.selectionPreviews?.length"
|
||||
:content="row.selectionSummary || ''"
|
||||
v-if="row.selectionSummary"
|
||||
:content="row.selectionSummary"
|
||||
placement="top"
|
||||
:show-after="300"
|
||||
>
|
||||
<div class="bet-content-cell">
|
||||
<div
|
||||
v-for="(leg, i) in (row.selectionPreviews ?? []).slice(0, 3)"
|
||||
:key="i"
|
||||
class="bet-leg-line"
|
||||
>
|
||||
<span class="bet-match">{{ leg.matchLabel }}</span>
|
||||
<span class="bet-pick">{{ leg.selectionName }}</span>
|
||||
<span class="bet-leg-odds">@{{ leg.odds }}</span>
|
||||
</div>
|
||||
<div v-if="(row.selectionPreviews?.length ?? 0) > 3" class="bet-leg-more">
|
||||
{{ t('bet.legs_more', { n: (row.selectionPreviews?.length ?? 0) - 3 }) }}
|
||||
</div>
|
||||
</div>
|
||||
<span class="bet-content-summary">{{ betContentCounts(row) }}</span>
|
||||
</el-tooltip>
|
||||
<span v-else class="bet-content-empty">—</span>
|
||||
</template>
|
||||
@@ -345,38 +342,10 @@ async function openDetail(row: BetListRow) {
|
||||
min-width: 1380px;
|
||||
}
|
||||
|
||||
.bet-content-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.bet-leg-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 4px 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.bet-match {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.bet-pick {
|
||||
color: var(--green-glow);
|
||||
}
|
||||
|
||||
.bet-leg-odds {
|
||||
color: #888;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.bet-leg-more {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
.bet-content-summary {
|
||||
font-size: 13px;
|
||||
color: #ccc;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bet-content-empty {
|
||||
|
||||
Reference in New Issue
Block a user