feat: v1.2.0 房间详情/聊天/踢人 + 速率限制 + WebSocket增强

Server:
- API速率限制中间件 (120 req/min per IP, X-RateLimit headers)
- 房间聊天API: POST /rooms/:id/chat
- 认证中间件放行公开GET路由和房间join
- WebSocket: 房间订阅/取消订阅 (subscribe/unsubscribe)
- WebSocket: 房间聊天广播 (chat -> broadcastToRoom)
- WebSocket: 房间事件通知 (roomCreated/Deleted/playerJoined/Left)

Client:
- 房间详情弹窗: 点击房间卡片打开
  - 房间信息网格 (房间号/房主/版本/人数)
  - 在线玩家列表 (5秒自动刷新)
  - 踢出玩家 (确认对话框)
  - 房间聊天 (实时发送/显示)
  - 加入房间 / 删除房间按钮
- 连接状态指示器动画 (online/offline/connecting)
- 房间卡片hover效果
- 版本更新到 v1.2.0
- ApiClient: 新增 getRoomDetail/kickPlayer/sendChat
- Preload: 新增对应IPC方法
- Main: 新增 rooms:detail/kick/chat handlers
This commit is contained in:
FunMC
2026-02-23 08:21:09 +08:00
parent 7fdc570391
commit 80fe5e6e6e
8 changed files with 415 additions and 5 deletions

View File

@@ -57,4 +57,19 @@ export class ApiClient {
const res = await this.http.get('/traffic');
return res.data;
}
async getRoomDetail(roomId: string) {
const res = await this.http.get(`/rooms/${roomId}`);
return res.data;
}
async kickPlayer(roomId: string, playerId: string, reason?: string) {
const res = await this.http.post(`/rooms/${roomId}/kick/${playerId}`, { reason });
return res.data;
}
async sendChat(roomId: string, sender: string, message: string) {
const res = await this.http.post(`/rooms/${roomId}/chat`, { sender, message });
return res.data;
}
}

View File

@@ -245,3 +245,36 @@ ipcMain.handle('rooms:verify', async (_event, opts: { roomId: string; password?:
return { success: false, error: err.response?.data?.error || err.message };
}
});
// ===== Room detail (players list) =====
ipcMain.handle('rooms:detail', async (_event, roomId: string) => {
if (!apiClient) return { success: false, error: '未连接服务器' };
try {
const data = await apiClient.getRoomDetail(roomId);
return { success: true, data };
} catch (err: any) {
return { success: false, error: err.response?.data?.error || err.message };
}
});
// ===== Kick player =====
ipcMain.handle('rooms:kick', async (_event, roomId: string, playerId: string) => {
if (!apiClient) return { success: false, error: '未连接服务器' };
try {
const data = await apiClient.kickPlayer(roomId, playerId);
return { success: true, data };
} catch (err: any) {
return { success: false, error: err.response?.data?.error || err.message };
}
});
// ===== Room chat =====
ipcMain.handle('rooms:chat', async (_event, roomId: string, sender: string, message: string) => {
if (!apiClient) return { success: false, error: '未连接服务器' };
try {
const data = await apiClient.sendChat(roomId, sender, message);
return { success: true, data };
} catch (err: any) {
return { success: false, error: err.response?.data?.error || err.message };
}
});

View File

@@ -18,7 +18,10 @@ contextBridge.exposeInMainWorld('funmc', {
hostRoom: (opts: any) => ipcRenderer.invoke('rooms:host', opts),
disconnect: () => ipcRenderer.invoke('relay:disconnect'),
// Room verification
// Room detail & actions
getRoomDetail: (id: string) => ipcRenderer.invoke('rooms:detail', id),
kickPlayer: (roomId: string, playerId: string) => ipcRenderer.invoke('rooms:kick', roomId, playerId),
sendChat: (roomId: string, sender: string, message: string) => ipcRenderer.invoke('rooms:chat', roomId, sender, message),
verifyRoom: (opts: any) => ipcRenderer.invoke('rooms:verify', opts),
// Settings