feat: 重构游戏房间架构

This commit is contained in:
2026-06-30 10:19:41 +08:00
parent e1faf45683
commit 94dfa907f9
6 changed files with 18 additions and 102 deletions

View File

@@ -12,15 +12,12 @@ import type {
/**
* 获取指定游戏的房间列表
*
* 返回某个游戏下所有房间(桌台)的信息,支持按状态筛选。
* 该接口无需登录即可调用。
*
* @param params - 查询参数,包含 gameId 和可选的 status 筛选
* @returns 房间列表
*/
export function getRoomList(params: GetRoomListRequest) {
return requestClient.get<GetRoomListResponse>(
'/game/room/list',
'/room/list',
params as unknown as Record<string, unknown>
)
}
@@ -28,39 +25,29 @@ export function getRoomList(params: GetRoomListRequest) {
/**
* 创建房间
*
* 根据当前游戏和选定的参数创建新房间。
* 房间名称留空时由后端自动生成"桌 XX"编号。
* 密码留空时房间为公开房间。
*
* @param data - 创建房间参数
* @returns 新建的房间信息
*/
export function createRoom(data: CreateRoomRequest) {
return requestClient.post<CreateRoomResponse>('/game/room/create', data)
return requestClient.post<CreateRoomResponse>('/room/create', data)
}
/**
* 加入房间
*
* 玩家通过此接口加入指定房间,成为房间内的玩家。
* 公开房间无需传密码。加入成功后需通过 WebSocket 进行后续交互。
*
* @param data - 加入房间参数roomId 和可选的 password
* @returns 更新后的房间信息
*/
export function joinRoom(data: JoinRoomRequest) {
return requestClient.post<RoomItem>('/game/room/join', data)
return requestClient.post<RoomItem>('/room/join', data)
}
/**
* 获取房间详情
*
* 用于进入房间页时获取初始快照(棋盘状态、玩家信息等)。
* 无需登录即可调用(观战者也需访问)。
*
* @param roomId 房间 ID
* @returns 房间详情
*/
export function getRoomDetail(roomId: string) {
return requestClient.get<RoomDetailResponse>(`/game/room/${roomId}`)
return requestClient.get<RoomDetailResponse>(`/room/${roomId}`)
}

View File

@@ -65,8 +65,6 @@ export interface RoomItem {
mode: string
/** 房主昵称 */
creatorName: string
/** 是否允许观战 */
allowSpectate: boolean
}
/** 获取游戏列表 — 无请求参数 */
@@ -101,8 +99,6 @@ export interface CreateRoomRequest {
stakes: number
/** 最大玩家数 */
maxPlayers: number
/** 是否允许观战 */
allowSpectate?: boolean
}
/** 创建房间响应 */
@@ -124,7 +120,6 @@ export interface RoomDetailResponse {
status: RoomStatus
creatorId: string
creatorName: string
allowSpectate: boolean
players: RoomPlayerDetail[]
/** 棋盘状态(对局中才有) */
boardState?: number[][]
@@ -185,8 +180,8 @@ export type WsOutboundType =
| 'game_over'
| 'player_join'
| 'player_leave'
| 'player_disconnected'
| 'player_reconnected'
| 'ready_change'
| 'spectator_join'
| 'spectator_leave'
| 'move_history'
| 'error'

View File

@@ -58,11 +58,6 @@
<div class="create-room-dialog__hint">设置密码后其他玩家需输入密码才能加入</div>
</el-form-item>
<!-- 是否允许观战 -->
<el-form-item label="观战">
<el-switch v-model="form.allowSpectate" />
<div class="create-room-dialog__hint">开启后其他玩家可旁观对局</div>
</el-form-item>
</el-form>
<template #footer>
@@ -87,7 +82,6 @@ interface CreateRoomForm {
mode: string
stakes: number | undefined
password: string
allowSpectate: boolean
}
const props = defineProps<{
@@ -123,8 +117,7 @@ const form = reactive<CreateRoomForm>({
name: '',
mode: '',
stakes: undefined,
password: '',
allowSpectate: true
password: ''
})
/** 初始化默认值:取当前游戏配置的第一个选项 */
@@ -135,7 +128,6 @@ function initDefaults() {
}
form.name = ''
form.password = ''
form.allowSpectate = true
}
/** 弹窗打开时设置默认值 */
@@ -171,8 +163,7 @@ async function handleSubmit() {
password: form.password || undefined,
mode: form.mode,
stakes: form.stakes!,
maxPlayers: gameConfig.value?.maxPlayers ?? 0,
allowSpectate: form.allowSpectate
maxPlayers: gameConfig.value?.maxPlayers ?? 0
})
visible.value = false
emit('created')

View File

@@ -15,7 +15,6 @@ export const useRoomStore = defineStore('room', () => {
const status = ref<RoomStatus>('waiting')
const creatorId = ref<string>('')
const creatorName = ref<string>('')
const allowSpectate = ref<boolean>(true)
// ===== 玩家信息 =====
const players = ref<RoomPlayerDetail[]>([])
@@ -43,18 +42,15 @@ export const useRoomStore = defineStore('room', () => {
// ===== 当前用户身份 =====
const currentUserId = ref<string>('')
/** 当前用户房间中的身份player玩家| spectator观战者 */
const myRole = ref<'player' | 'spectator'>('spectator')
/** 当前用户是否为玩家 */
const isPlayer = computed(() => myRole.value === 'player')
/** 当前用户是否为房间中的玩家 */
const isPlayer = computed(() =>
players.value.some((p) => p.userId === currentUserId.value)
)
// ===== 求和状态 =====
const drawRequested = ref<boolean>(false)
const drawRequestFrom = ref<string | null>(null)
// ===== 观战者 =====
const spectatorCount = ref<number>(0)
/**
* 从房间详情 HTTP 响应初始化 Store
* @param detail - 房间详情响应数据
@@ -72,16 +68,11 @@ export const useRoomStore = defineStore('room', () => {
status.value = detail.status
creatorId.value = detail.creatorId
creatorName.value = detail.creatorName
allowSpectate.value = detail.allowSpectate
players.value = detail.players
currentUserId.value = userId
boardState.value = detail.boardState ?? []
currentTurn.value = detail.currentTurn ?? null
moves.value = detail.moves ?? []
// 判定当前用户是玩家还是观战者
const isPlayerInRoom = detail.players.some((p) => p.userId === userId)
myRole.value = isPlayerInRoom ? 'player' : 'spectator'
}
/** 更新棋盘状态WS move 事件) */
@@ -145,11 +136,6 @@ export const useRoomStore = defineStore('room', () => {
resultType.value = type
}
/** 更新观战人数 */
function setSpectatorCount(count: number) {
spectatorCount.value = count
}
/** 收到求和请求 */
function receiveDrawRequest(fromUserId: string) {
drawRequested.value = true
@@ -178,14 +164,14 @@ export const useRoomStore = defineStore('room', () => {
return {
roomId, roomName, gameId, gameIcon, gameName,
maxPlayers, mode, stakes, status, creatorId, creatorName, allowSpectate,
players, isOwner, currentUserId, myRole, isPlayer,
maxPlayers, mode, stakes, status, creatorId, creatorName,
players, isOwner, currentUserId, isPlayer,
boardState, currentTurn, moves, winner, resultType,
isPlaying, isFinished,
messages,
drawRequested, drawRequestFrom, spectatorCount,
drawRequested, drawRequestFrom,
initFromDetail, applyMove, setCurrentTurn, addMessage,
updateReady, addPlayer, removePlayer, setGameOver, setSpectatorCount,
updateReady, addPlayer, removePlayer, setGameOver,
receiveDrawRequest, clearDrawRequest, reset
}
})

View File

@@ -16,9 +16,6 @@
<!-- 右侧面板 -->
<RoomRightPanel />
</div>
<!-- 底部观战栏 -->
<SpectatorBar v-if="roomStore.allowSpectate" />
</div>
</template>
@@ -36,7 +33,6 @@ 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()
@@ -154,14 +150,10 @@ 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)
case 'player_disconnected': {
break
}
case 'spectator_leave': {
const data = msg.data as { count: number }
roomStore.setSpectatorCount(data.count)
case 'player_reconnected': {
break
}
case 'draw_requested': {

View File

@@ -1,35 +0,0 @@
<template>
<div class="spectator-bar">
<span class="spectator-bar__icon">👀</span>
<span class="spectator-bar__text">
观战中{{ spectatorCount }}
</span>
</div>
</template>
<script setup lang="ts">
import { useRoomStore } from '@/stores'
import { storeToRefs } from 'pinia'
const store = useRoomStore()
const { spectatorCount } = storeToRefs(store)
</script>
<style scoped lang="scss">
.spectator-bar {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
height: 32px;
background-color: var(--bg-surface);
border-top: 1px solid var(--border-light);
font-size: 12px;
color: var(--text-secondary);
flex-shrink: 0;
}
.spectator-bar__icon {
font-size: 14px;
}
</style>