Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
305 changes: 0 additions & 305 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
"dependencies": {
"long": "^5.3.2",
"protobufjs": "^8.0.0",
"socket.io": "^4.8.3",
"socket.io-client": "^4.8.3",
"ws": "^8.19.0"
},
"devDependencies": {
Expand Down
138 changes: 54 additions & 84 deletions server/ipc.js
Original file line number Diff line number Diff line change
@@ -1,100 +1,70 @@
/**
* IPC 通道处理
* 注册所有 ipcMain.handle 通道,调用 bot.js 并返回结果
* 将 bot.js 事件推送到渲染进程
* WebSocket 通道 - 替代 Socket.IO,更简单的路径配置
*/
const {Server} = require('socket.io');
const { WebSocketServer } = require('ws');
const bot = require('./bot');

let io = null;

function handle(socket, ev, cb) {
if (!io)
throw new Error('IPC 尚未初始化');
socket.on(ev, async (data, ioCb) => {
try {
ioCb(await cb(data));
} catch (e) {
ioCb({success: false, error: e.message,});
}
let wss = null;
const WS_PATH = '/ws';

// 命令映射
const HANDLERS = {
'bot:connect': async (d) => await bot.botConnect(d.code, d.platform),
'bot:disconnect': () => bot.botDisconnect(),
'bot:status': () => bot.getStatus(),
'bot:feature-toggle': (d) => bot.setFeatureEnabled(d.feature, d.enabled),
'bot:get-config': () => bot.getConfig(),
'bot:save-config': (d) => bot.saveConfig(d),
'bot:get-plant-plan': () => bot.getPlantPlan(),
'bot:get-logs': () => bot.getLogs(),
'bot:clear-logs': () => { bot.clearLogs(); return { success: true }; },
};

function broadcast(msg) {
if (!wss) return;
const data = JSON.stringify(msg);
wss.clients.forEach((client) => {
if (client.readyState === 1) client.send(data);
});
}

/**
* 注册所有 IPC 通道
*/
function registerIPC(server) {
io = new Server(server);

io.on('connection', (socket) => {
console.log('👤 用户连接:', socket.id);
// socket.onAny((event, ...args) => {
// console.log(`[收到消息] 来自: ${socket.id} | 事件: ${event} | 数据:`, args);
// });

// === 请求/响应通道 ===

handle(socket, 'bot:connect', async ({code, platform}) => {
return await bot.botConnect(code, platform);
});

handle(socket, 'bot:connect', async ({code, platform}) => {
return await bot.botConnect(code, platform);
});

handle(socket, 'bot:disconnect', () => {
return bot.botDisconnect();
});

handle(socket, 'bot:status', () => {
return bot.getStatus();
});

handle(socket, 'bot:feature-toggle', ({feature, enabled}) => {
return bot.setFeatureEnabled(feature, enabled);
});

handle(socket, 'bot:get-config', () => {
return bot.getConfig();
});

handle(socket, 'bot:save-config', (partial) => {
return bot.saveConfig(partial);
});

handle(socket, 'bot:get-plant-plan', () => {
return bot.getPlantPlan();
});

handle(socket, 'bot:get-logs', () => {
return bot.getLogs();
});

handle(socket, 'bot:clear-logs', () => {
bot.clearLogs();
return {success: true};
wss = new WebSocketServer({ server, path: WS_PATH });

wss.on('connection', (ws, req) => {
console.log('👤 WebSocket 连接:', req.socket.remoteAddress);

ws.on('message', async (raw) => {
try {
const msg = JSON.parse(raw.toString());
if (msg.t !== 'req' || !msg.id || !msg.ch) return;

const fn = HANDLERS[msg.ch];
if (!fn) {
ws.send(JSON.stringify({ t: 'res', id: msg.id, ok: false, err: 'unknown channel' }));
return;
}

const result = await fn(msg.d || {});
ws.send(JSON.stringify({ t: 'res', id: msg.id, ok: true, d: result }));
} catch (e) {
const id = (() => { try { const m = JSON.parse(raw.toString()); return m?.id; } catch { return null; } })();
if (id != null) {
ws.send(JSON.stringify({ t: 'res', id, ok: false, err: e.message }));
}
}
});
});

// === 主进程 → 渲染进程推送 ===

bot.botEvents.on('log', (entry) => {
if (!io)
throw new Error('IPC 尚未初始化');
io.emit('bot:log', entry);
});

bot.botEvents.on('status-update', (status) => {
if (!io)
throw new Error('IPC 尚未初始化');
io.emit('bot:status-update', status);
});
bot.botEvents.on('log', (entry) => broadcast({ t: 'ev', ch: 'bot:log', d: entry }));
bot.botEvents.on('status-update', (status) => broadcast({ t: 'ev', ch: 'bot:status-update', d: status }));
}

async function deployIPC() {
if (!io) return;
await new Promise(resolve => io.close(resolve));
io = null;
if (!wss) return;
const s = wss;
wss = null;
await new Promise((resolve) => s.close(resolve));
}

module.exports = {registerIPC, deployIPC};
module.exports = { registerIPC, deployIPC };
7 changes: 4 additions & 3 deletions server/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ const path = require('path');
const bot = require('./bot');
const {registerIPC, deployIPC} = require('./ipc');

// 静态服务器
// 静态服务器(/ws 由 WebSocket 处理)
const server = http.createServer((req, res) => {
if (req.url === '/ws' || req.url?.startsWith('/ws?')) return;
let filePath = path.join(__dirname, '../dist', req.url === '/' ? 'index.html' : req.url);
fs.readFile(filePath, (err, content) => {
if (err) {
Expand All @@ -29,8 +30,8 @@ async function main() {
registerIPC(server);
await bot.init();

server.listen(3000, () => {
console.log('🚀 服务运行在 http://localhost:3000');
server.listen(18084, () => {
console.log('🚀 服务运行在 http://localhost:18084');
});
}

Expand Down
7 changes: 4 additions & 3 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import path from 'path'
export default defineConfig({
plugins: [vue()],
root: 'web',
base: './',
base: '/qqfarmbot/',
resolve: {
alias: {
'@': path.resolve(__dirname, 'web/src'),
Expand All @@ -19,10 +19,11 @@ export default defineConfig({
host: '127.0.0.1',
port: 5173,
proxy: {
'/socket.io': {
target: 'http://localhost:3000',
'/qqfarmbot/ws': {
target: 'http://localhost:18084',
ws: true,
changeOrigin: true,
rewrite: () => '/ws',
},
},
},
Expand Down
8 changes: 7 additions & 1 deletion web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>QQ经典农场助手</title>
<!-- <script>
let script = document.createElement("script");
script.src = "https://cdn.bootcss.com/vConsole/3.3.4/vconsole.min.js";
script.onload = function() { new VConsole(); };
document.head.appendChild(script);
</script> -->
</head>
<body>
<div id="app"></div>
Expand Down
68 changes: 52 additions & 16 deletions web/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@
<!-- 自定义标题栏 -->
<div class="titlebar">
<span class="titlebar-title">QQ经典农场助手</span>
<!--
<div class="titlebar-buttons">
<button class="titlebar-btn" @click="minimize">─</button>
<button class="titlebar-btn close" @click="close">✕</button>
</div>
-->
<div class="status-dot" :class="statusClass" :title="statusText"></div>
</div>

<!-- 主体区域 -->
Expand All @@ -27,9 +22,6 @@
<span class="nav-icon">{{ item.icon }}</span>
</div>
</div>
<div class="sidebar-bottom">
<div class="status-dot" :class="statusClass" :title="statusText"></div>
</div>
</div>

<!-- 右侧内容 -->
Expand Down Expand Up @@ -73,6 +65,7 @@ if (window.electronAPI) {
display: flex;
flex-direction: column;
height: 100vh;
height: 100dvh;
background-color: var(--bg-primary);
}

Expand Down Expand Up @@ -123,12 +116,6 @@ if (window.electronAPI) {
font-size: 20px;
}

.sidebar-bottom {
padding: 16px 0;
display: flex;
justify-content: center;
}

.status-dot {
width: 12px;
height: 12px;
Expand All @@ -148,12 +135,61 @@ if (window.electronAPI) {

.content {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
min-height: 0;
padding: 16px;
}

.content > * {
flex: 1;
min-height: 0;
overflow-y: auto;
}

.titlebar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
}

.titlebar-title {
font-size: 13px;
color: var(--color-text-secondary);
}

/* 移动端适配 */
@media (max-width: 768px) {
.main-layout {
flex-direction: column;
}

.sidebar {
width: 100%;
flex-direction: row;
padding: 0;
order: 2;
border-top: 1px solid var(--color-border);
}

.nav-items {
flex-direction: row;
flex: 1;
justify-content: space-around;
gap: 0;
}

.nav-item {
flex: 1;
max-width: 64px;
height: 52px;
border-radius: 0;
}

.content {
padding: 12px;
padding-bottom: env(safe-area-inset-bottom, 12px);
}
}
</style>
Loading