fix: 路由bug修改
This commit is contained in:
@@ -4,7 +4,9 @@ import type {
|
||||
CreateRoomResponse,
|
||||
GetRoomListRequest,
|
||||
GetRoomListResponse,
|
||||
RoomDetailResponse
|
||||
JoinRoomRequest,
|
||||
RoomDetailResponse,
|
||||
RoomItem
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
@@ -37,6 +39,19 @@ export function createRoom(data: CreateRoomRequest) {
|
||||
return requestClient.post<CreateRoomResponse>('/game/room/create', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入房间
|
||||
*
|
||||
* 玩家通过此接口加入指定房间,成为房间内的玩家。
|
||||
* 公开房间无需传密码。加入成功后需通过 WebSocket 进行后续交互。
|
||||
*
|
||||
* @param data - 加入房间参数(roomId 和可选的 password)
|
||||
* @returns 更新后的房间信息
|
||||
*/
|
||||
export function joinRoom(data: JoinRoomRequest) {
|
||||
return requestClient.post<RoomItem>('/game/room/join', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间详情
|
||||
*
|
||||
|
||||
@@ -151,6 +151,14 @@ export interface MoveItem {
|
||||
col: number
|
||||
}
|
||||
|
||||
/** 加入房间请求参数 */
|
||||
export interface JoinRoomRequest {
|
||||
/** 房间 ID */
|
||||
roomId: string
|
||||
/** 房间密码(可选,公开房间留空) */
|
||||
password?: string
|
||||
}
|
||||
|
||||
/** WebSocket 消息通用封装 */
|
||||
export interface WsMessage<T = unknown> {
|
||||
type: string
|
||||
|
||||
@@ -1,25 +1,40 @@
|
||||
import type { WsMessage } from '@/api/modules/game'
|
||||
|
||||
/** 重连配置 */
|
||||
const RECONNECT_MAX_RETRIES = 5
|
||||
/** 重连基础延迟(ms),指数退避:1s, 2s, 4s, 8s, 16s */
|
||||
const RECONNECT_BASE_DELAY = 1000
|
||||
/** 重连最大延迟(ms) */
|
||||
const RECONNECT_MAX_DELAY = 16000
|
||||
|
||||
/** WebSocket 连接回调 */
|
||||
export interface RoomSocketCallbacks {
|
||||
onOpen?: () => void
|
||||
onClose?: (event: CloseEvent) => void
|
||||
onError?: (event: Event) => void
|
||||
onMessage?: (message: WsMessage) => void
|
||||
/** 自动重连成功回调(重连后需调用方重新拉取房间状态) */
|
||||
onReconnect?: () => void
|
||||
/** 重连次数耗尽回调 */
|
||||
onReconnectFailed?: () => void
|
||||
}
|
||||
|
||||
/** WebSocket 客户端封装 */
|
||||
export interface RoomSocket {
|
||||
/** 发送消息 */
|
||||
send: (type: string, data?: unknown) => void
|
||||
/** 关闭连接 */
|
||||
/** 关闭连接(主动关闭不触发重连) */
|
||||
close: () => void
|
||||
/** 连接状态 */
|
||||
isOpen: () => boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建房间 WebSocket 连接
|
||||
* 创建房间 WebSocket 连接(含自动重连)
|
||||
*
|
||||
* 连接断开时自动指数退避重连,最多重试 5 次。
|
||||
* 主动调用 close() 或重连耗尽时触发 onClose 回调。
|
||||
* 重连成功后触发 onReconnect 回调,调用方应在此重新拉取房间状态。
|
||||
*
|
||||
* @param roomId 房间 ID
|
||||
* @param token 用户认证 token
|
||||
@@ -33,45 +48,113 @@ export function createRoomSocket(
|
||||
): RoomSocket {
|
||||
const wsUrl = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws/room?roomId=${encodeURIComponent(roomId)}&token=${encodeURIComponent(token)}`
|
||||
|
||||
const ws = new WebSocket(wsUrl)
|
||||
/** 当前 WebSocket 实例 */
|
||||
let ws: WebSocket | null = null
|
||||
/** 当前重试次数 */
|
||||
let retryCount = 0
|
||||
/** 是否主动关闭(主动关闭不重连) */
|
||||
let intentionalClose = false
|
||||
/** 重连定时器 ID,用于组件卸载时取消 */
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
ws.onopen = () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[WS] 房间连接已建立, roomId=', roomId)
|
||||
callbacks.onOpen?.()
|
||||
}
|
||||
/** 建立 WebSocket 连接 */
|
||||
function connect(): void {
|
||||
// 如果已有旧的重连定时器,取消它
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer)
|
||||
reconnectTimer = null
|
||||
}
|
||||
|
||||
ws.onclose = (event) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[WS] 房间连接已关闭, roomId=', roomId, 'code=', event.code)
|
||||
callbacks.onClose?.(event)
|
||||
}
|
||||
ws = new WebSocket(wsUrl)
|
||||
|
||||
ws.onerror = (event) => {
|
||||
console.error('[WS] 房间连接错误, roomId=', roomId, event)
|
||||
callbacks.onError?.(event)
|
||||
}
|
||||
ws.onopen = () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[WS] 房间连接已建立, roomId=', roomId)
|
||||
if (retryCount > 0) {
|
||||
// 这是重连成功
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[WS] 重连成功, roomId=', roomId, '重试次数=', retryCount)
|
||||
retryCount = 0
|
||||
callbacks.onReconnect?.()
|
||||
} else {
|
||||
callbacks.onOpen?.()
|
||||
}
|
||||
}
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const message: WsMessage = JSON.parse(event.data as string)
|
||||
callbacks.onMessage?.(message)
|
||||
} catch (e) {
|
||||
console.error('[WS] 消息解析失败:', e)
|
||||
ws.onclose = (event) => {
|
||||
// 主动关闭,不重连
|
||||
if (intentionalClose) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[WS] 主动关闭连接, roomId=', roomId, 'code=', event.code)
|
||||
callbacks.onClose?.(event)
|
||||
return
|
||||
}
|
||||
|
||||
// 已达最大重试次数
|
||||
if (retryCount >= RECONNECT_MAX_RETRIES) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[WS] 重连次数耗尽, roomId=', roomId, 'retries=', retryCount)
|
||||
callbacks.onReconnectFailed?.()
|
||||
callbacks.onClose?.(event)
|
||||
return
|
||||
}
|
||||
|
||||
// 指数退避重连
|
||||
retryCount++
|
||||
const delay = Math.min(
|
||||
RECONNECT_BASE_DELAY * Math.pow(2, retryCount - 1),
|
||||
RECONNECT_MAX_DELAY
|
||||
)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`[WS] 连接断开,${delay / 1000}s 后重连 (${retryCount}/${RECONNECT_MAX_RETRIES}), roomId=`,
|
||||
roomId
|
||||
)
|
||||
|
||||
reconnectTimer = setTimeout(() => {
|
||||
reconnectTimer = null
|
||||
connect()
|
||||
}, delay)
|
||||
}
|
||||
|
||||
ws.onerror = (event) => {
|
||||
console.error('[WS] 连接错误, roomId=', roomId, event)
|
||||
callbacks.onError?.(event)
|
||||
}
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const message: WsMessage = JSON.parse(event.data as string)
|
||||
callbacks.onMessage?.(message)
|
||||
} catch (e) {
|
||||
console.error('[WS] 消息解析失败:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始连接
|
||||
connect()
|
||||
|
||||
return {
|
||||
send(type: string, data?: unknown) {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type, data }))
|
||||
}
|
||||
},
|
||||
close() {
|
||||
ws.close(1000, '用户离开')
|
||||
intentionalClose = true
|
||||
// 取消进行中的重连定时器
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer)
|
||||
reconnectTimer = null
|
||||
}
|
||||
if (ws) {
|
||||
ws.close(1000, '用户离开')
|
||||
ws = null
|
||||
}
|
||||
},
|
||||
isOpen() {
|
||||
return ws.readyState === WebSocket.OPEN
|
||||
return ws?.readyState === WebSocket.OPEN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,9 @@ import { useRouter } from 'vue-router'
|
||||
import type { RoomItem } from '@/api/modules/game'
|
||||
import { useBaseLayoutStore } from '@/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { getRoomList } from '@/api/modules/game'
|
||||
import { getRoomList, joinRoom } from '@/api/modules/game'
|
||||
import { sseBus } from '@/stores/sseBus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
/** 状态筛选选项 */
|
||||
const statusFilters = [
|
||||
@@ -129,17 +130,26 @@ const filteredRooms = computed(() => {
|
||||
return list
|
||||
})
|
||||
|
||||
/** 加入房间 */
|
||||
function handleJoin(roomId: string) {
|
||||
router.push(`/room/${roomId}`)
|
||||
/** 加入房间(需先调用后端 join API 成为玩家) */
|
||||
async function handleJoin(roomId: string) {
|
||||
try {
|
||||
await joinRoom({ roomId })
|
||||
router.push(`/room/${roomId}`)
|
||||
} catch (e: unknown) {
|
||||
// 后端业务错误通过 ApiResponse.message 传递,需兼容多种 reject 类型
|
||||
const msg =
|
||||
(e as { message?: string })?.message ||
|
||||
(e instanceof Error ? e.message : '加入房间失败,请稍后重试')
|
||||
ElMessage.error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
/** 加入指定座位 */
|
||||
function handleJoinSeat(roomId: string, seatIndex: number) {
|
||||
router.push({ path: `/room/${roomId}`, query: { seat: String(seatIndex) } })
|
||||
/** 加入指定座位(后端自动分配座位,调用 join API 即可) */
|
||||
async function handleJoinSeat(roomId: string, _seatIndex: number) {
|
||||
await handleJoin(roomId)
|
||||
}
|
||||
|
||||
/** 观战 */
|
||||
/** 观战(不调用 join API,仅建立 WebSocket 连接接收对局数据) */
|
||||
function handleSpectate(roomId: string) {
|
||||
router.push(`/room/${roomId}`)
|
||||
}
|
||||
|
||||
@@ -76,12 +76,26 @@ onMounted(async () => {
|
||||
const res = await getRoomDetail(roomId)
|
||||
roomStore.initFromDetail(res.data, userId)
|
||||
|
||||
// 2. 建立 WebSocket 连接
|
||||
// 2. 建立 WebSocket 连接(含自动重连)
|
||||
const token = accountStore.accessToken
|
||||
socket = createRoomSocket(roomId, token, {
|
||||
onMessage: handleWsMessage,
|
||||
onClose: () => {
|
||||
// 连接关闭,提示并返回大厅
|
||||
/**
|
||||
* 重连成功 → 重新拉取房间详情恢复完整状态(棋盘、轮次、玩家列表等)
|
||||
*/
|
||||
onReconnect: async () => {
|
||||
try {
|
||||
const res = await getRoomDetail(roomId)
|
||||
roomStore.initFromDetail(res.data, userId)
|
||||
} catch {
|
||||
// 重连后拉取房间状态失败(房间可能已结束),返回大厅
|
||||
router.push('/')
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 重连次数耗尽 → 返回大厅
|
||||
*/
|
||||
onReconnectFailed: () => {
|
||||
router.push('/')
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user