feat(i18n): 管理端与玩家端三语支持(中/英/马来语)
- 管理后台 adminT 文案库、结算与代理端页面、表单校验 - 玩家端 vue-i18n 补全首页/公告/串关与 ms 文案 - Element Plus ms 语言包与共享 locale 工具
This commit is contained in:
@@ -4,7 +4,9 @@ import api from '../api';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const token = ref(localStorage.getItem('token') || '');
|
||||
const user = ref(JSON.parse(localStorage.getItem('user') || 'null'));
|
||||
const user = ref<{ id?: string; username?: string; locale?: string } | null>(
|
||||
JSON.parse(localStorage.getItem('user') || 'null'),
|
||||
);
|
||||
|
||||
async function login(username: string, password: string) {
|
||||
const { data } = await api.post('/player/auth/login', { username, password });
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import {
|
||||
PARLAY_MIN_LEGS,
|
||||
PARLAY_MAX_LEGS,
|
||||
canSelectForParlay,
|
||||
type ParlayRejectReason,
|
||||
} from '@thebet365/shared';
|
||||
|
||||
export interface SlipItem {
|
||||
selectionId: string;
|
||||
@@ -9,20 +15,30 @@ export interface SlipItem {
|
||||
selectionName: string;
|
||||
odds: number;
|
||||
marketType: string;
|
||||
lineValue?: number | null;
|
||||
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);
|
||||
|
||||
const count = computed(() => items.value.length);
|
||||
const isParlay = computed(() => mode.value === 'parlay' && items.value.length >= 2);
|
||||
const isParlay = computed(() => mode.value === 'parlay' && items.value.length >= PARLAY_MIN_LEGS);
|
||||
|
||||
function parseLineValue(v: number | string | null | undefined): number | null {
|
||||
if (v == null || v === '') return null;
|
||||
const n = typeof v === 'number' ? v : parseFloat(String(v));
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
/** 球赛/详情页:仅单关,且投注单同时只能有 1 项 */
|
||||
function addItem(item: SlipItem) {
|
||||
if (mode.value === 'parlay') items.value = [];
|
||||
mode.value = 'single';
|
||||
lastParlayError.value = null;
|
||||
|
||||
const existing = items.value.findIndex(
|
||||
(i) => i.selectionId === item.selectionId,
|
||||
@@ -35,27 +51,48 @@ export const useBetSlipStore = defineStore('betSlip', () => {
|
||||
}
|
||||
|
||||
/** 串关页专用:跨场组合,同场只保留一个选项 */
|
||||
function addParlayLeg(item: SlipItem) {
|
||||
function addParlayLeg(item: SlipItem): ParlayRejectReason | 'MAX_LEGS' | null {
|
||||
if (mode.value === 'single') items.value = [];
|
||||
mode.value = 'parlay';
|
||||
|
||||
const samePick = items.value.findIndex((i) => i.selectionId === item.selectionId);
|
||||
if (samePick >= 0) {
|
||||
items.value.splice(samePick, 1);
|
||||
return;
|
||||
lastParlayError.value = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
const check = canSelectForParlay({
|
||||
marketType: item.marketType,
|
||||
lineValue: parseLineValue(item.lineValue),
|
||||
allowParlay: item.allowParlay,
|
||||
});
|
||||
if (!check.ok) {
|
||||
lastParlayError.value = check.reason;
|
||||
return check.reason;
|
||||
}
|
||||
|
||||
if (items.value.length >= PARLAY_MAX_LEGS) {
|
||||
lastParlayError.value = 'MAX_LEGS';
|
||||
return 'MAX_LEGS';
|
||||
}
|
||||
|
||||
items.value = items.value.filter((i) => i.matchId !== item.matchId);
|
||||
items.value.push(item);
|
||||
lastParlayError.value = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
function removeItem(selectionId: string) {
|
||||
items.value = items.value.filter((i) => i.selectionId !== selectionId);
|
||||
if (!items.value.length) mode.value = 'single';
|
||||
lastParlayError.value = null;
|
||||
}
|
||||
|
||||
function clear() {
|
||||
items.value = [];
|
||||
mode.value = 'single';
|
||||
lastParlayError.value = null;
|
||||
}
|
||||
|
||||
const totalOdds = computed(() =>
|
||||
@@ -64,7 +101,7 @@ export const useBetSlipStore = defineStore('betSlip', () => {
|
||||
|
||||
const potentialReturn = computed(() => {
|
||||
if (!items.value.length) return 0;
|
||||
if (mode.value === 'parlay' && items.value.length >= 2) {
|
||||
if (mode.value === 'parlay' && items.value.length >= PARLAY_MIN_LEGS) {
|
||||
return stake.value * totalOdds.value;
|
||||
}
|
||||
return stake.value * items.value[0].odds;
|
||||
@@ -75,6 +112,14 @@ export const useBetSlipStore = defineStore('betSlip', () => {
|
||||
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,
|
||||
);
|
||||
|
||||
const drawerOpen = ref(false);
|
||||
|
||||
function openDrawer() {
|
||||
@@ -94,6 +139,8 @@ export const useBetSlipStore = defineStore('betSlip', () => {
|
||||
totalOdds,
|
||||
potentialReturn,
|
||||
hasSameMatch,
|
||||
canPlaceParlay,
|
||||
lastParlayError,
|
||||
drawerOpen,
|
||||
addItem,
|
||||
addParlayLeg,
|
||||
|
||||
Reference in New Issue
Block a user