Files
thebet365/apps/player/src/components/outright/OutrightEventSection.vue
Mars d3c211411b feat(admin+api+player): 优胜赛结算态、禁止新增单场与钱包流水展示优化
- API/管理端:优胜赛 SETTLED 后禁止新增单场,列表与子页展示结算状态
- 玩家端:已结算 outright 只读展示并高亮冠军
- 管理端:结算后 stale 标记驱动列表刷新;财务流水时间与备注 i18n 优化
- shared:txDisplayAmount 与 LEAGUE_OUTRIGHT_SETTLED 错误码
2026-06-23 12:20:40 +08:00

225 lines
4.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import OutrightOptionCard from './OutrightOptionCard.vue';
import saishiImg from '../../assets/images/saishi.webp';
import cardBg from '../../assets/images/card-bg.webp';
const matchCardBg = `url(${cardBg})`;
export interface OutrightSelection {
id: string;
teamCode: string;
teamName: string;
logoUrl?: string | null;
odds: string;
oddsVersion: string;
isWinner?: boolean;
}
export interface OutrightEvent {
id: string;
leagueId: string;
leagueCode?: string;
leagueName: string;
title: string;
status?: string;
bettingOpen?: boolean;
selectionCount?: number;
selections: OutrightSelection[];
}
const props = defineProps<{
event: OutrightEvent;
expanded: boolean;
}>();
const emit = defineEmits<{
toggle: [];
pick: [selection: OutrightSelection];
}>();
const { t } = useI18n();
const headTitle = computed(() => {
const raw = props.event.title.replace(/^\*+/, '').trim();
return raw || props.event.leagueName || t('bet.tab_outright');
});
const headMeta = computed(() => {
const total = props.event.selectionCount ?? props.event.selections.length;
return t('bet.outright_teams_count', { n: total });
});
const isSettled = computed(() => props.event.bettingOpen === false || props.event.status === 'SETTLED');
</script>
<template>
<section class="event-block">
<button type="button" class="event-head" :class="{ 'is-expanded': expanded, 'is-settled': isSettled }" :aria-expanded="expanded" @click="emit('toggle')">
<span class="toggle-icon" :class="{ open: expanded }">
<span class="toggle-mark">{{ expanded ? '' : '+' }}</span>
</span>
<span class="event-head-text">
<span class="event-title-row">
<span class="event-title">{{ headTitle }}</span>
<span v-if="isSettled" class="event-settled-tag">{{ t('bet.outright_settled') }}</span>
</span>
<span v-if="event.leagueName && event.leagueName !== headTitle" class="event-league">
{{ event.leagueName }}
</span>
<span class="event-meta">{{ headMeta }}</span>
</span>
<img :src="saishiImg" alt="" class="event-saishi" />
</button>
<div v-show="expanded" class="options-scroll">
<div class="options-grid">
<OutrightOptionCard
v-for="sel in event.selections"
:key="sel.id"
:team-code="sel.teamCode"
:team-name="sel.teamName"
:logo-url="sel.logoUrl"
:odds="sel.odds"
:disabled="isSettled"
:is-winner="Boolean(sel.isWinner)"
@pick="emit('pick', sel)"
/>
</div>
</div>
</section>
</template>
<style scoped>
.event-block {
position: relative;
isolation: isolate;
margin-bottom: 12px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--bg-card);
overflow: hidden;
}
.event-head {
position: relative;
z-index: 1;
width: 100%;
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: none;
border: none;
border-radius: 0;
text-align: left;
cursor: pointer;
}
.event-head::before {
content: '';
position: absolute;
inset: 0;
background: v-bind(matchCardBg) center / 100% 100% no-repeat;
opacity: 0.25;
z-index: -1;
pointer-events: none;
}
.toggle-icon {
flex-shrink: 0;
width: 26px;
height: 26px;
border-radius: 50%;
background: #141414;
border: 1px solid var(--border-gold-soft);
display: flex;
align-items: center;
justify-content: center;
transition: border-color 0.15s;
}
.toggle-icon.open {
border-color: var(--primary-light);
}
.toggle-mark {
color: var(--primary-light);
font-size: 17px;
font-weight: 900;
line-height: 1;
}
.event-head-text {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.event-title-row {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.event-settled-tag {
flex-shrink: 0;
font-size: 10px;
font-weight: 800;
color: #c9a227;
border: 1px solid rgba(201, 162, 39, 0.45);
border-radius: 999px;
padding: 1px 7px;
line-height: 1.4;
}
.event-title {
font-size: 13px;
font-weight: 800;
color: var(--primary-light);
line-height: 1.35;
}
.event-league {
font-size: 11px;
font-weight: 600;
color: #888;
line-height: 1.3;
}
.event-meta {
font-size: 11px;
font-weight: 600;
color: #666;
}
.event-saishi {
flex-shrink: 0;
height: 44px;
width: auto;
max-width: 40px;
object-fit: contain;
padding-left: 8px;
border-left: 1px solid #2a2a2a;
}
.options-scroll {
max-height: 408px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: 8px 2px 2px;
scrollbar-width: thin;
scrollbar-color: #333 transparent;
}
.options-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 6px;
}
</style>