mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-01-18 14:30:29 +00:00
feat: 优化离线重连机制,增加前端登录错误提示与二维码刷新功能
- 增加全局掉线检测弹窗 - 增强登录错误解析,支持显示 serverErrorCode 和 message - 优化二维码登录 UI,错误时显示详细原因并提供大按钮重新获取 - 核心层解耦,通过事件抛出 KickedOffLine 通知 - 支持前端点击刷新二维码接口
This commit is contained in:
parent
fbccf8be24
commit
0918b17257
@ -21,4 +21,4 @@ export interface IStatusHelperSubscription {
|
||||
on (event: 'statusUpdate', listener: (status: SystemStatus) => void): this;
|
||||
off (event: 'statusUpdate', listener: (status: SystemStatus) => void): this;
|
||||
emit (event: 'statusUpdate', status: SystemStatus): boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +125,7 @@ export class NapCatCore {
|
||||
container.bind(TypedEventEmitter).toConstantValue(this.event);
|
||||
ReceiverServiceRegistry.forEach((ServiceClass, serviceName) => {
|
||||
container.bind(ServiceClass).toSelf();
|
||||
//console.log(`Registering service handler for: ${serviceName}`);
|
||||
// console.log(`Registering service handler for: ${serviceName}`);
|
||||
this.context.packetHandler.onCmd(serviceName, ({ seq, hex_data }) => {
|
||||
const serviceInstance = container.get(ServiceClass);
|
||||
return serviceInstance.handler(seq, hex_data);
|
||||
@ -177,8 +177,10 @@ export class NapCatCore {
|
||||
|
||||
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
|
||||
// 下线通知
|
||||
this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
|
||||
const tips = `[KickedOffLine] [${Info.tipsTitle}] ${Info.tipsDesc}`;
|
||||
this.context.logger.logError(tips);
|
||||
this.selfInfo.online = false;
|
||||
this.event.emit('KickedOffLine', tips);
|
||||
};
|
||||
msgListener.onRecvMsg = (msgs) => {
|
||||
msgs.forEach(msg => this.context.logger.logMessage(msg, this.selfInfo));
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { TypedEventEmitter } from './typeEvent';
|
||||
|
||||
export interface AppEvents {
|
||||
'event:emoji_like': { groupId: string; senderUin: string; emojiId: string, msgSeq: string, isAdd: boolean, count: number };
|
||||
'event:emoji_like': { groupId: string; senderUin: string; emojiId: string, msgSeq: string, isAdd: boolean, count: number; };
|
||||
KickedOffLine: string;
|
||||
}
|
||||
export const appEvent = new TypedEventEmitter<AppEvents>();
|
||||
|
||||
@ -246,7 +246,7 @@ export class NapCatOneBot11Adapter {
|
||||
await this.handleConfigChange(prev.network.websocketClients, now.network.websocketClients, OB11WebSocketClientAdapter);
|
||||
}
|
||||
|
||||
private async handleConfigChange<CT extends NetworkAdapterConfig>(
|
||||
private async handleConfigChange<CT extends NetworkAdapterConfig> (
|
||||
prevConfig: NetworkAdapterConfig[],
|
||||
nowConfig: NetworkAdapterConfig[],
|
||||
adapterClass: new (
|
||||
@ -384,6 +384,7 @@ export class NapCatOneBot11Adapter {
|
||||
}
|
||||
};
|
||||
msgListener.onKickedOffLine = async (kick) => {
|
||||
WebUiDataRuntime.setQQLoginStatus(false);
|
||||
const event = new BotOfflineEvent(this.core, kick.tipsTitle, kick.tipsDesc);
|
||||
this.networkManager
|
||||
.emitEvent(event)
|
||||
|
||||
@ -128,10 +128,13 @@ async function handleLogin (
|
||||
|
||||
const loginListener = new NodeIKernelLoginListener();
|
||||
loginListener.onUserLoggedIn = (userid: string) => {
|
||||
logger.logError(`当前账号(${userid})已登录,无法重复登录`);
|
||||
const tips = `当前账号(${userid})已登录,无法重复登录`;
|
||||
logger.logError(tips);
|
||||
WebUiDataRuntime.setQQLoginError(tips);
|
||||
};
|
||||
loginListener.onQRCodeLoginSucceed = async (loginResult) => {
|
||||
context.isLogined = true;
|
||||
WebUiDataRuntime.setQQLoginStatus(true);
|
||||
inner_resolve({
|
||||
uid: loginResult.uid,
|
||||
uin: loginResult.uin,
|
||||
@ -170,13 +173,16 @@ async function handleLogin (
|
||||
logger.logError('[Core] [Login] Login Error,ErrType: ', errType, ' ErrCode:', errCode);
|
||||
if (errType === 1 && errCode === 3) {
|
||||
// 二维码过期刷新
|
||||
WebUiDataRuntime.setQQLoginError('二维码已过期,请刷新');
|
||||
}
|
||||
loginService.getQRCodePicture();
|
||||
}
|
||||
};
|
||||
|
||||
loginListener.onLoginFailed = (...args) => {
|
||||
logger.logError('[Core] [Login] Login Error , ErrInfo: ', JSON.stringify(args));
|
||||
const errInfo = JSON.stringify(args);
|
||||
logger.logError('[Core] [Login] Login Error , ErrInfo: ', errInfo);
|
||||
WebUiDataRuntime.setQQLoginError(`登录失败: ${errInfo}`);
|
||||
};
|
||||
|
||||
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
|
||||
@ -184,17 +190,29 @@ async function handleLogin (
|
||||
return await selfInfo;
|
||||
}
|
||||
async function handleLoginInner (context: { isLogined: boolean; }, logger: LogWrapper, loginService: NodeIKernelLoginService, quickLoginUin: string | undefined, historyLoginList: LoginListItem[]) {
|
||||
// 注册刷新二维码回调
|
||||
WebUiDataRuntime.setRefreshQRCodeCallback(async () => {
|
||||
loginService.getQRCodePicture();
|
||||
});
|
||||
|
||||
WebUiDataRuntime.setQuickLoginCall(async (uin: string) => {
|
||||
return await new Promise((resolve) => {
|
||||
if (uin) {
|
||||
logger.log('正在快速登录 ', uin);
|
||||
loginService.quickLoginWithUin(uin).then(res => {
|
||||
if (res.loginErrorInfo.errMsg) {
|
||||
WebUiDataRuntime.setQQLoginError(res.loginErrorInfo.errMsg);
|
||||
loginService.getQRCodePicture();
|
||||
resolve({ result: false, message: res.loginErrorInfo.errMsg });
|
||||
} else {
|
||||
WebUiDataRuntime.setQQLoginStatus(true);
|
||||
WebUiDataRuntime.setQQLoginError('');
|
||||
resolve({ result: true, message: '' });
|
||||
}
|
||||
resolve({ result: true, message: '' });
|
||||
}).catch((e) => {
|
||||
logger.logError(e);
|
||||
WebUiDataRuntime.setQQLoginError('快速登录发生错误');
|
||||
loginService.getQRCodePicture();
|
||||
resolve({ result: false, message: '快速登录发生错误' });
|
||||
});
|
||||
} else {
|
||||
@ -209,6 +227,7 @@ async function handleLoginInner (context: { isLogined: boolean; }, logger: LogWr
|
||||
.then(result => {
|
||||
if (result.loginErrorInfo.errMsg) {
|
||||
logger.logError('快速登录错误:', result.loginErrorInfo.errMsg);
|
||||
WebUiDataRuntime.setQQLoginError(result.loginErrorInfo.errMsg);
|
||||
if (!context.isLogined) loginService.getQRCodePicture();
|
||||
}
|
||||
})
|
||||
@ -452,6 +471,10 @@ export class NapCatShell {
|
||||
|
||||
async InitNapCat () {
|
||||
await this.core.initCore();
|
||||
// 监听下线通知并同步到 WebUI
|
||||
this.core.event.on('KickedOffLine', (tips: string) => {
|
||||
WebUiDataRuntime.setQQLoginError(tips);
|
||||
});
|
||||
const oneBotAdapter = new NapCatOneBot11Adapter(this.core, this.context, this.context.pathWrapper);
|
||||
// 注册到 WebUiDataRuntime,供调试功能使用
|
||||
WebUiDataRuntime.setOneBotContext(oneBotAdapter);
|
||||
@ -459,4 +482,3 @@ export class NapCatShell {
|
||||
.catch(e => this.context.logger.logError('初始化OneBot失败', e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
{
|
||||
"name": "napcat-vite",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"build": "vite build"
|
||||
"name": "napcat-vite",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"_build": "vite build"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.ts"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./*"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"./*": {
|
||||
"import": "./*"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
@ -27,9 +27,17 @@ export const QQGetQRcodeHandler: RequestHandler = async (_, res) => {
|
||||
|
||||
// 获取QQ登录状态
|
||||
export const QQCheckLoginStatusHandler: RequestHandler = async (_, res) => {
|
||||
// 从 OneBot 上下文获取实时的 selfInfo.online 状态
|
||||
const oneBotContext = WebUiDataRuntime.getOneBotContext();
|
||||
const selfInfo = oneBotContext?.core?.selfInfo;
|
||||
const isOnline = selfInfo?.online;
|
||||
const qqLoginStatus = WebUiDataRuntime.getQQLoginStatus();
|
||||
// 必须同时满足:已登录且在线(online 必须明确为 true)
|
||||
const isLogin = qqLoginStatus && isOnline === true;
|
||||
const data = {
|
||||
isLogin: WebUiDataRuntime.getQQLoginStatus(),
|
||||
isLogin,
|
||||
qrcodeurl: WebUiDataRuntime.getQQLoginQrcodeURL(),
|
||||
loginError: WebUiDataRuntime.getQQLoginError(),
|
||||
};
|
||||
return sendSuccess(res, data);
|
||||
};
|
||||
@ -88,3 +96,15 @@ export const setAutoLoginAccountHandler: RequestHandler = async (req, res) => {
|
||||
await WebUiConfig.UpdateAutoLoginAccount(uin);
|
||||
return sendSuccess(res, null);
|
||||
};
|
||||
|
||||
// 刷新QQ登录二维码
|
||||
export const QQRefreshQRcodeHandler: RequestHandler = async (_, res) => {
|
||||
// 判断是否已经登录
|
||||
if (WebUiDataRuntime.getQQLoginStatus()) {
|
||||
// 已经登录
|
||||
return sendError(res, 'QQ Is Logined');
|
||||
}
|
||||
// 刷新二维码
|
||||
await WebUiDataRuntime.refreshQRCode();
|
||||
return sendSuccess(res, null);
|
||||
};
|
||||
|
||||
@ -14,6 +14,7 @@ const LoginRuntime: LoginRuntimeType = {
|
||||
uin: '',
|
||||
nick: '',
|
||||
},
|
||||
QQLoginError: '',
|
||||
QQVersion: 'unknown',
|
||||
OneBotContext: null,
|
||||
onQQLoginStatusChange: async (status: boolean) => {
|
||||
@ -21,6 +22,9 @@ const LoginRuntime: LoginRuntimeType = {
|
||||
},
|
||||
onWebUiTokenChange: async (_token: string) => {
|
||||
|
||||
},
|
||||
onRefreshQRCode: async () => {
|
||||
// 默认空实现,由 shell 注册真实回调
|
||||
},
|
||||
NapCatHelper: {
|
||||
onOB11ConfigChanged: async () => {
|
||||
@ -163,4 +167,22 @@ export const WebUiDataRuntime = {
|
||||
getOneBotContext (): any | null {
|
||||
return LoginRuntime.OneBotContext;
|
||||
},
|
||||
|
||||
setQQLoginError (error: string): void {
|
||||
LoginRuntime.QQLoginError = error;
|
||||
},
|
||||
|
||||
getQQLoginError (): string {
|
||||
return LoginRuntime.QQLoginError;
|
||||
},
|
||||
|
||||
setRefreshQRCodeCallback (func: () => Promise<void>): void {
|
||||
LoginRuntime.onRefreshQRCode = func;
|
||||
},
|
||||
|
||||
async refreshQRCode (): Promise<void> {
|
||||
// 清除错误信息
|
||||
LoginRuntime.QQLoginError = '';
|
||||
await LoginRuntime.onRefreshQRCode();
|
||||
},
|
||||
};
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
getQQLoginInfoHandler,
|
||||
getAutoLoginAccountHandler,
|
||||
setAutoLoginAccountHandler,
|
||||
QQRefreshQRcodeHandler,
|
||||
} from '@/napcat-webui-backend/src/api/QQLogin';
|
||||
|
||||
const router = Router();
|
||||
@ -28,5 +29,7 @@ router.post('/GetQQLoginInfo', getQQLoginInfoHandler);
|
||||
router.post('/GetQuickLoginQQ', getAutoLoginAccountHandler);
|
||||
// router:设置自动登录QQ账号
|
||||
router.post('/SetQuickLoginQQ', setAutoLoginAccountHandler);
|
||||
// router:刷新QQ登录二维码
|
||||
router.post('/RefreshQRcode', QQRefreshQRcodeHandler);
|
||||
|
||||
export { router as QQLoginRouter };
|
||||
|
||||
@ -43,9 +43,11 @@ export interface LoginRuntimeType {
|
||||
QQQRCodeURL: string;
|
||||
QQLoginUin: string;
|
||||
QQLoginInfo: SelfInfo;
|
||||
QQLoginError: string;
|
||||
QQVersion: string;
|
||||
onQQLoginStatusChange: (status: boolean) => Promise<void>;
|
||||
onWebUiTokenChange: (token: string) => Promise<void>;
|
||||
onRefreshQRCode: () => Promise<void>;
|
||||
WebUiConfigQuickFunction: () => Promise<void>;
|
||||
OneBotContext: any | null; // OneBot 上下文,用于调试功能
|
||||
NapCatHelper: {
|
||||
|
||||
@ -52,7 +52,7 @@ const Modal: React.FC<ModalProps> = React.memo((props) => {
|
||||
onNativeClose();
|
||||
}}
|
||||
classNames={{
|
||||
backdrop: 'z-[99]',
|
||||
backdrop: 'z-[99] backdrop-blur-sm',
|
||||
wrapper: 'z-[99]',
|
||||
}}
|
||||
{...rest}
|
||||
|
||||
@ -1,22 +1,70 @@
|
||||
import { Button } from '@heroui/button';
|
||||
import { Spinner } from '@heroui/spinner';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { IoAlertCircle, IoRefresh } from 'react-icons/io5';
|
||||
|
||||
interface QrCodeLoginProps {
|
||||
qrcode: string
|
||||
qrcode: string;
|
||||
loginError?: string;
|
||||
onRefresh?: () => void;
|
||||
}
|
||||
|
||||
const QrCodeLogin: React.FC<QrCodeLoginProps> = ({ qrcode }) => {
|
||||
const QrCodeLogin: React.FC<QrCodeLoginProps> = ({ qrcode, loginError, onRefresh }) => {
|
||||
return (
|
||||
<div className='flex flex-col items-center'>
|
||||
<div className='bg-white p-2 rounded-md w-fit mx-auto relative overflow-hidden'>
|
||||
{!qrcode && (
|
||||
<div className='absolute left-2 top-2 right-2 bottom-2 bg-white bg-opacity-50 backdrop-blur flex items-center justify-center'>
|
||||
<Spinner color='primary' />
|
||||
{loginError
|
||||
? (
|
||||
<div className='flex flex-col items-center py-4'>
|
||||
<div className='w-full flex justify-center mb-6'>
|
||||
<div className='p-4 bg-danger-50 rounded-full'>
|
||||
<IoAlertCircle className='text-danger' size={64} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-center space-y-2 px-4'>
|
||||
<div className='text-xl font-bold text-danger'>登录失败</div>
|
||||
<div className='text-default-600 text-sm leading-relaxed max-w-[300px]'>
|
||||
{loginError}
|
||||
</div>
|
||||
</div>
|
||||
{onRefresh && (
|
||||
<Button
|
||||
className='mt-8 min-w-[160px]'
|
||||
variant='solid'
|
||||
color='primary'
|
||||
size='lg'
|
||||
startContent={<IoRefresh />}
|
||||
onPress={onRefresh}
|
||||
>
|
||||
重新获取二维码
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<div className='bg-white p-2 rounded-md w-fit mx-auto relative overflow-hidden'>
|
||||
{!qrcode && (
|
||||
<div className='absolute left-0 top-0 right-0 bottom-0 bg-white dark:bg-zinc-900 bg-opacity-90 backdrop-blur-sm flex items-center justify-center z-10'>
|
||||
<Spinner color='primary' />
|
||||
</div>
|
||||
)}
|
||||
<QRCodeSVG size={180} value={qrcode || ' '} />
|
||||
</div>
|
||||
<div className='mt-5 text-center text-default-500 text-sm'>请使用QQ或者TIM扫描上方二维码</div>
|
||||
{onRefresh && qrcode && (
|
||||
<Button
|
||||
className='mt-4'
|
||||
variant='flat'
|
||||
color='primary'
|
||||
size='sm'
|
||||
startContent={<IoRefresh />}
|
||||
onPress={onRefresh}
|
||||
>
|
||||
刷新二维码
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<QRCodeSVG size={180} value={qrcode} />
|
||||
</div>
|
||||
<div className='mt-5 text-center'>请使用QQ或者TIM扫描上方二维码</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -20,8 +20,8 @@ export default class QQManager {
|
||||
public static async checkQQLoginStatus () {
|
||||
const data = await serverRequest.post<
|
||||
ServerResponse<{
|
||||
isLogin: string
|
||||
qrcodeurl: string
|
||||
isLogin: string;
|
||||
qrcodeurl: string;
|
||||
}>
|
||||
>('/QQLogin/CheckLoginStatus');
|
||||
|
||||
@ -30,16 +30,20 @@ export default class QQManager {
|
||||
|
||||
public static async checkQQLoginStatusWithQrcode () {
|
||||
const data = await serverRequest.post<
|
||||
ServerResponse<{ qrcodeurl: string; isLogin: string }>
|
||||
ServerResponse<{ qrcodeurl: string; isLogin: string; loginError?: string; }>
|
||||
>('/QQLogin/CheckLoginStatus');
|
||||
|
||||
return data.data.data;
|
||||
}
|
||||
|
||||
public static async refreshQRCode () {
|
||||
await serverRequest.post<ServerResponse<null>>('/QQLogin/RefreshQRcode');
|
||||
}
|
||||
|
||||
public static async getQQLoginQrcode () {
|
||||
const data = await serverRequest.post<
|
||||
ServerResponse<{
|
||||
qrcode: string
|
||||
qrcode: string;
|
||||
}>
|
||||
>('/QQLogin/GetQQLoginQrcode');
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ import errorFallbackRender from '@/components/error_fallback';
|
||||
import SideBar from '@/components/sidebar';
|
||||
|
||||
import useAuth from '@/hooks/auth';
|
||||
import useDialog from '@/hooks/use-dialog';
|
||||
|
||||
import type { MenuItem } from '@/config/site';
|
||||
import { siteConfig } from '@/config/site';
|
||||
@ -48,7 +49,43 @@ const Layout: React.FC<{ children: React.ReactNode; }> = ({ children }) => {
|
||||
const [openSideBar, setOpenSideBar] = useLocalStorage(key.sideBarOpen, true);
|
||||
const [b64img] = useLocalStorage(key.backgroundImage, '');
|
||||
const navigate = useNavigate();
|
||||
const { isAuth } = useAuth();
|
||||
const { isAuth, revokeAuth } = useAuth();
|
||||
const dialog = useDialog();
|
||||
const isOnlineRef = useRef(true);
|
||||
|
||||
// 定期检查 QQ 在线状态,掉线时弹窗提示
|
||||
useEffect(() => {
|
||||
if (!isAuth) return;
|
||||
const checkOnlineStatus = async () => {
|
||||
const currentPath = location.pathname;
|
||||
if (currentPath === '/qq_login' || currentPath === '/web_login') return;
|
||||
try {
|
||||
const info = await QQManager.getQQLoginInfo();
|
||||
if (info?.online === false && isOnlineRef.current === true) {
|
||||
isOnlineRef.current = false;
|
||||
dialog.confirm({
|
||||
title: '账号已离线',
|
||||
content: '您的 QQ 账号已下线,请重新登录。',
|
||||
confirmText: '重新登陆',
|
||||
cancelText: '退出账户',
|
||||
onConfirm: () => navigate('/qq_login'),
|
||||
onCancel: () => {
|
||||
revokeAuth();
|
||||
navigate('/web_login');
|
||||
},
|
||||
});
|
||||
} else if (info?.online === true) {
|
||||
isOnlineRef.current = true;
|
||||
}
|
||||
} catch (_e) {
|
||||
// 忽略请求错误
|
||||
}
|
||||
};
|
||||
const timer = setInterval(checkOnlineStatus, 5000);
|
||||
checkOnlineStatus();
|
||||
return () => clearInterval(timer);
|
||||
}, [isAuth, location.pathname]);
|
||||
|
||||
const checkIsQQLogin = async () => {
|
||||
try {
|
||||
const result = await QQManager.checkQQLoginStatus();
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import viteCompression from 'vite-plugin-compression';
|
||||
// import viteCompression from 'vite-plugin-compression';
|
||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
@ -13,7 +13,7 @@ export default defineConfig(({ mode }) => {
|
||||
plugins: [
|
||||
react(),
|
||||
tsconfigPaths(),
|
||||
ViteImageOptimizer({})
|
||||
ViteImageOptimizer({}),
|
||||
],
|
||||
base: '/webui/',
|
||||
server: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user