fix(settlement): 要求封盘后才能结算并优化预览流程

封盘前禁止录入比分与生成预览;待结算未确认前可解除封盘。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 17:49:34 +08:00
parent 03e72ca9b2
commit e469611138
9 changed files with 210 additions and 54 deletions

View File

@@ -523,10 +523,11 @@ export const adminPagesMs: Record<string, string> = {
'settlement.ht_score': 'Skor separuh masa',
'settlement.ft_score': 'Skor penuh masa',
'settlement.record_score': 'Simpan skor',
'settlement.preview_hint': 'Isi skor dan klik pratonton — skor disimpan secara automatik',
'settlement.preview_hint': 'Pratonton menukar perlawanan ke menunggu penyelesaian (skor disimpan selepas pengesahan; boleh buka semula sebelum itu)',
'settlement.preview_btn': 'Pratonton penyelesaian',
'settlement.preview_failed': 'Gagal menjana pratonton penyelesaian',
'settlement.err_score_not_recorded': 'Sila masukkan skor separuh masa dan penuh masa sebelum penyelesaian',
'settlement.must_close_first': 'Tutup pertaruhan sebelum penyelesaian',
'settlement.preview_title': 'Pratonton penyelesaian',
'settlement.single_count': 'Pertaruhan tunggal',
'settlement.est_payout': 'Anggaran bayaran',

View File

@@ -546,10 +546,11 @@ export const adminPagesZh: Record<string, string> = {
'settlement.ht_score': '半场比分',
'settlement.ft_score': '全场比分',
'settlement.record_score': '录入比分',
'settlement.preview_hint': '填写比分后点击生成预览,系统将自动保存并计算派彩',
'settlement.preview_hint': '填写比分后点击生成预览,赛事将进入待结算并计算派彩(正式比分在确认结算后保存;未确认前仍可解除封盘)',
'settlement.preview_btn': '生成结算预览',
'settlement.preview_failed': '生成结算预览失败',
'settlement.err_score_not_recorded': '请先填写半场与全场比分后再生成预览',
'settlement.must_close_first': '请先封盘后再结算',
'settlement.preview_title': '结算预览',
'settlement.single_count': '单关注单数',
'settlement.preview_pending_bets': '待结算注单',
@@ -1516,10 +1517,11 @@ export const adminPagesEn: Record<string, string> = {
'settlement.ht_score': 'Half-time score',
'settlement.ft_score': 'Full-time score',
'settlement.record_score': 'Save score',
'settlement.preview_hint': 'Enter scores and click preview — scores are saved automatically',
'settlement.preview_hint': 'Preview moves the match to pending settlement and calculates payouts (scores are saved on confirm; you can reopen betting before confirming)',
'settlement.preview_btn': 'Preview settlement',
'settlement.preview_failed': 'Failed to generate settlement preview',
'settlement.err_score_not_recorded': 'Enter half-time and full-time scores before preview',
'settlement.must_close_first': 'Close betting before settlement',
'settlement.preview_title': 'Settlement preview',
'settlement.single_count': 'Single bets',
'settlement.preview_pending_bets': 'Pending bets',

View File

@@ -300,6 +300,15 @@ async function loadMatch() {
router.replace('/matches');
return;
}
const settleable =
detail.status === 'CLOSED' ||
detail.status === 'PENDING_SETTLEMENT' ||
detail.status === 'SETTLED';
if (!settleable) {
ElMessage.warning(t('settlement.must_close_first'));
router.replace('/matches');
return;
}
match.value = detail;
if (detail.score) {
score.value = { ...detail.score };
@@ -372,6 +381,7 @@ async function previewSettlement() {
const itemsPage = data.data.items as PreviewItemsPage;
previewPage.value = itemsPage.page;
previewPageSize.value = itemsPage.pageSize;
await loadMatch();
} catch (e: unknown) {
ElMessage.error(settlementApiError(e, t('settlement.preview_failed')));
} finally {

View File

@@ -166,10 +166,12 @@ function canCloseRow(row: unknown) {
return matchStatus(row) === 'PUBLISHED';
}
function canReopenRow(row: unknown) {
return matchStatus(row) === 'CLOSED';
const s = matchStatus(row);
return s === 'CLOSED' || s === 'PENDING_SETTLEMENT';
}
function canSettleRow(row: unknown) {
return matchStatus(row) !== 'DRAFT';
const s = matchStatus(row);
return s === 'CLOSED' || s === 'PENDING_SETTLEMENT' || s === 'SETTLED';
}
function settleButtonLabel(row: unknown) {
return matchStatus(row) === 'SETTLED' ? t('common.resettle') : t('common.settle');