Add password login support to web UI and backend

Implement password-based QQ login across the stack: add a PasswordLogin React component, integrate it into the QQ login page, and add a frontend controller method to call a new /QQLogin/PasswordLogin API. On the backend, add QQPasswordLoginHandler, router entry, and WebUiDataRuntime hooks (setPasswordLoginCall / requestPasswordLogin) plus a default handler. Register a password login callback in the shell (base.ts) that calls the kernel login service, handles common error cases and falls back to QR code when needed. Update types to include onPasswordLoginRequested and adjust NodeIKernelLoginService method signatures (including passwordLogin return type changed to Promise<QuickLoginResult>) and minor formatting fixes.
This commit is contained in:
手瓜一十雪
2026-02-02 19:48:31 +08:00
parent 0fd1a32293
commit 168447dcbc
9 changed files with 269 additions and 20 deletions

View File

@@ -5,11 +5,13 @@ import { Tab, Tabs } from '@heroui/tabs';
import { useEffect, useRef, useState } from 'react';
import { toast } from 'react-hot-toast';
import { useNavigate } from 'react-router-dom';
import CryptoJS from 'crypto-js';
import logo from '@/assets/images/logo.png';
import HoverEffectCard from '@/components/effect_card';
import { title } from '@/components/primitives';
import PasswordLogin from '@/components/password_login';
import QrCodeLogin from '@/components/qr_code_login';
import QuickLogin from '@/components/quick_login';
import type { QQItem } from '@/components/quick_login';
@@ -51,6 +53,7 @@ export default function QQLoginPage () {
const lastErrorRef = useRef<string>('');
const [qqList, setQQList] = useState<(QQItem | LoginListItem)[]>([]);
const [refresh, setRefresh] = useState<boolean>(false);
const [activeTab, setActiveTab] = useState<string>('shortcut');
const firstLoad = useRef<boolean>(true);
const onSubmit = async () => {
if (!uinValue) {
@@ -72,6 +75,21 @@ export default function QQLoginPage () {
}
};
const onPasswordSubmit = async (uin: string, password: string) => {
setIsLoading(true);
try {
// 计算密码的MD5值
const passwordMd5 = CryptoJS.MD5(password).toString();
await QQManager.passwordLogin(uin, passwordMd5);
toast.success('密码登录请求已发送');
} catch (error) {
const msg = (error as Error).message;
toast.error(`密码登录失败: ${msg}`);
} finally {
setIsLoading(false);
}
};
const onUpdateQrCode = async () => {
if (firstLoad.current) setIsLoading(true);
try {
@@ -91,11 +109,17 @@ export default function QQLoginPage () {
setLoginError(data.loginError);
const friendlyMsg = parseLoginError(data.loginError);
dialog.alert({
title: '登录失败',
content: friendlyMsg,
confirmText: '确定',
});
// 仅在扫码登录 Tab 下才弹窗,或者错误不是"二维码已过期"
// 如果是 "二维码已过期",且不在 qrcode tab则不弹窗
const isQrCodeExpired = friendlyMsg.includes('二维码') && (friendlyMsg.includes('过期') || friendlyMsg.includes('失效'));
if (!isQrCodeExpired || activeTab === 'qrcode') {
dialog.alert({
title: '登录失败',
content: friendlyMsg,
confirmText: '确定',
});
}
} else if (!data.loginError) {
lastErrorRef.current = '';
setLoginError('');
@@ -197,6 +221,8 @@ export default function QQLoginPage () {
}}
isDisabled={isLoading}
size='lg'
selectedKey={activeTab}
onSelectionChange={(key) => setActiveTab(key.toString())}
>
<Tab key='shortcut' title='快速登录'>
<QuickLogin
@@ -209,6 +235,13 @@ export default function QQLoginPage () {
onUpdateQQList={onUpdateQQList}
/>
</Tab>
<Tab key='password' title='密码登录'>
<PasswordLogin
isLoading={isLoading}
onSubmit={onPasswordSubmit}
qqList={qqList}
/>
</Tab>
<Tab key='qrcode' title='扫码登录'>
<QrCodeLogin
loginError={parseLoginError(loginError)}