feat: 重构游戏房间架构
This commit is contained in:
@@ -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}`)
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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': {
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user