feat: add jackpot animations and enhance currency handling across components

- Introduced new CSS animations for jackpot effects to improve visual engagement.
- Integrated CurrencySwitcher into PlayerPanel and HallScreen for better currency management.
- Updated various components to utilize active player currency for consistent display.
- Enhanced event handling for currency changes to ensure real-time updates across the application.
This commit is contained in:
2026-05-25 14:31:38 +08:00
parent 2bf44e4c29
commit 9bd7cc9b9e
37 changed files with 1030 additions and 180 deletions

View File

@@ -0,0 +1,41 @@
import type { PublicCurrencyRow } from "@/types/api/currency";
import type { PlayerMeData } from "@/types/api/player-me";
import { resolvePlayerCurrency } from "@/lib/player-currency";
export function normalizeCurrencyCode(code: string | null | undefined): string | null {
const trimmed = code?.trim();
return trimmed ? trimmed.toUpperCase() : null;
}
export function listBettableCurrencies(currencies: PublicCurrencyRow[]): PublicCurrencyRow[] {
return currencies.filter((row) => row.is_bettable);
}
/**
* 解析玩家当前应使用的业务币种:持久化选择 → 玩家默认 → 首个可下注币种 → NPR 兜底。
*/
export function pickActivePlayerCurrency(
profile: Pick<PlayerMeData, "default_currency"> | null | undefined,
currencies: PublicCurrencyRow[],
persistedCode: string | null,
): string {
const bettable = listBettableCurrencies(currencies);
const bettableCodes = bettable.map((row) => row.code);
const persisted = normalizeCurrencyCode(persistedCode);
if (persisted && bettableCodes.includes(persisted)) {
return persisted;
}
const fromProfile = normalizeCurrencyCode(profile?.default_currency);
if (fromProfile && bettableCodes.includes(fromProfile)) {
return fromProfile;
}
if (bettable.length > 0) {
return bettable[0].code;
}
return resolvePlayerCurrency(profile);
}

View File

@@ -0,0 +1,38 @@
const STORAGE_KEY = "lottery.player.selected_currency";
export function persistPlayerSelectedCurrency(code: string): void {
try {
localStorage.setItem(STORAGE_KEY, code.trim().toUpperCase());
} catch {
/* ignore quota / private mode */
}
}
export function readPersistedPlayerSelectedCurrency(): string | null {
try {
const raw = localStorage.getItem(STORAGE_KEY);
return raw?.trim() ? raw.trim().toUpperCase() : null;
} catch {
return null;
}
}
export function clearPersistedPlayerSelectedCurrency(): void {
try {
localStorage.removeItem(STORAGE_KEY);
} catch {
/* ignore */
}
}
/** 切换币种后通知各业务模块重新拉取玩法/钱包等数据 */
export const PLAYER_CURRENCY_CHANGE_EVENT = "lottery-currency-change";
export function dispatchPlayerCurrencyChange(code: string): void {
if (typeof window === "undefined") return;
window.dispatchEvent(
new CustomEvent(PLAYER_CURRENCY_CHANGE_EVENT, {
detail: { currency: code.trim().toUpperCase() },
}),
);
}

View File

@@ -1,3 +1,5 @@
import { pickActivePlayerCurrency } from "@/lib/player-currency-options";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import type { PlayerMeData } from "@/types/api/player-me";
import type { WalletBalanceData } from "@/types/api/wallet-balance";
@@ -5,8 +7,7 @@ type CurrencyProfile = Pick<PlayerMeData, "default_currency"> | null | undefined
type CurrencyBalance = Pick<WalletBalanceData, "currency_code"> | null | undefined;
/**
* 玩家端当前业务币种:
* 优先钱包接口返回币种,再回退玩家默认币种,环境变量仅作联调兜底。
* 玩家档案/环境变量层面的默认币种(不含用户在大厅的切换选择)。
*/
export function resolvePlayerCurrency(
profile: CurrencyProfile,
@@ -29,3 +30,15 @@ export function resolvePlayerCurrency(
return "NPR";
}
/**
* 当前业务币种(含玩家在大厅切换的可下注币种)。
* 可在非 React 上下文轮询、WS 刷新)中调用。
*/
export function getActivePlayerCurrencyFromStore(): string {
const { selectedCurrency, profile, currencies } = usePlayerSessionStore.getState();
if (selectedCurrency) {
return selectedCurrency;
}
return pickActivePlayerCurrency(profile, currencies, null);
}