- 修改认证模块手机号验证规则适配马来西亚号码格式 - 添加新的投注限制提示文本支持多语言 - 重命名结算阶段标签为Drawing统一显示 - 新增桌面版动物游戏遮罩组件分离功能 - 添加回合开始投注提醒弹窗组件 - 优化开奖动画流程和视觉效果 - 添加奖励动画显示和投注汇总展示 - 新增多种投注限制和状态提示信息
254 lines
8.9 KiB
TypeScript
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>
|
|
)
|
|
}
|