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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user