From f937b5cdb3f0e23127acec7294f814e1cd014b7d Mon Sep 17 00:00:00 2001 From: Azure <983547216@qq.com> Date: Tue, 23 Jun 2026 11:28:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BB=A3=E7=A0=81=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D=20=E2=80=94=20socket=20?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E6=8F=90=E4=BE=9B=20+=20inject=20=E7=A9=BA?= =?UTF-8?q?=E5=AE=89=E5=85=A8=20+=20=E8=A7=82=E6=88=98=E4=BA=BA=E6=95=B0?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/modules/room.ts | 12 ++++++-- src/views/game/RoomPage.vue | 35 ++++++++++++++++++++-- src/views/game/components/GomokuBoard.vue | 2 +- src/views/game/components/RoomActions.vue | 2 +- src/views/game/components/RoomChat.vue | 9 ++++-- src/views/game/components/SpectatorBar.vue | 15 ++-------- 6 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/stores/modules/room.ts b/src/stores/modules/room.ts index b456ca9..6549318 100644 --- a/src/stores/modules/room.ts +++ b/src/stores/modules/room.ts @@ -52,6 +52,9 @@ export const useRoomStore = defineStore('room', () => { const drawRequested = ref(false) const drawRequestFrom = ref(null) + // ===== 观战者 ===== + const spectatorCount = ref(0) + /** * 从房间详情 HTTP 响应初始化 Store * @param detail - 房间详情响应数据 @@ -129,6 +132,11 @@ export const useRoomStore = defineStore('room', () => { resultType.value = type } + /** 更新观战人数 */ + function setSpectatorCount(count: number) { + spectatorCount.value = count + } + /** 重置状态 */ function reset() { roomId.value = '' @@ -150,8 +158,8 @@ export const useRoomStore = defineStore('room', () => { boardState, currentTurn, moves, winner, resultType, isPlaying, isFinished, messages, - drawRequested, drawRequestFrom, + drawRequested, drawRequestFrom, spectatorCount, initFromDetail, applyMove, setCurrentTurn, addMessage, - updateReady, addPlayer, removePlayer, setGameOver, reset + updateReady, addPlayer, removePlayer, setGameOver, setSpectatorCount, reset } }) diff --git a/src/views/game/RoomPage.vue b/src/views/game/RoomPage.vue index f480e61..ce6aa31 100644 --- a/src/views/game/RoomPage.vue +++ b/src/views/game/RoomPage.vue @@ -45,6 +45,28 @@ const accountStore = useAccountStore() let socket: RoomSocket | null = null +/** + * WebSocket 代理对象 + * + * 在 WS 未建立之前 provide 给子组件的安全代理。 + * 子组件调用 send/close/isOpen 时,代理将请求转发到实际 socket。 + * WS 未连接时 send() 静默丢弃消息,避免子组件因 inject undefined 而崩溃。 + */ +const socketProxy: RoomSocket = { + send(type: string, data?: unknown) { + socket?.send(type, data) + }, + close() { + socket?.close() + }, + isOpen() { + return socket?.isOpen() ?? false + } +} + +// 在 setup 阶段同步 provide,确保子组件不会 inject 到 undefined +provide('roomSocket', socketProxy) + onMounted(async () => { const roomId = route.params.roomId as string const userId = accountStore.accountInfo?.userId ?? '' @@ -64,8 +86,7 @@ onMounted(async () => { } }) - // 3. 向子组件暴露 WebSocket 控制接口 - provide('roomSocket', socket) + // socketProxy 已在 setup 阶段同步 provide,子组件现在调用 send() 即可 } catch { // 房间不存在或加载失败,返回大厅 router.push('/') @@ -119,6 +140,16 @@ function handleWsMessage(msg: WsMessage) { roomStore.updateReady(data.userId, data.ready) break } + case 'spectator_join': { + const data = msg.data as { count: number } + roomStore.setSpectatorCount(data.count) + break + } + case 'spectator_leave': { + const data = msg.data as { count: number } + roomStore.setSpectatorCount(data.count) + break + } } } diff --git a/src/views/game/components/GomokuBoard.vue b/src/views/game/components/GomokuBoard.vue index 791c161..5209e9a 100644 --- a/src/views/game/components/GomokuBoard.vue +++ b/src/views/game/components/GomokuBoard.vue @@ -29,7 +29,7 @@ const canvasSize = PADDING * 2 + CELL_SIZE * (BOARD_SIZE - 1) const store = useRoomStore() const { boardState, currentTurn, currentUserId, isPlayer, isPlaying } = storeToRefs(store) -const socket = inject('roomSocket')! +const socket = inject('roomSocket', null)! const canvasRef = ref(null) const hoverPos = ref<{ row: number; col: number } | null>(null) diff --git a/src/views/game/components/RoomActions.vue b/src/views/game/components/RoomActions.vue index f0c118f..7fe5297 100644 --- a/src/views/game/components/RoomActions.vue +++ b/src/views/game/components/RoomActions.vue @@ -47,7 +47,7 @@ const { players, currentUserId } = storeToRefs(store) const router = useRouter() /** 通过 inject 获取 WS 连接(父组件 provide) */ -const socket = inject('roomSocket')! +const socket = inject('roomSocket', null)! /** 当前用户是否已准备 */ const isReady = computed(() => { diff --git a/src/views/game/components/RoomChat.vue b/src/views/game/components/RoomChat.vue index e2a7d3c..a8abf21 100644 --- a/src/views/game/components/RoomChat.vue +++ b/src/views/game/components/RoomChat.vue @@ -33,7 +33,12 @@ import type { RoomSocket } from '@/common/websocket/roomSocket' const store = useRoomStore() const { messages, players } = storeToRefs(store) -const socket = inject('roomSocket')! +const socket = inject('roomSocket', null) + +/** 发送消息(空安全,socket 尚未建立时静默丢弃) */ +function safeSend(type: string, data?: unknown) { + socket?.send(type, data) +} /** 输入框内容 */ const input = ref('') @@ -54,7 +59,7 @@ watch(() => messages.value.length, async () => { function sendMessage() { const content = input.value.trim() if (!content) return - socket.send('chat', { content }) + safeSend('chat', { content }) input.value = '' } diff --git a/src/views/game/components/SpectatorBar.vue b/src/views/game/components/SpectatorBar.vue index 5b8fb28..4ece912 100644 --- a/src/views/game/components/SpectatorBar.vue +++ b/src/views/game/components/SpectatorBar.vue @@ -8,22 +8,11 @@