Compare commits
3 Commits
33ebdac389
...
b549e30dd5
| Author | SHA1 | Date | |
|---|---|---|---|
| b549e30dd5 | |||
| c08aa84696 | |||
| 9f20988fbd |
28
apps/web-antdv-next/src/api/qqbot/index.spec.ts
Normal file
28
apps/web-antdv-next/src/api/qqbot/index.spec.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
const readApiFile = (name: string) =>
|
||||||
|
readFileSync(new URL(name, import.meta.url), 'utf8');
|
||||||
|
|
||||||
|
describe('qqbot core API caller boundary', () => {
|
||||||
|
it('keeps plugin platform and NapCat scan routes out of the core caller', () => {
|
||||||
|
const source = readApiFile('index.ts');
|
||||||
|
|
||||||
|
expect(source).not.toContain('/qqbot/plugin');
|
||||||
|
expect(source).not.toContain('/qqbot/plugin-platform');
|
||||||
|
expect(source).not.toContain('/qqbot/account/scan');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps domain-specific caller routes in plugin and napcat callers', () => {
|
||||||
|
expect(readApiFile('plugin.ts')).toEqual(
|
||||||
|
expect.stringContaining('/qqbot/plugin/operation/page'),
|
||||||
|
);
|
||||||
|
expect(readApiFile('plugin.ts')).toEqual(
|
||||||
|
expect.stringContaining('/qqbot/plugin-platform/runtime-events'),
|
||||||
|
);
|
||||||
|
expect(readApiFile('napcat.ts')).toEqual(
|
||||||
|
expect.stringContaining('/qqbot/account/scan/events'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,7 +1,5 @@
|
|||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import type { NapcatLoginNewDeviceStatus } from './napcat';
|
|
||||||
|
|
||||||
import { encryptPassword } from '#/api/core/auth';
|
import { encryptPassword } from '#/api/core/auth';
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
@ -93,39 +91,6 @@ export namespace QqbotApi {
|
|||||||
selfId: string;
|
selfId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AccountScanResult {
|
|
||||||
accountId?: string;
|
|
||||||
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;
|
|
||||||
status: 'error' | 'expired' | 'pending' | 'success';
|
|
||||||
webuiPort?: null | number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AccountScanEvent {
|
|
||||||
createdAt: number;
|
|
||||||
message: string;
|
|
||||||
result?: AccountScanResult;
|
|
||||||
status: 'error' | 'info' | 'processing' | 'success';
|
|
||||||
step: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AccountScanCaptchaBody {
|
|
||||||
randstr: string;
|
|
||||||
sessionId: string;
|
|
||||||
sid?: string;
|
|
||||||
ticket: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Rule {
|
export interface Rule {
|
||||||
cooldownMs: number;
|
cooldownMs: number;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -284,6 +249,13 @@ export namespace QqbotApi {
|
|||||||
triggerMode: PluginTriggerMode;
|
triggerMode: PluginTriggerMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PluginOperationQuery extends Recordable<any> {
|
||||||
|
pageNo?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
pluginKey?: string;
|
||||||
|
triggerMode?: PluginTriggerMode;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PluginHealth {
|
export interface PluginHealth {
|
||||||
checkedAt: string;
|
checkedAt: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
@ -384,62 +356,6 @@ export function kickQqbotAccount(selfId: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startQqbotAccountScanCreate() {
|
|
||||||
return requestClient.post<QqbotApi.AccountScanResult>(
|
|
||||||
'/qqbot/account/scan/create',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function startQqbotAccountScanRefresh(id: string) {
|
|
||||||
return requestClient.post<QqbotApi.AccountScanResult>(
|
|
||||||
`/qqbot/account/scan/refresh?id=${id}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getQqbotAccountScanStatus(sessionId: string) {
|
|
||||||
return requestClient.get<QqbotApi.AccountScanResult>(
|
|
||||||
'/qqbot/account/scan/status',
|
|
||||||
{ params: { sessionId } },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function refreshQqbotAccountScanQrcode(sessionId: string) {
|
|
||||||
return requestClient.post<QqbotApi.AccountScanResult>(
|
|
||||||
`/qqbot/account/scan/qrcode/refresh?sessionId=${sessionId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function submitQqbotAccountScanCaptcha(
|
|
||||||
data: QqbotApi.AccountScanCaptchaBody,
|
|
||||||
) {
|
|
||||||
return requestClient.post<QqbotApi.AccountScanResult>(
|
|
||||||
'/qqbot/account/scan/captcha/submit',
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function cancelQqbotAccountScan(sessionId: string) {
|
|
||||||
return requestClient.post<boolean>(
|
|
||||||
`/qqbot/account/scan/cancel?sessionId=${sessionId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getQqbotAccountScanEventsUrl(sessionId: string) {
|
|
||||||
return buildApiUrl(
|
|
||||||
`/qqbot/account/scan/events?sessionId=${encodeURIComponent(sessionId)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildApiUrl(path: string) {
|
|
||||||
const baseUrl = requestClient.getBaseUrl() || '';
|
|
||||||
if (!baseUrl) return path;
|
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
|
||||||
if (/^https?:\/\//i.test(baseUrl)) {
|
|
||||||
return new URL(path, baseUrl).toString();
|
|
||||||
}
|
|
||||||
return `${baseUrl.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getQqbotRuleList(params: QqbotApi.Query) {
|
export function getQqbotRuleList(params: QqbotApi.Query) {
|
||||||
return requestClient.get<QqbotApi.PageResult<QqbotApi.Rule>>(
|
return requestClient.get<QqbotApi.PageResult<QqbotApi.Rule>>(
|
||||||
'/qqbot/rule/list',
|
'/qqbot/rule/list',
|
||||||
@ -588,48 +504,3 @@ export function testQqbotCommand(data: {
|
|||||||
data,
|
data,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getQqbotPluginList(triggerMode?: QqbotApi.PluginTriggerMode) {
|
|
||||||
return requestClient.get<QqbotApi.Plugin[]>('/qqbot/plugin/list', {
|
|
||||||
params: { triggerMode },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getQqbotPluginOperationList(
|
|
||||||
pluginKey?: string,
|
|
||||||
triggerMode?: QqbotApi.PluginTriggerMode,
|
|
||||||
) {
|
|
||||||
return requestClient.get<QqbotApi.PluginOperation[]>(
|
|
||||||
'/qqbot/plugin/operation/list',
|
|
||||||
{ params: { pluginKey, triggerMode } },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getQqbotPluginHealth(
|
|
||||||
pluginKey?: string,
|
|
||||||
triggerMode?: QqbotApi.PluginTriggerMode,
|
|
||||||
) {
|
|
||||||
return requestClient.get<QqbotApi.PluginHealth[]>('/qqbot/plugin/health', {
|
|
||||||
params: { pluginKey, triggerMode },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getQqbotEventPluginList(params?: { selfId?: string }) {
|
|
||||||
return requestClient.get<QqbotApi.EventPlugin[]>('/qqbot/plugin/event/list', {
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function bindQqbotEventPlugin(selfId: string, pluginKey: string) {
|
|
||||||
const params = new URLSearchParams({ pluginKey, selfId });
|
|
||||||
return requestClient.post<QqbotApi.EventPlugin>(
|
|
||||||
`/qqbot/plugin/event/bind?${params.toString()}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unbindQqbotEventPlugin(selfId: string, pluginKey: string) {
|
|
||||||
const params = new URLSearchParams({ pluginKey, selfId });
|
|
||||||
return requestClient.post<boolean>(
|
|
||||||
`/qqbot/plugin/event/unbind?${params.toString()}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,12 +1,33 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
cancelQqbotAccountScan,
|
||||||
getNapcatNewDeviceStatusMessage,
|
getNapcatNewDeviceStatusMessage,
|
||||||
|
getQqbotAccountScanEventsUrl,
|
||||||
|
getQqbotAccountScanStatus,
|
||||||
NAPCAT_LOGIN_PROGRESS_LABELS,
|
NAPCAT_LOGIN_PROGRESS_LABELS,
|
||||||
|
refreshQqbotAccountScanQrcode,
|
||||||
resolveNapcatLoginDisplayQrcode,
|
resolveNapcatLoginDisplayQrcode,
|
||||||
|
startQqbotAccountScanCreate,
|
||||||
|
startQqbotAccountScanRefresh,
|
||||||
|
submitQqbotAccountScanCaptcha,
|
||||||
} from './napcat';
|
} from './napcat';
|
||||||
|
|
||||||
|
vi.mock('#/api/request', () => ({
|
||||||
|
requestClient: {
|
||||||
|
get: vi.fn(),
|
||||||
|
getBaseUrl: vi.fn(() => '/api'),
|
||||||
|
post: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
describe('napcat login display helpers', () => {
|
describe('napcat login display helpers', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
it('keeps all Chinese progress labels required by the login flow', () => {
|
it('keeps all Chinese progress labels required by the login flow', () => {
|
||||||
expect(NAPCAT_LOGIN_PROGRESS_LABELS).toMatchObject({
|
expect(NAPCAT_LOGIN_PROGRESS_LABELS).toMatchObject({
|
||||||
'captcha-submit': '验证码已提交,等待确认',
|
'captcha-submit': '验证码已提交,等待确认',
|
||||||
@ -41,4 +62,73 @@ describe('napcat login display helpers', () => {
|
|||||||
'新设备二维码已扫码',
|
'新设备二维码已扫码',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps the complete new-device progress key set in caller helpers', () => {
|
||||||
|
expect(
|
||||||
|
Object.keys(NAPCAT_LOGIN_PROGRESS_LABELS)
|
||||||
|
.filter((key) => key.startsWith('new-device-'))
|
||||||
|
.toSorted(),
|
||||||
|
).toEqual([
|
||||||
|
'new-device-confirming',
|
||||||
|
'new-device-qrcode-ready',
|
||||||
|
'new-device-required',
|
||||||
|
'new-device-scanned',
|
||||||
|
'new-device-verified',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('owns create, refresh, status, QR refresh, captcha, cancel, and SSE caller routes', async () => {
|
||||||
|
const scanResult = {
|
||||||
|
mode: 'refresh' as const,
|
||||||
|
sessionId: 'scan-1',
|
||||||
|
status: 'pending' as const,
|
||||||
|
};
|
||||||
|
vi.mocked(requestClient.post).mockResolvedValue(scanResult);
|
||||||
|
vi.mocked(requestClient.get).mockResolvedValue(scanResult);
|
||||||
|
|
||||||
|
await expect(startQqbotAccountScanCreate()).resolves.toBe(scanResult);
|
||||||
|
await expect(startQqbotAccountScanRefresh('account-1')).resolves.toBe(
|
||||||
|
scanResult,
|
||||||
|
);
|
||||||
|
await expect(getQqbotAccountScanStatus('scan-1')).resolves.toBe(scanResult);
|
||||||
|
await expect(refreshQqbotAccountScanQrcode('scan-1')).resolves.toBe(
|
||||||
|
scanResult,
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
submitQqbotAccountScanCaptcha({
|
||||||
|
randstr: '@rand',
|
||||||
|
sessionId: 'scan-1',
|
||||||
|
ticket: 'ticket',
|
||||||
|
}),
|
||||||
|
).resolves.toBe(scanResult);
|
||||||
|
await expect(cancelQqbotAccountScan('scan-1')).resolves.toBe(scanResult);
|
||||||
|
|
||||||
|
expect(requestClient.post).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/account/scan/create',
|
||||||
|
);
|
||||||
|
expect(requestClient.post).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/account/scan/refresh?id=account-1',
|
||||||
|
);
|
||||||
|
expect(requestClient.get).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/account/scan/status',
|
||||||
|
{ params: { sessionId: 'scan-1' } },
|
||||||
|
);
|
||||||
|
expect(requestClient.post).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/account/scan/qrcode/refresh?sessionId=scan-1',
|
||||||
|
);
|
||||||
|
expect(requestClient.post).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/account/scan/captcha/submit',
|
||||||
|
{
|
||||||
|
randstr: '@rand',
|
||||||
|
sessionId: 'scan-1',
|
||||||
|
ticket: 'ticket',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(requestClient.post).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/account/scan/cancel?sessionId=scan-1',
|
||||||
|
);
|
||||||
|
expect(getQqbotAccountScanEventsUrl('scan 1')).toBe(
|
||||||
|
'/api/qqbot/account/scan/events?sessionId=scan%201',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export type NapcatLoginNewDeviceStatus =
|
export type NapcatLoginNewDeviceStatus =
|
||||||
| 'confirming'
|
| 'confirming'
|
||||||
| 'expired'
|
| 'expired'
|
||||||
@ -6,6 +8,41 @@ export type NapcatLoginNewDeviceStatus =
|
|||||||
| 'scanned'
|
| 'scanned'
|
||||||
| 'verified';
|
| 'verified';
|
||||||
|
|
||||||
|
export namespace QqbotNapcatApi {
|
||||||
|
export interface AccountScanResult {
|
||||||
|
accountId?: string;
|
||||||
|
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;
|
||||||
|
status: 'error' | 'expired' | 'pending' | 'success';
|
||||||
|
webuiPort?: null | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccountScanEvent {
|
||||||
|
createdAt: number;
|
||||||
|
message: string;
|
||||||
|
result?: AccountScanResult;
|
||||||
|
status: 'error' | 'info' | 'processing' | 'success';
|
||||||
|
step: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccountScanCaptchaBody {
|
||||||
|
randstr: string;
|
||||||
|
sessionId: string;
|
||||||
|
sid?: string;
|
||||||
|
ticket: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type NapcatLoginDisplayQrcodeSource = {
|
export type NapcatLoginDisplayQrcodeSource = {
|
||||||
captchaUrl?: string;
|
captchaUrl?: string;
|
||||||
newDeviceQrcode?: string;
|
newDeviceQrcode?: string;
|
||||||
@ -48,3 +85,59 @@ export function getNapcatNewDeviceStatusMessage(
|
|||||||
if (status === 'failed') return '新设备验证失败';
|
if (status === 'failed') return '新设备验证失败';
|
||||||
return '新设备二维码待扫码';
|
return '新设备二维码待扫码';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function startQqbotAccountScanCreate() {
|
||||||
|
return requestClient.post<QqbotNapcatApi.AccountScanResult>(
|
||||||
|
'/qqbot/account/scan/create',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startQqbotAccountScanRefresh(id: string) {
|
||||||
|
return requestClient.post<QqbotNapcatApi.AccountScanResult>(
|
||||||
|
`/qqbot/account/scan/refresh?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotAccountScanStatus(sessionId: string) {
|
||||||
|
return requestClient.get<QqbotNapcatApi.AccountScanResult>(
|
||||||
|
'/qqbot/account/scan/status',
|
||||||
|
{ params: { sessionId } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function refreshQqbotAccountScanQrcode(sessionId: string) {
|
||||||
|
return requestClient.post<QqbotNapcatApi.AccountScanResult>(
|
||||||
|
`/qqbot/account/scan/qrcode/refresh?sessionId=${sessionId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function submitQqbotAccountScanCaptcha(
|
||||||
|
data: QqbotNapcatApi.AccountScanCaptchaBody,
|
||||||
|
) {
|
||||||
|
return requestClient.post<QqbotNapcatApi.AccountScanResult>(
|
||||||
|
'/qqbot/account/scan/captcha/submit',
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancelQqbotAccountScan(sessionId: string) {
|
||||||
|
return requestClient.post<boolean>(
|
||||||
|
`/qqbot/account/scan/cancel?sessionId=${sessionId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotAccountScanEventsUrl(sessionId: string) {
|
||||||
|
return buildApiUrl(
|
||||||
|
`/qqbot/account/scan/events?sessionId=${encodeURIComponent(sessionId)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildApiUrl(path: string) {
|
||||||
|
const baseUrl = requestClient.getBaseUrl() || '';
|
||||||
|
if (!baseUrl) return path;
|
||||||
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
|
if (/^https?:\/\//i.test(baseUrl)) {
|
||||||
|
return new URL(path, baseUrl).toString();
|
||||||
|
}
|
||||||
|
return `${baseUrl.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
|
||||||
|
}
|
||||||
|
|||||||
102
apps/web-antdv-next/src/api/qqbot/plugin.spec.ts
Normal file
102
apps/web-antdv-next/src/api/qqbot/plugin.spec.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
enableQqbotPluginInstallation,
|
||||||
|
getQqbotPluginAccountBindings,
|
||||||
|
getQqbotPluginOperationPage,
|
||||||
|
getQqbotPluginPlatformInstallations,
|
||||||
|
getQqbotPluginRuntimeEvents,
|
||||||
|
installLocalQqbotPluginPackage,
|
||||||
|
uploadQqbotPluginPackage,
|
||||||
|
validateQqbotPluginManifest,
|
||||||
|
} from './plugin';
|
||||||
|
|
||||||
|
vi.mock('#/api/request', () => ({
|
||||||
|
requestClient: {
|
||||||
|
get: vi.fn(),
|
||||||
|
getBaseUrl: vi.fn(() => ''),
|
||||||
|
post: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('qqbot plugin API wrappers', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the paged plugin operation endpoint for KtTable', async () => {
|
||||||
|
const pageResult = {
|
||||||
|
list: [],
|
||||||
|
pageNo: 2,
|
||||||
|
pageSize: 1,
|
||||||
|
total: 3,
|
||||||
|
};
|
||||||
|
vi.mocked(requestClient.get).mockResolvedValueOnce(pageResult);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
getQqbotPluginOperationPage({
|
||||||
|
pageNo: 2,
|
||||||
|
pageSize: 1,
|
||||||
|
pluginKey: 'bangdream',
|
||||||
|
triggerMode: 'command',
|
||||||
|
}),
|
||||||
|
).resolves.toBe(pageResult);
|
||||||
|
|
||||||
|
expect(requestClient.get).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/plugin/operation/page',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
pageNo: 2,
|
||||||
|
pageSize: 1,
|
||||||
|
pluginKey: 'bangdream',
|
||||||
|
triggerMode: 'command',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('owns plugin-platform management caller routes', async () => {
|
||||||
|
const manifest = { capabilities: [], key: 'demo' };
|
||||||
|
const packageBody = { manifest, packagePath: '/tmp/demo.zip' };
|
||||||
|
vi.mocked(requestClient.get).mockResolvedValue([]);
|
||||||
|
vi.mocked(requestClient.post).mockResolvedValue({});
|
||||||
|
|
||||||
|
await getQqbotPluginPlatformInstallations();
|
||||||
|
await uploadQqbotPluginPackage(packageBody);
|
||||||
|
await validateQqbotPluginManifest(manifest);
|
||||||
|
await installLocalQqbotPluginPackage(packageBody);
|
||||||
|
await enableQqbotPluginInstallation('installation-1');
|
||||||
|
await getQqbotPluginRuntimeEvents('demo');
|
||||||
|
await getQqbotPluginAccountBindings('demo');
|
||||||
|
|
||||||
|
expect(requestClient.get).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/plugin-platform/installations',
|
||||||
|
);
|
||||||
|
expect(requestClient.post).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/plugin-platform/upload',
|
||||||
|
packageBody,
|
||||||
|
);
|
||||||
|
expect(requestClient.post).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/plugin-platform/validate',
|
||||||
|
{ manifest },
|
||||||
|
);
|
||||||
|
expect(requestClient.post).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/plugin-platform/install-local',
|
||||||
|
packageBody,
|
||||||
|
);
|
||||||
|
expect(requestClient.post).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/plugin-platform/enable',
|
||||||
|
{ id: 'installation-1' },
|
||||||
|
);
|
||||||
|
expect(requestClient.get).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/plugin-platform/runtime-events',
|
||||||
|
{ params: { pluginId: 'demo' } },
|
||||||
|
);
|
||||||
|
expect(requestClient.get).toHaveBeenCalledWith(
|
||||||
|
'/qqbot/plugin-platform/account-bindings',
|
||||||
|
{ params: { pluginId: 'demo' } },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,5 +1,7 @@
|
|||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import type { QqbotApi } from './index';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace QqbotPluginPlatformApi {
|
export namespace QqbotPluginPlatformApi {
|
||||||
@ -66,6 +68,60 @@ export namespace QqbotPluginPlatformApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getQqbotPluginList(triggerMode?: QqbotApi.PluginTriggerMode) {
|
||||||
|
return requestClient.get<QqbotApi.Plugin[]>('/qqbot/plugin/list', {
|
||||||
|
params: { triggerMode },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotPluginOperationList(
|
||||||
|
pluginKey?: string,
|
||||||
|
triggerMode?: QqbotApi.PluginTriggerMode,
|
||||||
|
) {
|
||||||
|
return requestClient.get<QqbotApi.PluginOperation[]>(
|
||||||
|
'/qqbot/plugin/operation/list',
|
||||||
|
{ params: { pluginKey, triggerMode } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotPluginOperationPage(
|
||||||
|
params: QqbotApi.PluginOperationQuery,
|
||||||
|
) {
|
||||||
|
return requestClient.get<QqbotApi.PageResult<QqbotApi.PluginOperation>>(
|
||||||
|
'/qqbot/plugin/operation/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotPluginHealth(
|
||||||
|
pluginKey?: string,
|
||||||
|
triggerMode?: QqbotApi.PluginTriggerMode,
|
||||||
|
) {
|
||||||
|
return requestClient.get<QqbotApi.PluginHealth[]>('/qqbot/plugin/health', {
|
||||||
|
params: { pluginKey, triggerMode },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotEventPluginList(params?: { selfId?: string }) {
|
||||||
|
return requestClient.get<QqbotApi.EventPlugin[]>('/qqbot/plugin/event/list', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bindQqbotEventPlugin(selfId: string, pluginKey: string) {
|
||||||
|
const params = new URLSearchParams({ pluginKey, selfId });
|
||||||
|
return requestClient.post<QqbotApi.EventPlugin>(
|
||||||
|
`/qqbot/plugin/event/bind?${params.toString()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unbindQqbotEventPlugin(selfId: string, pluginKey: string) {
|
||||||
|
const params = new URLSearchParams({ pluginKey, selfId });
|
||||||
|
return requestClient.post<boolean>(
|
||||||
|
`/qqbot/plugin/event/unbind?${params.toString()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function getQqbotPluginPlatformInstallations() {
|
export function getQqbotPluginPlatformInstallations() {
|
||||||
return requestClient.get<QqbotPluginPlatformApi.Installation[]>(
|
return requestClient.get<QqbotPluginPlatformApi.Installation[]>(
|
||||||
'/qqbot/plugin-platform/installations',
|
'/qqbot/plugin-platform/installations',
|
||||||
|
|||||||
@ -12,14 +12,16 @@ import { message, Spin, Tabs, Tag } from 'antdv-next';
|
|||||||
import {
|
import {
|
||||||
bindQqbotAccountCommand,
|
bindQqbotAccountCommand,
|
||||||
bindQqbotAccountRule,
|
bindQqbotAccountRule,
|
||||||
bindQqbotEventPlugin,
|
|
||||||
getQqbotCommandList,
|
getQqbotCommandList,
|
||||||
getQqbotEventPluginList,
|
|
||||||
getQqbotRuleList,
|
getQqbotRuleList,
|
||||||
unbindQqbotAccountCommand,
|
unbindQqbotAccountCommand,
|
||||||
unbindQqbotAccountRule,
|
unbindQqbotAccountRule,
|
||||||
unbindQqbotEventPlugin,
|
|
||||||
} from '#/api/qqbot';
|
} from '#/api/qqbot';
|
||||||
|
import {
|
||||||
|
bindQqbotEventPlugin,
|
||||||
|
getQqbotEventPluginList,
|
||||||
|
unbindQqbotEventPlugin,
|
||||||
|
} from '#/api/qqbot/plugin';
|
||||||
import { KtTable } from '#/components/ktTable';
|
import { KtTable } from '#/components/ktTable';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -27,6 +29,7 @@ import {
|
|||||||
qqbotRuleMatchOptions,
|
qqbotRuleMatchOptions,
|
||||||
qqbotRuleTargetOptions,
|
qqbotRuleTargetOptions,
|
||||||
} from '../../modules/options';
|
} from '../../modules/options';
|
||||||
|
import { getQqbotStatusColor, getQqbotStatusLabel } from '../../modules/status';
|
||||||
|
|
||||||
const AKtTable = KtTable as any;
|
const AKtTable = KtTable as any;
|
||||||
const ASpin = Spin as any;
|
const ASpin = Spin as any;
|
||||||
@ -342,9 +345,10 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderEnabledTag = (enabled: boolean) => {
|
const renderEnabledTag = (enabled: boolean) => {
|
||||||
|
const status = enabled ? 'enabled' : 'disabled';
|
||||||
return (
|
return (
|
||||||
<Tag color={enabled ? 'success' : 'default'}>
|
<Tag color={getQqbotStatusColor(status)}>
|
||||||
{enabled ? '启用' : '停用'}
|
{getQqbotStatusLabel(status)}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import type { TableColumnType } from 'antdv-next';
|
import type { TableColumnType } from 'antdv-next';
|
||||||
|
|
||||||
import type { QqbotApi } from '#/api/qqbot';
|
import type { QqbotApi } from '#/api/qqbot';
|
||||||
import type { NapcatLoginNewDeviceStatus } from '#/api/qqbot/napcat';
|
import type {
|
||||||
|
NapcatLoginNewDeviceStatus,
|
||||||
|
QqbotNapcatApi,
|
||||||
|
} from '#/api/qqbot/napcat';
|
||||||
import type {
|
import type {
|
||||||
KtTableApi,
|
KtTableApi,
|
||||||
KtTableButton,
|
KtTableButton,
|
||||||
@ -27,22 +30,22 @@ import {
|
|||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import {
|
import {
|
||||||
cancelQqbotAccountScan,
|
|
||||||
createQqbotAccount,
|
createQqbotAccount,
|
||||||
deleteQqbotAccount,
|
deleteQqbotAccount,
|
||||||
getQqbotAccountList,
|
getQqbotAccountList,
|
||||||
getQqbotAccountScanEventsUrl,
|
|
||||||
getQqbotAccountScanStatus,
|
|
||||||
kickQqbotAccount,
|
kickQqbotAccount,
|
||||||
refreshQqbotAccountScanQrcode,
|
|
||||||
startQqbotAccountScanCreate,
|
|
||||||
startQqbotAccountScanRefresh,
|
|
||||||
submitQqbotAccountScanCaptcha,
|
|
||||||
updateQqbotAccount,
|
updateQqbotAccount,
|
||||||
} from '#/api/qqbot';
|
} from '#/api/qqbot';
|
||||||
import {
|
import {
|
||||||
|
cancelQqbotAccountScan,
|
||||||
getNapcatNewDeviceStatusMessage,
|
getNapcatNewDeviceStatusMessage,
|
||||||
|
getQqbotAccountScanEventsUrl,
|
||||||
|
getQqbotAccountScanStatus,
|
||||||
|
refreshQqbotAccountScanQrcode,
|
||||||
resolveNapcatLoginDisplayQrcode,
|
resolveNapcatLoginDisplayQrcode,
|
||||||
|
startQqbotAccountScanCreate,
|
||||||
|
startQqbotAccountScanRefresh,
|
||||||
|
submitQqbotAccountScanCaptcha,
|
||||||
} from '#/api/qqbot/napcat';
|
} from '#/api/qqbot/napcat';
|
||||||
import { KtTable, useKtTable } from '#/components/ktTable';
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||||
|
|
||||||
@ -87,7 +90,7 @@ export default defineComponent({
|
|||||||
const scanQrcodeImageFailed = ref(false);
|
const scanQrcodeImageFailed = ref(false);
|
||||||
const scanQrcodeRevision = ref(0);
|
const scanQrcodeRevision = ref(0);
|
||||||
const scanQrcodeText = ref('');
|
const scanQrcodeText = ref('');
|
||||||
const scanEvents = ref<QqbotApi.AccountScanEvent[]>([]);
|
const scanEvents = ref<QqbotNapcatApi.AccountScanEvent[]>([]);
|
||||||
const scanState = reactive<{
|
const scanState = reactive<{
|
||||||
captchaUrl?: string;
|
captchaUrl?: string;
|
||||||
containerId?: string;
|
containerId?: string;
|
||||||
@ -434,7 +437,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function applyScanResult(
|
async function applyScanResult(
|
||||||
result: QqbotApi.AccountScanResult,
|
result: QqbotNapcatApi.AccountScanResult,
|
||||||
options: { reloadQrcode?: boolean } = {},
|
options: { reloadQrcode?: boolean } = {},
|
||||||
) {
|
) {
|
||||||
scanState.captchaUrl = result.captchaUrl;
|
scanState.captchaUrl = result.captchaUrl;
|
||||||
@ -572,7 +575,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
function handleScanEvent(payload: string) {
|
function handleScanEvent(payload: string) {
|
||||||
try {
|
try {
|
||||||
const event = JSON.parse(payload) as QqbotApi.AccountScanEvent;
|
const event = JSON.parse(payload) as QqbotNapcatApi.AccountScanEvent;
|
||||||
const index = scanEvents.value.findIndex(
|
const index = scanEvents.value.findIndex(
|
||||||
(item) => item.step === event.step,
|
(item) => item.step === event.step,
|
||||||
);
|
);
|
||||||
@ -666,7 +669,9 @@ export default defineComponent({
|
|||||||
return '扫码登录请求失败';
|
return '扫码登录请求失败';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getScanStepStatus(status: QqbotApi.AccountScanEvent['status']) {
|
function getScanStepStatus(
|
||||||
|
status: QqbotNapcatApi.AccountScanEvent['status'],
|
||||||
|
) {
|
||||||
if (status === 'error') return 'error';
|
if (status === 'error') return 'error';
|
||||||
if (status === 'processing') return 'process';
|
if (status === 'processing') return 'process';
|
||||||
if (status === 'success') return 'finish';
|
if (status === 'success') return 'finish';
|
||||||
@ -1192,7 +1197,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
function requestTencentCaptcha(
|
function requestTencentCaptcha(
|
||||||
proofWaterUrl: string,
|
proofWaterUrl: string,
|
||||||
): Promise<Omit<QqbotApi.AccountScanCaptchaBody, 'sessionId'>> {
|
): Promise<Omit<QqbotNapcatApi.AccountScanCaptchaBody, 'sessionId'>> {
|
||||||
const params = parseUrlParams(proofWaterUrl);
|
const params = parseUrlParams(proofWaterUrl);
|
||||||
const appid = params.aid || '2081081773';
|
const appid = params.aid || '2081081773';
|
||||||
const sid = params.sid || '';
|
const sid = params.sid || '';
|
||||||
@ -1209,7 +1214,7 @@ function requestTencentCaptcha(
|
|||||||
let settled = false;
|
let settled = false;
|
||||||
const finish = (
|
const finish = (
|
||||||
error?: Error,
|
error?: Error,
|
||||||
value?: Omit<QqbotApi.AccountScanCaptchaBody, 'sessionId'>,
|
value?: Omit<QqbotNapcatApi.AccountScanCaptchaBody, 'sessionId'>,
|
||||||
) => {
|
) => {
|
||||||
if (settled) return;
|
if (settled) return;
|
||||||
settled = true;
|
settled = true;
|
||||||
|
|||||||
@ -19,12 +19,14 @@ import {
|
|||||||
createQqbotCommand,
|
createQqbotCommand,
|
||||||
deleteQqbotCommand,
|
deleteQqbotCommand,
|
||||||
getQqbotCommandList,
|
getQqbotCommandList,
|
||||||
getQqbotPluginList,
|
|
||||||
getQqbotPluginOperationList,
|
|
||||||
testQqbotCommand,
|
testQqbotCommand,
|
||||||
toggleQqbotCommand,
|
toggleQqbotCommand,
|
||||||
updateQqbotCommand,
|
updateQqbotCommand,
|
||||||
} from '#/api/qqbot';
|
} from '#/api/qqbot';
|
||||||
|
import {
|
||||||
|
getQqbotPluginList,
|
||||||
|
getQqbotPluginOperationList,
|
||||||
|
} from '#/api/qqbot/plugin';
|
||||||
import { KtTable, useKtTable } from '#/components/ktTable';
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -32,6 +34,7 @@ import {
|
|||||||
qqbotCommandParserOptions,
|
qqbotCommandParserOptions,
|
||||||
qqbotRuleTargetOptions,
|
qqbotRuleTargetOptions,
|
||||||
} from '../modules/options';
|
} from '../modules/options';
|
||||||
|
import { getQqbotStatusColor, getQqbotStatusLabel } from '../modules/status';
|
||||||
|
|
||||||
const AKtTable = KtTable as any;
|
const AKtTable = KtTable as any;
|
||||||
|
|
||||||
@ -562,9 +565,10 @@ export default defineComponent({
|
|||||||
bodyCell: ({ column, record }: any) => {
|
bodyCell: ({ column, record }: any) => {
|
||||||
const row = record as QqbotApi.Command;
|
const row = record as QqbotApi.Command;
|
||||||
if (column.key === 'enabled') {
|
if (column.key === 'enabled') {
|
||||||
|
const status = row.enabled ? 'enabled' : 'disabled';
|
||||||
return (
|
return (
|
||||||
<Tag color={row.enabled ? 'success' : 'default'}>
|
<Tag color={getQqbotStatusColor(status)}>
|
||||||
{row.enabled ? '启用' : '停用'}
|
{getQqbotStatusLabel(status)}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
84
apps/web-antdv-next/src/views/qqbot/modules/actions.spec.tsx
Normal file
84
apps/web-antdv-next/src/views/qqbot/modules/actions.spec.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/* eslint-disable vue/one-component-per-file */
|
||||||
|
|
||||||
|
import { defineComponent, h, isVNode } from 'vue';
|
||||||
|
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
vi.mock('antdv-next', () => ({
|
||||||
|
Button: defineComponent({
|
||||||
|
name: 'MockButton',
|
||||||
|
setup(_, { slots }) {
|
||||||
|
return () => h('button', slots.default?.());
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Popconfirm: defineComponent({
|
||||||
|
name: 'MockPopconfirm',
|
||||||
|
setup(_, { slots }) {
|
||||||
|
return () => h('span', slots.default?.());
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Space: defineComponent({
|
||||||
|
name: 'MockSpace',
|
||||||
|
setup(_, { slots }) {
|
||||||
|
return () => h('div', slots.default?.());
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('qqbot shared action renderer', () => {
|
||||||
|
it('renders link buttons and preserves action props', async () => {
|
||||||
|
const { renderQqbotActions } = await import('./actions');
|
||||||
|
const TestIcon = defineComponent({
|
||||||
|
name: 'TestIcon',
|
||||||
|
setup: () => () => h('i'),
|
||||||
|
});
|
||||||
|
const onEdit = vi.fn();
|
||||||
|
const onDelete = vi.fn();
|
||||||
|
|
||||||
|
const vnode = renderQqbotActions([
|
||||||
|
{
|
||||||
|
disabled: true,
|
||||||
|
icon: TestIcon,
|
||||||
|
key: 'edit',
|
||||||
|
label: '编辑',
|
||||||
|
loading: true,
|
||||||
|
onClick: onEdit,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
confirmText: '确认删除吗?',
|
||||||
|
danger: true,
|
||||||
|
key: 'delete',
|
||||||
|
label: '删除',
|
||||||
|
onClick: onDelete,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(isVNode(vnode)).toBe(true);
|
||||||
|
const children = vnode.children as {
|
||||||
|
default?: () => unknown[];
|
||||||
|
};
|
||||||
|
const actionNodes = children.default?.().flat() as any[];
|
||||||
|
expect(actionNodes).toHaveLength(2);
|
||||||
|
|
||||||
|
const editButton = actionNodes[0].children[0];
|
||||||
|
expect(editButton.props).toMatchObject({
|
||||||
|
disabled: true,
|
||||||
|
loading: true,
|
||||||
|
size: 'small',
|
||||||
|
type: 'link',
|
||||||
|
});
|
||||||
|
expect(isVNode(editButton.props.icon)).toBe(true);
|
||||||
|
expect(editButton.props.onClick).toBe(onEdit);
|
||||||
|
|
||||||
|
const deleteConfirm = actionNodes[1];
|
||||||
|
expect(deleteConfirm.props.title).toBe('确认删除吗?');
|
||||||
|
expect(deleteConfirm.props.onConfirm).toBe(onDelete);
|
||||||
|
|
||||||
|
const confirmSlots = deleteConfirm.children as {
|
||||||
|
default?: () => unknown;
|
||||||
|
};
|
||||||
|
const deleteButton = confirmSlots.default?.() as any;
|
||||||
|
expect(deleteButton.props.danger).toBe(true);
|
||||||
|
expect(deleteButton.props.onClick).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
54
apps/web-antdv-next/src/views/qqbot/modules/actions.tsx
Normal file
54
apps/web-antdv-next/src/views/qqbot/modules/actions.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { Button, Popconfirm, Space } from 'antdv-next';
|
||||||
|
|
||||||
|
const AButton = Button as any;
|
||||||
|
const APopconfirm = Popconfirm as any;
|
||||||
|
const ASpace = Space as any;
|
||||||
|
|
||||||
|
export interface QqbotActionItem {
|
||||||
|
confirmText?: string;
|
||||||
|
danger?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
icon?: Component;
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
loading?: boolean;
|
||||||
|
onClick: () => Promise<void> | void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const renderQqbotActions = (actions: QqbotActionItem[]) => {
|
||||||
|
return (
|
||||||
|
<ASpace size="small">
|
||||||
|
{actions.map((action) => {
|
||||||
|
const button = (
|
||||||
|
<AButton
|
||||||
|
danger={action.danger}
|
||||||
|
disabled={action.disabled}
|
||||||
|
icon={action.icon ? h(action.icon) : undefined}
|
||||||
|
loading={action.loading}
|
||||||
|
onClick={action.confirmText ? undefined : action.onClick}
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
>
|
||||||
|
{action.label}
|
||||||
|
</AButton>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!action.confirmText) return <span key={action.key}>{button}</span>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<APopconfirm
|
||||||
|
key={action.key}
|
||||||
|
onConfirm={action.onClick}
|
||||||
|
title={action.confirmText}
|
||||||
|
>
|
||||||
|
{{ default: () => button }}
|
||||||
|
</APopconfirm>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ASpace>
|
||||||
|
);
|
||||||
|
};
|
||||||
33
apps/web-antdv-next/src/views/qqbot/modules/status.spec.ts
Normal file
33
apps/web-antdv-next/src/views/qqbot/modules/status.spec.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getQqbotStatusColor,
|
||||||
|
getQqbotStatusLabel,
|
||||||
|
qqbotStatusLabels,
|
||||||
|
} from './status';
|
||||||
|
|
||||||
|
describe('qqbot shared status helpers', () => {
|
||||||
|
it('maps common QQBot status keys to Chinese labels', () => {
|
||||||
|
expect(qqbotStatusLabels).toMatchObject({
|
||||||
|
disabled: '已停用',
|
||||||
|
enabled: '已启用',
|
||||||
|
failed: '失败',
|
||||||
|
offline: '离线',
|
||||||
|
online: '在线',
|
||||||
|
pending: '处理中',
|
||||||
|
unknown: '未知',
|
||||||
|
});
|
||||||
|
expect(getQqbotStatusLabel(undefined)).toBe('未知');
|
||||||
|
expect(getQqbotStatusLabel('custom')).toBe('custom');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses stable tag colors for common QQBot status keys', () => {
|
||||||
|
expect(getQqbotStatusColor('online')).toBe('success');
|
||||||
|
expect(getQqbotStatusColor('enabled')).toBe('success');
|
||||||
|
expect(getQqbotStatusColor('offline')).toBe('default');
|
||||||
|
expect(getQqbotStatusColor('disabled')).toBe('default');
|
||||||
|
expect(getQqbotStatusColor('failed')).toBe('error');
|
||||||
|
expect(getQqbotStatusColor('pending')).toBe('processing');
|
||||||
|
expect(getQqbotStatusColor(undefined)).toBe('default');
|
||||||
|
});
|
||||||
|
});
|
||||||
28
apps/web-antdv-next/src/views/qqbot/modules/status.ts
Normal file
28
apps/web-antdv-next/src/views/qqbot/modules/status.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { TagProps } from 'antdv-next';
|
||||||
|
|
||||||
|
export const qqbotStatusLabels = {
|
||||||
|
disabled: '已停用',
|
||||||
|
enabled: '已启用',
|
||||||
|
failed: '失败',
|
||||||
|
offline: '离线',
|
||||||
|
online: '在线',
|
||||||
|
pending: '处理中',
|
||||||
|
unknown: '未知',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type QqbotStatusKey = keyof typeof qqbotStatusLabels;
|
||||||
|
|
||||||
|
export function getQqbotStatusLabel(status: string | undefined): string {
|
||||||
|
if (!status) return qqbotStatusLabels.unknown;
|
||||||
|
return qqbotStatusLabels[status as QqbotStatusKey] ?? status;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotStatusColor(
|
||||||
|
status: string | undefined,
|
||||||
|
): TagProps['color'] {
|
||||||
|
if (status === 'online' || status === 'enabled') return 'success';
|
||||||
|
if (status === 'offline' || status === 'disabled') return 'default';
|
||||||
|
if (status === 'failed') return 'error';
|
||||||
|
if (status === 'pending') return 'processing';
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
@ -29,6 +29,7 @@ import {
|
|||||||
getOptionLabel,
|
getOptionLabel,
|
||||||
qqbotPermissionTargetOptions,
|
qqbotPermissionTargetOptions,
|
||||||
} from '../modules/options';
|
} from '../modules/options';
|
||||||
|
import { getQqbotStatusColor, getQqbotStatusLabel } from '../modules/status';
|
||||||
|
|
||||||
const AKtTable = KtTable as any;
|
const AKtTable = KtTable as any;
|
||||||
const ASwitch = Switch as any;
|
const ASwitch = Switch as any;
|
||||||
@ -463,9 +464,10 @@ export default defineComponent({
|
|||||||
bodyCell: ({ column, record }: any) => {
|
bodyCell: ({ column, record }: any) => {
|
||||||
const row = record as QqbotApi.Permission;
|
const row = record as QqbotApi.Permission;
|
||||||
if (column.key === 'enabled') {
|
if (column.key === 'enabled') {
|
||||||
|
const status = row.enabled ? 'enabled' : 'disabled';
|
||||||
return (
|
return (
|
||||||
<Tag color={row.enabled ? 'success' : 'default'}>
|
<Tag color={getQqbotStatusColor(status)}>
|
||||||
{row.enabled ? '启用' : '停用'}
|
{getQqbotStatusLabel(status)}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import { Modal } from 'antdv-next';
|
||||||
|
|
||||||
|
const AModal = Modal as any;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QqBotPluginManifestModal',
|
||||||
|
props: {
|
||||||
|
loading: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
default: '',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
default: '',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['close', 'submit', 'update:value'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
return () => (
|
||||||
|
<AModal
|
||||||
|
confirmLoading={props.loading}
|
||||||
|
onCancel={() => emit('close')}
|
||||||
|
onOk={() => emit('submit')}
|
||||||
|
open={props.open}
|
||||||
|
title={props.title}
|
||||||
|
width={760}
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
class="w-full resize-y rounded border border-solid border-gray-200 p-3 font-mono text-sm outline-none"
|
||||||
|
onInput={(event) => {
|
||||||
|
emit('update:value', (event.target as HTMLTextAreaElement).value);
|
||||||
|
}}
|
||||||
|
rows={18}
|
||||||
|
value={props.value}
|
||||||
|
/>
|
||||||
|
</AModal>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -0,0 +1,155 @@
|
|||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
|
import type { QqbotPluginPlatformApi } from '#/api/qqbot/plugin';
|
||||||
|
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import { Drawer, Tag } from 'antdv-next';
|
||||||
|
|
||||||
|
import { renderQqbotActions } from '../../modules/actions';
|
||||||
|
import { getQqbotStatusColor, getQqbotStatusLabel } from '../../modules/status';
|
||||||
|
|
||||||
|
const ADrawer = Drawer as any;
|
||||||
|
|
||||||
|
export type PluginPlatformDrawerMode = 'bindings' | 'events' | 'installations';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QqBotPluginPlatformStateDrawer',
|
||||||
|
props: {
|
||||||
|
accountBindings: {
|
||||||
|
default: () => [],
|
||||||
|
type: Array as PropType<QqbotPluginPlatformApi.AccountBinding[]>,
|
||||||
|
},
|
||||||
|
installations: {
|
||||||
|
default: () => [],
|
||||||
|
type: Array as PropType<QqbotPluginPlatformApi.Installation[]>,
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
default: 'installations',
|
||||||
|
type: String as PropType<PluginPlatformDrawerMode>,
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
runtimeEvents: {
|
||||||
|
default: () => [],
|
||||||
|
type: Array as PropType<QqbotPluginPlatformApi.RuntimeEvent[]>,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
default: '',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['close', 'installationAction'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const renderStatusTag = (status?: string) => {
|
||||||
|
if (!status) return <Tag color="default">-</Tag>;
|
||||||
|
const color =
|
||||||
|
status === 'uninstalled' ? 'error' : getQqbotStatusColor(status);
|
||||||
|
return <Tag color={color}>{getQqbotStatusLabel(status)}</Tag>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderEvents = () =>
|
||||||
|
props.runtimeEvents.length > 0 ? (
|
||||||
|
<div class="space-y-3">
|
||||||
|
{props.runtimeEvents.map((item) => (
|
||||||
|
<div
|
||||||
|
class="border-b border-solid border-gray-100 pb-3"
|
||||||
|
key={item.id}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Tag color={item.level === 'error' ? 'error' : 'processing'}>
|
||||||
|
{item.level}
|
||||||
|
</Tag>
|
||||||
|
<span>{item.eventType}</span>
|
||||||
|
</div>
|
||||||
|
<pre class="mt-2 whitespace-pre-wrap text-xs">
|
||||||
|
{JSON.stringify(item.safeSummary || {}, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span>暂无运行事件</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderBindings = () =>
|
||||||
|
props.accountBindings.length > 0 ? (
|
||||||
|
<div class="space-y-3">
|
||||||
|
{props.accountBindings.map((item) => (
|
||||||
|
<div
|
||||||
|
class="border-b border-solid border-gray-100 pb-3"
|
||||||
|
key={item.id}
|
||||||
|
>
|
||||||
|
{renderStatusTag(item.enabled ? 'enabled' : 'disabled')}
|
||||||
|
<span>
|
||||||
|
插件 {item.pluginId} / 账号 {item.accountId}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span>暂无账号绑定</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderInstallations = () =>
|
||||||
|
props.installations.length > 0 ? (
|
||||||
|
<div class="space-y-3">
|
||||||
|
{props.installations.map((item) => (
|
||||||
|
<div
|
||||||
|
class="border-b border-solid border-gray-100 pb-3"
|
||||||
|
key={item.id}
|
||||||
|
>
|
||||||
|
<div class="mb-2 flex items-center gap-2">
|
||||||
|
{renderStatusTag(item.status)}
|
||||||
|
<Tag>{item.runtimeStatus || '-'}</Tag>
|
||||||
|
<span>
|
||||||
|
插件 {item.pluginId} / 版本 {item.versionId}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{renderQqbotActions([
|
||||||
|
{
|
||||||
|
disabled: item.status === 'enabled',
|
||||||
|
key: 'enable',
|
||||||
|
label: '启用',
|
||||||
|
onClick: () => emit('installationAction', item, 'enable'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disabled: item.status === 'disabled',
|
||||||
|
key: 'disable',
|
||||||
|
label: '禁用',
|
||||||
|
onClick: () => emit('installationAction', item, 'disable'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
danger: true,
|
||||||
|
key: 'uninstall',
|
||||||
|
label: '卸载',
|
||||||
|
onClick: () => emit('installationAction', item, 'uninstall'),
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span>暂无安装记录</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
if (props.mode === 'events') return renderEvents();
|
||||||
|
if (props.mode === 'bindings') return renderBindings();
|
||||||
|
return renderInstallations();
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<ADrawer
|
||||||
|
onClose={() => emit('close')}
|
||||||
|
open={props.open}
|
||||||
|
size="large"
|
||||||
|
title={props.title}
|
||||||
|
>
|
||||||
|
{renderContent()}
|
||||||
|
</ADrawer>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,5 +1,7 @@
|
|||||||
import type { TableColumnType } from 'antdv-next';
|
import type { TableColumnType } from 'antdv-next';
|
||||||
|
|
||||||
|
import type { PluginPlatformDrawerMode } from './components/PluginPlatformStateDrawer';
|
||||||
|
|
||||||
import type { QqbotApi } from '#/api/qqbot';
|
import type { QqbotApi } from '#/api/qqbot';
|
||||||
import type { QqbotPluginPlatformApi } from '#/api/qqbot/plugin';
|
import type { QqbotPluginPlatformApi } from '#/api/qqbot/plugin';
|
||||||
import type { KtTableApi, KtTableButton } from '#/components/ktTable';
|
import type { KtTableApi, KtTableButton } from '#/components/ktTable';
|
||||||
@ -9,17 +11,15 @@ import { computed, defineComponent, onMounted, ref } from 'vue';
|
|||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Button, Drawer, message, Modal, Space, Tag } from 'antdv-next';
|
import { message, Tag } from 'antdv-next';
|
||||||
|
|
||||||
import {
|
|
||||||
getQqbotPluginHealth,
|
|
||||||
getQqbotPluginList,
|
|
||||||
getQqbotPluginOperationList,
|
|
||||||
} from '#/api/qqbot';
|
|
||||||
import {
|
import {
|
||||||
disableQqbotPluginInstallation,
|
disableQqbotPluginInstallation,
|
||||||
enableQqbotPluginInstallation,
|
enableQqbotPluginInstallation,
|
||||||
getQqbotPluginAccountBindings,
|
getQqbotPluginAccountBindings,
|
||||||
|
getQqbotPluginHealth,
|
||||||
|
getQqbotPluginList,
|
||||||
|
getQqbotPluginOperationPage,
|
||||||
getQqbotPluginPlatformInstallations,
|
getQqbotPluginPlatformInstallations,
|
||||||
getQqbotPluginRuntimeEvents,
|
getQqbotPluginRuntimeEvents,
|
||||||
installLocalQqbotPluginPackage,
|
installLocalQqbotPluginPackage,
|
||||||
@ -30,10 +30,9 @@ import {
|
|||||||
import { KtTable, useKtTable } from '#/components/ktTable';
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||||
import { useDict } from '#/hooks/useDict';
|
import { useDict } from '#/hooks/useDict';
|
||||||
|
|
||||||
const AButton = Button as any;
|
import PluginManifestModal from './components/PluginManifestModal';
|
||||||
const ADrawer = Drawer as any;
|
import PluginPlatformStateDrawer from './components/PluginPlatformStateDrawer';
|
||||||
const AModal = Modal as any;
|
|
||||||
const ASpace = Space as any;
|
|
||||||
const AKtTable = KtTable as any;
|
const AKtTable = KtTable as any;
|
||||||
const QQBOT_PLUGIN_TRIGGER_MODE_DICT = 'QQBOT_PLUGIN_TRIGGER_MODE';
|
const QQBOT_PLUGIN_TRIGGER_MODE_DICT = 'QQBOT_PLUGIN_TRIGGER_MODE';
|
||||||
const qqbotPluginTriggerModeFallback: Array<
|
const qqbotPluginTriggerModeFallback: Array<
|
||||||
@ -73,9 +72,7 @@ export default defineComponent({
|
|||||||
name: 'QqBotPluginList',
|
name: 'QqBotPluginList',
|
||||||
setup() {
|
setup() {
|
||||||
const accountBindings = ref<QqbotPluginPlatformApi.AccountBinding[]>([]);
|
const accountBindings = ref<QqbotPluginPlatformApi.AccountBinding[]>([]);
|
||||||
const drawerMode = ref<'bindings' | 'events' | 'installations'>(
|
const drawerMode = ref<PluginPlatformDrawerMode>('installations');
|
||||||
'installations',
|
|
||||||
);
|
|
||||||
const drawerOpen = ref(false);
|
const drawerOpen = ref(false);
|
||||||
const installations = ref<QqbotPluginPlatformApi.Installation[]>([]);
|
const installations = ref<QqbotPluginPlatformApi.Installation[]>([]);
|
||||||
const manifestMode = ref<'install' | 'upload' | 'validate'>('validate');
|
const manifestMode = ref<'install' | 'upload' | 'validate'>('validate');
|
||||||
@ -128,8 +125,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
const api: KtTableApi<QqbotApi.PluginOperation> = {
|
const api: KtTableApi<QqbotApi.PluginOperation> = {
|
||||||
list: async (params) =>
|
list: async (params) => await getQqbotPluginOperationPage(params),
|
||||||
await getQqbotPluginOperationList(params.pluginKey, params.triggerMode),
|
|
||||||
};
|
};
|
||||||
const buttons: Array<KtTableButton<QqbotApi.PluginOperation>> = [
|
const buttons: Array<KtTableButton<QqbotApi.PluginOperation>> = [
|
||||||
{
|
{
|
||||||
@ -300,121 +296,6 @@ export default defineComponent({
|
|||||||
await loadInstallations(false);
|
await loadInstallations(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderStatusTag = (status?: string) => {
|
|
||||||
let color = 'processing';
|
|
||||||
switch (status) {
|
|
||||||
case 'disabled': {
|
|
||||||
color = 'warning';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'enabled': {
|
|
||||||
color = 'success';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'failed':
|
|
||||||
case 'uninstalled': {
|
|
||||||
color = 'error';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return <Tag color={color}>{status || '-'}</Tag>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderDrawerContent = () => {
|
|
||||||
if (drawerMode.value === 'events') {
|
|
||||||
return runtimeEvents.value.length > 0 ? (
|
|
||||||
<div class="space-y-3">
|
|
||||||
{runtimeEvents.value.map((item) => (
|
|
||||||
<div
|
|
||||||
class="border-b border-solid border-gray-100 pb-3"
|
|
||||||
key={item.id}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<Tag color={item.level === 'error' ? 'error' : 'processing'}>
|
|
||||||
{item.level}
|
|
||||||
</Tag>
|
|
||||||
<span>{item.eventType}</span>
|
|
||||||
</div>
|
|
||||||
<pre class="mt-2 whitespace-pre-wrap text-xs">
|
|
||||||
{JSON.stringify(item.safeSummary || {}, null, 2)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span>暂无运行事件</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawerMode.value === 'bindings') {
|
|
||||||
return accountBindings.value.length > 0 ? (
|
|
||||||
<div class="space-y-3">
|
|
||||||
{accountBindings.value.map((item) => (
|
|
||||||
<div
|
|
||||||
class="border-b border-solid border-gray-100 pb-3"
|
|
||||||
key={item.id}
|
|
||||||
>
|
|
||||||
<Tag color={item.enabled ? 'success' : 'default'}>
|
|
||||||
{item.enabled ? '已启用' : '已停用'}
|
|
||||||
</Tag>
|
|
||||||
<span>
|
|
||||||
插件 {item.pluginId} / 账号 {item.accountId}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span>暂无账号绑定</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return installations.value.length > 0 ? (
|
|
||||||
<div class="space-y-3">
|
|
||||||
{installations.value.map((item) => (
|
|
||||||
<div
|
|
||||||
class="border-b border-solid border-gray-100 pb-3"
|
|
||||||
key={item.id}
|
|
||||||
>
|
|
||||||
<div class="mb-2 flex items-center gap-2">
|
|
||||||
{renderStatusTag(item.status)}
|
|
||||||
<Tag>{item.runtimeStatus || '-'}</Tag>
|
|
||||||
<span>
|
|
||||||
插件 {item.pluginId} / 版本 {item.versionId}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<ASpace>
|
|
||||||
<AButton
|
|
||||||
disabled={item.status === 'enabled'}
|
|
||||||
onClick={() => void updateInstallationStatus(item, 'enable')}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
启用
|
|
||||||
</AButton>
|
|
||||||
<AButton
|
|
||||||
disabled={item.status === 'disabled'}
|
|
||||||
onClick={() => void updateInstallationStatus(item, 'disable')}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
禁用
|
|
||||||
</AButton>
|
|
||||||
<AButton
|
|
||||||
danger
|
|
||||||
onClick={() =>
|
|
||||||
void updateInstallationStatus(item, 'uninstall')
|
|
||||||
}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
卸载
|
|
||||||
</AButton>
|
|
||||||
</ASpace>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span>暂无安装记录</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<Page autoContentHeight>
|
<Page autoContentHeight>
|
||||||
<AKtTable
|
<AKtTable
|
||||||
@ -446,35 +327,34 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<AModal
|
<PluginManifestModal
|
||||||
confirmLoading={platformLoading.value}
|
loading={platformLoading.value}
|
||||||
onCancel={() => {
|
onClose={() => {
|
||||||
manifestModalOpen.value = false;
|
manifestModalOpen.value = false;
|
||||||
}}
|
}}
|
||||||
onOk={() => void submitManifest()}
|
onSubmit={() => void submitManifest()}
|
||||||
|
onUpdate:value={(value: string) => {
|
||||||
|
manifestText.value = value;
|
||||||
|
}}
|
||||||
open={manifestModalOpen.value}
|
open={manifestModalOpen.value}
|
||||||
title={manifestModalTitle.value}
|
title={manifestModalTitle.value}
|
||||||
width={760}
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
class="w-full resize-y rounded border border-solid border-gray-200 p-3 font-mono text-sm outline-none"
|
|
||||||
onInput={(event) => {
|
|
||||||
manifestText.value = (event.target as HTMLTextAreaElement).value;
|
|
||||||
}}
|
|
||||||
rows={18}
|
|
||||||
value={manifestText.value}
|
value={manifestText.value}
|
||||||
/>
|
/>
|
||||||
</AModal>
|
<PluginPlatformStateDrawer
|
||||||
<ADrawer
|
accountBindings={accountBindings.value}
|
||||||
|
installations={installations.value}
|
||||||
|
mode={drawerMode.value}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
drawerOpen.value = false;
|
drawerOpen.value = false;
|
||||||
}}
|
}}
|
||||||
|
onInstallationAction={(
|
||||||
|
row: QqbotPluginPlatformApi.Installation,
|
||||||
|
action: 'disable' | 'enable' | 'uninstall',
|
||||||
|
) => void updateInstallationStatus(row, action)}
|
||||||
open={drawerOpen.value}
|
open={drawerOpen.value}
|
||||||
|
runtimeEvents={runtimeEvents.value}
|
||||||
title={drawerTitle.value}
|
title={drawerTitle.value}
|
||||||
width={760}
|
/>
|
||||||
>
|
|
||||||
{renderDrawerContent()}
|
|
||||||
</ADrawer>
|
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import {
|
|||||||
qqbotRuleMatchOptions,
|
qqbotRuleMatchOptions,
|
||||||
qqbotRuleTargetOptions,
|
qqbotRuleTargetOptions,
|
||||||
} from '../modules/options';
|
} from '../modules/options';
|
||||||
|
import { getQqbotStatusColor, getQqbotStatusLabel } from '../modules/status';
|
||||||
|
|
||||||
const AKtTable = KtTable as any;
|
const AKtTable = KtTable as any;
|
||||||
|
|
||||||
@ -299,9 +300,10 @@ export default defineComponent({
|
|||||||
bodyCell: ({ column, record }: any) => {
|
bodyCell: ({ column, record }: any) => {
|
||||||
const row = record as QqbotApi.Rule;
|
const row = record as QqbotApi.Rule;
|
||||||
if (column.key === 'enabled') {
|
if (column.key === 'enabled') {
|
||||||
|
const status = row.enabled ? 'enabled' : 'disabled';
|
||||||
return (
|
return (
|
||||||
<Tag color={row.enabled ? 'success' : 'default'}>
|
<Tag color={getQqbotStatusColor(status)}>
|
||||||
{row.enabled ? '启用' : '停用'}
|
{getQqbotStatusLabel(status)}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user