feat(player): 完善 H5 投注端与 API 演示数据

- 球赛/串关/优胜冠军、赛事详情、历史投注与个人资料编辑
- 固定顶栏、公告与底栏,仅内容区滚动
- 底部导航与站点 favicon 使用 logo,登录页精简
- API 种子、冠军盘与历史注单增强

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-02 17:18:11 +08:00
parent 7af2e418c3
commit b5dca1bfb1
75 changed files with 7077 additions and 384 deletions

View File

@@ -1,62 +1,59 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import api from '../api';
import BetHistoryCard, { type BetHistoryItem } from '../components/BetHistoryCard.vue';
const bets = ref<{ items: Bet[]; total: number }>({ items: [], total: 0 });
const { t } = useI18n();
interface Bet {
betNo: string;
betType: string;
stake: string;
totalOdds: string;
potentialReturn: string;
actualReturn: string;
status: string;
placedAt: string;
selections: Array<{ selectionNameSnapshot: string; odds: string; resultStatus?: string }>;
}
const bets = ref<{ items: BetHistoryItem[]; total: number }>({ items: [], total: 0 });
const loading = ref(true);
onMounted(load);
async function load() {
const { data } = await api.get('/player/bets');
bets.value = data.data;
loading.value = true;
try {
const { data } = await api.get('/player/bets');
bets.value = data.data ?? { items: [], total: 0 };
} finally {
loading.value = false;
}
}
</script>
<template>
<div>
<h2>我的投注</h2>
<div v-for="bet in bets.items" :key="bet.betNo" class="card bet-card">
<div class="bet-header">
<span class="bet-no">{{ bet.betNo }}</span>
<span :class="['status', bet.status.toLowerCase()]">{{ bet.status }}</span>
</div>
<div v-for="(sel, i) in bet.selections" :key="i" class="sel">
{{ sel.selectionNameSnapshot }} @ {{ sel.odds }}
<span v-if="sel.resultStatus"> {{ sel.resultStatus }}</span>
</div>
<div class="bet-footer">
<span>{{ bet.betType }} · 投注 {{ bet.stake }}</span>
<span v-if="bet.status === 'WON'">返还 {{ bet.actualReturn }}</span>
<span v-else-if="bet.status === 'PENDING'">预计 {{ bet.potentialReturn }}</span>
</div>
<div class="time">{{ new Date(bet.placedAt).toLocaleString() }}</div>
</div>
<div v-if="!bets.items.length" class="empty">暂无投注</div>
<div class="history-page">
<h2 class="page-title">{{ t('nav.bet_history') }}</h2>
<div v-if="loading" class="state">{{ t('bet.loading') }}</div>
<template v-else-if="bets.items.length">
<BetHistoryCard v-for="bet in bets.items" :key="bet.betNo" :bet="bet" />
</template>
<div v-else class="state">{{ t('history.empty') }}</div>
</div>
</template>
<style scoped>
h2 { margin-bottom: 16px; }
.bet-header { display: flex; justify-content: space-between; margin-bottom: 8px; }
.bet-no { font-size: 12px; color: var(--text-muted); }
.status { font-size: 12px; font-weight: 600; }
.status.pending { color: #ff9800; }
.status.won { color: var(--primary); }
.status.lost { color: var(--danger); }
.sel { font-size: 13px; margin-bottom: 4px; }
.bet-footer { font-size: 13px; margin-top: 8px; display: flex; justify-content: space-between; }
.time { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
.empty { text-align: center; color: var(--text-muted); padding: 40px; }
.history-page {
padding-bottom: 8px;
}
.page-title {
font-size: 20px;
font-weight: 800;
color: var(--text);
margin-bottom: 16px;
letter-spacing: 0.02em;
}
.state {
text-align: center;
color: var(--text-muted);
padding: 56px 20px;
font-weight: 600;
font-size: 14px;
}
</style>