refactor: 收敛Admin全域管理边界
This commit is contained in:
parent
33ebdac389
commit
9f20988fbd
@ -41,4 +41,18 @@ 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',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
qqbotRuleMatchOptions,
|
||||
qqbotRuleTargetOptions,
|
||||
} from '../../modules/options';
|
||||
import { getQqbotStatusColor, getQqbotStatusLabel } from '../../modules/status';
|
||||
|
||||
const AKtTable = KtTable as any;
|
||||
const ASpin = Spin as any;
|
||||
@ -342,9 +343,10 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
const renderEnabledTag = (enabled: boolean) => {
|
||||
const status = enabled ? 'enabled' : 'disabled';
|
||||
return (
|
||||
<Tag color={enabled ? 'success' : 'default'}>
|
||||
{enabled ? '启用' : '停用'}
|
||||
<Tag color={getQqbotStatusColor(status)}>
|
||||
{getQqbotStatusLabel(status)}
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
@ -32,6 +32,7 @@ import {
|
||||
qqbotCommandParserOptions,
|
||||
qqbotRuleTargetOptions,
|
||||
} from '../modules/options';
|
||||
import { getQqbotStatusColor, getQqbotStatusLabel } from '../modules/status';
|
||||
|
||||
const AKtTable = KtTable as any;
|
||||
|
||||
@ -562,9 +563,10 @@ export default defineComponent({
|
||||
bodyCell: ({ column, record }: any) => {
|
||||
const row = record as QqbotApi.Command;
|
||||
if (column.key === 'enabled') {
|
||||
const status = row.enabled ? 'enabled' : 'disabled';
|
||||
return (
|
||||
<Tag color={row.enabled ? 'success' : 'default'}>
|
||||
{row.enabled ? '启用' : '停用'}
|
||||
<Tag color={getQqbotStatusColor(status)}>
|
||||
{getQqbotStatusLabel(status)}
|
||||
</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,
|
||||
qqbotPermissionTargetOptions,
|
||||
} from '../modules/options';
|
||||
import { getQqbotStatusColor, getQqbotStatusLabel } from '../modules/status';
|
||||
|
||||
const AKtTable = KtTable as any;
|
||||
const ASwitch = Switch as any;
|
||||
@ -463,9 +464,10 @@ export default defineComponent({
|
||||
bodyCell: ({ column, record }: any) => {
|
||||
const row = record as QqbotApi.Permission;
|
||||
if (column.key === 'enabled') {
|
||||
const status = row.enabled ? 'enabled' : 'disabled';
|
||||
return (
|
||||
<Tag color={row.enabled ? 'success' : 'default'}>
|
||||
{row.enabled ? '启用' : '停用'}
|
||||
<Tag color={getQqbotStatusColor(status)}>
|
||||
{getQqbotStatusLabel(status)}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import { computed, defineComponent, onMounted, ref } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { Button, Drawer, message, Modal, Space, Tag } from 'antdv-next';
|
||||
import { Drawer, message, Modal, Tag } from 'antdv-next';
|
||||
|
||||
import {
|
||||
getQqbotPluginHealth,
|
||||
@ -30,10 +30,11 @@ import {
|
||||
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||
import { useDict } from '#/hooks/useDict';
|
||||
|
||||
const AButton = Button as any;
|
||||
import { renderQqbotActions } from '../modules/actions';
|
||||
import { getQqbotStatusColor, getQqbotStatusLabel } from '../modules/status';
|
||||
|
||||
const ADrawer = Drawer as any;
|
||||
const AModal = Modal as any;
|
||||
const ASpace = Space as any;
|
||||
const AKtTable = KtTable as any;
|
||||
const QQBOT_PLUGIN_TRIGGER_MODE_DICT = 'QQBOT_PLUGIN_TRIGGER_MODE';
|
||||
const qqbotPluginTriggerModeFallback: Array<
|
||||
@ -301,23 +302,10 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
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>;
|
||||
if (!status) return <Tag color="default">-</Tag>;
|
||||
const color =
|
||||
status === 'uninstalled' ? 'error' : getQqbotStatusColor(status);
|
||||
return <Tag color={color}>{getQqbotStatusLabel(status)}</Tag>;
|
||||
};
|
||||
|
||||
const renderDrawerContent = () => {
|
||||
@ -354,9 +342,7 @@ export default defineComponent({
|
||||
class="border-b border-solid border-gray-100 pb-3"
|
||||
key={item.id}
|
||||
>
|
||||
<Tag color={item.enabled ? 'success' : 'default'}>
|
||||
{item.enabled ? '已启用' : '已停用'}
|
||||
</Tag>
|
||||
{renderStatusTag(item.enabled ? 'enabled' : 'disabled')}
|
||||
<span>
|
||||
插件 {item.pluginId} / 账号 {item.accountId}
|
||||
</span>
|
||||
@ -382,31 +368,26 @@ export default defineComponent({
|
||||
插件 {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>
|
||||
{renderQqbotActions([
|
||||
{
|
||||
disabled: item.status === 'enabled',
|
||||
key: 'enable',
|
||||
label: '启用',
|
||||
onClick: () => updateInstallationStatus(item, 'enable'),
|
||||
},
|
||||
{
|
||||
disabled: item.status === 'disabled',
|
||||
key: 'disable',
|
||||
label: '禁用',
|
||||
onClick: () => updateInstallationStatus(item, 'disable'),
|
||||
},
|
||||
{
|
||||
danger: true,
|
||||
key: 'uninstall',
|
||||
label: '卸载',
|
||||
onClick: () => updateInstallationStatus(item, 'uninstall'),
|
||||
},
|
||||
])}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
qqbotRuleMatchOptions,
|
||||
qqbotRuleTargetOptions,
|
||||
} from '../modules/options';
|
||||
import { getQqbotStatusColor, getQqbotStatusLabel } from '../modules/status';
|
||||
|
||||
const AKtTable = KtTable as any;
|
||||
|
||||
@ -299,9 +300,10 @@ export default defineComponent({
|
||||
bodyCell: ({ column, record }: any) => {
|
||||
const row = record as QqbotApi.Rule;
|
||||
if (column.key === 'enabled') {
|
||||
const status = row.enabled ? 'enabled' : 'disabled';
|
||||
return (
|
||||
<Tag color={row.enabled ? 'success' : 'default'}>
|
||||
{row.enabled ? '启用' : '停用'}
|
||||
<Tag color={getQqbotStatusColor(status)}>
|
||||
{getQqbotStatusLabel(status)}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user