feat: 添加房间页面全屏容器和 WS 中枢
This commit is contained in:
151
src/views/game/RoomPage.vue
Normal file
151
src/views/game/RoomPage.vue
Normal 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>
|
||||||
Reference in New Issue
Block a user