mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-01 16:20:25 +00:00
feat: 优化离线重连机制,支持通过前端实现重新登录
* feat: 优化离线重连机制,增加前端登录错误提示与二维码刷新功能 - 增加全局掉线检测弹窗 - 增强登录错误解析,支持显示 serverErrorCode 和 message - 优化二维码登录 UI,错误时显示详细原因并提供大按钮重新获取 - 核心层解耦,通过事件抛出 KickedOffLine 通知 - 支持前端点击刷新二维码接口 * feat: 新增看门狗汪汪汪 * cp napcat-shell-loader/launcher-win.bat * refactor: 重构重启流程,移除旧的重启逻辑,新增基于 WebUI 的重启请求处理 * fix: 刷新二维码清楚错误信息
This commit is contained in:
@@ -10,6 +10,7 @@ import PageLoading from '@/components/page_loading';
|
||||
|
||||
import QQManager from '@/controllers/qq_manager';
|
||||
import ProcessManager from '@/controllers/process_manager';
|
||||
import { waitForBackendReady } from '@/utils/process_utils';
|
||||
|
||||
const LoginConfigCard = () => {
|
||||
const [isRestarting, setIsRestarting] = useState(false);
|
||||
@@ -60,11 +61,24 @@ const LoginConfigCard = () => {
|
||||
setIsRestarting(true);
|
||||
try {
|
||||
const result = await ProcessManager.restartProcess();
|
||||
toast.success(result.message || '进程重启成功');
|
||||
// 等待 5 秒后刷新页面
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 5000);
|
||||
toast.success(result.message || '进程重启请求已发送');
|
||||
|
||||
// 轮询探测后端是否恢复
|
||||
const isReady = await waitForBackendReady(
|
||||
30000, // 30秒超时
|
||||
() => {
|
||||
setIsRestarting(false);
|
||||
toast.success('进程重启完成');
|
||||
},
|
||||
() => {
|
||||
setIsRestarting(false);
|
||||
toast.error('后端在 30 秒内未响应,请检查 NapCat 运行日志');
|
||||
}
|
||||
);
|
||||
|
||||
if (!isReady) {
|
||||
setIsRestarting(false);
|
||||
}
|
||||
} catch (error) {
|
||||
const msg = (error as Error).message;
|
||||
toast.error(`进程重启失败: ${msg}`);
|
||||
@@ -114,7 +128,7 @@ const LoginConfigCard = () => {
|
||||
{isRestarting ? '正在重启进程...' : '重启进程'}
|
||||
</Button>
|
||||
<div className='mt-2 text-xs text-default-500'>
|
||||
重启进程将关闭当前 Worker 进程,等待 3 秒后启动新进程,页面将在 5 秒后自动刷新
|
||||
重启进程将关闭当前 Worker 进程,等待 3 秒后启动新进程
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -16,14 +16,39 @@ import type { QQItem } from '@/components/quick_login';
|
||||
import { ThemeSwitch } from '@/components/theme-switch';
|
||||
|
||||
import QQManager from '@/controllers/qq_manager';
|
||||
import useDialog from '@/hooks/use-dialog';
|
||||
import PureLayout from '@/layouts/pure';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
const parseLoginError = (errorStr: string) => {
|
||||
if (errorStr.startsWith('登录失败: ')) {
|
||||
const jsonPart = errorStr.substring('登录失败: '.length);
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(jsonPart);
|
||||
|
||||
if (Array.isArray(parsed) && parsed[1]) {
|
||||
const info = parsed[1];
|
||||
const codeStr = info.serverErrorCode ? ` (错误码: ${info.serverErrorCode})` : '';
|
||||
|
||||
return `${info.message || errorStr}${codeStr}`;
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
|
||||
return errorStr;
|
||||
};
|
||||
|
||||
export default function QQLoginPage () {
|
||||
const navigate = useNavigate();
|
||||
const dialog = useDialog();
|
||||
const [uinValue, setUinValue] = useState<string>('');
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [qrcode, setQrcode] = useState<string>('');
|
||||
const [loginError, setLoginError] = useState<string>('');
|
||||
const lastErrorRef = useRef<string>('');
|
||||
const [qqList, setQQList] = useState<(QQItem | LoginListItem)[]>([]);
|
||||
const [refresh, setRefresh] = useState<boolean>(false);
|
||||
const firstLoad = useRef<boolean>(true);
|
||||
@@ -61,6 +86,20 @@ export default function QQLoginPage () {
|
||||
navigate('/', { replace: true });
|
||||
} else {
|
||||
setQrcode(data.qrcodeurl);
|
||||
if (data.loginError && data.loginError !== lastErrorRef.current) {
|
||||
lastErrorRef.current = data.loginError;
|
||||
setLoginError(data.loginError);
|
||||
const friendlyMsg = parseLoginError(data.loginError);
|
||||
|
||||
dialog.alert({
|
||||
title: '登录失败',
|
||||
content: friendlyMsg,
|
||||
confirmText: '确定',
|
||||
});
|
||||
} else if (!data.loginError) {
|
||||
lastErrorRef.current = '';
|
||||
setLoginError('');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const msg = (error as Error).message;
|
||||
@@ -99,6 +138,18 @@ export default function QQLoginPage () {
|
||||
setUinValue(e.target.value);
|
||||
};
|
||||
|
||||
const onRefreshQRCode = async () => {
|
||||
try {
|
||||
lastErrorRef.current = '';
|
||||
setLoginError('');
|
||||
await QQManager.refreshQRCode();
|
||||
toast.success('已发送刷新请求');
|
||||
} catch (error) {
|
||||
const msg = (error as Error).message;
|
||||
toast.error(`刷新二维码失败: ${msg}`);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
onUpdateQrCode();
|
||||
@@ -159,7 +210,11 @@ export default function QQLoginPage () {
|
||||
/>
|
||||
</Tab>
|
||||
<Tab key='qrcode' title='扫码登录'>
|
||||
<QrCodeLogin qrcode={qrcode} />
|
||||
<QrCodeLogin
|
||||
loginError={parseLoginError(loginError)}
|
||||
qrcode={qrcode}
|
||||
onRefresh={onRefreshQRCode}
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user