feat(game): 更新游戏数据结构并优化历史记录显示

- 将动物图片资源配置重构为统一的花朵资源管理模块
- 添加奖励图片资源支持,实现动物和奖励图片的双重映射
- 在游戏历史记录中显示实际数字对应的奖励图标
- 修复历史记录中数字显示的空值处理逻辑
- 更新GitNexus项目索引统计信息
- 优化桌面游戏中花朵选择组件的渲染性能
This commit is contained in:
JiaJun
2026-05-29 16:47:41 +08:00
parent 15c519a42c
commit f388aabfda
43 changed files with 97 additions and 26 deletions

View File

@@ -1,7 +1,7 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **36-character-flower** (2505 symbols, 4694 relationships, 215 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **36-character-flower** (2527 symbols, 4819 relationships, 217 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

View File

@@ -1,7 +1,7 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **36-character-flower** (2505 symbols, 4694 relationships, 215 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **36-character-flower** (2527 symbols, 4819 relationships, 217 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

BIN
src/assets/reward/1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/10.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/11.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/assets/reward/12.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/assets/reward/13.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/14.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/assets/reward/15.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/16.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/assets/reward/17.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/assets/reward/18.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/19.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/assets/reward/2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
src/assets/reward/20.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/reward/21.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/assets/reward/22.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/23.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/24.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/reward/25.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/26.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
src/assets/reward/27.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/assets/reward/28.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/29.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/3.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
src/assets/reward/30.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/31.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/32.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/assets/reward/33.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/34.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/assets/reward/35.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/36.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/4.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/5.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
src/assets/reward/6.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
src/assets/reward/7.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/reward/8.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
src/assets/reward/9.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -12,27 +12,11 @@ import refreshIcon from '@/assets/system/refresh.webp'
import { SmartBackground } from '@/components/smart-background.tsx'
import { SmartImage } from '@/components/smart-image'
import { useAnimalVm } from '@/features/game/hooks/use-animal-vm'
import { FLOWER_IMAGE_LIST } from '@/features/game/shared'
import { cn } from '@/lib/utils'
import { useAuthStore } from '@/store/auth'
import { useGameAutoHostingStore, useGameRoundStore } from '@/store/game'
const animalModules = import.meta.glob('../../../../assets/animal/*.webp', {
eager: true,
import: 'default',
}) as Record<string, string>
const animalImageList = Object.entries(animalModules)
.map(([path, url]) => {
const match = path.match(/\/(\d+)\.webp$/)
return {
id: Number(match?.[1] ?? 0),
url,
}
})
.filter((item) => item.id > 0)
.sort((left, right) => left.id - right.id)
const SETTLEMENT_REVEAL_RANDOM_DURATION_MS = 4_000
const SETTLEMENT_REVEAL_RESULT_HOLD_MS = 1_000
const SETTLEMENT_REVEAL_MIN_STEP_MS = 90
@@ -82,7 +66,7 @@ export function DesktopAnimal({
}: DesktopAnimalProps) {
const { i18n, t } = useTranslation()
const prefersReducedMotion = useReducedMotion()
const animalIds = useMemo(() => animalImageList.map((item) => item.id), [])
const animalIds = useMemo(() => FLOWER_IMAGE_LIST.map((item) => item.id), [])
const containerRef = useRef<HTMLElement | null>(null)
const cellRefs = useRef(new Map<number, HTMLButtonElement>())
const [revealCellId, setRevealCellId] = useState<number | null>(null)
@@ -241,7 +225,7 @@ export function DesktopAnimal({
className,
)}
>
{animalImageList.map((item) => {
{FLOWER_IMAGE_LIST.map((item) => {
const selectionMeta = selectionByCell[item.id]
const hasPlacedSelection = Boolean(selectionMeta)
const isMarqueeActive = showStandbyState && item.id === marqueeId
@@ -355,7 +339,7 @@ export function DesktopAnimal({
/>
) : null}
<SmartImage
src={item.url}
src={item.animalUrl}
alt={`animal-${item.id}`}
className={cn(
'absolute left-[1.5%] right-[1.5%] top-[2.9%] bottom-[2.9%] z-10 overflow-hidden rounded-[calc(var(--design-unit)*14)]',

View File

@@ -2,7 +2,39 @@ import { useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import historyBg from '@/assets/system/history-bg.png'
import { SmartBackground } from '@/components/smart-background.tsx'
import { SmartImage } from '@/components/smart-image'
import { useGameHistoryVm } from '@/features/game/hooks/use-game-history-vm.ts'
import { FLOWER_IMAGE_BY_ID } from '@/features/game/shared'
function HistoryRewardNumber({
className,
number,
}: {
className?: string
number: number
}) {
const image = FLOWER_IMAGE_BY_ID[number]
const label = String(number).padStart(2, '0')
if (!image?.rewardUrl) {
return <span className={className}>{label}</span>
}
return (
<span
className={`inline-flex h-design-36 w-design-36 shrink-0 items-center justify-center align-middle ${className ?? ''}`}
title={label}
>
<SmartImage
src={image.rewardUrl}
alt={label}
showSkeleton={false}
className="h-full w-full overflow-visible"
imgClassName="object-contain"
/>
</span>
)
}
export function DesktopGameHistory() {
const { t } = useTranslation()
@@ -126,7 +158,18 @@ export function DesktopGameHistory() {
<span className={'text-[#84A2A2]'}>
{t('gameDesktop.history.numbers')}:{' '}
</span>
{item.numbers.length === 0 ? (
<span>{item.numbersLabel}</span>
) : (
<span className="inline-flex flex-wrap items-center gap-design-4 align-middle">
{item.numbers.map((number) => (
<HistoryRewardNumber
key={`${item.id}-${number}`}
number={number}
/>
))}
</span>
)}
</div>
<div>
<span className={'text-[#84A2A2]'}>
@@ -140,9 +183,16 @@ export function DesktopGameHistory() {
<span className={'text-[#84A2A2]'}>
{t('gameDesktop.history.winningResult')}:{' '}
</span>
{item.resultNumber === null ? (
<span className={'text-[#FF7575]'}>
{item.resultNumberLabel}
</span>
) : (
<HistoryRewardNumber
className="text-[#FF7575]"
number={item.resultNumber}
/>
)}
</div>
</div>
</div>

View File

@@ -89,6 +89,7 @@ export function useGameHistoryVm() {
numbers: entry.numbers,
orderNo: entry.order_no,
periodNo: entry.period_no,
resultNumber,
resultNumberLabel:
resultNumber === null
? '--'

View File

@@ -0,0 +1,35 @@
const animalModules = import.meta.glob('../../../assets/animal/*.webp', {
eager: true,
import: 'default',
}) as Record<string, string>
const rewardModules = import.meta.glob('../../../assets/reward/*.webp', {
eager: true,
import: 'default',
}) as Record<string, string>
export interface FlowerImageAsset {
animalUrl: string
id: number
rewardUrl: string
}
export const FLOWER_IMAGE_LIST: FlowerImageAsset[] = Array.from(
{ length: 36 },
(_, index) => {
const id = index + 1
return {
animalUrl: animalModules[`../../../assets/animal/${id}.webp`] ?? '',
id,
rewardUrl: rewardModules[`../../../assets/reward/${id}.webp`] ?? '',
}
},
).filter((item) => item.animalUrl && item.rewardUrl)
export const FLOWER_IMAGE_BY_ID = FLOWER_IMAGE_LIST.reduce<
Record<number, FlowerImageAsset>
>((map, item) => {
map[item.id] = item
return map
}, {})

View File

@@ -1,4 +1,5 @@
export * from './constants'
export * from './flower-assets'
export * from './initial-state'
export * from './selectors'
export * from './types'