feat: v1.1.0 迭代更新

Server:
- 添加房间密码验证 POST /rooms/:id/join
- 添加玩家踢出 POST /rooms/:id/kick/:playerId
- 添加房间过期自动清理(30分钟无活动)
- 添加流量统计 GET /traffic
- 添加token认证中间件保护写操作API
- 房间详情返回玩家列表

Client:
- 添加设置持久化(electron-store)
- 添加设置页面(玩家名、本地端口、自动重连、托盘最小化)
- 添加系统托盘支持(最小化到托盘、右键菜单)
- 添加最近连接服务器记录
- 连接成功自动保存服务器地址
- 加入房间自动填充默认端口
This commit is contained in:
FunMC
2026-02-22 23:38:41 +08:00
parent b17679cec6
commit 9649519745
9 changed files with 411 additions and 3 deletions

View File

@@ -47,4 +47,14 @@ export class ApiClient {
const res = await this.http.get(`/rooms/${roomId}`);
return res.data;
}
async joinRoom(roomId: string, password?: string) {
const res = await this.http.post(`/rooms/${roomId}/join`, { password });
return res.data;
}
async getTraffic() {
const res = await this.http.get('/traffic');
return res.data;
}
}

View File

@@ -1,10 +1,23 @@
import { app, BrowserWindow, ipcMain, dialog } from 'electron';
import { app, BrowserWindow, ipcMain, dialog, Tray, Menu, nativeImage } from 'electron';
import * as path from 'path';
import Store from 'electron-store';
import { RelayClient } from './relay-client';
import { LocalProxy } from './local-proxy';
import { ApiClient } from './api-client';
const store = new Store({
defaults: {
serverUrl: 'http://localhost:3000',
playerName: 'Player',
localPort: 25566,
recentServers: [] as string[],
autoReconnect: true,
minimizeToTray: true,
}
});
let mainWindow: BrowserWindow | null = null;
let tray: Tray | null = null;
let relayClient: RelayClient | null = null;
let localProxy: LocalProxy | null = null;
let apiClient: ApiClient | null = null;
@@ -28,12 +41,33 @@ function createWindow() {
mainWindow.loadFile(path.join(__dirname, '../../renderer/index.html'));
mainWindow.on('close', (e) => {
if (store.get('minimizeToTray') && tray) {
e.preventDefault();
mainWindow?.hide();
}
});
mainWindow.on('closed', () => {
mainWindow = null;
cleanup();
});
}
function createTray() {
const icon = nativeImage.createEmpty();
tray = new Tray(icon);
tray.setToolTip('FunConnect - Minecraft 联机客户端');
tray.setContextMenu(Menu.buildFromTemplate([
{ label: '显示主窗口', click: () => { mainWindow?.show(); mainWindow?.focus(); } },
{ type: 'separator' },
{ label: '断开连接', click: () => cleanup() },
{ type: 'separator' },
{ label: '退出', click: () => { cleanup(); app.quit(); } },
]));
tray.on('double-click', () => { mainWindow?.show(); mainWindow?.focus(); });
}
function cleanup() {
if (localProxy) {
localProxy.stop();
@@ -47,9 +81,11 @@ function cleanup() {
app.whenReady().then(() => {
createWindow();
createTray();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
else mainWindow?.show();
});
});
@@ -171,3 +207,41 @@ ipcMain.handle('server:stats', async () => {
return { success: false, error: err.message };
}
});
// ===== Settings =====
ipcMain.handle('settings:get', () => {
return {
serverUrl: store.get('serverUrl'),
playerName: store.get('playerName'),
localPort: store.get('localPort'),
recentServers: store.get('recentServers'),
autoReconnect: store.get('autoReconnect'),
minimizeToTray: store.get('minimizeToTray'),
};
});
ipcMain.handle('settings:set', (_event, settings: Record<string, any>) => {
for (const [key, value] of Object.entries(settings)) {
store.set(key, value);
}
return { success: true };
});
ipcMain.handle('settings:addRecentServer', (_event, url: string) => {
const recent = store.get('recentServers') as string[];
const filtered = recent.filter((s: string) => s !== url);
filtered.unshift(url);
store.set('recentServers', filtered.slice(0, 10));
return { success: true };
});
// ===== Room password verification =====
ipcMain.handle('rooms:verify', async (_event, opts: { roomId: string; password?: string }) => {
if (!apiClient) return { success: false, error: '未连接服务器' };
try {
const data = await apiClient.joinRoom(opts.roomId, opts.password);
return { success: true, data };
} catch (err: any) {
return { success: false, error: err.response?.data?.error || err.message };
}
});

View File

@@ -18,6 +18,14 @@ contextBridge.exposeInMainWorld('funmc', {
hostRoom: (opts: any) => ipcRenderer.invoke('rooms:host', opts),
disconnect: () => ipcRenderer.invoke('relay:disconnect'),
// Room verification
verifyRoom: (opts: any) => ipcRenderer.invoke('rooms:verify', opts),
// Settings
getSettings: () => ipcRenderer.invoke('settings:get'),
setSettings: (settings: any) => ipcRenderer.invoke('settings:set', settings),
addRecentServer: (url: string) => ipcRenderer.invoke('settings:addRecentServer', url),
// Events from main
onRelayStatus: (callback: (data: any) => void) => {
ipcRenderer.on('relay:status', (_event, data) => callback(data));