feat(frontend): App 路由集成 + 木纹风格 CSS

App.tsx 添加 menu/game/replay 三页面路由切换,MainMenu 新增
onReplayStart 属性区分对局与回放入口。App.css 实现经典木纹
视觉风格(深棕底色、米黄文字、皮革纹理按钮),index.css 基础
重置。修复 tsconfig 中 erasableSyntaxOnly 无效选项并安装
@types/node。
This commit is contained in:
2026-05-31 00:27:34 +08:00
parent 0138d80f2a
commit e395ea424b
6 changed files with 232 additions and 12 deletions
+183
View File
@@ -1,7 +1,190 @@
:root {
--bg-primary: #3C2415;
--bg-secondary: #F5DEB3;
--text-primary: #F5DEB3;
--text-secondary: #3C2415;
--accent: #8B4513;
--accent-hover: #A0522D;
--button-bg: #DEB887;
--button-hover: #D2B48C;
--border: #8B7355;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
}
.app {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
}
.main-menu {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
gap: 40px;
}
.menu-title {
font-size: 42px;
color: var(--text-primary);
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.menu-buttons {
display: flex;
flex-direction: column;
gap: 16px;
}
.menu-buttons button {
width: 240px;
padding: 14px 28px;
font-size: 18px;
border: 2px solid var(--border);
border-radius: 8px;
background: var(--button-bg);
color: var(--text-secondary);
cursor: pointer;
transition: all 0.2s;
}
.menu-buttons button:hover {
background: var(--button-hover);
transform: scale(1.03);
}
.setup-panel {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
gap: 24px;
}
.setup-panel h2 {
font-size: 28px;
}
.setup-panel label {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
}
.setup-panel select, .setup-panel input {
padding: 6px 12px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg-secondary);
color: var(--text-secondary);
font-size: 14px;
}
.setup-actions {
display: flex;
gap: 12px;
margin-top: 16px;
}
button {
padding: 10px 24px;
font-size: 16px;
border: 2px solid var(--border);
border-radius: 6px;
background: var(--button-bg);
color: var(--text-secondary);
cursor: pointer;
transition: all 0.2s;
}
button:hover {
background: var(--button-hover);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.game-view {
display: flex;
flex-direction: column;
align-items: center;
width: 100vw;
height: 100vh;
padding: 12px;
gap: 8px;
}
.board-container {
flex: 1;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.game-info {
font-size: 20px;
font-weight: bold;
padding: 8px;
}
.game-controls {
display: flex;
gap: 12px;
padding: 8px;
}
.timer-display {
font-size: 24px;
font-family: monospace;
}
.timer-warning {
color: #ff4444;
animation: blink 0.5s infinite alternate;
}
@keyframes blink {
from { opacity: 1; }
to { opacity: 0.3; }
}
.replay-view {
display: flex;
flex-direction: column;
align-items: center;
width: 100vw;
height: 100vh;
padding: 12px;
gap: 8px;
}
.step-slider {
width: 80%;
accent-color: var(--accent);
}
.replay-controls {
display: flex;
gap: 12px;
}
+21 -3
View File
@@ -1,8 +1,26 @@
import { useState } from 'react';
import MainMenu from './components/menu/MainMenu';
import GameView from './components/game/GameView';
import ReplayView from './components/replay/ReplayView';
import './App.css';
type Page = 'menu' | 'game' | 'replay';
function App() {
const [page, setPage] = useState<Page>('menu');
const handleGameStart = () => setPage('game');
const handleReplayStart = () => setPage('replay');
const handleBackToMenu = () => setPage('menu');
if (page === 'game') return <GameView onBackToMenu={handleBackToMenu} />;
if (page === 'replay') return <ReplayView onBackToMenu={handleBackToMenu} />;
return (
<div className="app">
<h1> v2.0</h1>
</div>
<MainMenu
onGameStart={handleGameStart}
onReplayStart={handleReplayStart}
/>
);
}
+3 -2
View File
@@ -9,16 +9,17 @@ type View = 'main' | 'local' | 'ai' | 'online' | 'replay';
interface Props {
onGameStart: () => void;
onReplayStart: () => void;
}
export default function MainMenu({ onGameStart }: Props) {
export default function MainMenu({ onGameStart, onReplayStart }: Props) {
const { t } = useTranslation();
const [view, setView] = useState<View>('main');
if (view === 'local') return <LocalGameSetup onBack={() => setView('main')} onStart={onGameStart} />;
if (view === 'ai') return <AiGameSetup onBack={() => setView('main')} onStart={onGameStart} />;
if (view === 'online') return <OnlineSetup onBack={() => setView('main')} onStart={onGameStart} />;
if (view === 'replay') return <LoadReplay onBack={() => setView('main')} onStart={onGameStart} />;
if (view === 'replay') return <LoadReplay onBack={() => setView('main')} onStart={onReplayStart} />;
return (
<div className="main-menu">