feat(game): 添加钱包余额刷新功能

- 在桌面端和移动端头部组件中添加余额刷新按钮
- 实现 useWalletBalanceRefresh 钩子用于余额刷新逻辑
- 添加刷新图标并集成到钻石币显示区域
- 支持多语言本地化包括英语、印尼语、马来语和中文
- 优化消息广播组件的动画过渡效果
- 添加防重复提交机制避免并发刷新请求
- 集成加载状态显示和错误处理通知功能
This commit is contained in:
JiaJun
2026-07-17 17:26:06 +08:00
parent c841eb6688
commit 2a76f1d722
8 changed files with 117 additions and 23 deletions

View File

@@ -120,7 +120,7 @@ export function MessageBroadcast({
src={broadcast} src={broadcast}
/> />
<div className="relative h-design-16 min-w-0 flex-1 overflow-hidden md:h-design-28"> <div className="relative h-design-16 min-w-0 flex-1 overflow-hidden md:h-design-28">
<AnimatePresence> <AnimatePresence mode="wait">
{activeBroadcast ? ( {activeBroadcast ? (
<motion.div <motion.div
aria-label={activeBroadcastLabel} aria-label={activeBroadcastLabel}
@@ -136,7 +136,10 @@ export function MessageBroadcast({
? { opacity: 1 } ? { opacity: 1 }
: { opacity: 1, x: '-108%' } : { opacity: 1, x: '-108%' }
} }
exit={{ opacity: 0 }} exit={{
opacity: 0,
transition: { duration: 0.15, ease: 'easeOut' },
}}
transition={{ transition={{
duration: prefersReducedMotion duration: prefersReducedMotion
? 0.18 ? 0.18

View File

@@ -3,6 +3,7 @@ import {
Mail, Mail,
Maximize, Maximize,
Minimize, Minimize,
RefreshCw,
UserKey, UserKey,
UserRoundPlus, UserRoundPlus,
Volume2, Volume2,
@@ -17,6 +18,8 @@ import diamond from '@/assets/system/diamond.webp'
import logo from '@/assets/system/logo.webp' import logo from '@/assets/system/logo.webp'
import { SmartImage } from '@/components/smart-image.tsx' import { SmartImage } from '@/components/smart-image.tsx'
import { useHeaderClockLabel, useHeaderVm } from '@/hooks/use-header-vm' import { useHeaderClockLabel, useHeaderVm } from '@/hooks/use-header-vm'
import { useWalletBalanceRefresh } from '@/hooks/use-wallet-balance-refresh'
import { cn } from '@/lib/utils'
import { useModalStore } from '@/store' import { useModalStore } from '@/store'
function HeaderClock() { function HeaderClock() {
@@ -60,6 +63,8 @@ function SignalBars({
export function DesktopHeader() { export function DesktopHeader() {
const { t } = useTranslation() const { t } = useTranslation()
const setModalOpen = useModalStore((state) => state.setModalOpen) const setModalOpen = useModalStore((state) => state.setModalOpen)
const { isRefreshingBalance, refreshWalletBalance } =
useWalletBalanceRefresh()
const { const {
authStatus, authStatus,
currentLanguageLabel, currentLanguageLabel,
@@ -217,10 +222,7 @@ export function DesktopHeader() {
</div> </div>
</button> </button>
<button <div className="group relative flex items-center justify-center">
type="button"
className="group relative flex items-center justify-center transition-transform duration-150"
>
<SmartImage <SmartImage
src={diamond} src={diamond}
alt="diamond" alt="diamond"
@@ -234,14 +236,32 @@ export function DesktopHeader() {
> >
<div className="truncate">{currentUser?.coin || '--'}</div> <div className="truncate">{currentUser?.coin || '--'}</div>
<SmartImage <button
onClick={onOpenProcedures} type="button"
className={'w-design-24 h-design-24 cursor-pointer'} aria-label={t('commonUi.actions.refreshBalance')}
alt={'add'} title={t('commonUi.actions.refreshBalance')}
src={add} disabled={isRefreshingBalance}
onClick={() => void refreshWalletBalance()}
className="flex h-design-24 w-design-24 shrink-0 cursor-pointer items-center justify-center rounded-[3px] text-[#8DE4EA] transition-colors hover:bg-white/10 disabled:cursor-wait disabled:opacity-65"
>
<RefreshCw
aria-hidden="true"
className={cn(
'h-design-16 w-design-16',
isRefreshingBalance && 'animate-spin',
)}
/> />
</div>
</button> </button>
<button
type="button"
onClick={onOpenProcedures}
className="h-design-24 w-design-24 shrink-0 cursor-pointer"
>
<SmartImage className="h-full w-full" alt="add" src={add} />
</button>
</div>
</div>
</div> </div>
) : ( ) : (
<div <div

View File

@@ -1,6 +1,7 @@
import { import {
Info, Info,
Mail, Mail,
RefreshCw,
UserKey, UserKey,
UserRoundPlus, UserRoundPlus,
Volume2, Volume2,
@@ -15,6 +16,7 @@ import diamond from '@/assets/system/diamond.webp'
import logo from '@/assets/system/logo.webp' import logo from '@/assets/system/logo.webp'
import { SmartImage } from '@/components/smart-image.tsx' import { SmartImage } from '@/components/smart-image.tsx'
import { useHeaderClockLabel, useHeaderVm } from '@/hooks/use-header-vm' import { useHeaderClockLabel, useHeaderVm } from '@/hooks/use-header-vm'
import { useWalletBalanceRefresh } from '@/hooks/use-wallet-balance-refresh'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useModalStore } from '@/store' import { useModalStore } from '@/store'
@@ -68,6 +70,8 @@ function SignalBars({
export function MobileHeader() { export function MobileHeader() {
const { t } = useTranslation() const { t } = useTranslation()
const setModalOpen = useModalStore((state) => state.setModalOpen) const setModalOpen = useModalStore((state) => state.setModalOpen)
const { isRefreshingBalance, refreshWalletBalance } =
useWalletBalanceRefresh()
const { const {
authStatus, authStatus,
currentLanguageLabel, currentLanguageLabel,
@@ -133,11 +137,7 @@ export function MobileHeader() {
</div> </div>
</button> </button>
<button <div className="group relative flex min-w-0 items-center justify-center">
type="button"
onClick={onOpenProcedures}
className="group relative flex min-w-0 items-center justify-center transition-transform duration-150 hover:-translate-y-[1px] active:translate-y-[1px]"
>
<SmartImage <SmartImage
src={diamond} src={diamond}
alt="diamond" alt="diamond"
@@ -150,15 +150,37 @@ export function MobileHeader() {
<span className="min-w-0 truncate"> <span className="min-w-0 truncate">
{currentUser?.coin || '--'} {currentUser?.coin || '--'}
</span> </span>
<button
type="button"
aria-label={t('commonUi.actions.refreshBalance')}
title={t('commonUi.actions.refreshBalance')}
disabled={isRefreshingBalance}
onClick={() => void refreshWalletBalance()}
className="flex h-design-13 w-design-13 shrink-0 cursor-pointer items-center justify-center text-[#8DE4EA] disabled:cursor-wait disabled:opacity-65"
>
<RefreshCw
aria-hidden="true"
className={cn(
'h-design-9 w-design-9',
isRefreshingBalance && 'animate-spin',
)}
/>
</button>
<button
type="button"
onClick={onOpenProcedures}
className="h-design-13 w-design-13 shrink-0 cursor-pointer"
>
<SmartImage <SmartImage
src={add} src={add}
alt="add" alt="add"
priority priority
className="h-design-13 w-design-13 shrink-0 cursor-pointer" className="h-full w-full"
/> />
</div>
</button> </button>
</div> </div>
</div>
</div>
) : ( ) : (
<div className="flex h-full shrink-0 items-center justify-end gap-design-8 px-design-9"> <div className="flex h-full shrink-0 items-center justify-end gap-design-8 px-design-9">
<button <button

View File

@@ -0,0 +1,45 @@
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { getCurrentUserProfile } from '@/api'
import { notify } from '@/lib/notify'
import { useAuthStore } from '@/store'
export function useWalletBalanceRefresh() {
const { t } = useTranslation()
const setCurrentUser = useAuthStore((state) => state.setCurrentUser)
const [isRefreshingBalance, setIsRefreshingBalance] = useState(false)
const refreshPromiseRef = useRef<Promise<void> | null>(null)
const refreshWalletBalance = useCallback(() => {
if (refreshPromiseRef.current) {
return refreshPromiseRef.current
}
setIsRefreshingBalance(true)
const refreshPromise = getCurrentUserProfile()
.then((currentUser) => {
setCurrentUser(currentUser)
})
.catch((error: unknown) => {
notify.error(
error instanceof Error
? error.message
: t('commonUi.toast.requestFailed'),
)
})
.finally(() => {
refreshPromiseRef.current = null
setIsRefreshingBalance(false)
})
refreshPromiseRef.current = refreshPromise
return refreshPromise
}, [setCurrentUser, t])
return {
isRefreshingBalance,
refreshWalletBalance,
}
}

View File

@@ -479,6 +479,7 @@ export default {
}, },
commonUi: { commonUi: {
actions: { actions: {
refreshBalance: 'Refresh balance',
submitting: TEXT_SUBMITTING, submitting: TEXT_SUBMITTING,
}, },
dialog: { dialog: {

View File

@@ -478,6 +478,7 @@ export default {
}, },
commonUi: { commonUi: {
actions: { actions: {
refreshBalance: 'Segarkan saldo',
submitting: TEXT_SUBMITTING, submitting: TEXT_SUBMITTING,
}, },
dialog: { dialog: {

View File

@@ -481,6 +481,7 @@ export default {
}, },
commonUi: { commonUi: {
actions: { actions: {
refreshBalance: 'Segarkan baki',
submitting: TEXT_SUBMITTING, submitting: TEXT_SUBMITTING,
}, },
dialog: { dialog: {

View File

@@ -462,6 +462,7 @@ export default {
}, },
commonUi: { commonUi: {
actions: { actions: {
refreshBalance: '刷新余额',
submitting: TEXT_SUBMITTING, submitting: TEXT_SUBMITTING,
}, },
dialog: { dialog: {