Files
36-character-flower/src/features/game/components/desktop/desktop-header.tsx
JiaJun 522b8a1f28 refactor(game): 重构游戏组件和数据结构
- 移除中心模态框的背景模糊效果并调整透明度
- 为桌面游戏历史组件添加空状态显示组件
- 重构头部时钟显示逻辑,提取为独立组件并优化时间同步
- 移除用户ID遮罩功能,直接使用昵称显示中奖信息
- 调整入口页面的模态框渲染结构和认证状态检查逻辑
- 更新奖池广播数据结构,替换用户ID为昵称字段
- 优化实时同步中的数据验证和映射逻辑
- 调整移动端头部时钟组件的实现方式
2026-06-02 10:02:08 +08:00

262 lines
9.0 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 {
useHeaderClockLabel,
useHeaderVm,
} from '@/features/game/hooks/use-header-vm'
function HeaderClock() {
const systemTimeLabel = useHeaderClockLabel()
return <div className={'text-[#D2FCFF] font-bold'}>{systemTimeLabel}</div>
}
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,
toggleSoundEnabled,
} = useHeaderVm()
return (
<header className="sticky top-0 z-30 border-b border-white/8 bg-[#020B14]/95 ">
<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>
<HeaderClock />
</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>
)
}