Files
36-character-flower/src/features/game/components/desktop/desktop-header.tsx
JiaJun 87e8aca97d feat(game): 更新马来西亚手机号验证和游戏界面优化
- 修改认证模块手机号验证规则适配马来西亚号码格式
- 添加新的投注限制提示文本支持多语言
- 重命名结算阶段标签为Drawing统一显示
- 新增桌面版动物游戏遮罩组件分离功能
- 添加回合开始投注提醒弹窗组件
- 优化开奖动画流程和视觉效果
- 添加奖励动画显示和投注汇总展示
- 新增多种投注限制和状态提示信息
2026-05-30 17:22:57 +08:00

254 lines
8.9 KiB
TypeScript

import {
CircleAlert,
Mail,
Maximize,
Minimize,
UserKey,
UserRoundPlus,
Volume2,
VolumeX,
} from 'lucide-react'
import { useTranslation } from 'react-i18next'
import add from '@/assets/game/add.webp'
import avatar from '@/assets/system/avatar.webp'
import diamond from '@/assets/system/diamond.webp'
import logo from '@/assets/system/logo.webp'
import { SmartImage } from '@/components/smart-image.tsx'
import { useHeaderVm } from '@/features/game/hooks/use-header-vm'
function SignalBars({
activeBars,
toneClassName,
}: {
activeBars: number
toneClassName: string
}) {
const barHeights = ['h-[6px]', 'h-[10px]', 'h-[14px]', 'h-[18px]'] as const
return (
<div
className="flex h-design-20 w-design-28 items-end gap-[2px]"
aria-hidden="true"
>
{barHeights.map((heightClassName, index) => {
const isActive = index < activeBars
return (
<div
key={heightClassName}
className={[
'w-[5px] rounded-t-[2px] transition-colors',
heightClassName,
isActive ? `bg-current ${toneClassName}` : 'bg-white/18',
].join(' ')}
/>
)
})}
</div>
)
}
export function DesktopHeader() {
const { t } = useTranslation()
const {
authStatus,
currentLanguageLabel,
currentLanguageOption,
currentUser,
handleFullscreenToggle,
isFullscreen,
isSoundEnabled,
onOpenLanguage,
onOpenLogin,
onOpenNotice,
onOpenProcedures,
onOpenRegister,
onOpenRules,
onOpenUserInfo,
signalPresentation,
systemTimeLabel,
toggleSoundEnabled,
} = useHeaderVm()
return (
<header className="sticky top-0 z-30 border-b border-white/8 bg-slate-950/70 backdrop-blur-xl">
<div className="flex h-design-70 w-full items-center px-design-12">
<div className="flex h-full w-design-375 items-center justify-center border-r border-[rgba(128,223,231,0.65)] px-design-10">
<SmartImage
src={logo}
alt="logo"
priority
className="h-design-40 w-design-320"
/>
</div>
<div className="flex h-full w-design-130 items-center justify-center gap-design-10 border-r border-[rgba(128,223,231,0.65)]">
<div className={signalPresentation.toneClassName}>
<SignalBars
activeBars={signalPresentation.activeBars}
toneClassName={signalPresentation.toneClassName}
/>
</div>
<div className={`${signalPresentation.toneClassName} text-design-20`}>
{signalPresentation.latencyLabel}{' '}
<span className={'text-design-16'}>ms</span>
</div>
</div>
<div className="flex h-full w-design-175 flex-col items-center justify-center gap-design-5 border-r border-[rgba(128,223,231,0.65)]">
<div className={'text-[#B4E4E9]'}>
{t('gameDesktop.header.systemTime')}
</div>
<div className={'text-[#D2FCFF] font-bold'}>{systemTimeLabel}</div>
</div>
<div className="flex h-full flex-1 items-center justify-around gap-design-10 border-r border-[rgba(128,223,231,0.65)] px-design-20">
<button
type="button"
onClick={onOpenRules}
className="min-w-design-120 common-neon-inset flex cursor-pointer items-center justify-center gap-design-10 !px-design-16 transition-opacity hover:opacity-85"
>
<CircleAlert color={'#57B8BF'} size={16} />
<div>{t('gameDesktop.header.rules')}</div>
</button>
<button
type="button"
onClick={onOpenNotice}
className="min-w-design-120 common-neon-inset flex cursor-pointer items-center justify-center gap-design-10 !px-design-16 transition-opacity hover:opacity-85"
>
<Mail color={'#57B8BF'} size={16} />
<div>{t('gameDesktop.header.message')}</div>
</button>
<button
type="button"
onClick={toggleSoundEnabled}
className="min-w-design-120 common-neon-inset flex cursor-pointer items-center justify-center gap-design-10 !px-design-16 transition-opacity hover:opacity-85"
>
{isSoundEnabled ? (
<Volume2 color={'#57B8BF'} size={16} />
) : (
<VolumeX color={'#57B8BF'} size={16} />
)}
<div>{t('gameDesktop.header.bgm')}</div>
</button>
<div className={'flex items-center justify-center'}>
<button
type="button"
onClick={onOpenLanguage}
className={
'common-neon-inset text-design-16 !py-design-20 box-border flex h-design-36 w-fit items-center justify-between gap-design-8 !px-design-20 transition-opacity hover:opacity-85'
}
>
<div className="flex items-center gap-design-14">
<SmartImage
src={currentLanguageOption.icon}
alt={currentLanguageLabel}
className="h-design-24 w-design-24 rounded-[2px] object-cover"
/>
<div className="truncate">{currentLanguageLabel}</div>
</div>
</button>
</div>
<button
type="button"
onClick={handleFullscreenToggle}
className="min-w-design-120 common-neon-inset flex cursor-pointer items-center justify-center gap-design-10 !px-design-16 transition-opacity hover:opacity-85"
>
{isFullscreen ? (
<Minimize color={'#57B8BF'} size={16} />
) : (
<Maximize color={'#57B8BF'} size={16} />
)}
<div>{t('gameDesktop.header.fullscreen')}</div>
</button>
</div>
{authStatus === 'authenticated' ? (
<div
className={
'flex items-center justify-center gap-design-30 pl-design-30 pr-design-10'
}
>
<button
type="button"
onClick={onOpenUserInfo}
className="group relative flex items-center justify-center transition-transform duration-150 hover:-translate-y-[1px] active:translate-y-[1px]"
>
<SmartImage
src={avatar}
alt="avatar"
priority
className="absolute -left-5 z-20 h-design-50 w-design-50"
/>
<div
className={
'common-neon-inset text-design-16 !py-design-20 flex h-design-36 w-design-180 items-center justify-end transition-[opacity,transform] duration-150 group-hover:opacity-90 group-active:scale-[0.98]'
}
>
{currentUser?.username || '--'}
</div>
</button>
<button
type="button"
className="group relative flex items-center justify-center transition-transform duration-150"
>
<SmartImage
src={diamond}
alt="diamond"
priority
className="absolute -left-5 z-20 h-design-50 w-design-50"
/>
<div
className={
'common-neon-inset text-design-16 !py-design-20 box-border flex h-design-36 w-design-180 items-center justify-end gap-design-12 transition-[opacity,transform] duration-150 group-hover:opacity-90 group-active:scale-[0.98]'
}
>
<div className="truncate">{currentUser?.coin || '--'}</div>
<SmartImage
onClick={onOpenProcedures}
className={'w-design-24 h-design-24 cursor-pointer'}
alt={'add'}
src={add}
/>
</div>
</button>
</div>
) : (
<div
className={
'flex items-center justify-center gap-design-30 pl-design-30 pr-design-10'
}
>
<button
type="button"
className={
'min-w-design-120 common-neon-inset flex cursor-pointer items-center justify-center gap-design-10 !px-design-16 transition-opacity hover:opacity-85'
}
onClick={onOpenLogin}
>
<UserKey color={'#57B8BF'} size={16} />
<div>{t('gameDesktop.header.login')}</div>
</button>
<button
type="button"
className={
'min-w-design-120 common-neon-inset flex cursor-pointer items-center justify-center gap-design-10 !px-design-16 transition-opacity hover:opacity-85'
}
onClick={onOpenRegister}
>
<UserRoundPlus color={'#57B8BF'} size={16} />
<div>{t('gameDesktop.header.register')}</div>
</button>
</div>
)}
</div>
</header>
)
}