diff --git a/src/stores/modules/room.ts b/src/stores/modules/room.ts index 6549318..70ae6a5 100644 --- a/src/stores/modules/room.ts +++ b/src/stores/modules/room.ts @@ -137,6 +137,18 @@ export const useRoomStore = defineStore('room', () => { spectatorCount.value = count } + /** 收到求和请求 */ + function receiveDrawRequest(fromUserId: string) { + drawRequested.value = true + drawRequestFrom.value = fromUserId + } + + /** 清除求和请求状态 */ + function clearDrawRequest() { + drawRequested.value = false + drawRequestFrom.value = null + } + /** 重置状态 */ function reset() { roomId.value = '' @@ -160,6 +172,7 @@ export const useRoomStore = defineStore('room', () => { messages, drawRequested, drawRequestFrom, spectatorCount, initFromDetail, applyMove, setCurrentTurn, addMessage, - updateReady, addPlayer, removePlayer, setGameOver, setSpectatorCount, reset + updateReady, addPlayer, removePlayer, setGameOver, setSpectatorCount, + receiveDrawRequest, clearDrawRequest, reset } }) diff --git a/src/views/game/RoomPage.vue b/src/views/game/RoomPage.vue index b66cdba..cd9848b 100644 --- a/src/views/game/RoomPage.vue +++ b/src/views/game/RoomPage.vue @@ -164,6 +164,15 @@ function handleWsMessage(msg: WsMessage) { roomStore.setSpectatorCount(data.count) break } + case 'draw_requested': { + const data = msg.data as { fromUserId: string } + roomStore.receiveDrawRequest(data.fromUserId) + break + } + case 'draw_rejected': { + roomStore.clearDrawRequest() + break + } } } diff --git a/src/views/game/components/RoomActions.vue b/src/views/game/components/RoomActions.vue index 7fe5297..c047e51 100644 --- a/src/views/game/components/RoomActions.vue +++ b/src/views/game/components/RoomActions.vue @@ -20,12 +20,27 @@ @@ -40,6 +55,7 @@ import { computed, inject } from 'vue' import { useRouter } from 'vue-router' import { useRoomStore } from '@/stores' import { storeToRefs } from 'pinia' +import { ElMessageBox } from 'element-plus' import type { RoomSocket } from '@/common/websocket/roomSocket' const store = useRoomStore() @@ -55,6 +71,12 @@ const isReady = computed(() => { return me?.ready ?? false }) +/** 对手昵称 */ +const opponentName = computed(() => { + const opponent = players.value.find((p) => p.userId !== currentUserId.value) + return opponent?.nickname ?? '对手' +}) + function toggleReady() { socket.send('ready') } @@ -71,7 +93,29 @@ function requestDraw() { socket.send('draw_request') } -function leaveRoom() { +function respondDraw(accept: boolean) { + socket.send('draw_response', { accept }) + store.clearDrawRequest() +} + +/** 离开房间(需二次确认) */ +async function leaveRoom() { + const message = + store.isPlaying + ? '对局进行中,离开将视为认输,确定要离开吗?' + : '确定要离开房间吗?' + + try { + await ElMessageBox.confirm(message, '离开房间', { + confirmButtonText: '确定离开', + cancelButtonText: '取消', + type: store.isPlaying ? 'warning' : 'info' + }) + } catch { + // 用户取消 + return + } + socket.send('leave') socket.close() router.push('/') @@ -130,5 +174,27 @@ function leaveRoom() { border: 1px solid var(--border-default); &:hover { border-color: var(--text-secondary); color: var(--text-primary); } } + &--accept { + background-color: var(--color-success); + color: white; + &:hover { opacity: 0.9; } + } + &--reject { + background-color: transparent; + color: var(--color-danger); + border: 1px solid var(--color-danger); + &:hover { background-color: var(--color-danger); color: white; } + } +} + +.room-actions__draw-notice { + padding: 10px 12px; + background-color: rgba(250, 173, 20, 0.1); + border: 1px solid var(--color-warning); + border-radius: 8px; + font-size: 13px; + color: var(--color-warning); + text-align: center; + line-height: 1.5; }