包含 NestJS 后端、三端前端、Prisma 数据模型、结算引擎测试与 PRD 文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
103 lines
2.6 KiB
Vue
103 lines
2.6 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted, computed } from 'vue';
|
|
import { useRoute } from 'vue-router';
|
|
import api from '../api';
|
|
import { useBetSlipStore } from '../stores/betSlip';
|
|
|
|
const route = useRoute();
|
|
const slip = useBetSlipStore();
|
|
const match = ref<MatchDetail | null>(null);
|
|
|
|
interface MatchDetail {
|
|
id: string;
|
|
homeTeamName: string;
|
|
awayTeamName: string;
|
|
startTime: string;
|
|
markets: Market[];
|
|
}
|
|
|
|
interface Market {
|
|
id: string;
|
|
marketType: string;
|
|
period: string;
|
|
lineValue?: string;
|
|
selections: Selection[];
|
|
}
|
|
|
|
interface Selection {
|
|
id: string;
|
|
selectionCode: string;
|
|
selectionName: string;
|
|
odds: string;
|
|
oddsVersion: string;
|
|
}
|
|
|
|
const marketLabels: Record<string, string> = {
|
|
FT_1X2: '全场独赢',
|
|
FT_HANDICAP: '全场让球',
|
|
FT_OVER_UNDER: '全场大小',
|
|
FT_ODD_EVEN: '全场单双',
|
|
HT_1X2: '半场独赢',
|
|
FT_CORRECT_SCORE: '波胆',
|
|
};
|
|
|
|
onMounted(async () => {
|
|
const { data } = await api.get(`/player/matches/${route.params.id}`);
|
|
match.value = data.data;
|
|
});
|
|
|
|
function isSelected(id: string) {
|
|
return slip.items.some((i) => i.selectionId === id);
|
|
}
|
|
|
|
function toggleSelection(sel: Selection, market: Market) {
|
|
if (!match.value) return;
|
|
slip.addItem({
|
|
selectionId: sel.id,
|
|
oddsVersion: sel.oddsVersion,
|
|
matchId: match.value.id,
|
|
matchName: `${match.value.homeTeamName} vs ${match.value.awayTeamName}`,
|
|
selectionName: sel.selectionName,
|
|
odds: parseFloat(sel.odds),
|
|
marketType: market.marketType,
|
|
});
|
|
}
|
|
|
|
const groupedMarkets = computed(() => {
|
|
if (!match.value) return [];
|
|
return match.value.markets;
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="match">
|
|
<div class="match-header card">
|
|
<h2>{{ match.homeTeamName }} vs {{ match.awayTeamName }}</h2>
|
|
<p class="time">{{ new Date(match.startTime).toLocaleString() }}</p>
|
|
</div>
|
|
|
|
<div v-for="market in groupedMarkets" :key="market.id" class="card market-group">
|
|
<h3>{{ marketLabels[market.marketType] || market.marketType }}</h3>
|
|
<div class="selections">
|
|
<button
|
|
v-for="sel in market.selections"
|
|
:key="sel.id"
|
|
class="odds-btn"
|
|
:class="{ selected: isSelected(sel.id) }"
|
|
@click="toggleSelection(sel, market)"
|
|
>
|
|
<div class="label">{{ sel.selectionName }}</div>
|
|
<div class="value">{{ sel.odds }}</div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.match-header h2 { font-size: 18px; margin-bottom: 4px; }
|
|
.time { color: var(--text-muted); font-size: 13px; }
|
|
.market-group h3 { font-size: 14px; margin-bottom: 12px; color: var(--text-muted); }
|
|
.selections { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
</style>
|