feat: FunConnect v1.0.0 - Minecraft联机平台完整版
- server: Node.js TCP中继服务器,支持多节点集群 - web: React管理面板(仪表盘、房间管理、节点管理) - client: Electron桌面客户端(连接、创建/加入房间、本地代理) - deploy: Ubuntu一键部署脚本
This commit is contained in:
278
client/renderer/app.js
Normal file
278
client/renderer/app.js
Normal file
@@ -0,0 +1,278 @@
|
||||
// FunConnect Client - Renderer Logic
|
||||
const $ = (sel) => document.querySelector(sel);
|
||||
const $$ = (sel) => document.querySelectorAll(sel);
|
||||
|
||||
let isConnected = false;
|
||||
let currentServerUrl = '';
|
||||
|
||||
// ===== Window Controls =====
|
||||
$('#btn-min').addEventListener('click', () => window.funmc.minimize());
|
||||
$('#btn-max').addEventListener('click', () => window.funmc.maximize());
|
||||
$('#btn-close').addEventListener('click', () => window.funmc.close());
|
||||
|
||||
// ===== Navigation =====
|
||||
$$('.nav-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
const page = item.dataset.page;
|
||||
$$('.nav-item').forEach(n => n.classList.remove('active'));
|
||||
item.classList.add('active');
|
||||
$$('.page').forEach(p => p.classList.remove('active'));
|
||||
$(`#page-${page}`).classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// ===== Connect Page =====
|
||||
$('#btn-connect').addEventListener('click', async () => {
|
||||
const url = $('#server-url').value.trim();
|
||||
if (!url) {
|
||||
showMsg('#connect-msg', '请输入服务器地址', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = $('#btn-connect');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner"></span> 连接中...';
|
||||
showMsg('#connect-msg', '', '');
|
||||
|
||||
const result = await window.funmc.connectServer(url);
|
||||
|
||||
if (result.success) {
|
||||
isConnected = true;
|
||||
currentServerUrl = url;
|
||||
updateServerStatus(true);
|
||||
showMsg('#connect-msg', '连接成功!', 'success');
|
||||
showServerInfo(result.data);
|
||||
} else {
|
||||
isConnected = false;
|
||||
updateServerStatus(false);
|
||||
showMsg('#connect-msg', '连接失败: ' + result.error, 'error');
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
btn.textContent = '连接服务器';
|
||||
});
|
||||
|
||||
function showServerInfo(data) {
|
||||
const grid = $('#server-info-grid');
|
||||
grid.innerHTML = '';
|
||||
const items = [
|
||||
{ label: '节点名称', value: data.nodeName || 'N/A' },
|
||||
{ label: '节点ID', value: data.nodeId || 'N/A' },
|
||||
{ label: '运行模式', value: data.isMaster ? '主节点' : '工作节点' },
|
||||
{ label: '运行状态', value: data.status === 'ok' ? '正常' : '异常' },
|
||||
{ label: '运行时间', value: formatUptime(data.uptime || 0) },
|
||||
{ label: '版本', value: 'v1.0.0' },
|
||||
];
|
||||
items.forEach(item => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'info-item';
|
||||
div.innerHTML = `<div class="label">${item.label}</div><div class="value">${item.value}</div>`;
|
||||
grid.appendChild(div);
|
||||
});
|
||||
$('#server-info').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function updateServerStatus(online) {
|
||||
const dot = $('#server-status .status-dot');
|
||||
const text = $('#server-status .status-text');
|
||||
if (online) {
|
||||
dot.className = 'status-dot online';
|
||||
text.textContent = '已连接';
|
||||
} else {
|
||||
dot.className = 'status-dot offline';
|
||||
text.textContent = '未连接';
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Rooms Page =====
|
||||
$('#btn-refresh-rooms').addEventListener('click', loadRooms);
|
||||
|
||||
async function loadRooms() {
|
||||
if (!isConnected) {
|
||||
$('#rooms-list').innerHTML = '<div class="empty-state"><span class="empty-icon">🔗</span><p>请先连接服务器</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$('#rooms-list').innerHTML = '<div class="empty-state"><span class="spinner"></span><p>加载中...</p></div>';
|
||||
|
||||
const result = await window.funmc.listRooms();
|
||||
if (!result.success) {
|
||||
$('#rooms-list').innerHTML = `<div class="empty-state"><span class="empty-icon">❌</span><p>${result.error}</p></div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const rooms = result.data.rooms || [];
|
||||
if (rooms.length === 0) {
|
||||
$('#rooms-list').innerHTML = '<div class="empty-state"><span class="empty-icon">🎮</span><p>暂无在线房间</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$('#rooms-list').innerHTML = rooms.map(room => `
|
||||
<div class="room-card" data-id="${room.id}">
|
||||
<div class="room-left">
|
||||
<div class="room-icon">🎮</div>
|
||||
<div>
|
||||
<div class="room-name">${escapeHtml(room.name)} ${room.password ? '🔒' : ''}</div>
|
||||
<div class="room-meta">房间号: ${room.id} | 房主: ${escapeHtml(room.hostName)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="room-right">
|
||||
<span class="room-players">👥 ${room.currentPlayers}/${room.maxPlayers}</span>
|
||||
<span class="room-version">${room.gameEdition === 'java' ? 'Java' : '基岩'} ${room.gameVersion}</span>
|
||||
<button class="room-btn-join" onclick="quickJoin('${room.id}')">加入</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function quickJoin(roomId) {
|
||||
// Navigate to join page and fill in room ID
|
||||
$$('.nav-item').forEach(n => n.classList.remove('active'));
|
||||
document.querySelector('[data-page="join"]').classList.add('active');
|
||||
$$('.page').forEach(p => p.classList.remove('active'));
|
||||
$('#page-join').classList.add('active');
|
||||
$('#join-room-id').value = roomId;
|
||||
}
|
||||
|
||||
// ===== Host Page =====
|
||||
$('#btn-host').addEventListener('click', async () => {
|
||||
if (!isConnected) {
|
||||
showMsg('#host-msg', '请先连接服务器', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const name = $('#host-name').value.trim();
|
||||
const port = parseInt($('#host-port').value);
|
||||
const version = $('#host-version').value.trim();
|
||||
const edition = $('#host-edition').value;
|
||||
const maxPlayers = parseInt($('#host-maxplayers').value);
|
||||
const password = $('#host-password').value;
|
||||
|
||||
if (!name) {
|
||||
showMsg('#host-msg', '请输入房间名称', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = $('#btn-host');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner"></span> 创建中...';
|
||||
showMsg('#host-msg', '', '');
|
||||
|
||||
const result = await window.funmc.hostRoom({
|
||||
serverUrl: currentServerUrl,
|
||||
roomName: name,
|
||||
localMcPort: port,
|
||||
gameVersion: version,
|
||||
gameEdition: edition,
|
||||
maxPlayers: maxPlayers,
|
||||
password: password || undefined,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
showMsg('#host-msg', '', '');
|
||||
$('#host-room-id').textContent = result.data.room.id;
|
||||
$('#host-result').classList.remove('hidden');
|
||||
} else {
|
||||
showMsg('#host-msg', '创建失败: ' + result.error, 'error');
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
btn.textContent = '创建房间';
|
||||
});
|
||||
|
||||
$('#btn-copy-room').addEventListener('click', () => {
|
||||
const roomId = $('#host-room-id').textContent;
|
||||
navigator.clipboard.writeText(roomId).then(() => {
|
||||
$('#btn-copy-room').textContent = '已复制!';
|
||||
setTimeout(() => { $('#btn-copy-room').textContent = '复制房间号'; }, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
// ===== Join Page =====
|
||||
$('#btn-join').addEventListener('click', async () => {
|
||||
const roomId = $('#join-room-id').value.trim();
|
||||
const host = $('#join-host').value.trim();
|
||||
const port = parseInt($('#join-port').value);
|
||||
const localPort = parseInt($('#join-local-port').value);
|
||||
|
||||
if (!roomId) {
|
||||
showMsg('#join-msg', '请输入房间号', 'error');
|
||||
return;
|
||||
}
|
||||
if (!host) {
|
||||
showMsg('#join-msg', '请输入中继服务器地址', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = $('#btn-join');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner"></span> 连接中...';
|
||||
showMsg('#join-msg', '', '');
|
||||
|
||||
const result = await window.funmc.joinRoom({
|
||||
serverHost: host,
|
||||
serverPort: port,
|
||||
roomId: roomId,
|
||||
localPort: localPort,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
showMsg('#join-msg', '', '');
|
||||
$('#join-active-port').textContent = String(localPort);
|
||||
$('#join-status').classList.remove('hidden');
|
||||
$('#btn-join').classList.add('hidden');
|
||||
$('#btn-leave').classList.remove('hidden');
|
||||
} else {
|
||||
showMsg('#join-msg', '加入失败: ' + result.error, 'error');
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
btn.textContent = '加入房间';
|
||||
});
|
||||
|
||||
$('#btn-leave').addEventListener('click', async () => {
|
||||
await window.funmc.disconnect();
|
||||
$('#join-status').classList.add('hidden');
|
||||
$('#btn-join').classList.remove('hidden');
|
||||
$('#btn-leave').classList.add('hidden');
|
||||
showMsg('#join-msg', '已断开连接', 'success');
|
||||
});
|
||||
|
||||
// ===== Relay Status Events =====
|
||||
window.funmc.onRelayStatus((data) => {
|
||||
if (data.status === 'disconnected') {
|
||||
$('#join-status').classList.add('hidden');
|
||||
$('#btn-join').classList.remove('hidden');
|
||||
$('#btn-leave').classList.add('hidden');
|
||||
showMsg('#join-msg', '连接已断开', 'error');
|
||||
} else if (data.status === 'error') {
|
||||
showMsg('#join-msg', '连接错误: ' + data.error, 'error');
|
||||
}
|
||||
});
|
||||
|
||||
// ===== Auto-refresh rooms when navigating =====
|
||||
document.querySelector('[data-page="rooms"]').addEventListener('click', () => {
|
||||
if (isConnected) loadRooms();
|
||||
});
|
||||
|
||||
// ===== Utilities =====
|
||||
function showMsg(selector, text, type) {
|
||||
const el = $(selector);
|
||||
el.textContent = text;
|
||||
el.className = 'msg' + (type ? ' ' + type : '');
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function formatUptime(seconds) {
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
const s = Math.floor(seconds % 60);
|
||||
if (h > 0) return `${h}时${m}分${s}秒`;
|
||||
if (m > 0) return `${m}分${s}秒`;
|
||||
return `${s}秒`;
|
||||
}
|
||||
Reference in New Issue
Block a user