This commit is contained in:
wchino
2026-06-13 17:38:25 +08:00
parent e7e938f261
commit 7b33d9f9fa
190 changed files with 23222 additions and 4336 deletions

View File

@@ -12,6 +12,8 @@ export interface SlipItem {
oddsVersion: string;
matchId: string;
matchName: string;
marketId?: string;
marketName?: string;
selectionName: string;
odds: number;
marketType: string;
@@ -19,14 +21,28 @@ export interface SlipItem {
allowParlay?: boolean;
}
export const useBetSlipStore = defineStore('betSlip', () => {
const items = ref<SlipItem[]>([]);
const stake = ref<number>(10);
const mode = ref<'single' | 'parlay'>('single');
const lastParlayError = ref<ParlayRejectReason | 'MAX_LEGS' | null>(null);
export type SlipMode = 'single' | 'parlay';
export type ParlaySlipError = ParlayRejectReason | 'MAX_LEGS' | 'SAME_MATCH';
export const useBetSlipStore = defineStore('betSlip', () => {
const singleItem = ref<SlipItem | null>(null);
const parlayItems = ref<SlipItem[]>([]);
const stake = ref<number>(5);
const mode = ref<SlipMode>('single');
const lastParlayError = ref<ParlaySlipError | null>(null);
const items = computed(() =>
mode.value === 'parlay'
? parlayItems.value
: singleItem.value
? [singleItem.value]
: [],
);
const count = computed(() => items.value.length);
const isParlay = computed(() => mode.value === 'parlay' && items.value.length >= PARLAY_MIN_LEGS);
const parlayCount = computed(() => parlayItems.value.length);
const singleCount = computed(() => (singleItem.value ? 1 : 0));
const totalCount = computed(() => singleCount.value + parlayCount.value);
const isParlay = computed(() => mode.value === 'parlay' && parlayItems.value.length >= PARLAY_MIN_LEGS);
function parseLineValue(v: number | string | null | undefined): number | null {
if (v == null || v === '') return null;
@@ -34,35 +50,33 @@ export const useBetSlipStore = defineStore('betSlip', () => {
return Number.isFinite(n) ? n : null;
}
/** 球赛/详情页:同场可多选,分笔单关;再点同一项可取消 */
function addItem(item: SlipItem) {
if (mode.value === 'parlay') items.value = [];
mode.value = 'single';
function setMode(nextMode: SlipMode) {
mode.value = nextMode;
lastParlayError.value = null;
const existing = items.value.findIndex(
(i) => i.selectionId === item.selectionId,
);
if (existing >= 0) {
items.value.splice(existing, 1);
return;
}
items.value.push(item);
}
/** 串关页:须为不同赛事,每场最多 1 项 */
function addParlayLeg(item: SlipItem): ParlayRejectReason | 'MAX_LEGS' | null {
if (mode.value === 'single') items.value = [];
/** 球赛/详情页:点击任意选项后作为当前单注,打开投注抽屉 */
function setSingleItem(item: SlipItem) {
mode.value = 'single';
singleItem.value = item;
lastParlayError.value = null;
}
function addItem(item: SlipItem) {
setSingleItem(item);
}
/** 串关:必须来自不同赛事,仍按盘口规则过滤不可串关项 */
function addParlayLeg(item: SlipItem): ParlaySlipError | null {
mode.value = 'parlay';
const samePick = items.value.findIndex((i) => i.selectionId === item.selectionId);
const samePick = parlayItems.value.findIndex((i) => i.selectionId === item.selectionId);
if (samePick >= 0) {
items.value.splice(samePick, 1);
lastParlayError.value = null;
return null;
}
if (items.value.some((i) => i.matchId === item.matchId)) {
if (parlayItems.value.some((i) => i.matchId === item.matchId)) {
lastParlayError.value = 'SAME_MATCH';
return 'SAME_MATCH';
}
@@ -77,24 +91,52 @@ export const useBetSlipStore = defineStore('betSlip', () => {
return check.reason;
}
if (items.value.length >= PARLAY_MAX_LEGS) {
if (parlayItems.value.length >= PARLAY_MAX_LEGS) {
lastParlayError.value = 'MAX_LEGS';
return 'MAX_LEGS';
}
items.value.push(item);
parlayItems.value.push(item);
lastParlayError.value = null;
return null;
}
function addSingleToParlay(): ParlaySlipError | null {
if (!singleItem.value) return null;
return addParlayLeg(singleItem.value);
}
function removeItem(selectionId: string) {
items.value = items.value.filter((i) => i.selectionId !== selectionId);
if (!items.value.length) mode.value = 'single';
if (singleItem.value?.selectionId === selectionId) {
singleItem.value = null;
}
parlayItems.value = parlayItems.value.filter((i) => i.selectionId !== selectionId);
if (mode.value === 'parlay' && !parlayItems.value.length) mode.value = 'single';
lastParlayError.value = null;
}
function clearSingle() {
singleItem.value = null;
if (mode.value === 'single') lastParlayError.value = null;
}
function clearParlay() {
parlayItems.value = [];
mode.value = 'single';
lastParlayError.value = null;
}
function clear() {
items.value = [];
if (mode.value === 'parlay') {
clearParlay();
return;
}
clearSingle();
}
function clearAll() {
singleItem.value = null;
parlayItems.value = [];
mode.value = 'single';
lastParlayError.value = null;
}
@@ -111,22 +153,15 @@ export const useBetSlipStore = defineStore('betSlip', () => {
return items.value.reduce((acc, i) => acc + stake.value * i.odds, 0);
});
const hasSameMatch = computed(() => {
const matchIds = items.value.map((i) => i.matchId);
return new Set(matchIds).size !== matchIds.length;
});
const canPlaceParlay = computed(
() =>
mode.value === 'parlay' &&
items.value.length >= PARLAY_MIN_LEGS &&
items.value.length <= PARLAY_MAX_LEGS &&
!hasSameMatch.value,
parlayItems.value.length >= PARLAY_MIN_LEGS &&
parlayItems.value.length <= PARLAY_MAX_LEGS,
);
/** 详情页等同场多笔单关(串关模式不走此路径) */
const canPlaceBatchSingles = computed(
() => mode.value === 'single' && items.value.length >= 1,
() => mode.value === 'single' && singleItem.value != null,
);
const canSubmit = computed(
@@ -144,23 +179,33 @@ export const useBetSlipStore = defineStore('betSlip', () => {
}
return {
singleItem,
parlayItems,
items,
stake,
mode,
singleCount,
parlayCount,
totalCount,
count,
isParlay,
totalOdds,
potentialReturn,
hasSameMatch,
canPlaceParlay,
canPlaceBatchSingles,
canSubmit,
lastParlayError,
drawerOpen,
setMode,
setSingleItem,
addItem,
addParlayLeg,
addSingleToParlay,
removeItem,
clearSingle,
clearParlay,
clear,
clearAll,
openDrawer,
closeDrawer,
};