refactor(game): 重构项目结构,优化链路, 移动端适配

- 移除 useGameBoardVm 数据层实施说明文档
- 移除核心玩法与前端规则摘要文档
- 移除游戏模块数据与界面分层第一阶段实施稿文档
- 清理与数据层重构相关的技术方案说明
- 删除关于 PC 和 Mobile 界面分离的设计规划
- 移除 view-model hooks 架构设计相关内容
This commit is contained in:
JiaJun
2026-06-03 17:21:13 +08:00
parent 3efcb3bba6
commit bfb4b76611
129 changed files with 4534 additions and 4227 deletions

View File

@@ -3,9 +3,11 @@ import dayjs from 'dayjs'
import { RotateCw } from 'lucide-react'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { getNoticeList } from '@/api'
import lengthGreenBtn from '@/assets/system/length-green-btn.webp'
import checkIcon from '@/assets/system/right.webp'
import { CenterModal } from '@/components/center-modal.tsx'
import { MobileCenterModal } from '@/components/mobile-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'
@@ -13,7 +15,6 @@ import {
ENTRY_NOTICE_CONFIRM_INTERVAL_MS,
ENTRY_NOTICE_LAST_CONFIRMED_AT_KEY,
} from '@/constants'
import { getNoticeList } from '@/features/game/api'
import { cn } from '@/lib/utils'
import { useAuthStore } from '@/store/auth'
import { useModalStore } from '@/store/modal'
@@ -36,8 +37,15 @@ function setLastConfirmedAt(storageKey: string, timestamp: number) {
localStorage.setItem(storageKey, String(timestamp))
}
export function EntryNoticeGateModal() {
interface EntryNoticeGateModalProps {
variant?: 'desktop' | 'mobile'
}
export function EntryNoticeGateModal({
variant = 'desktop',
}: EntryNoticeGateModalProps = {}) {
const { t } = useTranslation()
const isMobile = variant === 'mobile'
const authStatus = useAuthStore((state) => state.status)
const authIsHydrated = useAuthStore((state) => state.isHydrated)
const accessToken = useAuthStore((state) => state.accessToken)
@@ -118,29 +126,64 @@ export function EntryNoticeGateModal() {
return null
}
const Modal = isMobile ? MobileCenterModal : CenterModal
return (
<CenterModal
<Modal
open={shouldShowModal}
isShowClose={false}
isNormalBg={true}
title={
<div className="modal-title-glow text-design-26">
<div
className={cn(
'modal-title-glow',
isMobile ? 'text-design-16' : 'text-design-26',
)}
>
{t('game.modals.entryNotice.title')}
</div>
}
titleAlign="left"
className="h-design-700 w-design-1000 max-h-[92vh] max-w-[92vw]"
className={
isMobile
? 'h-design-400'
: 'h-design-700 w-design-1000 max-h-[92vh] max-w-[92vw]'
}
>
<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)]">
<div
className={cn(
'flex h-full w-full flex-col',
isMobile
? 'gap-design-10 px-design-8 pb-design-8 pt-design-4'
: 'gap-design-20 px-design-14 pb-design-30 pt-design-8',
)}
>
<div
className={cn(
'min-h-0 flex-1 overflow-y-auto rounded-md border border-[#2B8CA3]/45 bg-[#001B24]/70 shadow-[inset_0_0_calc(var(--design-unit)*18)_rgba(39,175,205,0.1)]',
isMobile ? 'p-design-8' : 'p-design-18',
)}
>
{noticeListQuery.isPending ? (
<DataLoadingIndicator
label={t('game.modals.entryNotice.loading')}
className="h-full min-h-[calc(var(--design-unit)*320)]"
className={cn(
'h-full',
isMobile
? 'min-h-[calc(var(--design-unit)*220)] text-design-12'
: '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">
<div
className={cn(
'flex h-full flex-col items-center justify-center text-center text-[#9CE8F2]',
isMobile
? 'min-h-[calc(var(--design-unit)*220)] gap-design-10'
: 'min-h-[calc(var(--design-unit)*320)] gap-design-18',
)}
>
<div className={isMobile ? 'text-design-13' : 'text-design-22'}>
{t('game.modals.entryNotice.loadFailed')}
</div>
<button
@@ -148,30 +191,75 @@ export function EntryNoticeGateModal() {
onClick={() => {
void noticeListQuery.refetch()
}}
className="inline-flex items-center gap-design-8 rounded-md border border-[#4AC6DE]/45 bg-[#0B4454] px-design-18 py-design-10 text-design-18 text-[#D7FFFF] transition hover:bg-[#0E576D]"
className={cn(
'inline-flex items-center rounded-md border border-[#4AC6DE]/45 bg-[#0B4454] text-[#D7FFFF] transition hover:bg-[#0E576D]',
isMobile
? 'gap-design-5 px-design-10 py-design-6 text-design-12'
: 'gap-design-8 px-design-18 py-design-10 text-design-18',
)}
>
<RotateCw className="h-design-18 w-design-18" />
<RotateCw
className={
isMobile
? 'h-design-13 w-design-13'
: 'h-design-18 w-design-18'
}
/>
{t('game.modals.entryNotice.retry')}
</button>
</div>
) : (
<div className="flex flex-col gap-design-16">
<div
className={cn(
'flex flex-col',
isMobile ? 'gap-design-8' : 'gap-design-16',
)}
>
{popoutNotices.map((notice, index) => (
<article
key={notice.notice_id}
className="rounded-md border border-[#2B8CA3]/45 bg-[linear-gradient(180deg,rgba(9,63,78,0.96)_0%,rgba(6,42,53,0.98)_100%)] p-design-20"
className={cn(
'rounded-md border border-[#2B8CA3]/45 bg-[linear-gradient(180deg,rgba(9,63,78,0.96)_0%,rgba(6,42,53,0.98)_100%)]',
isMobile ? 'p-design-9' : 'p-design-20',
)}
>
<div className="mb-design-12 flex flex-wrap items-center justify-between gap-design-12">
<div className="min-w-0 flex-1 text-design-24 font-semibold leading-tight text-white">
<div
className={cn(
'flex flex-wrap items-center justify-between',
isMobile
? 'mb-design-6 gap-design-6'
: 'mb-design-12 gap-design-12',
)}
>
<div
className={cn(
'min-w-0 flex-1 font-semibold leading-tight text-white',
isMobile ? 'text-design-13' : 'text-design-24',
)}
>
{index + 1}. {notice.title}
</div>
<div className="rounded-full border border-[#51BCD1]/35 bg-[#0A4252]/80 px-design-12 py-design-5 text-design-15 text-[#9CE8F2]">
<div
className={cn(
'rounded-full border border-[#51BCD1]/35 bg-[#0A4252]/80 text-[#9CE8F2]',
isMobile
? 'px-design-7 py-design-3 text-design-9'
: 'px-design-12 py-design-5 text-design-15',
)}
>
{dayjs(notice.publish_time * 1000).format(
'YYYY-MM-DD HH:mm:ss',
)}
</div>
</div>
<div className="whitespace-pre-wrap text-design-18 leading-[1.8] text-[#C4F2F7]">
<div
className={cn(
'whitespace-pre-wrap text-[#C4F2F7]',
isMobile
? 'text-design-11 leading-[1.55]'
: 'text-design-18 leading-[1.8]',
)}
>
{notice.content ?? ''}
</div>
</article>
@@ -180,8 +268,20 @@ export function EntryNoticeGateModal() {
)}
</div>
<div className="flex shrink-0 flex-col items-center justify-center gap-design-20">
<label className="inline-flex cursor-pointer items-center justify-center gap-design-12 text-design-20 text-[#C4F2F7]">
<div
className={cn(
'flex shrink-0 flex-col items-center justify-center',
isMobile ? 'gap-design-8' : 'gap-design-20',
)}
>
<label
className={cn(
'inline-flex cursor-pointer items-center justify-center text-[#C4F2F7]',
isMobile
? 'gap-design-6 text-design-11'
: 'gap-design-12 text-design-20',
)}
>
<input
type="checkbox"
checked={hasAgreed}
@@ -192,7 +292,10 @@ export function EntryNoticeGateModal() {
<span
aria-hidden="true"
className={cn(
'flex h-design-32 w-design-32 shrink-0 items-center justify-center rounded-[calc(var(--design-unit)*5)] border transition',
'flex shrink-0 items-center justify-center border transition',
isMobile
? 'h-design-20 w-design-20 rounded-[calc(var(--design-unit)*4)]'
: 'h-design-32 w-design-32 rounded-[calc(var(--design-unit)*5)]',
hasAgreed
? 'border-[#4AFF49]/80 bg-[#071F11]'
: 'border-[#6CCDCF]/70 bg-[#031D25]',
@@ -204,7 +307,12 @@ export function EntryNoticeGateModal() {
alt=""
priority={true}
showSkeleton={false}
className="h-design-34 w-design-38 overflow-visible"
className={cn(
'overflow-visible',
isMobile
? 'h-design-22 w-design-24'
: 'h-design-34 w-design-38',
)}
imgClassName="object-contain"
/>
) : null}
@@ -230,7 +338,10 @@ export function EntryNoticeGateModal() {
}
}}
className={cn(
'flex h-design-72 w-design-270 items-center justify-center rounded-md pb-design-5 text-design-22 font-bold transition',
'flex items-center justify-center rounded-md font-bold transition',
isMobile
? 'h-design-42 w-design-156 pb-design-3 text-design-14'
: 'h-design-72 w-design-270 pb-design-5 text-design-22',
canEnter
? 'modal-title-glow cursor-pointer text-white hover:brightness-110 active:brightness-95'
: 'cursor-not-allowed text-white opacity-80 grayscale',
@@ -248,6 +359,10 @@ export function EntryNoticeGateModal() {
</SmartBackground>
</div>
</div>
</CenterModal>
</Modal>
)
}
export function MobileEntryNoticeGateModal() {
return <EntryNoticeGateModal variant="mobile" />
}

View File

@@ -1,5 +1,5 @@
import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator'
import type { PeriodHistoryDisplayItem } from '@/features/game/hooks/use-period-history-vm'
import type { PeriodHistoryDisplayItem } from '@/hooks/use-period-history-vm'
import { cn } from '@/lib/utils'
interface PeriodHistoryListLabels {