feat: 完善QQBot账号配置交互
This commit is contained in:
parent
77270c51eb
commit
c30d7d04a6
14
apps/web-antdv-next/src/api/core/dict.ts
Normal file
14
apps/web-antdv-next/src/api/core/dict.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace DictApi {
|
||||||
|
export interface Option {
|
||||||
|
label: string;
|
||||||
|
value: number | string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDictByKey(dictKey: string) {
|
||||||
|
return requestClient.get<DictApi.Option[]>('/dict/getDictByKey', {
|
||||||
|
params: { dictKey },
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
export * from './auth';
|
export * from './auth';
|
||||||
|
export * from './dict';
|
||||||
export * from './menu';
|
export * from './menu';
|
||||||
export * from './timezone';
|
export * from './timezone';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
|
|||||||
@ -18,6 +18,8 @@ const SUPPORTED_ADMIN_MENU_NAMES = new Set([
|
|||||||
'BlogTagEdit',
|
'BlogTagEdit',
|
||||||
'QqBot',
|
'QqBot',
|
||||||
'QqBotAccount',
|
'QqBotAccount',
|
||||||
|
'QqBotAccountConfig',
|
||||||
|
'QqBotAccountConfigButton',
|
||||||
'QqBotAccountCreate',
|
'QqBotAccountCreate',
|
||||||
'QqBotAccountDelete',
|
'QqBotAccountDelete',
|
||||||
'QqBotAccountEdit',
|
'QqBotAccountEdit',
|
||||||
@ -75,9 +77,11 @@ function filterSupportedAdminMenus(
|
|||||||
const children = menu.children
|
const children = menu.children
|
||||||
? filterSupportedAdminMenus(menu.children)
|
? filterSupportedAdminMenus(menu.children)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const menuWithoutChildren = { ...menu };
|
||||||
|
delete menuWithoutChildren.children;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...menu,
|
...menuWithoutChildren,
|
||||||
...(children && children.length > 0 ? { children } : {}),
|
...(children && children.length > 0 ? { children } : {}),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import type { Recordable } from '@vben/types';
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace QqbotApi {
|
export namespace QqbotApi {
|
||||||
|
export type PluginTriggerMode = 'command' | 'event';
|
||||||
|
|
||||||
export interface PageResult<T> {
|
export interface PageResult<T> {
|
||||||
list: T[];
|
list: T[];
|
||||||
pageNo?: number;
|
pageNo?: number;
|
||||||
@ -212,6 +214,7 @@ export namespace QqbotApi {
|
|||||||
key: string;
|
key: string;
|
||||||
name: string;
|
name: string;
|
||||||
operationCount: number;
|
operationCount: number;
|
||||||
|
triggerMode: PluginTriggerMode;
|
||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,12 +226,29 @@ export namespace QqbotApi {
|
|||||||
name: string;
|
name: string;
|
||||||
outputSchema?: Recordable<any>;
|
outputSchema?: Recordable<any>;
|
||||||
pluginKey: string;
|
pluginKey: string;
|
||||||
|
triggerMode: PluginTriggerMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginHealth {
|
export interface PluginHealth {
|
||||||
checkedAt: string;
|
checkedAt: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
|
name?: string;
|
||||||
|
pluginKey?: string;
|
||||||
status: 'degraded' | 'healthy' | 'offline';
|
status: 'degraded' | 'healthy' | 'offline';
|
||||||
|
triggerMode?: PluginTriggerMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventPlugin {
|
||||||
|
accountName?: string;
|
||||||
|
bound: boolean;
|
||||||
|
connectStatus?: string;
|
||||||
|
description?: string;
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
remark?: string;
|
||||||
|
selfId: string;
|
||||||
|
triggerType: 'message';
|
||||||
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Query = Recordable<any>;
|
export type Query = Recordable<any>;
|
||||||
@ -265,6 +285,26 @@ export function deleteQqbotAccount(id: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function bindQqbotAccountCommand(selfId: string, commandId: string) {
|
||||||
|
const params = new URLSearchParams({ commandId, selfId });
|
||||||
|
return requestClient.post<boolean>(`/qqbot/account/bind/command?${params}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unbindQqbotAccountCommand(selfId: string, commandId: string) {
|
||||||
|
const params = new URLSearchParams({ commandId, selfId });
|
||||||
|
return requestClient.post<boolean>(`/qqbot/account/unbind/command?${params}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bindQqbotAccountRule(selfId: string, ruleId: string) {
|
||||||
|
const params = new URLSearchParams({ ruleId, selfId });
|
||||||
|
return requestClient.post<boolean>(`/qqbot/account/bind/rule?${params}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unbindQqbotAccountRule(selfId: string, ruleId: string) {
|
||||||
|
const params = new URLSearchParams({ ruleId, selfId });
|
||||||
|
return requestClient.post<boolean>(`/qqbot/account/unbind/rule?${params}`);
|
||||||
|
}
|
||||||
|
|
||||||
export function kickQqbotAccount(selfId: string) {
|
export function kickQqbotAccount(selfId: string) {
|
||||||
return requestClient.post<{ count: number }>(
|
return requestClient.post<{ count: number }>(
|
||||||
`/qqbot/account/kick?selfId=${selfId}`,
|
`/qqbot/account/kick?selfId=${selfId}`,
|
||||||
@ -451,19 +491,47 @@ export function testQqbotCommand(data: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getQqbotPluginList() {
|
export function getQqbotPluginList(triggerMode?: QqbotApi.PluginTriggerMode) {
|
||||||
return requestClient.get<QqbotApi.Plugin[]>('/qqbot/plugin/list');
|
return requestClient.get<QqbotApi.Plugin[]>('/qqbot/plugin/list', {
|
||||||
|
params: { triggerMode },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getQqbotPluginOperationList(pluginKey?: string) {
|
export function getQqbotPluginOperationList(
|
||||||
|
pluginKey?: string,
|
||||||
|
triggerMode?: QqbotApi.PluginTriggerMode,
|
||||||
|
) {
|
||||||
return requestClient.get<QqbotApi.PluginOperation[]>(
|
return requestClient.get<QqbotApi.PluginOperation[]>(
|
||||||
'/qqbot/plugin/operation/list',
|
'/qqbot/plugin/operation/list',
|
||||||
{ params: { pluginKey } },
|
{ params: { pluginKey, triggerMode } },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getQqbotPluginHealth(pluginKey?: string) {
|
export function getQqbotPluginHealth(
|
||||||
|
pluginKey?: string,
|
||||||
|
triggerMode?: QqbotApi.PluginTriggerMode,
|
||||||
|
) {
|
||||||
return requestClient.get<QqbotApi.PluginHealth[]>('/qqbot/plugin/health', {
|
return requestClient.get<QqbotApi.PluginHealth[]>('/qqbot/plugin/health', {
|
||||||
params: { pluginKey },
|
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()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -100,9 +100,11 @@ function filterSupportedSystemMenus(
|
|||||||
const children = menu.children
|
const children = menu.children
|
||||||
? filterSupportedSystemMenus(menu.children)
|
? filterSupportedSystemMenus(menu.children)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const menuWithoutChildren = { ...menu };
|
||||||
|
delete menuWithoutChildren.children;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...menu,
|
...menuWithoutChildren,
|
||||||
...(children && children.length > 0 ? { children } : {}),
|
...(children && children.length > 0 ? { children } : {}),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|||||||
198
apps/web-antdv-next/src/hooks/useDict.ts
Normal file
198
apps/web-antdv-next/src/hooks/useDict.ts
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import type { DictApi } from '#/api/core';
|
||||||
|
|
||||||
|
import { computed, readonly, ref, shallowRef } from 'vue';
|
||||||
|
|
||||||
|
import { getDictByKey } from '#/api/core';
|
||||||
|
|
||||||
|
export interface DictOption<TValue = number | string> {
|
||||||
|
label: string;
|
||||||
|
raw?: DictApi.Option;
|
||||||
|
value: TValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoadDictOptions<TValue = number | string> {
|
||||||
|
fallbackOptions?: Array<DictOption<TValue>>;
|
||||||
|
refresh?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseDictOptions<
|
||||||
|
TValue = number | string,
|
||||||
|
> extends LoadDictOptions<TValue> {
|
||||||
|
immediate?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DICT_CACHE = new Map<string, Array<DictOption>>();
|
||||||
|
const DICT_PENDING = new Map<string, Promise<Array<DictOption>>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dictKey 字典编码,对应后端 admin_dict.dict_code。
|
||||||
|
* @param options 字典加载配置;refresh 为 true 时跳过缓存重新请求。
|
||||||
|
*/
|
||||||
|
export async function loadDictOptions<TValue = number | string>(
|
||||||
|
dictKey: string,
|
||||||
|
options: LoadDictOptions<TValue> = {},
|
||||||
|
): Promise<Array<DictOption<TValue>>> {
|
||||||
|
if (!dictKey) {
|
||||||
|
return normalizeFallbackOptions(options.fallbackOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.refresh && DICT_CACHE.has(dictKey)) {
|
||||||
|
return DICT_CACHE.get(dictKey) as Array<DictOption<TValue>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.refresh && DICT_PENDING.has(dictKey)) {
|
||||||
|
return DICT_PENDING.get(dictKey) as Promise<Array<DictOption<TValue>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pending = getDictByKey(dictKey)
|
||||||
|
.then((list) => {
|
||||||
|
const normalized = normalizeDictOptions<TValue>(list);
|
||||||
|
const nextOptions =
|
||||||
|
normalized.length > 0
|
||||||
|
? normalized
|
||||||
|
: normalizeFallbackOptions(options.fallbackOptions);
|
||||||
|
DICT_CACHE.set(dictKey, nextOptions as Array<DictOption>);
|
||||||
|
return nextOptions;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const fallback = normalizeFallbackOptions(options.fallbackOptions);
|
||||||
|
if (fallback.length > 0) {
|
||||||
|
DICT_CACHE.set(dictKey, fallback as Array<DictOption>);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
DICT_PENDING.delete(dictKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
DICT_PENDING.set(dictKey, pending as Promise<Array<DictOption>>);
|
||||||
|
return pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dictKey 字典编码,对应后端 admin_dict.dict_code。
|
||||||
|
*/
|
||||||
|
export function getCachedDictOptions<TValue = number | string>(
|
||||||
|
dictKey: string,
|
||||||
|
) {
|
||||||
|
return (DICT_CACHE.get(dictKey) || []) as Array<DictOption<TValue>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dictKey 可选字典编码;不传时清空全部前端字典缓存。
|
||||||
|
*/
|
||||||
|
export function clearDictCache(dictKey?: string) {
|
||||||
|
if (dictKey) {
|
||||||
|
DICT_CACHE.delete(dictKey);
|
||||||
|
DICT_PENDING.delete(dictKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DICT_CACHE.clear();
|
||||||
|
DICT_PENDING.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param options 字典选项列表。
|
||||||
|
* @param value 需要翻译的字典值。
|
||||||
|
* @param fallback 未命中字典时展示的兜底文案;默认返回原值字符串。
|
||||||
|
*/
|
||||||
|
export function getDictLabel(
|
||||||
|
options: Array<DictOption>,
|
||||||
|
value: unknown,
|
||||||
|
fallback?: string,
|
||||||
|
) {
|
||||||
|
const valueKey = getDictValueKey(value);
|
||||||
|
const matched = options.find(
|
||||||
|
(item) => getDictValueKey(item.value) === valueKey,
|
||||||
|
);
|
||||||
|
return matched?.label ?? fallback ?? valueKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dictKey 字典编码,对应后端 admin_dict.dict_code。
|
||||||
|
* @param options 组合式字典配置;immediate 默认为 true。
|
||||||
|
*/
|
||||||
|
export function useDict<TValue = number | string>(
|
||||||
|
dictKey: string,
|
||||||
|
options: UseDictOptions<TValue> = {},
|
||||||
|
) {
|
||||||
|
const dictOptions = shallowRef<Array<DictOption<TValue>>>(
|
||||||
|
normalizeFallbackOptions(options.fallbackOptions),
|
||||||
|
);
|
||||||
|
const error = ref<unknown>();
|
||||||
|
const loading = ref(false);
|
||||||
|
const optionMap = computed(() => {
|
||||||
|
const map: Record<string, DictOption<TValue>> = {};
|
||||||
|
for (const item of dictOptions.value) {
|
||||||
|
map[getDictValueKey(item.value)] = item;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function reload(refresh = false) {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = undefined;
|
||||||
|
try {
|
||||||
|
dictOptions.value = await loadDictOptions<TValue>(dictKey, {
|
||||||
|
fallbackOptions: options.fallbackOptions,
|
||||||
|
refresh,
|
||||||
|
});
|
||||||
|
return dictOptions.value;
|
||||||
|
} catch (currentError) {
|
||||||
|
error.value = currentError;
|
||||||
|
dictOptions.value = normalizeFallbackOptions(options.fallbackOptions);
|
||||||
|
return dictOptions.value;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function labelOf(value: unknown, fallback?: string) {
|
||||||
|
const valueKey = getDictValueKey(value);
|
||||||
|
return optionMap.value[valueKey]?.label ?? fallback ?? valueKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.immediate !== false) {
|
||||||
|
void reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: readonly(error),
|
||||||
|
labelOf,
|
||||||
|
loading: readonly(loading),
|
||||||
|
options: dictOptions,
|
||||||
|
reload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list 后端字典接口返回的原始列表。
|
||||||
|
*/
|
||||||
|
function normalizeDictOptions<TValue = number | string>(
|
||||||
|
list: DictApi.Option[] = [],
|
||||||
|
): Array<DictOption<TValue>> {
|
||||||
|
return list
|
||||||
|
.filter((item) => item && item.label && item.value !== undefined)
|
||||||
|
.map((item) => ({
|
||||||
|
label: item.label,
|
||||||
|
raw: item,
|
||||||
|
value: item.value as TValue,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param options 业务传入的兜底字典项。
|
||||||
|
*/
|
||||||
|
function normalizeFallbackOptions<TValue = number | string>(
|
||||||
|
options: Array<DictOption<TValue>> = [],
|
||||||
|
) {
|
||||||
|
return options.map((item) => ({ ...item }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value 字典值;统一转字符串后比较,兼容后端数字/字符串混合返回。
|
||||||
|
*/
|
||||||
|
function getDictValueKey(value: unknown) {
|
||||||
|
return value === undefined || value === null ? '' : String(value);
|
||||||
|
}
|
||||||
@ -29,6 +29,16 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'QqBotAccount',
|
name: 'QqBotAccount',
|
||||||
path: '/qqbot/account',
|
path: '/qqbot/account',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: () => import('#/views/qqbot/account/config'),
|
||||||
|
meta: {
|
||||||
|
activePath: '/qqbot/account',
|
||||||
|
hideInMenu: true,
|
||||||
|
title: '账号功能配置',
|
||||||
|
},
|
||||||
|
name: 'QqBotAccountConfig',
|
||||||
|
path: '/qqbot/account/config',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: () => import('#/views/qqbot/rule/list'),
|
component: () => import('#/views/qqbot/rule/list'),
|
||||||
meta: {
|
meta: {
|
||||||
|
|||||||
@ -0,0 +1,489 @@
|
|||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
|
import type { QqbotApi } from '#/api/qqbot';
|
||||||
|
|
||||||
|
import { computed, defineComponent, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
Popconfirm,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
Tabs,
|
||||||
|
Tag,
|
||||||
|
} from 'antdv-next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
bindQqbotAccountCommand,
|
||||||
|
bindQqbotAccountRule,
|
||||||
|
bindQqbotEventPlugin,
|
||||||
|
getQqbotCommandList,
|
||||||
|
getQqbotEventPluginList,
|
||||||
|
getQqbotRuleList,
|
||||||
|
unbindQqbotAccountCommand,
|
||||||
|
unbindQqbotAccountRule,
|
||||||
|
unbindQqbotEventPlugin,
|
||||||
|
} from '#/api/qqbot';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getOptionLabel,
|
||||||
|
qqbotRuleMatchOptions,
|
||||||
|
qqbotRuleTargetOptions,
|
||||||
|
} from '../../modules/options';
|
||||||
|
|
||||||
|
const AButton = Button as any;
|
||||||
|
const APopconfirm = Popconfirm as any;
|
||||||
|
const ASpace = Space as any;
|
||||||
|
const ATable = Table as any;
|
||||||
|
const ATabs = Tabs as any;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QqBotAccountConfigPanel',
|
||||||
|
props: {
|
||||||
|
account: {
|
||||||
|
default: undefined,
|
||||||
|
type: Object as PropType<QqbotApi.Account | undefined>,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const activeTab = ref('command');
|
||||||
|
const boundCommands = ref<QqbotApi.Command[]>([]);
|
||||||
|
const boundRules = ref<QqbotApi.Rule[]>([]);
|
||||||
|
const commandTemplates = ref<QqbotApi.Command[]>([]);
|
||||||
|
const eventPlugins = ref<QqbotApi.EventPlugin[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const ruleTemplates = ref<QqbotApi.Rule[]>([]);
|
||||||
|
|
||||||
|
const currentSelfId = computed(() => props.account?.selfId || '');
|
||||||
|
const boundCommandIds = computed(
|
||||||
|
() => new Set(boundCommands.value.map((item) => item.id)),
|
||||||
|
);
|
||||||
|
const boundRuleIds = computed(
|
||||||
|
() => new Set(boundRules.value.map((item) => item.id)),
|
||||||
|
);
|
||||||
|
const mergedCommandTemplates = computed(() =>
|
||||||
|
mergeById(commandTemplates.value, boundCommands.value),
|
||||||
|
);
|
||||||
|
const mergedRuleTemplates = computed(() =>
|
||||||
|
mergeById(ruleTemplates.value, boundRules.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
const commandColumns = [
|
||||||
|
{ dataIndex: 'name', key: 'name', title: '命令模板', width: 160 },
|
||||||
|
{ dataIndex: 'code', key: 'code', title: '命令编码', width: 140 },
|
||||||
|
{ dataIndex: 'aliases', key: 'aliases', title: '别名', width: 200 },
|
||||||
|
{ dataIndex: 'pluginKey', key: 'pluginKey', title: '插件', width: 140 },
|
||||||
|
{
|
||||||
|
dataIndex: 'targetType',
|
||||||
|
key: 'targetType',
|
||||||
|
title: '目标范围',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{ dataIndex: 'enabled', key: 'enabled', title: '模板状态', width: 100 },
|
||||||
|
{ dataIndex: 'bound', key: 'bound', title: '绑定状态', width: 100 },
|
||||||
|
{
|
||||||
|
dataIndex: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
key: 'action',
|
||||||
|
title: '操作',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const eventColumns = [
|
||||||
|
{ dataIndex: 'name', key: 'name', title: '插件模板', width: 160 },
|
||||||
|
{ dataIndex: 'key', key: 'key', title: '插件 Key', width: 160 },
|
||||||
|
{
|
||||||
|
dataIndex: 'triggerType',
|
||||||
|
key: 'triggerType',
|
||||||
|
title: '触发类型',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'description',
|
||||||
|
key: 'description',
|
||||||
|
title: '说明',
|
||||||
|
width: 320,
|
||||||
|
},
|
||||||
|
{ dataIndex: 'bound', key: 'bound', title: '绑定状态', width: 100 },
|
||||||
|
{
|
||||||
|
dataIndex: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
key: 'action',
|
||||||
|
title: '操作',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const ruleColumns = [
|
||||||
|
{ dataIndex: 'name', key: 'name', title: '规则模板', width: 160 },
|
||||||
|
{ dataIndex: 'keyword', key: 'keyword', title: '关键词', width: 180 },
|
||||||
|
{
|
||||||
|
dataIndex: 'matchType',
|
||||||
|
key: 'matchType',
|
||||||
|
title: '匹配方式',
|
||||||
|
width: 110,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'targetType',
|
||||||
|
key: 'targetType',
|
||||||
|
title: '目标范围',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'replyContent',
|
||||||
|
key: 'replyContent',
|
||||||
|
title: '回复模板',
|
||||||
|
width: 320,
|
||||||
|
},
|
||||||
|
{ dataIndex: 'enabled', key: 'enabled', title: '模板状态', width: 100 },
|
||||||
|
{ dataIndex: 'bound', key: 'bound', title: '绑定状态', width: 100 },
|
||||||
|
{
|
||||||
|
dataIndex: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
key: 'action',
|
||||||
|
title: '操作',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
watch(
|
||||||
|
currentSelfId,
|
||||||
|
(selfId) => {
|
||||||
|
if (!selfId) {
|
||||||
|
boundCommands.value = [];
|
||||||
|
boundRules.value = [];
|
||||||
|
commandTemplates.value = [];
|
||||||
|
eventPlugins.value = [];
|
||||||
|
ruleTemplates.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void refreshAll();
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
async function refreshAll() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
await Promise.all([
|
||||||
|
refreshCommandTemplates(),
|
||||||
|
refreshEventPlugins(),
|
||||||
|
refreshRuleTemplates(),
|
||||||
|
]);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshCommandTemplates() {
|
||||||
|
const [templateResult, boundResult] = await Promise.all([
|
||||||
|
getQqbotCommandList({ pageNo: 1, pageSize: 500 }),
|
||||||
|
getQqbotCommandList({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 500,
|
||||||
|
selfId: currentSelfId.value,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
commandTemplates.value = templateResult.list || [];
|
||||||
|
boundCommands.value = boundResult.list || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshCommandBindings() {
|
||||||
|
const result = await getQqbotCommandList({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 500,
|
||||||
|
selfId: currentSelfId.value,
|
||||||
|
});
|
||||||
|
boundCommands.value = result.list || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshEventPlugins() {
|
||||||
|
eventPlugins.value = await getQqbotEventPluginList({
|
||||||
|
selfId: currentSelfId.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshRuleTemplates() {
|
||||||
|
const [templateResult, boundResult] = await Promise.all([
|
||||||
|
getQqbotRuleList({ pageNo: 1, pageSize: 500 }),
|
||||||
|
getQqbotRuleList({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 500,
|
||||||
|
selfId: currentSelfId.value,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
ruleTemplates.value = templateResult.list || [];
|
||||||
|
boundRules.value = boundResult.list || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshRuleBindings() {
|
||||||
|
const result = await getQqbotRuleList({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 500,
|
||||||
|
selfId: currentSelfId.value,
|
||||||
|
});
|
||||||
|
boundRules.value = result.list || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCommandBind(row: QqbotApi.Command) {
|
||||||
|
if (!ensureSelfId()) return;
|
||||||
|
await bindQqbotAccountCommand(currentSelfId.value, row.id);
|
||||||
|
message.success('命令已绑定到当前账号');
|
||||||
|
await refreshCommandBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCommandUnbind(row: QqbotApi.Command) {
|
||||||
|
if (!ensureSelfId()) return;
|
||||||
|
await unbindQqbotAccountCommand(currentSelfId.value, row.id);
|
||||||
|
message.success('命令已从当前账号解绑');
|
||||||
|
await refreshCommandBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleEventBind(row: QqbotApi.EventPlugin) {
|
||||||
|
if (!ensureSelfId()) return;
|
||||||
|
await bindQqbotEventPlugin(currentSelfId.value, row.key);
|
||||||
|
message.success('事件插件已绑定到当前账号');
|
||||||
|
await refreshEventPlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleEventUnbind(row: QqbotApi.EventPlugin) {
|
||||||
|
if (!ensureSelfId()) return;
|
||||||
|
await unbindQqbotEventPlugin(currentSelfId.value, row.key);
|
||||||
|
message.success('事件插件已从当前账号解绑');
|
||||||
|
await refreshEventPlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRuleBind(row: QqbotApi.Rule) {
|
||||||
|
if (!ensureSelfId()) return;
|
||||||
|
await bindQqbotAccountRule(currentSelfId.value, row.id);
|
||||||
|
message.success('规则已绑定到当前账号');
|
||||||
|
await refreshRuleBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRuleUnbind(row: QqbotApi.Rule) {
|
||||||
|
if (!ensureSelfId()) return;
|
||||||
|
await unbindQqbotAccountRule(currentSelfId.value, row.id);
|
||||||
|
message.success('规则已从当前账号解绑');
|
||||||
|
await refreshRuleBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureSelfId() {
|
||||||
|
if (currentSelfId.value) return true;
|
||||||
|
message.warning('缺少账号 Self ID,请从账号连接列表进入配置页');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeById<T extends { id: string }>(templates: T[], bound: T[]) {
|
||||||
|
const map = new Map<string, T>();
|
||||||
|
templates.forEach((item) => map.set(item.id, item));
|
||||||
|
bound.forEach((item) => {
|
||||||
|
if (!map.has(item.id)) map.set(item.id, item);
|
||||||
|
});
|
||||||
|
return [...map.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBindAction(options: {
|
||||||
|
bound: boolean;
|
||||||
|
name: string;
|
||||||
|
onBind: () => Promise<void>;
|
||||||
|
onUnbind: () => Promise<void>;
|
||||||
|
}) {
|
||||||
|
if (!options.bound) {
|
||||||
|
return (
|
||||||
|
<AButton onClick={options.onBind} type="link">
|
||||||
|
绑定
|
||||||
|
</AButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<APopconfirm
|
||||||
|
onConfirm={options.onUnbind}
|
||||||
|
title={`确认从当前账号解绑「${options.name}」吗?`}
|
||||||
|
>
|
||||||
|
<AButton danger type="link">
|
||||||
|
解绑
|
||||||
|
</AButton>
|
||||||
|
</APopconfirm>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBoundTag(bound: boolean) {
|
||||||
|
return (
|
||||||
|
<Tag color={bound ? 'success' : 'default'}>
|
||||||
|
{bound ? '已绑定' : '未绑定'}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderEnabledTag(enabled: boolean) {
|
||||||
|
return (
|
||||||
|
<Tag color={enabled ? 'success' : 'default'}>
|
||||||
|
{enabled ? '启用' : '停用'}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCommandTable() {
|
||||||
|
return (
|
||||||
|
<ATable
|
||||||
|
columns={commandColumns}
|
||||||
|
dataSource={mergedCommandTemplates.value}
|
||||||
|
loading={loading.value}
|
||||||
|
pagination={false}
|
||||||
|
rowKey="id"
|
||||||
|
scroll={{ x: 1200, y: 420 }}
|
||||||
|
size="small"
|
||||||
|
v-slots={{
|
||||||
|
bodyCell: ({ column, record }: any) => {
|
||||||
|
const row = record as QqbotApi.Command;
|
||||||
|
const bound = boundCommandIds.value.has(row.id);
|
||||||
|
if (column.key === 'aliases') {
|
||||||
|
return row.aliases?.join(' / ') || '-';
|
||||||
|
}
|
||||||
|
if (column.key === 'targetType') {
|
||||||
|
return getOptionLabel(qqbotRuleTargetOptions, row.targetType);
|
||||||
|
}
|
||||||
|
if (column.key === 'enabled') {
|
||||||
|
return renderEnabledTag(row.enabled);
|
||||||
|
}
|
||||||
|
if (column.key === 'bound') {
|
||||||
|
return renderBoundTag(bound);
|
||||||
|
}
|
||||||
|
if (column.key === 'action') {
|
||||||
|
return (
|
||||||
|
<ASpace>
|
||||||
|
{renderBindAction({
|
||||||
|
bound,
|
||||||
|
name: row.name || row.code,
|
||||||
|
onBind: () => handleCommandBind(row),
|
||||||
|
onUnbind: () => handleCommandUnbind(row),
|
||||||
|
})}
|
||||||
|
</ASpace>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderEventTable() {
|
||||||
|
return (
|
||||||
|
<ATable
|
||||||
|
columns={eventColumns}
|
||||||
|
dataSource={eventPlugins.value}
|
||||||
|
loading={loading.value}
|
||||||
|
pagination={false}
|
||||||
|
rowKey={(row: QqbotApi.EventPlugin) =>
|
||||||
|
`${currentSelfId.value}:${row.key}`
|
||||||
|
}
|
||||||
|
scroll={{ x: 960, y: 420 }}
|
||||||
|
size="small"
|
||||||
|
v-slots={{
|
||||||
|
bodyCell: ({ column, record }: any) => {
|
||||||
|
const row = record as QqbotApi.EventPlugin;
|
||||||
|
if (column.key === 'triggerType') {
|
||||||
|
return row.triggerType === 'message'
|
||||||
|
? '消息事件'
|
||||||
|
: row.triggerType;
|
||||||
|
}
|
||||||
|
if (column.key === 'bound') {
|
||||||
|
return renderBoundTag(row.bound);
|
||||||
|
}
|
||||||
|
if (column.key === 'action') {
|
||||||
|
return (
|
||||||
|
<ASpace>
|
||||||
|
{renderBindAction({
|
||||||
|
bound: row.bound,
|
||||||
|
name: row.name,
|
||||||
|
onBind: () => handleEventBind(row),
|
||||||
|
onUnbind: () => handleEventUnbind(row),
|
||||||
|
})}
|
||||||
|
</ASpace>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRuleTable() {
|
||||||
|
return (
|
||||||
|
<ATable
|
||||||
|
columns={ruleColumns}
|
||||||
|
dataSource={mergedRuleTemplates.value}
|
||||||
|
loading={loading.value}
|
||||||
|
pagination={false}
|
||||||
|
rowKey="id"
|
||||||
|
scroll={{ x: 1200, y: 420 }}
|
||||||
|
size="small"
|
||||||
|
v-slots={{
|
||||||
|
bodyCell: ({ column, record }: any) => {
|
||||||
|
const row = record as QqbotApi.Rule;
|
||||||
|
const bound = boundRuleIds.value.has(row.id);
|
||||||
|
if (column.key === 'matchType') {
|
||||||
|
return getOptionLabel(qqbotRuleMatchOptions, row.matchType);
|
||||||
|
}
|
||||||
|
if (column.key === 'targetType') {
|
||||||
|
return getOptionLabel(qqbotRuleTargetOptions, row.targetType);
|
||||||
|
}
|
||||||
|
if (column.key === 'replyContent') {
|
||||||
|
return (
|
||||||
|
<span class="qqbot-account-config-panel__ellipsis">
|
||||||
|
{row.replyContent || '-'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (column.key === 'enabled') {
|
||||||
|
return renderEnabledTag(row.enabled);
|
||||||
|
}
|
||||||
|
if (column.key === 'bound') {
|
||||||
|
return renderBoundTag(bound);
|
||||||
|
}
|
||||||
|
if (column.key === 'action') {
|
||||||
|
return (
|
||||||
|
<ASpace>
|
||||||
|
{renderBindAction({
|
||||||
|
bound,
|
||||||
|
name: row.name || row.keyword,
|
||||||
|
onBind: () => handleRuleBind(row),
|
||||||
|
onUnbind: () => handleRuleUnbind(row),
|
||||||
|
})}
|
||||||
|
</ASpace>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<div class="qqbot-account-config-panel">
|
||||||
|
<div class="qqbot-account-config-panel__account">
|
||||||
|
<Tag color="processing">Self ID:{currentSelfId.value || '-'}</Tag>
|
||||||
|
{props.account?.name ? <Tag>{props.account.name}</Tag> : null}
|
||||||
|
</div>
|
||||||
|
<ATabs
|
||||||
|
class="qqbot-account-config-panel__tabs"
|
||||||
|
items={[
|
||||||
|
{ key: 'command', label: '在线命令' },
|
||||||
|
{ key: 'event', label: '事件触发' },
|
||||||
|
{ key: 'rule', label: '自动回复规则' },
|
||||||
|
]}
|
||||||
|
v-model:activeKey={activeTab.value}
|
||||||
|
/>
|
||||||
|
<div class="qqbot-account-config-panel__content">
|
||||||
|
{activeTab.value === 'command' ? renderCommandTable() : null}
|
||||||
|
{activeTab.value === 'event' ? renderEventTable() : null}
|
||||||
|
{activeTab.value === 'rule' ? renderRuleTable() : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
83
apps/web-antdv-next/src/views/qqbot/account/config.scss
Normal file
83
apps/web-antdv-next/src/views/qqbot/account/config.scss
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
.qqbot-account-config {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__back {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
padding-inline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__back-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__card {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__card,
|
||||||
|
&__card > .ant-card-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__card > .ant-card-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.qqbot-account-config-panel {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
&__account {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tabs {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__ellipsis {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
126
apps/web-antdv-next/src/views/qqbot/account/config.tsx
Normal file
126
apps/web-antdv-next/src/views/qqbot/account/config.tsx
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import type { QqbotApi } from '#/api/qqbot';
|
||||||
|
|
||||||
|
import { computed, defineComponent, ref, watch } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
import { ArrowLeft } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Alert, Button, Card, Spin, Tag } from 'antdv-next';
|
||||||
|
|
||||||
|
import { getQqbotAccountList } from '#/api/qqbot';
|
||||||
|
|
||||||
|
import AccountConfigPanel from './components/AccountConfigPanel';
|
||||||
|
|
||||||
|
import './config.scss';
|
||||||
|
|
||||||
|
const AButton = Button as any;
|
||||||
|
const ACard = Card as any;
|
||||||
|
const ASpin = Spin as any;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QqBotAccountConfig',
|
||||||
|
setup() {
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const account = ref<QqbotApi.Account>();
|
||||||
|
const errorMessage = ref('');
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const selfId = computed(() => normalizeQueryValue(route.query.selfId));
|
||||||
|
const accountTitle = computed(() => {
|
||||||
|
if (!account.value) return '账号功能配置';
|
||||||
|
return account.value.name
|
||||||
|
? `${account.value.name}(${account.value.selfId})`
|
||||||
|
: account.value.selfId;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
selfId,
|
||||||
|
() => {
|
||||||
|
void loadAccount();
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
async function loadAccount() {
|
||||||
|
const currentSelfId = selfId.value;
|
||||||
|
account.value = undefined;
|
||||||
|
errorMessage.value = '';
|
||||||
|
|
||||||
|
if (!currentSelfId) {
|
||||||
|
errorMessage.value = '缺少账号 Self ID,请从账号连接列表进入配置页。';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const result = await getQqbotAccountList({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
selfId: currentSelfId,
|
||||||
|
});
|
||||||
|
const matched = (result.list || []).find(
|
||||||
|
(item) => item.selfId === currentSelfId,
|
||||||
|
);
|
||||||
|
if (!matched) {
|
||||||
|
errorMessage.value = `未找到账号 ${currentSelfId},请返回账号连接列表确认账号状态。`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
account.value = matched;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeQueryValue(value: unknown) {
|
||||||
|
if (Array.isArray(value)) return `${value[0] || ''}`.trim();
|
||||||
|
return `${value || ''}`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
void router.push({ name: 'QqBotAccount' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Page autoContentHeight>
|
||||||
|
<div class="qqbot-account-config">
|
||||||
|
<div class="qqbot-account-config__header">
|
||||||
|
<AButton
|
||||||
|
class="qqbot-account-config__back"
|
||||||
|
onClick={goBack}
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
<ArrowLeft class="qqbot-account-config__back-icon" />
|
||||||
|
返回账号列表
|
||||||
|
</AButton>
|
||||||
|
<div class="qqbot-account-config__title">
|
||||||
|
<span>{accountTitle.value}</span>
|
||||||
|
{account.value ? (
|
||||||
|
<Tag
|
||||||
|
color={
|
||||||
|
account.value.connectStatus === 'online'
|
||||||
|
? 'success'
|
||||||
|
: 'default'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{account.value.connectStatus === 'online' ? '在线' : '离线'}
|
||||||
|
</Tag>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ACard bordered={false} class="qqbot-account-config__card">
|
||||||
|
<ASpin spinning={loading.value}>
|
||||||
|
{errorMessage.value ? (
|
||||||
|
<Alert message={errorMessage.value} showIcon type="warning" />
|
||||||
|
) : (
|
||||||
|
<AccountConfigPanel account={account.value} />
|
||||||
|
)}
|
||||||
|
</ASpin>
|
||||||
|
</ACard>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -8,6 +8,7 @@ import type {
|
|||||||
} from '#/components/ktTable';
|
} from '#/components/ktTable';
|
||||||
|
|
||||||
import { computed, defineComponent, onBeforeUnmount, reactive, ref } from 'vue';
|
import { computed, defineComponent, onBeforeUnmount, reactive, ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Plus } from '@vben/icons';
|
import { Plus } from '@vben/icons';
|
||||||
@ -38,6 +39,7 @@ export default defineComponent({
|
|||||||
name: 'QqBotAccountList',
|
name: 'QqBotAccountList',
|
||||||
setup() {
|
setup() {
|
||||||
const editingId = ref<string>();
|
const editingId = ref<string>();
|
||||||
|
const router = useRouter();
|
||||||
const scanLoading = ref(false);
|
const scanLoading = ref(false);
|
||||||
const scanQrcodeText = ref('');
|
const scanQrcodeText = ref('');
|
||||||
const scanState = reactive<{
|
const scanState = reactive<{
|
||||||
@ -158,6 +160,12 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
const rowActions: Array<KtTableRowAction<QqbotApi.Account>> = [
|
const rowActions: Array<KtTableRowAction<QqbotApi.Account>> = [
|
||||||
|
{
|
||||||
|
key: 'config',
|
||||||
|
label: '配置',
|
||||||
|
onClick: openConfig,
|
||||||
|
permissionCodes: ['QqBot:Account:Config'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'refreshLogin',
|
key: 'refreshLogin',
|
||||||
label: '更新登录',
|
label: '更新登录',
|
||||||
@ -267,7 +275,6 @@ export default defineComponent({
|
|||||||
void resetAccountForm(values || getAccountFormDefaults());
|
void resetAccountForm(values || getAccountFormDefaults());
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
stopScanPolling();
|
stopScanPolling();
|
||||||
});
|
});
|
||||||
@ -446,6 +453,15 @@ export default defineComponent({
|
|||||||
accountModalApi.setData({ values: getAccountFormDefaults() }).open();
|
accountModalApi.setData({ values: getAccountFormDefaults() }).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openConfig(row: QqbotApi.Account) {
|
||||||
|
void router.push({
|
||||||
|
name: 'QqBotAccountConfig',
|
||||||
|
query: {
|
||||||
|
selfId: row.selfId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function openEdit(row: QqbotApi.Account) {
|
function openEdit(row: QqbotApi.Account) {
|
||||||
editingId.value = row.id;
|
editingId.value = row.id;
|
||||||
accountModalApi
|
accountModalApi
|
||||||
|
|||||||
@ -390,8 +390,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
async function loadPlugins() {
|
async function loadPlugins() {
|
||||||
const [plugins, operations] = await Promise.all([
|
const [plugins, operations] = await Promise.all([
|
||||||
getQqbotPluginList(),
|
getQqbotPluginList('command'),
|
||||||
getQqbotPluginOperationList(),
|
getQqbotPluginOperationList(undefined, 'command'),
|
||||||
]);
|
]);
|
||||||
pluginOptions.value = plugins.map((item) => ({
|
pluginOptions.value = plugins.map((item) => ({
|
||||||
label: `${item.name} (${item.key})`,
|
label: `${item.name} (${item.key})`,
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { TableColumnType } from 'antdv-next';
|
|||||||
|
|
||||||
import type { QqbotApi } from '#/api/qqbot';
|
import type { QqbotApi } from '#/api/qqbot';
|
||||||
import type { KtTableApi, KtTableButton } from '#/components/ktTable';
|
import type { KtTableApi, KtTableButton } from '#/components/ktTable';
|
||||||
|
import type { DictOption } from '#/hooks/useDict';
|
||||||
|
|
||||||
import { defineComponent, onMounted, ref } from 'vue';
|
import { defineComponent, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
@ -15,17 +16,39 @@ import {
|
|||||||
getQqbotPluginOperationList,
|
getQqbotPluginOperationList,
|
||||||
} from '#/api/qqbot';
|
} from '#/api/qqbot';
|
||||||
import { KtTable, useKtTable } from '#/components/ktTable';
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||||
|
import { useDict } from '#/hooks/useDict';
|
||||||
|
|
||||||
const AKtTable = KtTable as any;
|
const AKtTable = KtTable as any;
|
||||||
|
const QQBOT_PLUGIN_TRIGGER_MODE_DICT = 'QQBOT_PLUGIN_TRIGGER_MODE';
|
||||||
|
const qqbotPluginTriggerModeFallback: Array<
|
||||||
|
DictOption<QqbotApi.PluginTriggerMode>
|
||||||
|
> = [
|
||||||
|
{ label: '命令', value: 'command' },
|
||||||
|
{ label: '事件', value: 'event' },
|
||||||
|
];
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'QqBotPluginList',
|
name: 'QqBotPluginList',
|
||||||
setup() {
|
setup() {
|
||||||
const pluginOptions = ref<Array<{ label: string; value: string }>>([]);
|
const pluginOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||||
const pluginMap = ref<Record<string, QqbotApi.Plugin>>({});
|
const pluginMap = ref<Record<string, QqbotApi.Plugin>>({});
|
||||||
|
const {
|
||||||
|
labelOf: getTriggerModeLabel,
|
||||||
|
options: triggerModeOptions,
|
||||||
|
reload: reloadTriggerModeDict,
|
||||||
|
} = useDict<QqbotApi.PluginTriggerMode>(QQBOT_PLUGIN_TRIGGER_MODE_DICT, {
|
||||||
|
fallbackOptions: qqbotPluginTriggerModeFallback,
|
||||||
|
immediate: false,
|
||||||
|
});
|
||||||
|
|
||||||
const columns: Array<TableColumnType<QqbotApi.PluginOperation>> = [
|
const columns: Array<TableColumnType<QqbotApi.PluginOperation>> = [
|
||||||
{ dataIndex: 'pluginKey', key: 'pluginKey', title: '插件', width: 160 },
|
{ dataIndex: 'pluginKey', key: 'pluginKey', title: '插件', width: 160 },
|
||||||
|
{
|
||||||
|
dataIndex: 'triggerMode',
|
||||||
|
key: 'triggerMode',
|
||||||
|
title: '触发方式',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
{ dataIndex: 'key', key: 'key', title: '能力 Key', width: 220 },
|
{ dataIndex: 'key', key: 'key', title: '能力 Key', width: 220 },
|
||||||
{ dataIndex: 'name', key: 'name', title: '能力名称', width: 160 },
|
{ dataIndex: 'name', key: 'name', title: '能力名称', width: 160 },
|
||||||
{
|
{
|
||||||
@ -43,7 +66,7 @@ export default defineComponent({
|
|||||||
];
|
];
|
||||||
const api: KtTableApi<QqbotApi.PluginOperation> = {
|
const api: KtTableApi<QqbotApi.PluginOperation> = {
|
||||||
list: async (params) =>
|
list: async (params) =>
|
||||||
await getQqbotPluginOperationList(params.pluginKey),
|
await getQqbotPluginOperationList(params.pluginKey, params.triggerMode),
|
||||||
};
|
};
|
||||||
const buttons: Array<KtTableButton<QqbotApi.PluginOperation>> = [
|
const buttons: Array<KtTableButton<QqbotApi.PluginOperation>> = [
|
||||||
{
|
{
|
||||||
@ -52,7 +75,10 @@ export default defineComponent({
|
|||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const health = await getQqbotPluginHealth();
|
const health = await getQqbotPluginHealth();
|
||||||
const content = health
|
const content = health
|
||||||
.map((item) => `${item.status}: ${item.message || 'OK'}`)
|
.map(
|
||||||
|
(item) =>
|
||||||
|
`${getTriggerModeLabel(item.triggerMode, '-')} ${item.name || item.pluginKey || ''}: ${item.status}${item.message ? ` ${item.message}` : ''}`,
|
||||||
|
)
|
||||||
.join(';');
|
.join(';');
|
||||||
message.success(content || '插件健康检查完成');
|
message.success(content || '插件健康检查完成');
|
||||||
},
|
},
|
||||||
@ -64,6 +90,15 @@ export default defineComponent({
|
|||||||
columns,
|
columns,
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: [
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: () => ({
|
||||||
|
allowClear: true,
|
||||||
|
options: triggerModeOptions.value,
|
||||||
|
}),
|
||||||
|
fieldName: 'triggerMode',
|
||||||
|
label: '触发方式',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: () => ({
|
componentProps: () => ({
|
||||||
@ -80,18 +115,21 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
void loadPlugins();
|
void loadMetadata();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadPlugins() {
|
async function loadMetadata() {
|
||||||
const plugins = await getQqbotPluginList();
|
const [plugins] = await Promise.all([
|
||||||
|
getQqbotPluginList(),
|
||||||
|
reloadTriggerModeDict(),
|
||||||
|
]);
|
||||||
const nextPluginMap: Record<string, QqbotApi.Plugin> = {};
|
const nextPluginMap: Record<string, QqbotApi.Plugin> = {};
|
||||||
for (const item of plugins) {
|
for (const item of plugins) {
|
||||||
nextPluginMap[item.key] = item;
|
nextPluginMap[item.key] = item;
|
||||||
}
|
}
|
||||||
pluginMap.value = nextPluginMap;
|
pluginMap.value = nextPluginMap;
|
||||||
pluginOptions.value = plugins.map((item) => ({
|
pluginOptions.value = plugins.map((item) => ({
|
||||||
label: `${item.name} (${item.key})`,
|
label: `${item.name} (${item.key} / ${getTriggerModeLabel(item.triggerMode, '-')})`,
|
||||||
value: item.key,
|
value: item.key,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -113,6 +151,13 @@ export default defineComponent({
|
|||||||
row.pluginKey
|
row.pluginKey
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (column.key === 'triggerMode') {
|
||||||
|
return (
|
||||||
|
<Tag color={row.triggerMode === 'event' ? 'warning' : 'blue'}>
|
||||||
|
{getTriggerModeLabel(row.triggerMode, '-')}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
if (column.key === 'cacheTtlMs') {
|
if (column.key === 'cacheTtlMs') {
|
||||||
return row.cacheTtlMs ? `${row.cacheTtlMs} ms` : '-';
|
return row.cacheTtlMs ? `${row.cacheTtlMs} ms` : '-';
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user