feat: 添加房间页面全屏容器和 WS 中枢

This commit is contained in:
2026-06-23 11:16:40 +08:00
parent b435256157
commit f91b76084a

151
src/views/game/RoomPage.vue Normal file
View File

@@ -0,0 +1,151 @@
<template>
<div class="room-page">
<!-- 顶部信息栏 -->
<RoomTopBar />
<!-- 主体区域 -->
<div class="room-page__main">
<!-- 左侧面板 -->
<RoomSidePanel />
<!-- 中央游戏画布 -->
<div class="room-page__canvas">
<GameCanvas />
</div>
<!-- 右侧面板 -->
<RoomRightPanel />
</div>
<!-- 底部观战栏 -->
<SpectatorBar v-if="roomStore.allowSpectate" />
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, provide } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRoomStore } from '@/stores'
import { useAccountStore } from '@/stores'
import { getRoomDetail } from '@/api/modules/game'
import { createRoomSocket } from '@/common/websocket/roomSocket'
import type { RoomSocket } from '@/common/websocket/roomSocket'
import type { WsMessage } from '@/api/modules/game'
import RoomTopBar from './components/RoomTopBar.vue'
import RoomSidePanel from './components/RoomSidePanel.vue'
import GameCanvas from './components/GameCanvas.vue'
import RoomRightPanel from './components/RoomRightPanel.vue'
import SpectatorBar from './components/SpectatorBar.vue'
const route = useRoute()
const router = useRouter()
const roomStore = useRoomStore()
const accountStore = useAccountStore()
let socket: RoomSocket | null = null
onMounted(async () => {
const roomId = route.params.roomId as string
const userId = accountStore.accountInfo?.userId ?? ''
try {
// 1. 获取房间详情(初始快照)
const res = await getRoomDetail(roomId)
roomStore.initFromDetail(res.data, userId)
// 2. 建立 WebSocket 连接
const token = accountStore.accessToken
socket = createRoomSocket(roomId, token, {
onMessage: handleWsMessage,
onClose: () => {
// 连接关闭,提示并返回大厅
router.push('/')
}
})
// 3. 向子组件暴露 WebSocket 控制接口
provide('roomSocket', socket)
} catch {
// 房间不存在或加载失败,返回大厅
router.push('/')
}
})
onUnmounted(() => {
socket?.close()
roomStore.reset()
})
/** 处理 WebSocket 下行消息 */
function handleWsMessage(msg: WsMessage) {
switch (msg.type) {
case 'board_sync': {
const data = msg.data as { board: number[][]; currentTurn: string }
roomStore.boardState = data.board
roomStore.setCurrentTurn(data.currentTurn)
break
}
case 'move': {
const data = msg.data as { row: number; col: number; playerId: string; moveIndex: number }
roomStore.applyMove(data.row, data.col, data.playerId, data.moveIndex)
break
}
case 'turn_change': {
const data = msg.data as { playerId: string }
roomStore.setCurrentTurn(data.playerId)
break
}
case 'chat_broadcast':
roomStore.addMessage(msg.data as { userId: string; content: string; timestamp: number })
break
case 'game_over': {
const data = msg.data as { winnerId: string | null; resultType: string }
roomStore.setGameOver(data.winnerId, data.resultType)
break
}
case 'player_join': {
const data = msg.data as { userId: string; avatar: string; nickname: string; seatNumber: number; ready: boolean }
roomStore.addPlayer(data)
break
}
case 'player_leave': {
const data = msg.data as { userId: string }
roomStore.removePlayer(data.userId)
break
}
case 'ready_change': {
const data = msg.data as { userId: string; ready: boolean }
roomStore.updateReady(data.userId, data.ready)
break
}
}
}
</script>
<style scoped lang="scss">
.room-page {
display: flex;
flex-direction: column;
height: 100lvh;
width: 100lvw;
overflow: hidden;
background-color: var(--bg-body);
}
.room-page__main {
display: flex;
flex: 1;
min-height: 0;
overflow: hidden;
}
.room-page__canvas {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
overflow: hidden;
}
</style>