# 前端技术架构详设文档 > **文档版本**: v1.0 > **撰写人**: 前端架构师 > **创建日期**: 2024年9月11日 ## 1. 前端架构总览 ### 1.1 技术栈详细说明 ```typescript // 核心技术栈配置 { "framework": "Taro 4.x", // 跨端开发框架 "ui": "React 18.2.0", // UI框架 "language": "TypeScript 5.0+", // 开发语言 "state": "Zustand 4.4.0", // 状态管理 "css": "Sass + CSS Modules", // 样式方案 "build": "Webpack 5 + SWC", // 构建工具 "lint": "ESLint + Prettier", // 代码规范 "test": "Jest + Testing Library" // 测试框架 } ``` ### 1.2 项目目录结构 ``` src/ ├── components/ # 通用组件 │ ├── GameBoard/ # 游戏棋盘组件 │ ├── PlaneShape/ # 飞机形状组件 │ ├── Modal/ # 模态框组件 │ └── Loading/ # 加载组件 ├── pages/ # 页面组件 │ ├── entry/ # 入口页面 │ ├── room/ # 房间相关页面 │ └── game/ # 游戏页面 ├── hooks/ # 自定义Hooks │ ├── useGame.ts # 游戏逻辑Hook │ ├── useWebSocket.ts # WebSocket Hook │ └── useAuth.ts # 认证Hook ├── store/ # 状态管理 │ ├── gameStore.ts # 游戏状态 │ ├── userStore.ts # 用户状态 │ └── roomStore.ts # 房间状态 ├── services/ # 业务服务 │ ├── api.ts # API封装 │ ├── websocket.ts # WebSocket服务 │ └── storage.ts # 本地存储 ├── utils/ # 工具函数 │ ├── gameLogic.ts # 游戏逻辑工具 │ ├── helpers.ts # 通用工具 │ └── constants.ts # 常量定义 └── styles/ # 样式文件 ├── variables.scss # 变量定义 ├── mixins.scss # 混合器 └── global.scss # 全局样式 ``` ## 2. 状态管理架构 ### 2.1 Zustand Store 设计 ```typescript // 用户状态Store interface UserState { user: User | null isAuthenticated: boolean login: (userInfo: WxUserInfo) => Promise logout: () => void updateProfile: (profile: Partial) => void } export const useUserStore = create((set, get) => ({ user: null, isAuthenticated: false, login: async (userInfo: WxUserInfo) => { try { const user = await apiService.login(userInfo) set({ user, isAuthenticated: true }) // 持久化存储 await storage.setUser(user) } catch (error) { throw new Error('登录失败') } }, logout: () => { set({ user: null, isAuthenticated: false }) storage.clearUser() }, updateProfile: (profile: Partial) => { const currentUser = get().user if (currentUser) { set({ user: { ...currentUser, ...profile } }) } } })) // 游戏状态Store interface GameState { // 当前游戏状态 currentGame: GameSession | null gamePhase: GamePhase currentPlayer: string myBoard: GameBoard opponentBoard: GameBoard // 操作方法 initGame: (gameData: GameInitData) => void placePlane: (plane: PlaneData) => boolean attack: (position: Position) => void updateGameState: (state: GameStateUpdate) => void resetGame: () => void } export const useGameStore = create((set, get) => ({ currentGame: null, gamePhase: GamePhase.WAITING, currentPlayer: '', myBoard: createEmptyBoard(), opponentBoard: createEmptyBoard(), initGame: (gameData: GameInitData) => { set({ currentGame: gameData.session, gamePhase: GamePhase.PLACING, currentPlayer: gameData.firstPlayer, myBoard: createEmptyBoard(), opponentBoard: createEmptyBoard() }) }, placePlane: (plane: PlaneData): boolean => { const state = get() const newBoard = GameLogic.placePlane(state.myBoard, plane) if (newBoard) { set({ myBoard: newBoard }) return true } return false }, attack: (position: Position) => { const state = get() if (state.gamePhase !== GamePhase.BATTLING) return // 发送攻击请求 websocketService.sendAttack(position) }, updateGameState: (update: GameStateUpdate) => { set(state => ({ ...state, ...update })) }, resetGame: () => { set({ currentGame: null, gamePhase: GamePhase.WAITING, currentPlayer: '', myBoard: createEmptyBoard(), opponentBoard: createEmptyBoard() }) } })) // 房间状态Store interface RoomState { currentRoom: Room | null availableRooms: Room[] connectionStatus: ConnectionStatus createRoom: () => Promise joinRoom: (roomCode: string) => Promise leaveRoom: () => void refreshRoomList: () => Promise } export const useRoomStore = create((set, get) => ({ currentRoom: null, availableRooms: [], connectionStatus: ConnectionStatus.DISCONNECTED, createRoom: async (): Promise => { const room = await apiService.createRoom() set({ currentRoom: room }) // 连接WebSocket await websocketService.joinRoom(room.code) return room }, joinRoom: async (roomCode: string): Promise => { try { const room = await apiService.joinRoom(roomCode) set({ currentRoom: room }) await websocketService.joinRoom(roomCode) return true } catch (error) { return false } }, leaveRoom: () => { const room = get().currentRoom if (room) { websocketService.leaveRoom(room.code) set({ currentRoom: null }) } }, refreshRoomList: async () => { const rooms = await apiService.getRoomList() set({ availableRooms: rooms }) } })) ``` ### 2.2 状态持久化策略 ```typescript // 状态持久化中间件 const persistMiddleware = ( config: StateCreator, options: { name: string storage: Storage partialize?: (state: T) => Partial } ) => (set: any, get: any, api: any) => config( (partial, replace) => { const nextState = typeof partial === 'function' ? partial(get()) : partial const stateToStore = options.partialize ? options.partialize(nextState) : nextState // 更新状态 set(partial, replace) // 持久化存储 options.storage.setItem(options.name, JSON.stringify(stateToStore)) }, get, api ) // 使用示例 export const useUserStore = create()( persistMiddleware( (set, get) => ({ // 状态定义... }), { name: 'user-store', storage: wx.getStorageSync ? wxStorage : localStorage, partialize: (state) => ({ user: state.user, isAuthenticated: state.isAuthenticated }) } ) ) ``` ## 3. 组件设计架构 ### 3.1 基础组件设计 ```typescript // 游戏棋盘组件 interface GameBoardProps { board: GameBoard mode: 'placement' | 'battle' | 'readonly' onCellClick?: (position: Position) => void onPlanePlace?: (plane: PlaneData) => void showAttackResults?: boolean className?: string } export const GameBoard: React.FC = ({ board, mode, onCellClick, onPlanePlace, showAttackResults = false, className = '' }) => { const [selectedCell, setSelectedCell] = useState(null) const [dragState, setDragState] = useState(null) // 处理单元格点击 const handleCellClick = useCallback((position: Position) => { if (mode === 'readonly') return if (mode === 'placement') { handlePlacementClick(position) } else if (mode === 'battle') { handleBattleClick(position) } }, [mode, onCellClick, onPlanePlace]) // 处理飞机放置 const handlePlacementClick = (position: Position) => { // 飞机放置逻辑 if (onPlanePlace) { const plane = createPlaneAt(position, currentDirection) onPlanePlace(plane) } } // 处理攻击点击 const handleBattleClick = (position: Position) => { if (onCellClick) { onCellClick(position) } } return (
{board.cells.map((row, x) => row.map((cell, y) => ( )) )}
) } // 棋盘单元格组件 interface BoardCellProps { cell: BoardCell position: Position isSelected: boolean onClick: (position: Position) => void showAttackResult: boolean } const BoardCell: React.FC = ({ cell, position, isSelected, onClick, showAttackResult }) => { const handleClick = () => onClick(position) const cellClass = [ 'board-cell', cell.state, isSelected && 'selected', cell.planeId && 'has-plane' ].filter(Boolean).join(' ') return (
{showAttackResult && cell.attackResult && (
{cell.attackResult.type === 'HIT' ? '🎯' : '💥'}
)} {cell.planeId && (
)}
) } ``` ### 3.2 页面组件架构 ```typescript // 游戏对战页面 const BattlePage: React.FC = () => { const gameState = useGameStore() const userState = useUserStore() const { connectWebSocket, sendMessage } = useWebSocket() // 游戏初始化 useEffect(() => { initializeGame() }, []) // WebSocket消息处理 useEffect(() => { const handleMessage = (message: GameMessage) => { switch (message.type) { case 'GAME_STATE_UPDATE': gameState.updateGameState(message.data) break case 'ATTACK_RESULT': handleAttackResult(message.data) break case 'PLAYER_DISCONNECTED': handlePlayerDisconnect(message.data) break } } connectWebSocket(handleMessage) }, []) const initializeGame = async () => { try { const gameData = await apiService.getGameState(gameState.currentGame?.id) gameState.initGame(gameData) } catch (error) { showError('游戏初始化失败') } } const handleAttack = (position: Position) => { gameState.attack(position) } const handleAttackResult = (result: AttackResult) => { gameState.updateGameState({ opponentBoard: applyAttackResult(gameState.opponentBoard, result) }) if (result.gameEnded) { showGameResult(result.winner) } } return (

我的棋盘

对手棋盘

gameState.surrender()} />
) } ``` ## 4. 自定义Hooks设计 ### 4.1 游戏逻辑Hooks ```typescript // 游戏逻辑Hook export const useGame = () => { const gameStore = useGameStore() const [loading, setLoading] = useState(false) const [error, setError] = useState(null) // 开始游戏 const startGame = async (roomCode: string) => { setLoading(true) setError(null) try { const gameData = await apiService.startGame(roomCode) gameStore.initGame(gameData) } catch (err) { setError('游戏启动失败') } finally { setLoading(false) } } // 放置飞机 const placePlane = (plane: PlaneData) => { const success = gameStore.placePlane(plane) if (!success) { setError('飞机放置失败,请检查位置') return false } // 如果已放置3架飞机,自动进入准备状态 if (gameStore.myBoard.planes.length === 3) { confirmPlacement() } return true } // 确认飞机布置 const confirmPlacement = async () => { try { await apiService.confirmPlacement(gameStore.myBoard.planes) gameStore.updateGameState({ gamePhase: GamePhase.WAITING_OPPONENT }) } catch (err) { setError('确认布置失败') } } // 执行攻击 const attack = async (position: Position) => { if (gameStore.gamePhase !== GamePhase.BATTLING) return try { const result = await apiService.attack(position) gameStore.updateGameState({ opponentBoard: applyAttackResult(gameStore.opponentBoard, result) }) if (result.gameEnded) { gameStore.updateGameState({ gamePhase: GamePhase.FINISHED }) } } catch (err) { setError('攻击失败') } } return { ...gameStore, loading, error, startGame, placePlane, confirmPlacement, attack, clearError: () => setError(null) } } // WebSocket Hook export const useWebSocket = () => { const [socket, setSocket] = useState(null) const [connectionState, setConnectionState] = useState('disconnected') const [lastMessage, setLastMessage] = useState(null) // 连接WebSocket const connect = useCallback((onMessage: (message: GameMessage) => void) => { if (socket?.readyState === WebSocket.OPEN) return const ws = new WebSocketConnection({ url: WS_URL, onOpen: () => setConnectionState('connected'), onClose: () => setConnectionState('disconnected'), onError: () => setConnectionState('error'), onMessage: (message) => { setLastMessage(message) onMessage(message) } }) setSocket(ws) setConnectionState('connecting') }, [socket]) // 发送消息 const sendMessage = useCallback((message: GameMessage) => { if (socket?.readyState === WebSocket.OPEN) { socket.send(JSON.stringify(message)) } }, [socket]) // 断线重连 useEffect(() => { let reconnectTimer: NodeJS.Timeout if (connectionState === 'disconnected') { reconnectTimer = setTimeout(() => { if (socket) { connect(() => {}) // 重连 } }, 3000) } return () => clearTimeout(reconnectTimer) }, [connectionState, connect]) // 清理连接 useEffect(() => { return () => { if (socket) { socket.close() } } }, []) return { connectionState, lastMessage, connect, sendMessage, disconnect: () => socket?.close() } } ``` ### 4.2 UI交互Hooks ```typescript // 模态框Hook export const useModal = () => { const [isOpen, setIsOpen] = useState(false) const [title, setTitle] = useState('') const [content, setContent] = useState(null) const openModal = (modalTitle: string, modalContent: React.ReactNode) => { setTitle(modalTitle) setContent(modalContent) setIsOpen(true) } const closeModal = () => { setIsOpen(false) } const Modal = ({ children }: { children?: React.ReactNode }) => { if (!isOpen) return null return (
e.stopPropagation()}>

{title}

{content || children}
) } return { isOpen, openModal, closeModal, Modal } } // 加载状态Hook export const useLoading = () => { const [loading, setLoading] = useState(false) const [loadingText, setLoadingText] = useState('加载中...') const showLoading = (text = '加载中...') => { setLoadingText(text) setLoading(true) } const hideLoading = () => { setLoading(false) } const LoadingComponent = () => { if (!loading) return null return (

{loadingText}

) } return { loading, showLoading, hideLoading, LoadingComponent } } ``` ## 5. 样式架构设计 ### 5.1 设计Token系统 ```scss // styles/variables.scss - 设计变量定义 :root { // 颜色系统 - 基于原型的深色科技主题 --color-primary: #6366f1; --color-secondary: #40e0d0; --color-accent: #ff6b6b; --color-success: #51cf66; --color-warning: #ffd43b; --color-danger: #ff6b6b; // 背景色 --bg-primary: #0f1419; --bg-secondary: #1a1a2e; --bg-card: #16213e; --bg-overlay: rgba(15, 20, 25, 0.9); // 渐变背景 --gradient-primary: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 30%, #16213e 70%, #0f3460 100%); --gradient-card: linear-gradient(145deg, #1e2a47 0%, #2a3f5f 100%); // 文字颜色 --text-primary: #ffffff; --text-secondary: #b8c5d3; --text-muted: #6c7983; --text-disabled: #4a5568; // 边框和分割线 --border-color: #2d3748; --border-hover: #4a5568; --divider: #2d3748; // 阴影 --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12); --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.16); --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.2); --shadow-glow: 0 0 20px rgba(99, 102, 241, 0.3); // 间距系统 --space-xs: 4px; --space-sm: 8px; --space-md: 16px; --space-lg: 24px; --space-xl: 32px; --space-xxl: 48px; // 字体大小 --font-xs: 12px; --font-sm: 14px; --font-base: 16px; --font-lg: 18px; --font-xl: 20px; --font-2xl: 24px; --font-3xl: 30px; // 圆角 --radius-sm: 4px; --radius-md: 8px; --radius-lg: 12px; --radius-xl: 16px; --radius-full: 50%; // Z-index层级 --z-modal: 1000; --z-loading: 999; --z-toast: 998; --z-header: 100; } ``` ### 5.2 组件样式库 ```scss // styles/components.scss - 组件样式 .game-board { display: grid; grid-template-columns: repeat(10, 1fr); grid-template-rows: repeat(10, 1fr); gap: 2px; padding: var(--space-md); background: var(--gradient-card); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); &.placement { .board-cell { cursor: pointer; &:hover { background: var(--color-primary); opacity: 0.7; } } } &.battle { .board-cell { cursor: crosshair; &:hover:not(.attacked) { background: var(--color-accent); opacity: 0.8; } } } } .board-cell { aspect-ratio: 1; background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: var(--radius-sm); position: relative; transition: all 0.2s ease; &.has-plane { background: var(--color-secondary); } &.attacked-miss { background: var(--bg-card); &::after { content: '○'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: var(--text-muted); font-size: var(--font-sm); } } &.attacked-hit { background: var(--color-danger); &::after { content: '✕'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: var(--text-primary); font-size: var(--font-base); font-weight: bold; } } &.selected { background: var(--color-primary); box-shadow: var(--shadow-glow); } } .attack-result { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: var(--font-lg); animation: attackResult 0.8s ease-out; &.HIT { color: var(--color-success); } &.MISS { color: var(--text-muted); } &.DESTROY { color: var(--color-danger); font-size: var(--font-xl); } } @keyframes attackResult { 0% { transform: translate(-50%, -50%) scale(0); opacity: 0; } 50% { transform: translate(-50%, -50%) scale(1.5); opacity: 1; } 100% { transform: translate(-50%, -50%) scale(1); opacity: 1; } } ``` ## 6. 性能优化策略 ### 6.1 渲染优化 ```typescript // 使用React.memo优化组件渲染 export const GameBoard = React.memo(({ board, mode, onCellClick, onPlanePlace }) => { // 只有相关props变化时才重渲染 }, (prevProps, nextProps) => { return ( prevProps.board === nextProps.board && prevProps.mode === nextProps.mode && prevProps.onCellClick === nextProps.onCellClick && prevProps.onPlanePlace === nextProps.onPlanePlace ) }) // 使用useMemo缓存计算结果 const BoardCell = React.memo(({ cell, position, onClick }) => { const cellClass = useMemo(() => { return [ 'board-cell', cell.state, cell.planeId && 'has-plane' ].filter(Boolean).join(' ') }, [cell.state, cell.planeId]) const handleClick = useCallback(() => { onClick(position) }, [onClick, position]) return (
{/* 单元格内容 */}
) }) ``` ### 6.2 状态更新优化 ```typescript // 批量状态更新 class GameStateManager { private updateQueue: StateUpdate[] = [] private isUpdating = false queueUpdate(update: StateUpdate) { this.updateQueue.push(update) if (!this.isUpdating) { this.flushUpdates() } } private async flushUpdates() { this.isUpdating = true // 合并所有更新 const mergedUpdate = this.updateQueue.reduce((merged, update) => { return { ...merged, ...update } }, {}) // 批量应用更新 useGameStore.setState(state => ({ ...state, ...mergedUpdate })) this.updateQueue = [] this.isUpdating = false } } ``` ## 7. 错误处理机制 ### 7.1 错误边界设计 ```typescript // 游戏错误边界 class GameErrorBoundary extends React.Component< { children: React.ReactNode }, { hasError: boolean; error: Error | null } > { constructor(props: any) { super(props) this.state = { hasError: false, error: null } } static getDerivedStateFromError(error: Error) { return { hasError: true, error } } componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error('游戏错误:', error, errorInfo) // 上报错误 this.reportError(error, errorInfo) } private reportError(error: Error, errorInfo: ErrorInfo) { // 调用错误上报服务 errorReportingService.log({ message: error.message, stack: error.stack, componentStack: errorInfo.componentStack }) } render() { if (this.state.hasError) { return (

游戏出错了

我们已经记录了错误信息,请稍后重试。

) } return this.props.children } }