feat(app): 添加应用启动资源预加载功能

- 实现了 AppBootResourceGate 组件用于管理应用启动时的资源加载
- 集成了图片和字体资源的预加载机制
- 添加了启动时的加载进度指示器和动画效果
- 创建了 DataLoadingIndicator 组件用于显示数据加载状态
- 在多个组件中替换原有的加载提示为新的 DataLoadingIndicator
- 更新了样式文件以支持新的加载动画和视觉效果
This commit is contained in:
JiaJun
2026-05-29 17:43:47 +08:00
parent f388aabfda
commit 54410aaac5
12 changed files with 516 additions and 69 deletions

View File

@@ -3,6 +3,7 @@ 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 { DataLoadingIndicator } from '@/components/ui/data-loading-indicator'
import { useGameHistoryVm } from '@/features/game/hooks/use-game-history-vm.ts'
import { FLOWER_IMAGE_BY_ID } from '@/features/game/shared'
@@ -17,12 +18,18 @@ function HistoryRewardNumber({
const label = String(number).padStart(2, '0')
if (!image?.rewardUrl) {
return <span className={className}>{label}</span>
return (
<span
className={`inline-flex h-design-38 min-w-design-38 shrink-0 items-center justify-center px-design-5 text-design-15 font-bold text-[#D5FBFF] shadow-[inset_0_0_calc(var(--design-unit)*10)_rgba(75,233,255,0.12),0_0_calc(var(--design-unit)*10)_rgba(75,233,255,0.1)] align-middle ${className ?? ''}`}
>
{label}
</span>
)
}
return (
<span
className={`inline-flex h-design-36 w-design-36 shrink-0 items-center justify-center align-middle ${className ?? ''}`}
className={`inline-flex h-design-38 w-design-38 shrink-0 items-center justify-center p-design-1 align-middle ${className ?? ''}`}
title={label}
>
<SmartImage
@@ -74,7 +81,7 @@ export function DesktopGameHistory() {
>
<div
className={
'relative z-20 flex h-design-50 shrink-0 items-center justify-center text-design-30 text-[#D5FBFF]'
'relative z-20 flex h-design-50 shrink-0 items-center justify-center text-design-28 font-bold text-[#D5FBFF]'
}
>
{t('gameDesktop.history.title')}
@@ -87,13 +94,10 @@ export function DesktopGameHistory() {
}
>
{isInitialLoading ? (
<div
className={
'flex w-full flex-1 items-center justify-center text-design-18 text-[#84A2A2]'
}
>
{loadingText}
</div>
<DataLoadingIndicator
label={loadingText}
className="min-h-full flex-1"
/>
) : isEmpty ? (
<div
className={
@@ -124,44 +128,94 @@ export function DesktopGameHistory() {
: isWin
? '0 0 calc(var(--design-unit)*10) #FFE375, 0 0 calc(var(--design-unit)*22) rgba(255,227,117,0.48)'
: '0 0 calc(var(--design-unit)*10) #8DFF98, 0 0 calc(var(--design-unit)*22) rgba(141,255,152,0.48)'
const statusBorderColor =
item.resultState === 'pending'
? 'rgba(143,241,255,0.34)'
: isWin
? 'rgba(255,227,117,0.5)'
: 'rgba(141,255,152,0.36)'
const statusBg =
item.resultState === 'pending'
? 'linear-gradient(180deg,rgba(28,106,126,0.38),rgba(5,25,36,0.9))'
: isWin
? 'linear-gradient(180deg,rgba(127,92,14,0.5),rgba(29,22,8,0.92))'
: 'linear-gradient(180deg,rgba(31,111,54,0.38),rgba(6,28,20,0.92))'
return (
<div key={item.id} className="w-full pb-design-12 last:pb-0">
<div
className={
'common-neon-inset flex w-full flex-col items-center !p-0 text-[#FFE375]'
'relative isolate flex w-full flex-col items-center overflow-hidden rounded-[calc(var(--design-unit)*14)] border bg-[linear-gradient(180deg,rgba(6,33,45,0.97),rgba(3,14,23,0.94))] text-[#FFE375] shadow-[0_0_calc(var(--design-unit)*16)_rgba(63,226,255,0.14),inset_0_1px_0_rgba(218,255,255,0.12)] transition-colors duration-200'
}
style={{
borderColor: statusBorderColor,
}}
>
<div
className="common-neon-inset w-full !rounded-b-none text-center text-design-20 font-bold tracking-[0.08em]"
<span
aria-hidden="true"
className="pointer-events-none absolute inset-x-design-10 top-0 h-px opacity-80"
style={{
color: statusColor,
textShadow: statusTextShadow,
background: `linear-gradient(90deg, transparent, ${statusColor}, transparent)`,
}}
/>
<span
aria-hidden="true"
className="pointer-events-none absolute -right-design-28 -top-design-30 h-design-90 w-design-90 rounded-full blur-[28px]"
style={{
background: statusColor,
opacity: isWin ? 0.14 : 0.08,
}}
/>
<div
className="relative z-10 flex min-h-design-42 w-full items-center justify-center border-b px-design-12 py-design-7"
style={{
background: statusBg,
borderColor: statusBorderColor,
}}
>
{statusLabel}
<span
className="inline-flex min-w-design-118 items-center justify-center gap-design-8 px-design-14 py-design-4 text-design-18 font-bold"
style={{
borderColor: statusBorderColor,
color: statusColor,
textShadow: statusTextShadow,
}}
>
<span
aria-hidden="true"
className="h-design-8 w-design-8 rounded-full shadow-[0_0_calc(var(--design-unit)*10)_currentColor]"
style={{ backgroundColor: statusColor }}
/>
{statusLabel}
</span>
</div>
<div
className={
'flex w-full flex-col gap-design-5 px-design-10 py-design-10 text-design-16'
'relative z-10 flex w-full flex-col gap-design-7 px-design-10 py-design-10 text-design-16'
}
>
<div>
<span className={'text-[#84A2A2]'}>
<div className="flex min-h-design-34 items-center justify-between gap-design-10 rounded-[calc(var(--design-unit)*8)] border border-[rgba(94,212,230,0.14)] bg-[rgba(5,23,33,0.58)] px-design-10 py-design-6">
<span className={'shrink-0 text-[#84A2A2]'}>
{t('gameDesktop.history.roundId')}:{' '}
</span>
<span className={'text-[#C0E7EB]'}>
<span
className={
'min-w-0 text-right text-design-15 font-medium text-[#C0E7EB]'
}
>
{item.periodNo}
</span>
</div>
<div>
<span className={'text-[#84A2A2]'}>
<div className="flex min-h-design-42 items-start justify-between gap-design-10 rounded-[calc(var(--design-unit)*8)] border border-[rgba(94,212,230,0.14)] bg-[rgba(5,23,33,0.58)] px-design-10 py-design-6">
<span className={'shrink-0 pt-design-7 text-[#84A2A2]'}>
{t('gameDesktop.history.numbers')}:{' '}
</span>
{item.numbers.length === 0 ? (
<span>{item.numbersLabel}</span>
<span className="pt-design-7 text-right">
{item.numbersLabel}
</span>
) : (
<span className="inline-flex flex-wrap items-center gap-design-4 align-middle">
<span className="inline-flex min-w-0 flex-1 flex-wrap items-center justify-end gap-design-5 align-middle">
{item.numbers.map((number) => (
<HistoryRewardNumber
key={`${item.id}-${number}`}
@@ -171,20 +225,28 @@ export function DesktopGameHistory() {
</span>
)}
</div>
<div>
<span className={'text-[#84A2A2]'}>
<div className="flex min-h-design-34 items-center justify-between gap-design-10 rounded-[calc(var(--design-unit)*8)] border border-[rgba(255,227,117,0.16)] bg-[linear-gradient(90deg,rgba(5,23,33,0.58),rgba(84,57,8,0.16))] px-design-10 py-design-6">
<span className={'shrink-0 text-[#84A2A2]'}>
{t('gameDesktop.history.totalPoolAmount')}:{' '}
</span>
<span className={'text-[#FFE375]'}>
<span
className={
'text-right text-design-17 font-bold text-[#FFE375] [text-shadow:0_0_calc(var(--design-unit)*10)_rgba(255,227,117,0.22)]'
}
>
{item.amountLabel}
</span>
</div>
<div>
<span className={'text-[#84A2A2]'}>
<div className="flex min-h-design-42 items-center justify-between gap-design-10 rounded-[calc(var(--design-unit)*8)] border border-[rgba(255,117,117,0.18)] bg-[linear-gradient(90deg,rgba(5,23,33,0.58),rgba(88,20,28,0.16))] px-design-10 py-design-6">
<span className={'shrink-0 text-[#84A2A2]'}>
{t('gameDesktop.history.winningResult')}:{' '}
</span>
{item.resultNumber === null ? (
<span className={'text-[#FF7575]'}>
<span
className={
'text-right font-bold text-[#FF7575] [text-shadow:0_0_calc(var(--design-unit)*10)_rgba(255,117,117,0.28)]'
}
>
{item.resultNumberLabel}
</span>
) : (
@@ -200,7 +262,13 @@ export function DesktopGameHistory() {
)
})}
<div className="flex min-h-[calc(var(--design-unit)*40)] items-center justify-center text-design-16 text-[#84A2A2]">
{isFetchingNextPage ? loadingText : hasNextPage ? '' : endText}
{isFetchingNextPage ? (
<DataLoadingIndicator compact label={loadingText} />
) : hasNextPage ? (
''
) : (
endText
)}
</div>
</>
)}

View File

@@ -1,6 +1,7 @@
import { useMutation } from '@tanstack/react-query'
import { useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator'
import { createDeposit, type DepositTierItem } from '@/features/game/api'
import { useDepositTierList } from '@/features/game/hooks/use-deposit-tier-list'
import { notify } from '@/lib/notify'
@@ -104,9 +105,10 @@ function DesktopTopup() {
</div>
{tierListQuery.isLoading ? (
<div className="flex h-full min-h-0 items-center justify-center rounded-[calc(var(--design-unit)*6)] border border-[rgba(103,227,239,0.18)] bg-[rgba(6,24,35,0.52)] text-design-16 text-[#8FDDE6]">
{t('gameDesktop.topup.tier.loading')}
</div>
<DataLoadingIndicator
label={t('gameDesktop.topup.tier.loading')}
className="h-full min-h-0 rounded-[calc(var(--design-unit)*6)] border border-[rgba(103,227,239,0.18)] bg-[rgba(6,24,35,0.52)]"
/>
) : tierListQuery.isError ? (
<div className="flex h-full min-h-0 items-center justify-center rounded-[calc(var(--design-unit)*6)] border border-[rgba(185,63,68,0.28)] bg-[rgba(34,13,16,0.42)] text-design-16 text-[#F4A9AE]">
{t('gameDesktop.topup.tier.failed')}

View File

@@ -8,6 +8,7 @@ import checkIcon from '@/assets/system/right.webp'
import { CenterModal } from '@/components/center-modal.tsx'
import { SmartBackground } from '@/components/smart-background.tsx'
import { SmartImage } from '@/components/smart-image.tsx'
import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator.tsx'
import {
ENTRY_NOTICE_CONFIRM_INTERVAL_MS,
ENTRY_NOTICE_LAST_CONFIRMED_AT_KEY,
@@ -117,9 +118,10 @@ export function EntryNoticeGateModal() {
<div className="flex h-full w-full flex-col gap-design-20 px-design-14 pb-design-30 pt-design-8">
<div className="min-h-0 flex-1 overflow-y-auto rounded-md border border-[#2B8CA3]/45 bg-[#001B24]/70 p-design-18 shadow-[inset_0_0_calc(var(--design-unit)*18)_rgba(39,175,205,0.1)]">
{noticeListQuery.isPending ? (
<div className="flex h-full min-h-[calc(var(--design-unit)*320)] items-center justify-center text-design-22 text-[#9CE8F2]">
{t('game.modals.entryNotice.loading')}
</div>
<DataLoadingIndicator
label={t('game.modals.entryNotice.loading')}
className="h-full min-h-[calc(var(--design-unit)*320)]"
/>
) : noticeListQuery.isError ? (
<div className="flex h-full min-h-[calc(var(--design-unit)*320)] flex-col items-center justify-center gap-design-18 text-center text-[#9CE8F2]">
<div className="text-design-22">

View File

@@ -2,6 +2,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'
import { motion } from 'motion/react'
import { useEffect, useRef } from 'react'
import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator'
import { useFinanceRecordsVm } from '@/features/game/hooks/use-finance-records-vm'
import { cn } from '@/lib/utils'
@@ -108,9 +109,7 @@ function DesktopFinanceRecordsTab({ enabled }: { enabled: boolean }) {
}
>
{vm.isLoading ? (
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
{vm.loadingText}
</div>
<DataLoadingIndicator label={vm.loadingText} />
) : vm.isError ? (
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
{vm.loadFailedText}
@@ -159,13 +158,11 @@ function DesktopFinanceRecordsTab({ enabled }: { enabled: boolean }) {
</div>
</motion.div>
) : (
<div
className={
'flex h-[calc(var(--design-unit)*62)] items-center justify-center rounded-md bg-[#0A4252]/60 text-design-16 text-[#6CCDCF]'
}
>
{vm.loadingText}
</div>
<DataLoadingIndicator
compact
label={vm.loadingText}
className="h-[calc(var(--design-unit)*62)] rounded-md bg-[#0A4252]/60"
/>
)}
</div>
)

View File

@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'
import blueBtnBg from '@/assets/system/blue-btn.webp'
import { CenterModal } from '@/components/center-modal.tsx'
import { SmartBackground } from '@/components/smart-background.tsx'
import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator'
import { getNoticeDetail, getNoticeList } from '@/features/game/api'
import { cn } from '@/lib/utils'
import { useModalStore } from '@/store'
@@ -104,9 +105,9 @@ function DesktopNoticeModal() {
}
>
{noticeListQuery.isLoading ? (
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
{t('game.modals.userInfo.message.loading')}
</div>
<DataLoadingIndicator
label={t('game.modals.userInfo.message.loading')}
/>
) : noticeListQuery.isError ? (
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
{t('game.modals.userInfo.message.loadFailed')}
@@ -186,9 +187,9 @@ function DesktopNoticeModal() {
}
>
{noticeDetailQuery.isLoading ? (
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
{t('game.modals.userInfo.message.loading')}
</div>
<DataLoadingIndicator
label={t('game.modals.userInfo.message.loading')}
/>
) : noticeDetailQuery.isError ? (
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
{t('game.modals.userInfo.message.loadFailed')}

View File

@@ -2,6 +2,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'
import { motion } from 'motion/react'
import { useEffect, useRef } from 'react'
import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator'
import { useWalletRecordsVm } from '@/features/game/hooks/use-wallet-records-vm'
function DesktopWalletRecordsTab({ enabled }: { enabled: boolean }) {
@@ -69,9 +70,7 @@ function DesktopWalletRecordsTab({ enabled }: { enabled: boolean }) {
}
>
{vm.isLoading ? (
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
{vm.loadingText}
</div>
<DataLoadingIndicator label={vm.loadingText} />
) : vm.isError ? (
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
{vm.loadFailedText}
@@ -126,13 +125,11 @@ function DesktopWalletRecordsTab({ enabled }: { enabled: boolean }) {
</div>
</motion.div>
) : (
<div
className={
'flex h-[calc(var(--design-unit)*62)] items-center justify-center rounded-md bg-[#0A4252]/60 text-design-16 text-[#6CCDCF]'
}
>
{vm.loadingText}
</div>
<DataLoadingIndicator
compact
label={vm.loadingText}
className="h-[calc(var(--design-unit)*62)] rounded-md bg-[#0A4252]/60"
/>
)}
</div>
)