fix(admin/player/api): 同步单关串关持久化、仅串关可选与盘口状态展示

1. 后台/API:赛事详情返回 allowSingle/allowParlay,管理端 mapMarkets 正确回读
2. 玩家端:仅关单关时仍可点选加入串关;投注抽屉禁用单关提交并提示
3. 玩家端:盘口暂停/已关闭状态标签;MarketTypeTile 展示 statusLabel

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-17 11:08:43 +08:00
parent d2c0b575b7
commit 78322578d3
10 changed files with 91 additions and 23 deletions

View File

@@ -36,6 +36,7 @@ interface Market {
lineKey?: string | null;
period: string;
lineValue?: string | number | null;
status?: string;
allowSingle?: boolean;
allowParlay?: boolean;
promoLabel?: string | null;
@@ -191,6 +192,28 @@ function marketLabel(market: Market | null | undefined) {
return market?.marketDisplayName?.trim() || market?.marketType || '';
}
function normalizeMarketStatus(status?: string) {
return (status ?? 'OPEN').toUpperCase();
}
function isMarketOpen(market: Market) {
return normalizeMarketStatus(market.status) === 'OPEN';
}
function marketStatusLabel(status?: string) {
const normalized = normalizeMarketStatus(status);
if (normalized === 'SUSPENDED') return t('bet.market_status_suspended');
if (normalized === 'CLOSED') return t('bet.market_status_closed');
return '';
}
function isMarketLocked(market: Market) {
if (!bettingOpen.value || !isMarketOpen(market)) return true;
const singleOk = market.allowSingle !== false;
const parlayOk = market.allowParlay !== false;
return !singleOk && !parlayOk;
}
function selectionLabel(sel: Selection) {
const parsedScore = parseScoreCode(sel.selectionCode, t);
if (parsedScore) return parsedScore.display;
@@ -224,8 +247,7 @@ function isSelected(id: string) {
}
function toggleSelection(sel: Selection, market: Market) {
if (!match.value || !bettingOpen.value) return;
if (market.allowSingle === false) return;
if (!match.value || isMarketLocked(market)) return;
if (!auth.token) {
goLogin();
return;
@@ -244,6 +266,7 @@ function toggleSelection(sel: Selection, market: Market) {
market.lineValue != null && market.lineValue !== ''
? parseFloat(String(market.lineValue))
: null,
allowSingle: market.allowSingle,
allowParlay: market.allowParlay,
});
slip.openDrawer();
@@ -388,24 +411,32 @@ function onPickSelection(selId: string, marketId: string) {
<MarketTypeTile
:label="marketLabel(market)"
:promo-label="market.promoLabel?.trim() || ''"
:status-label="bettingOpen && !isMarketOpen(market) ? marketStatusLabel(market.status) : ''"
:has-market="true"
:expanded="true"
/>
<div class="market-panel-wrap" :class="{ locked: !bettingOpen || market.allowSingle === false }">
<div class="market-panel-wrap" :class="{ locked: isMarketLocked(market) }">
<span v-if="!bettingOpen && matchPhase === 'settled'" class="market-status-tag market-status-tag--settled">{{ phaseLabel }}</span>
<span v-else-if="!bettingOpen" class="market-status-tag market-status-tag--pending">{{ phaseLabel }}</span>
<span
v-else-if="!isMarketOpen(market)"
class="market-status-tag"
:class="normalizeMarketStatus(market.status) === 'CLOSED' ? 'market-status-tag--closed' : 'market-status-tag--suspended'"
>
{{ marketStatusLabel(market.status) }}
</span>
<CorrectScorePanel
v-if="isCorrectScoreMarket(market.marketType)"
:market-type="market.marketType"
:selections="market.selections"
:locked="!bettingOpen || market.allowSingle === false"
:locked="isMarketLocked(market)"
:is-selected="isSelected"
@pick="onPickSelection($event, market.id)"
/>
<MarketSelectionsPanel
v-else
compact
:locked="!bettingOpen || market.allowSingle === false"
:locked="isMarketLocked(market)"
:line-value="market.lineValue"
:selections="market.selections"
:is-selected="isSelected"
@@ -558,6 +589,20 @@ function onPickSelection(selId: string, marketId: string) {
border-left: 1px solid rgba(255, 179, 0, 0.25);
}
.market-status-tag--suspended {
background: linear-gradient(180deg, rgba(240, 180, 41, 0.28) 0%, rgba(120, 80, 10, 0.92) 100%);
color: #ffe08a;
border-bottom: 1px solid rgba(240, 180, 41, 0.45);
border-left: 1px solid rgba(240, 180, 41, 0.45);
}
.market-status-tag--closed {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.12) 0%, rgba(40, 40, 55, 0.95) 100%);
color: #8a9ab8;
border-bottom: 1px solid rgba(120, 140, 180, 0.3);
border-left: 1px solid rgba(120, 140, 180, 0.3);
}
.market-panel-wrap {
position: relative;
overflow: hidden;