feat: 完成NapCat登录进度展示
This commit is contained in:
parent
15480ca705
commit
33ebdac389
@ -47,7 +47,7 @@ pnpm run build:antdv-next
|
||||
## 业务页面
|
||||
|
||||
- 系统管理 / 站内信是日志级通知列表,只展示 API 错误、QQBot 下线、NapCat 离线等后端自动捕获事件;页面提供筛选、处理/重新打开、置顶和删除,不提供人工新增或编辑。
|
||||
- QQBot / 账号连接页拆分 OneBot 连接、QQ 登录、NapCat 运行和运行说明列;更新登录通过 SSE 展示 quick / password / captcha / qrcode 每步进度,密码登录触发 QQ 安全验证时在弹窗内完成腾讯验证码并回交 API。
|
||||
- QQBot / 账号连接页拆分 OneBot 连接、QQ 登录、NapCat 运行和运行说明列;更新登录通过 SSE 展示 quick / password / captcha / new-device / qrcode 每步中文进度,密码登录触发 QQ 安全验证时在弹窗内完成腾讯验证码并回交 API,新设备验证二维码和腾讯验证码分开展示。
|
||||
- QQBot / 插件平台页保留在线命令能力表,并提供 manifest 校验、本地插件安装、安装记录、运行事件和账号绑定抽屉,接口走 `/qqbot/plugin-platform/*`。
|
||||
|
||||
## 部署说明
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import type { NapcatLoginNewDeviceStatus } from './napcat';
|
||||
|
||||
import { encryptPassword } from '#/api/core/auth';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
@ -96,9 +98,12 @@ export namespace QqbotApi {
|
||||
captchaUrl?: string;
|
||||
containerId?: string;
|
||||
containerName?: string;
|
||||
deviceVerifyUrl?: string;
|
||||
errorMessage?: string;
|
||||
expiresAt?: number;
|
||||
mode: 'create' | 'refresh';
|
||||
newDeviceQrcode?: string;
|
||||
newDeviceStatus?: NapcatLoginNewDeviceStatus;
|
||||
qrcode?: string;
|
||||
selfId?: string;
|
||||
sessionId?: string;
|
||||
|
||||
44
apps/web-antdv-next/src/api/qqbot/napcat.spec.ts
Normal file
44
apps/web-antdv-next/src/api/qqbot/napcat.spec.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
getNapcatNewDeviceStatusMessage,
|
||||
NAPCAT_LOGIN_PROGRESS_LABELS,
|
||||
resolveNapcatLoginDisplayQrcode,
|
||||
} from './napcat';
|
||||
|
||||
describe('napcat login display helpers', () => {
|
||||
it('keeps all Chinese progress labels required by the login flow', () => {
|
||||
expect(NAPCAT_LOGIN_PROGRESS_LABELS).toMatchObject({
|
||||
'captcha-submit': '验证码已提交,等待确认',
|
||||
'login-failed': '登录失败',
|
||||
'login-success': '登录成功',
|
||||
'manual-qr-required': '正在生成手动二维码',
|
||||
'new-device-confirming': '新设备确认中',
|
||||
'new-device-qrcode-ready': '新设备二维码待扫码',
|
||||
'new-device-required': '需要新设备验证二维码',
|
||||
'new-device-scanned': '新设备二维码已扫码',
|
||||
'new-device-verified': '新设备验证成功,继续登录',
|
||||
'password-login': '正在密码登录',
|
||||
'password-login-captcha': '需要验证码',
|
||||
'password-login-captcha-submit': '验证码已提交,等待确认',
|
||||
'password-login-failed': '登录失败',
|
||||
'password-login-start': '正在密码登录',
|
||||
'quick-login-fallback': '快速登录失败,进入密码登录',
|
||||
'quick-login-start': '正在快速登录',
|
||||
'runtime-cleanup-failed': '运行态清理失败',
|
||||
});
|
||||
});
|
||||
|
||||
it('shows new-device QR separately before normal login QR or captcha', () => {
|
||||
expect(
|
||||
resolveNapcatLoginDisplayQrcode({
|
||||
captchaUrl: 'https://captcha.qq.com/',
|
||||
newDeviceQrcode: 'data:image/png;base64,new-device',
|
||||
qrcode: 'normal-login-qrcode',
|
||||
}),
|
||||
).toBe('data:image/png;base64,new-device');
|
||||
expect(getNapcatNewDeviceStatusMessage('scanned')).toBe(
|
||||
'新设备二维码已扫码',
|
||||
);
|
||||
});
|
||||
});
|
||||
50
apps/web-antdv-next/src/api/qqbot/napcat.ts
Normal file
50
apps/web-antdv-next/src/api/qqbot/napcat.ts
Normal file
@ -0,0 +1,50 @@
|
||||
export type NapcatLoginNewDeviceStatus =
|
||||
| 'confirming'
|
||||
| 'expired'
|
||||
| 'failed'
|
||||
| 'qr-pending'
|
||||
| 'scanned'
|
||||
| 'verified';
|
||||
|
||||
export type NapcatLoginDisplayQrcodeSource = {
|
||||
captchaUrl?: string;
|
||||
newDeviceQrcode?: string;
|
||||
qrcode?: string;
|
||||
};
|
||||
|
||||
export const NAPCAT_LOGIN_PROGRESS_LABELS = {
|
||||
'captcha-submit': '验证码已提交,等待确认',
|
||||
'login-failed': '登录失败',
|
||||
'login-success': '登录成功',
|
||||
'manual-qr-required': '正在生成手动二维码',
|
||||
'new-device-confirming': '新设备确认中',
|
||||
'new-device-qrcode-ready': '新设备二维码待扫码',
|
||||
'new-device-required': '需要新设备验证二维码',
|
||||
'new-device-scanned': '新设备二维码已扫码',
|
||||
'new-device-verified': '新设备验证成功,继续登录',
|
||||
'password-login': '正在密码登录',
|
||||
'password-login-captcha': '需要验证码',
|
||||
'password-login-captcha-submit': '验证码已提交,等待确认',
|
||||
'password-login-failed': '登录失败',
|
||||
'password-login-start': '正在密码登录',
|
||||
'quick-login-fallback': '快速登录失败,进入密码登录',
|
||||
'quick-login-start': '正在快速登录',
|
||||
'runtime-cleanup-failed': '运行态清理失败',
|
||||
} as const;
|
||||
|
||||
export function resolveNapcatLoginDisplayQrcode(
|
||||
source: NapcatLoginDisplayQrcodeSource,
|
||||
) {
|
||||
return source.newDeviceQrcode || source.qrcode || '';
|
||||
}
|
||||
|
||||
export function getNapcatNewDeviceStatusMessage(
|
||||
status?: NapcatLoginNewDeviceStatus,
|
||||
) {
|
||||
if (status === 'scanned') return '新设备二维码已扫码';
|
||||
if (status === 'confirming') return '新设备确认中';
|
||||
if (status === 'verified') return '新设备验证成功,继续登录';
|
||||
if (status === 'expired') return '新设备二维码已过期';
|
||||
if (status === 'failed') return '新设备验证失败';
|
||||
return '新设备二维码待扫码';
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import type { TableColumnType } from 'antdv-next';
|
||||
|
||||
import type { QqbotApi } from '#/api/qqbot';
|
||||
import type { NapcatLoginNewDeviceStatus } from '#/api/qqbot/napcat';
|
||||
import type {
|
||||
KtTableApi,
|
||||
KtTableButton,
|
||||
@ -39,6 +40,10 @@ import {
|
||||
submitQqbotAccountScanCaptcha,
|
||||
updateQqbotAccount,
|
||||
} from '#/api/qqbot';
|
||||
import {
|
||||
getNapcatNewDeviceStatusMessage,
|
||||
resolveNapcatLoginDisplayQrcode,
|
||||
} from '#/api/qqbot/napcat';
|
||||
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||
|
||||
const AKtTable = KtTable as any;
|
||||
@ -87,9 +92,12 @@ export default defineComponent({
|
||||
captchaUrl?: string;
|
||||
containerId?: string;
|
||||
containerName?: string;
|
||||
deviceVerifyUrl?: string;
|
||||
errorMessage?: string;
|
||||
expiresAt?: number;
|
||||
mode: 'create' | 'refresh';
|
||||
newDeviceQrcode?: string;
|
||||
newDeviceStatus?: NapcatLoginNewDeviceStatus;
|
||||
selfId?: string;
|
||||
sessionId?: string;
|
||||
status: 'error' | 'expired' | 'idle' | 'pending' | 'success';
|
||||
@ -130,6 +138,9 @@ export default defineComponent({
|
||||
Math.max(scanProgressItems.value.length - 1, 0),
|
||||
);
|
||||
const scanQrcodePlaceholderText = computed(() => {
|
||||
if (scanState.newDeviceStatus) {
|
||||
return getNapcatNewDeviceStatusMessage(scanState.newDeviceStatus);
|
||||
}
|
||||
if (scanState.captchaUrl) {
|
||||
return '等待安全验证';
|
||||
}
|
||||
@ -429,14 +440,17 @@ export default defineComponent({
|
||||
scanState.captchaUrl = result.captchaUrl;
|
||||
scanState.containerId = result.containerId;
|
||||
scanState.containerName = result.containerName;
|
||||
scanState.deviceVerifyUrl = result.deviceVerifyUrl;
|
||||
scanState.errorMessage = result.errorMessage;
|
||||
scanState.expiresAt = result.expiresAt;
|
||||
scanState.mode = result.mode;
|
||||
scanState.newDeviceQrcode = result.newDeviceQrcode;
|
||||
scanState.newDeviceStatus = result.newDeviceStatus;
|
||||
scanState.selfId = result.selfId;
|
||||
scanState.sessionId = result.sessionId;
|
||||
scanState.status = result.status;
|
||||
scanState.webuiPort = result.webuiPort;
|
||||
const nextQrcode = result.qrcode || '';
|
||||
const nextQrcode = resolveNapcatLoginDisplayQrcode(result);
|
||||
const qrcodeChanged = nextQrcode !== scanQrcodeText.value;
|
||||
if (qrcodeChanged) {
|
||||
scanQrcodeImageFailed.value = false;
|
||||
@ -572,7 +586,9 @@ export default defineComponent({
|
||||
}
|
||||
if (event.result) {
|
||||
void applyScanResult(event.result, {
|
||||
reloadQrcode: event.step === 'qrcode-ready',
|
||||
reloadQrcode: ['new-device-qrcode-ready', 'qrcode-ready'].includes(
|
||||
event.step,
|
||||
),
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
@ -587,9 +603,12 @@ export default defineComponent({
|
||||
captchaUrl: undefined,
|
||||
containerId: undefined,
|
||||
containerName: undefined,
|
||||
deviceVerifyUrl: undefined,
|
||||
errorMessage: undefined,
|
||||
expiresAt: undefined,
|
||||
mode,
|
||||
newDeviceQrcode: undefined,
|
||||
newDeviceStatus: undefined,
|
||||
selfId: undefined,
|
||||
sessionId: undefined,
|
||||
status: 'idle',
|
||||
@ -621,6 +640,12 @@ export default defineComponent({
|
||||
return 'info';
|
||||
}
|
||||
|
||||
function getNewDeviceAlertType(status?: NapcatLoginNewDeviceStatus) {
|
||||
if (status === 'failed') return 'error';
|
||||
if (status === 'verified') return 'success';
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
function getScanMessage() {
|
||||
if (scanState.status === 'success') return '扫码登录成功';
|
||||
if (scanState.status === 'error') {
|
||||
@ -1021,7 +1046,11 @@ export default defineComponent({
|
||||
关闭
|
||||
</AButton>,
|
||||
<AButton
|
||||
disabled={!scanState.sessionId}
|
||||
disabled={
|
||||
!scanState.sessionId ||
|
||||
!!scanState.captchaUrl ||
|
||||
!!scanState.newDeviceStatus
|
||||
}
|
||||
key="refresh"
|
||||
loading={scanLoading.value}
|
||||
onClick={refreshScanQrcode}
|
||||
@ -1057,7 +1086,32 @@ export default defineComponent({
|
||||
type="info"
|
||||
/>
|
||||
) : null}
|
||||
{scanState.captchaUrl ? (
|
||||
{scanState.newDeviceStatus ? (
|
||||
<Alert
|
||||
description={
|
||||
<Space direction="vertical">
|
||||
<ATypographyText>
|
||||
请使用手机 QQ
|
||||
扫描下方新设备验证二维码,并在手机端确认登录。
|
||||
</ATypographyText>
|
||||
{scanState.deviceVerifyUrl ? (
|
||||
<ATypographyLink
|
||||
href={scanState.deviceVerifyUrl}
|
||||
target="_blank"
|
||||
>
|
||||
打开新设备验证链接
|
||||
</ATypographyLink>
|
||||
) : null}
|
||||
</Space>
|
||||
}
|
||||
message={getNapcatNewDeviceStatusMessage(
|
||||
scanState.newDeviceStatus,
|
||||
)}
|
||||
showIcon
|
||||
type={getNewDeviceAlertType(scanState.newDeviceStatus)}
|
||||
/>
|
||||
) : null}
|
||||
{scanState.captchaUrl && !scanState.newDeviceStatus ? (
|
||||
<Alert
|
||||
description={
|
||||
<Space>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user