feat: 新增首页和图片资源
This commit is contained in:
1
src/store/auth/index.ts
Normal file
1
src/store/auth/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './auth-store'
|
||||
171
src/store/game/game-round-store.ts
Normal file
171
src/store/game/game-round-store.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
import type {
|
||||
BetSelection,
|
||||
Chip,
|
||||
GameBootstrapSnapshot,
|
||||
GameCell,
|
||||
HistoryEntry,
|
||||
RoundPhase,
|
||||
RoundSnapshot,
|
||||
TrendEntry,
|
||||
} from '@/features/game/shared'
|
||||
import {
|
||||
buildGameCellViewModels,
|
||||
createMockGameBootstrapSnapshot,
|
||||
DEFAULT_ACTIVE_CHIP_ID,
|
||||
getChipById,
|
||||
getRecentWinningCellIds,
|
||||
getSelectionTotal,
|
||||
groupSelectionsByCell,
|
||||
} from '@/features/game/shared'
|
||||
|
||||
type GameRoundSlice = Pick<
|
||||
GameBootstrapSnapshot,
|
||||
'cells' | 'chips' | 'history' | 'round' | 'selections' | 'trends'
|
||||
>
|
||||
|
||||
export interface GameRoundStoreState extends GameRoundSlice {
|
||||
activeChipId: string
|
||||
clearSelections: () => void
|
||||
hydrateRound: (snapshot: GameRoundSlice) => void
|
||||
placeBet: (cellId: number) => void
|
||||
removeSelectionsForCell: (cellId: number) => void
|
||||
selectChip: (chipId: string) => void
|
||||
setPhase: (phase: RoundPhase) => void
|
||||
syncRound: (round: Partial<RoundSnapshot>) => void
|
||||
upsertSelections: (selections: BetSelection[]) => void
|
||||
}
|
||||
|
||||
function createInitialRoundState(): GameRoundSlice & { activeChipId: string } {
|
||||
const snapshot = createMockGameBootstrapSnapshot()
|
||||
|
||||
return {
|
||||
activeChipId:
|
||||
snapshot.chips.find((chip) => chip.isDefault)?.id ??
|
||||
DEFAULT_ACTIVE_CHIP_ID,
|
||||
cells: snapshot.cells,
|
||||
chips: snapshot.chips,
|
||||
history: snapshot.history,
|
||||
round: snapshot.round,
|
||||
selections: snapshot.selections,
|
||||
trends: snapshot.trends,
|
||||
}
|
||||
}
|
||||
|
||||
export const useGameRoundStore = create<GameRoundStoreState>()((set) => ({
|
||||
...createInitialRoundState(),
|
||||
clearSelections: () => {
|
||||
set({ selections: [] })
|
||||
},
|
||||
hydrateRound: (snapshot) => {
|
||||
set((state) => ({
|
||||
activeChipId: getChipById(snapshot.chips, state.activeChipId)
|
||||
? state.activeChipId
|
||||
: (snapshot.chips.find((chip) => chip.isDefault)?.id ??
|
||||
snapshot.chips[0]?.id ??
|
||||
DEFAULT_ACTIVE_CHIP_ID),
|
||||
cells: snapshot.cells,
|
||||
chips: snapshot.chips,
|
||||
history: snapshot.history,
|
||||
round: snapshot.round,
|
||||
selections: snapshot.selections,
|
||||
trends: snapshot.trends,
|
||||
}))
|
||||
},
|
||||
placeBet: (cellId) => {
|
||||
set((state) => {
|
||||
const activeChip =
|
||||
getChipById(state.chips, state.activeChipId) ??
|
||||
state.chips.find((chip) => chip.isDefault) ??
|
||||
state.chips[0]
|
||||
|
||||
if (!activeChip || state.round.phase !== 'betting') {
|
||||
return state
|
||||
}
|
||||
|
||||
return {
|
||||
selections: [
|
||||
...state.selections,
|
||||
{
|
||||
amount: activeChip.amount,
|
||||
cellId,
|
||||
chipId: activeChip.id,
|
||||
id: `bet-${cellId}-${state.selections.length + 1}-${Date.now()}`,
|
||||
placedAt: new Date().toISOString(),
|
||||
source: 'local',
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
},
|
||||
removeSelectionsForCell: (cellId) => {
|
||||
set((state) => ({
|
||||
selections: state.selections.filter(
|
||||
(selection) => selection.cellId !== cellId,
|
||||
),
|
||||
}))
|
||||
},
|
||||
selectChip: (chipId) => {
|
||||
set((state) => {
|
||||
if (!getChipById(state.chips, chipId)) {
|
||||
return state
|
||||
}
|
||||
|
||||
return { activeChipId: chipId }
|
||||
})
|
||||
},
|
||||
setPhase: (phase) => {
|
||||
set((state) => ({
|
||||
round: {
|
||||
...state.round,
|
||||
phase,
|
||||
},
|
||||
}))
|
||||
},
|
||||
syncRound: (round) => {
|
||||
set((state) => ({
|
||||
round: {
|
||||
...state.round,
|
||||
...round,
|
||||
},
|
||||
}))
|
||||
},
|
||||
upsertSelections: (selections) => {
|
||||
set({ selections })
|
||||
},
|
||||
}))
|
||||
|
||||
export const selectActiveChip = (state: GameRoundStoreState): Chip | null =>
|
||||
getChipById(state.chips, state.activeChipId) ??
|
||||
state.chips.find((chip) => chip.isDefault) ??
|
||||
state.chips[0] ??
|
||||
null
|
||||
|
||||
export const selectBoardCells = (state: GameRoundStoreState) =>
|
||||
buildGameCellViewModels({
|
||||
cells: state.cells,
|
||||
round: state.round,
|
||||
selections: state.selections,
|
||||
trends: state.trends,
|
||||
})
|
||||
|
||||
export const selectCanPlaceBets = (state: GameRoundStoreState) =>
|
||||
state.round.phase === 'betting'
|
||||
|
||||
export const selectRecentResults = (state: GameRoundStoreState) =>
|
||||
getRecentWinningCellIds(state.history)
|
||||
|
||||
export const selectSelectionTotal = (state: GameRoundStoreState) =>
|
||||
getSelectionTotal(state.selections)
|
||||
|
||||
export const selectSelectionsByCell = (state: GameRoundStoreState) =>
|
||||
groupSelectionsByCell(state.selections)
|
||||
|
||||
export type GameRoundStore = typeof useGameRoundStore
|
||||
export type GameRoundStoreData = Pick<
|
||||
GameRoundStoreState,
|
||||
'cells' | 'chips' | 'history' | 'round' | 'selections' | 'trends'
|
||||
>
|
||||
|
||||
export type { BetSelection, GameCell, HistoryEntry, RoundSnapshot, TrendEntry }
|
||||
131
src/store/game/game-session-store.ts
Normal file
131
src/store/game/game-session-store.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
import type {
|
||||
AnnouncementState,
|
||||
ConnectionState,
|
||||
ConnectionStatus,
|
||||
DashboardState,
|
||||
GameBootstrapSnapshot,
|
||||
} from '@/features/game/shared'
|
||||
import {
|
||||
createMockGameBootstrapSnapshot,
|
||||
getUnreadAnnouncementCount,
|
||||
getVisibleAnnouncements,
|
||||
} from '@/features/game/shared'
|
||||
|
||||
type GameSessionSlice = Pick<
|
||||
GameBootstrapSnapshot,
|
||||
'announcements' | 'connection' | 'dashboard'
|
||||
>
|
||||
|
||||
export interface GameSessionStoreState extends GameSessionSlice {
|
||||
dismissAnnouncement: (announcementId: string) => void
|
||||
hydrateSession: (snapshot: GameSessionSlice) => void
|
||||
markAnnouncementRead: (announcementId: string) => void
|
||||
setConnectionLatency: (latencyMs: number | null) => void
|
||||
setConnectionStatus: (status: ConnectionStatus) => void
|
||||
syncConnection: (patch: Partial<ConnectionState>) => void
|
||||
syncDashboard: (patch: Partial<DashboardState>) => void
|
||||
}
|
||||
|
||||
function createInitialSessionState(): GameSessionSlice {
|
||||
const snapshot = createMockGameBootstrapSnapshot()
|
||||
|
||||
return {
|
||||
announcements: snapshot.announcements,
|
||||
connection: snapshot.connection,
|
||||
dashboard: snapshot.dashboard,
|
||||
}
|
||||
}
|
||||
|
||||
export const useGameSessionStore = create<GameSessionStoreState>()((set) => ({
|
||||
...createInitialSessionState(),
|
||||
dismissAnnouncement: (announcementId) => {
|
||||
set((state) => ({
|
||||
announcements: {
|
||||
...state.announcements,
|
||||
activeAnnouncementId:
|
||||
state.announcements.activeAnnouncementId === announcementId
|
||||
? (state.announcements.items.find(
|
||||
(item) => item.id !== announcementId,
|
||||
)?.id ?? null)
|
||||
: state.announcements.activeAnnouncementId,
|
||||
items: state.announcements.items.filter(
|
||||
(item) => item.id !== announcementId,
|
||||
),
|
||||
},
|
||||
}))
|
||||
},
|
||||
hydrateSession: (snapshot) => {
|
||||
set(snapshot)
|
||||
},
|
||||
markAnnouncementRead: (announcementId) => {
|
||||
set((state) => ({
|
||||
announcements: {
|
||||
...state.announcements,
|
||||
items: state.announcements.items.map((item) =>
|
||||
item.id === announcementId ? { ...item, isRead: true } : item,
|
||||
),
|
||||
},
|
||||
}))
|
||||
},
|
||||
setConnectionLatency: (latencyMs) => {
|
||||
set((state) => ({
|
||||
connection: {
|
||||
...state.connection,
|
||||
latencyMs,
|
||||
},
|
||||
}))
|
||||
},
|
||||
setConnectionStatus: (status) => {
|
||||
set((state) => ({
|
||||
connection: {
|
||||
...state.connection,
|
||||
connectedAt:
|
||||
status === 'connected'
|
||||
? (state.connection.connectedAt ?? new Date().toISOString())
|
||||
: state.connection.connectedAt,
|
||||
status,
|
||||
},
|
||||
}))
|
||||
},
|
||||
syncConnection: (patch) => {
|
||||
set((state) => ({
|
||||
connection: {
|
||||
...state.connection,
|
||||
...patch,
|
||||
},
|
||||
}))
|
||||
},
|
||||
syncDashboard: (patch) => {
|
||||
set((state) => ({
|
||||
dashboard: {
|
||||
...state.dashboard,
|
||||
...patch,
|
||||
},
|
||||
}))
|
||||
},
|
||||
}))
|
||||
|
||||
export const selectActiveAnnouncement = (state: GameSessionStoreState) =>
|
||||
state.announcements.items.find(
|
||||
(item) => item.id === state.announcements.activeAnnouncementId,
|
||||
) ?? null
|
||||
|
||||
export const selectIsConnectionHealthy = (state: GameSessionStoreState) =>
|
||||
state.connection.status === 'connected' &&
|
||||
(state.connection.latencyMs === null || state.connection.latencyMs < 150)
|
||||
|
||||
export const selectUnreadAnnouncementCount = (state: GameSessionStoreState) =>
|
||||
getUnreadAnnouncementCount(state.announcements)
|
||||
|
||||
export const selectVisibleAnnouncements = (state: GameSessionStoreState) =>
|
||||
getVisibleAnnouncements(state.announcements)
|
||||
|
||||
export type GameSessionStore = typeof useGameSessionStore
|
||||
export type GameSessionStoreData = Pick<
|
||||
GameSessionStoreState,
|
||||
'announcements' | 'connection' | 'dashboard'
|
||||
>
|
||||
|
||||
export type { AnnouncementState, ConnectionState, DashboardState }
|
||||
2
src/store/game/index.ts
Normal file
2
src/store/game/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './game-round-store'
|
||||
export * from './game-session-store'
|
||||
2
src/store/index.ts
Normal file
2
src/store/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './auth'
|
||||
export * from './game'
|
||||
Reference in New Issue
Block a user