feat: 新增机器人上/下线按钮,识别「账号已在别处登录」与「等待验证码」

下线按钮
- 顶栏新增「下线 / 上线」切换:下线会断开连接、停止自动重连,并停掉定时消息与
  扩地任务;需要在游戏客户端登同一账号时必须先下线,否则会被服务端以
  「您已经连接到此代理服务器了」反复踢掉
- 接口 POST /api/bots/:id/offline 与 /online;上线时会清掉「已在别处登录」标记
  并把重连计数归零,否则重连会一直退避到最大间隔

识别两类此前「看起来像启动不了」的情况
- 「您已经连接到此代理服务器了」:服务端踢人时发的是聊天组件对象,
  原来 JSON.stringify 之后没法看,关键信息被淹没。新增 extractKickReason()
  提取纯文本,命中后给出「请先在客户端退出」的可操作提示,
  并直接把重连退避到最大间隔,不再频繁硬撞服务器
- 「请直接输入验证码进行验证」:账号绑定过之后服务器仍可能每次登录都要求验证码,
  而原来的正则只认「完成验证 / 身份验证」,漏掉了这句话,用户只看到机器人卡在大厅。
  现在补上匹配,并在控制台弹出醒目提示 + 把输入框 placeholder 换成「输入验证码后回车发送」

测试 343 -> 355,全部通过。
这个提交包含在:
lyizai
2026-09-28 14:21:14 +08:00
父节点 71e78f06dc
当前提交 42a9dd6cf0
共修改 9 个文件,包含 390 行新增和 13 行删除
+28
查看文件
@@ -511,6 +511,34 @@ test('HTTP:大地图图幅可按机器人位置与半径计算瓦片范围', a
}
});
test('HTTP:可让机器人下线(停止自动重连)与重新上线', async () => {
const { webConsole, pool } = setup();
const entry = pool.get('BotA');
let disconnected = false;
entry.manager.disconnect = () => { disconnected = true; };
const connected = [];
entry.manager.connect = () => connected.push('BotA');
await webConsole.start();
const base = webConsole.baseUrl();
try {
const offline = await fetch(base + '/api/bots/BotA/offline', { method: 'POST' }).then((r) => r.json());
assert.strictEqual(offline.ok, true);
assert.ok(offline.message.includes('已下线'));
assert.strictEqual(disconnected, true);
assert.ok(offline.data.bots, '应返回最新状态负载供前端直接渲染');
const online = await fetch(base + '/api/bots/BotA/online', { method: 'POST' }).then((r) => r.json());
assert.strictEqual(online.ok, true);
assert.ok(online.message.includes('正在上线'));
assert.deepStrictEqual(connected, ['BotA']);
const missing = await fetch(base + '/api/bots/没有这个/offline', { method: 'POST' });
assert.strictEqual(missing.status, 404);
} finally {
await webConsole.stop();
}
});
test('HTTP:未配置密码时免登录(本机开发模式)', async () => {
const { webConsole } = setup();
await webConsole.start();