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
+18
View File
@@ -18,6 +18,7 @@
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^2.0.0", "@tauri-apps/cli": "^2.0.0",
"@types/node": "^25.9.1",
"@types/react": "^19.0.0", "@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0", "@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.4.0", "@vitejs/plugin-react": "^4.4.0",
@@ -1472,6 +1473,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": {
"version": "25.9.1",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-25.9.1.tgz",
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": ">=7.24.0 <7.24.7"
}
},
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.2.15", "version": "19.2.15",
"resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.15.tgz", "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.15.tgz",
@@ -2360,6 +2371,13 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/undici-types": {
"version": "7.24.6",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.24.6.tgz",
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
"dev": true,
"license": "MIT"
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+7 -6
View File
@@ -11,21 +11,22 @@
"test:watch": "vitest --watch" "test:watch": "vitest --watch"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.0.0",
"@tauri-apps/plugin-opener": "^2.0.0",
"i18next": "^24.0.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"zustand": "^5.0.0",
"i18next": "^24.0.0",
"react-i18next": "^15.0.0", "react-i18next": "^15.0.0",
"@tauri-apps/api": "^2.0.0", "zustand": "^5.0.0"
"@tauri-apps/plugin-opener": "^2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^2.0.0",
"@types/node": "^25.9.1",
"@types/react": "^19.0.0", "@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0", "@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.4.0", "@vitejs/plugin-react": "^4.4.0",
"typescript": "~5.7.0", "typescript": "~5.7.0",
"vite": "^6.0.0", "vite": "^6.0.0",
"vitest": "^3.0.0", "vitest": "^3.0.0"
"@tauri-apps/cli": "^2.0.0"
} }
} }
+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 { .app {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
}
.main-menu {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 100vh; 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() { 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 ( return (
<div className="app"> <MainMenu
<h1> v2.0</h1> onGameStart={handleGameStart}
</div> onReplayStart={handleReplayStart}
/>
); );
} }
+3 -2
View File
@@ -9,16 +9,17 @@ type View = 'main' | 'local' | 'ai' | 'online' | 'replay';
interface Props { interface Props {
onGameStart: () => void; onGameStart: () => void;
onReplayStart: () => void;
} }
export default function MainMenu({ onGameStart }: Props) { export default function MainMenu({ onGameStart, onReplayStart }: Props) {
const { t } = useTranslation(); const { t } = useTranslation();
const [view, setView] = useState<View>('main'); const [view, setView] = useState<View>('main');
if (view === 'local') return <LocalGameSetup onBack={() => setView('main')} onStart={onGameStart} />; if (view === 'local') return <LocalGameSetup onBack={() => setView('main')} onStart={onGameStart} />;
if (view === 'ai') return <AiGameSetup 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 === '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 ( return (
<div className="main-menu"> <div className="main-menu">
-1
View File
@@ -17,7 +17,6 @@
/* Linting */ /* Linting */
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts"]