From 9f20988fbd8e8753b65b124afc6400819fa0d056 Mon Sep 17 00:00:00 2001 From: sunlei Date: Mon, 15 Jun 2026 12:39:39 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=94=B6=E6=95=9BAdmin=E5=85=A8?= =?UTF-8?q?=E5=9F=9F=E7=AE=A1=E7=90=86=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/api/qqbot/napcat.spec.ts | 14 ++++ .../account/components/AccountConfigPanel.tsx | 6 +- .../src/views/qqbot/command/list.tsx | 6 +- .../src/views/qqbot/modules/actions.spec.tsx | 84 +++++++++++++++++++ .../src/views/qqbot/modules/actions.tsx | 54 ++++++++++++ .../src/views/qqbot/modules/status.spec.ts | 33 ++++++++ .../src/views/qqbot/modules/status.ts | 28 +++++++ .../src/views/qqbot/permission/list.tsx | 6 +- .../src/views/qqbot/plugin/list.tsx | 77 +++++++---------- .../src/views/qqbot/rule/list.tsx | 6 +- 10 files changed, 258 insertions(+), 56 deletions(-) create mode 100644 apps/web-antdv-next/src/views/qqbot/modules/actions.spec.tsx create mode 100644 apps/web-antdv-next/src/views/qqbot/modules/actions.tsx create mode 100644 apps/web-antdv-next/src/views/qqbot/modules/status.spec.ts create mode 100644 apps/web-antdv-next/src/views/qqbot/modules/status.ts diff --git a/apps/web-antdv-next/src/api/qqbot/napcat.spec.ts b/apps/web-antdv-next/src/api/qqbot/napcat.spec.ts index 0064cfe..ce4ca21 100644 --- a/apps/web-antdv-next/src/api/qqbot/napcat.spec.ts +++ b/apps/web-antdv-next/src/api/qqbot/napcat.spec.ts @@ -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', + ]); + }); }); diff --git a/apps/web-antdv-next/src/views/qqbot/account/components/AccountConfigPanel.tsx b/apps/web-antdv-next/src/views/qqbot/account/components/AccountConfigPanel.tsx index ceb0769..0972a1e 100644 --- a/apps/web-antdv-next/src/views/qqbot/account/components/AccountConfigPanel.tsx +++ b/apps/web-antdv-next/src/views/qqbot/account/components/AccountConfigPanel.tsx @@ -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 ( - - {enabled ? '启用' : '停用'} + + {getQqbotStatusLabel(status)} ); }; diff --git a/apps/web-antdv-next/src/views/qqbot/command/list.tsx b/apps/web-antdv-next/src/views/qqbot/command/list.tsx index 5c8c3e6..285dec5 100644 --- a/apps/web-antdv-next/src/views/qqbot/command/list.tsx +++ b/apps/web-antdv-next/src/views/qqbot/command/list.tsx @@ -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 ( - - {row.enabled ? '启用' : '停用'} + + {getQqbotStatusLabel(status)} ); } diff --git a/apps/web-antdv-next/src/views/qqbot/modules/actions.spec.tsx b/apps/web-antdv-next/src/views/qqbot/modules/actions.spec.tsx new file mode 100644 index 0000000..a945790 --- /dev/null +++ b/apps/web-antdv-next/src/views/qqbot/modules/actions.spec.tsx @@ -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(); + }); +}); diff --git a/apps/web-antdv-next/src/views/qqbot/modules/actions.tsx b/apps/web-antdv-next/src/views/qqbot/modules/actions.tsx new file mode 100644 index 0000000..4ac455a --- /dev/null +++ b/apps/web-antdv-next/src/views/qqbot/modules/actions.tsx @@ -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; +} + +export const renderQqbotActions = (actions: QqbotActionItem[]) => { + return ( + + {actions.map((action) => { + const button = ( + + {action.label} + + ); + + if (!action.confirmText) return {button}; + + return ( + + {{ default: () => button }} + + ); + })} + + ); +}; diff --git a/apps/web-antdv-next/src/views/qqbot/modules/status.spec.ts b/apps/web-antdv-next/src/views/qqbot/modules/status.spec.ts new file mode 100644 index 0000000..ba71ac1 --- /dev/null +++ b/apps/web-antdv-next/src/views/qqbot/modules/status.spec.ts @@ -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'); + }); +}); diff --git a/apps/web-antdv-next/src/views/qqbot/modules/status.ts b/apps/web-antdv-next/src/views/qqbot/modules/status.ts new file mode 100644 index 0000000..a436b91 --- /dev/null +++ b/apps/web-antdv-next/src/views/qqbot/modules/status.ts @@ -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'; +} diff --git a/apps/web-antdv-next/src/views/qqbot/permission/list.tsx b/apps/web-antdv-next/src/views/qqbot/permission/list.tsx index 55eb6eb..532bd01 100644 --- a/apps/web-antdv-next/src/views/qqbot/permission/list.tsx +++ b/apps/web-antdv-next/src/views/qqbot/permission/list.tsx @@ -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 ( - - {row.enabled ? '启用' : '停用'} + + {getQqbotStatusLabel(status)} ); } diff --git a/apps/web-antdv-next/src/views/qqbot/plugin/list.tsx b/apps/web-antdv-next/src/views/qqbot/plugin/list.tsx index 619971c..3a62796 100644 --- a/apps/web-antdv-next/src/views/qqbot/plugin/list.tsx +++ b/apps/web-antdv-next/src/views/qqbot/plugin/list.tsx @@ -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 {status || '-'}; + if (!status) return -; + const color = + status === 'uninstalled' ? 'error' : getQqbotStatusColor(status); + return {getQqbotStatusLabel(status)}; }; const renderDrawerContent = () => { @@ -354,9 +342,7 @@ export default defineComponent({ class="border-b border-solid border-gray-100 pb-3" key={item.id} > - - {item.enabled ? '已启用' : '已停用'} - + {renderStatusTag(item.enabled ? 'enabled' : 'disabled')} 插件 {item.pluginId} / 账号 {item.accountId} @@ -382,31 +368,26 @@ export default defineComponent({ 插件 {item.pluginId} / 版本 {item.versionId} - - void updateInstallationStatus(item, 'enable')} - size="small" - > - 启用 - - void updateInstallationStatus(item, 'disable')} - size="small" - > - 禁用 - - - void updateInstallationStatus(item, 'uninstall') - } - size="small" - > - 卸载 - - + {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'), + }, + ])} ))} diff --git a/apps/web-antdv-next/src/views/qqbot/rule/list.tsx b/apps/web-antdv-next/src/views/qqbot/rule/list.tsx index 5719302..bff4a30 100644 --- a/apps/web-antdv-next/src/views/qqbot/rule/list.tsx +++ b/apps/web-antdv-next/src/views/qqbot/rule/list.tsx @@ -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 ( - - {row.enabled ? '启用' : '停用'} + + {getQqbotStatusLabel(status)} ); }