重构
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, defineAsyncComponent } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { formatApiErrorMessage, isApiErrorCode } from '@thebet365/shared';
|
||||
import api from '../api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { useAdminLocale } from '../composables/useAdminLocale';
|
||||
@@ -70,11 +71,82 @@ interface SettlementBetStats {
|
||||
const { t, locale, localeTag } = useAdminLocale();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const STAT_FACT_LABELS = {
|
||||
homeCorners: {
|
||||
'zh-CN': '主队角球',
|
||||
'en-US': 'Home corners',
|
||||
'ms-MY': 'Sepakan sudut tuan rumah',
|
||||
},
|
||||
awayCorners: {
|
||||
'zh-CN': '客队角球',
|
||||
'en-US': 'Away corners',
|
||||
'ms-MY': 'Sepakan sudut pelawat',
|
||||
},
|
||||
homeCards: {
|
||||
'zh-CN': '主队黄牌/红牌',
|
||||
'en-US': 'Home yellow/red cards',
|
||||
'ms-MY': 'Kad kuning/merah tuan rumah',
|
||||
},
|
||||
awayCards: {
|
||||
'zh-CN': '客队黄牌/红牌',
|
||||
'en-US': 'Away yellow/red cards',
|
||||
'ms-MY': 'Kad kuning/merah pelawat',
|
||||
},
|
||||
} as const;
|
||||
|
||||
const loading = ref(false);
|
||||
const previewing = ref(false);
|
||||
const match = ref<AdminMatchDetail | null>(null);
|
||||
const score = ref({ htHome: 0, htAway: 0, ftHome: 0, ftAway: 0 });
|
||||
const matchStats = ref<{
|
||||
homeCorners: number | null;
|
||||
awayCorners: number | null;
|
||||
homeYellowCards: number | null;
|
||||
awayYellowCards: number | null;
|
||||
homeRedCards: number | null;
|
||||
awayRedCards: number | null;
|
||||
homeCards: number | null;
|
||||
awayCards: number | null;
|
||||
}>(emptyMatchStats());
|
||||
|
||||
function emptyMatchStats() {
|
||||
return {
|
||||
homeCorners: null,
|
||||
awayCorners: null,
|
||||
homeYellowCards: null,
|
||||
awayYellowCards: null,
|
||||
homeRedCards: null,
|
||||
awayRedCards: null,
|
||||
homeCards: null,
|
||||
awayCards: null,
|
||||
};
|
||||
}
|
||||
|
||||
function cardTotalFromBreakdown(
|
||||
yellow: number | null,
|
||||
red: number | null,
|
||||
fallback: number | null,
|
||||
) {
|
||||
const hasBreakdown = yellow != null || red != null;
|
||||
if (!hasBreakdown) return fallback ?? null;
|
||||
if (yellow == null || red == null) return null;
|
||||
return yellow + red;
|
||||
}
|
||||
|
||||
function settlementStatsPayload() {
|
||||
const s = matchStats.value;
|
||||
return {
|
||||
...s,
|
||||
homeCards: cardTotalFromBreakdown(s.homeYellowCards, s.homeRedCards, s.homeCards),
|
||||
awayCards: cardTotalFromBreakdown(s.awayYellowCards, s.awayRedCards, s.awayCards),
|
||||
};
|
||||
}
|
||||
|
||||
function resetSettlementInputs() {
|
||||
score.value = { htHome: 0, htAway: 0, ftHome: 0, ftAway: 0 };
|
||||
matchStats.value = emptyMatchStats();
|
||||
}
|
||||
|
||||
const winnerTeamId = ref('');
|
||||
const outrightSelections = ref<
|
||||
Array<{ teamId: string; teamCode: string; teamZh: string; teamEn: string }>
|
||||
@@ -133,7 +205,7 @@ function buildSettlementPayload(): Record<string, unknown> | null {
|
||||
}
|
||||
return { winnerTeamId: Number(winnerTeamId.value) };
|
||||
}
|
||||
return { ...score.value };
|
||||
return { ...score.value, ...settlementStatsPayload() };
|
||||
}
|
||||
type PreviewItem = {
|
||||
betNo: string;
|
||||
@@ -162,7 +234,7 @@ const previewZeroHint = computed(() => {
|
||||
if (payout > 0 || refund > 0) return '';
|
||||
const pending = Number(p.pendingOtherMatches ?? 0);
|
||||
const wonLegs = Number(p.wonLegsOnMatch ?? 0);
|
||||
if (pending > 0 && wonLegs > 0) {
|
||||
if (pending > 0) {
|
||||
return t('settlement.preview_zero_parlay_hint', { pending: String(pending), legs: String(wonLegs) });
|
||||
}
|
||||
if (Number(p.lostOnThisMatch ?? 0) > 0) {
|
||||
@@ -380,8 +452,19 @@ async function loadMatch() {
|
||||
ftHome: detail.score.ftHome,
|
||||
ftAway: detail.score.ftAway,
|
||||
};
|
||||
matchStats.value = {
|
||||
homeCorners: detail.score.homeCorners ?? null,
|
||||
awayCorners: detail.score.awayCorners ?? null,
|
||||
homeYellowCards: detail.score.homeYellowCards ?? null,
|
||||
awayYellowCards: detail.score.awayYellowCards ?? null,
|
||||
homeRedCards: detail.score.homeRedCards ?? null,
|
||||
awayRedCards: detail.score.awayRedCards ?? null,
|
||||
homeCards: detail.score.homeCards ?? null,
|
||||
awayCards: detail.score.awayCards ?? null,
|
||||
};
|
||||
winnerTeamId.value = detail.score.winnerTeamId ?? '';
|
||||
} else {
|
||||
resetSettlementInputs();
|
||||
winnerTeamId.value = '';
|
||||
}
|
||||
if (detail.isOutright) {
|
||||
@@ -437,7 +520,21 @@ async function confirmResettle() {
|
||||
}
|
||||
|
||||
function settlementApiError(e: unknown, fallback: string) {
|
||||
const err = e as { response?: { data?: { error?: string; message?: string } } };
|
||||
const err = e as { response?: { data?: { code?: unknown; params?: Record<string, string | number>; error?: string; message?: string } } };
|
||||
const code = err.response?.data?.code;
|
||||
if (isApiErrorCode(code)) {
|
||||
const params = { ...(err.response?.data?.params ?? {}) };
|
||||
if (code === 'SETTLEMENT_FACTS_REQUIRED' && typeof params.fields === 'string') {
|
||||
params.fields = params.fields
|
||||
.split(',')
|
||||
.map((field) => {
|
||||
const labels = STAT_FACT_LABELS[field as keyof typeof STAT_FACT_LABELS];
|
||||
return labels?.[locale.value as keyof typeof labels] ?? field;
|
||||
})
|
||||
.join(locale.value === 'zh-CN' ? '、' : ', ');
|
||||
}
|
||||
return formatApiErrorMessage(code, locale.value, params);
|
||||
}
|
||||
const raw = err.response?.data?.error ?? err.response?.data?.message ?? fallback;
|
||||
if (raw === 'Score not recorded' || raw === 'Score not found') {
|
||||
return t('settlement.err_score_not_recorded');
|
||||
@@ -595,6 +692,69 @@ onMounted(() => {
|
||||
<el-input-number v-model="score.ftAway" :min="0" controls-position="right" style="width: 88px" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="score-block compact stat-block">
|
||||
<span class="score-title">{{ t('settlement.corners') }}</span>
|
||||
<div class="stat-inputs">
|
||||
<span class="stat-side">{{ t('settlement.home_stat') }}</span>
|
||||
<el-input-number
|
||||
v-model="matchStats.homeCorners"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 80px"
|
||||
/>
|
||||
<span class="stat-side">{{ t('settlement.away_stat') }}</span>
|
||||
<el-input-number
|
||||
v-model="matchStats.awayCorners"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 80px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="score-block compact stat-block">
|
||||
<span class="score-title">{{ t('settlement.yellow_cards') }}</span>
|
||||
<div class="stat-inputs">
|
||||
<span class="stat-side">{{ t('settlement.home_stat') }}</span>
|
||||
<el-input-number
|
||||
v-model="matchStats.homeYellowCards"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 80px"
|
||||
/>
|
||||
<span class="stat-side">{{ t('settlement.away_stat') }}</span>
|
||||
<el-input-number
|
||||
v-model="matchStats.awayYellowCards"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 80px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="score-block compact stat-block">
|
||||
<span class="score-title">{{ t('settlement.red_cards') }}</span>
|
||||
<div class="stat-inputs">
|
||||
<span class="stat-side">{{ t('settlement.home_stat') }}</span>
|
||||
<el-input-number
|
||||
v-model="matchStats.homeRedCards"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 80px"
|
||||
/>
|
||||
<span class="stat-side">{{ t('settlement.away_stat') }}</span>
|
||||
<el-input-number
|
||||
v-model="matchStats.awayRedCards"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 80px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settle-actions">
|
||||
@@ -873,7 +1033,7 @@ onMounted(() => {
|
||||
flex-wrap: wrap;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #2a2a2a;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.outright-settle-head {
|
||||
@@ -887,7 +1047,7 @@ onMounted(() => {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--green-text);
|
||||
color: var(--success-text);
|
||||
}
|
||||
|
||||
.outright-winner-row {
|
||||
@@ -938,19 +1098,19 @@ onMounted(() => {
|
||||
|
||||
.team-name-inline {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #eee;
|
||||
font-weight: 750;
|
||||
color: var(--text);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kickoff-inline {
|
||||
font-size: 13px;
|
||||
color: #aaa;
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kickoff-inline .meta-k {
|
||||
color: #777;
|
||||
color: #aaa49a;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
@@ -971,6 +1131,22 @@ onMounted(() => {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.stat-block {
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.stat-inputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stat-side {
|
||||
font-size: 11px;
|
||||
color: var(--warning-text);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
@@ -997,7 +1173,7 @@ onMounted(() => {
|
||||
.leg-badge {
|
||||
margin-left: 4px;
|
||||
font-size: 10px;
|
||||
color: var(--gold-text, #dcc078);
|
||||
color: var(--warning-text);
|
||||
}
|
||||
|
||||
.bet-content-cell {
|
||||
@@ -1083,7 +1259,7 @@ onMounted(() => {
|
||||
.vs {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--gold-text, #dcc078);
|
||||
color: var(--warning-text);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -1256,7 +1432,70 @@ onMounted(() => {
|
||||
|
||||
}
|
||||
|
||||
/* 智能比分弹窗样式(功能已关闭,保留便于恢复)
|
||||
.smart-hint { ... }
|
||||
*/
|
||||
.section-title,
|
||||
.preview-title,
|
||||
.preview-bar-title,
|
||||
.pstat-value {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.subsection-title,
|
||||
.bet-content-cell,
|
||||
.match-league,
|
||||
.score-title,
|
||||
.preview-metric-label,
|
||||
.preview-items-title,
|
||||
.period-tag,
|
||||
.empty-hint,
|
||||
.subsection-hint {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.stat-side,
|
||||
.leg-badge,
|
||||
.vs,
|
||||
.preview-metric-orange,
|
||||
.pstat-orange {
|
||||
color: var(--warning-text);
|
||||
}
|
||||
|
||||
.preview-metric-green,
|
||||
.pstat-green {
|
||||
color: var(--success-text);
|
||||
}
|
||||
|
||||
.mini-chart,
|
||||
.pstat {
|
||||
border-color: var(--border);
|
||||
background: #ffffff;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.preview-zero-hint {
|
||||
border-color: var(--warning-border);
|
||||
background: var(--warning-bg);
|
||||
color: var(--warning-text);
|
||||
}
|
||||
|
||||
.score-sep {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.stats-table-block--bets .table-wrap {
|
||||
border-color: var(--border);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.score-row,
|
||||
.preview-bar,
|
||||
.preview-metrics {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.preview-metric {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user