feat: 增加QQBot管理页面
This commit is contained in:
parent
14880dfcf1
commit
8644cf8eab
@ -16,6 +16,27 @@ const SUPPORTED_ADMIN_MENU_NAMES = new Set([
|
|||||||
'BlogTagCreate',
|
'BlogTagCreate',
|
||||||
'BlogTagDelete',
|
'BlogTagDelete',
|
||||||
'BlogTagEdit',
|
'BlogTagEdit',
|
||||||
|
'QqBot',
|
||||||
|
'QqBotAccount',
|
||||||
|
'QqBotAccountCreate',
|
||||||
|
'QqBotAccountDelete',
|
||||||
|
'QqBotAccountEdit',
|
||||||
|
'QqBotAccountKick',
|
||||||
|
'QqBotConversation',
|
||||||
|
'QqBotDashboard',
|
||||||
|
'QqBotMessage',
|
||||||
|
'QqBotPermission',
|
||||||
|
'QqBotPermissionCreate',
|
||||||
|
'QqBotPermissionDelete',
|
||||||
|
'QqBotPermissionEdit',
|
||||||
|
'QqBotRule',
|
||||||
|
'QqBotRuleCreate',
|
||||||
|
'QqBotRuleDelete',
|
||||||
|
'QqBotRuleEdit',
|
||||||
|
'QqBotRuleToggle',
|
||||||
|
'QqBotSendGroup',
|
||||||
|
'QqBotSendLog',
|
||||||
|
'QqBotSendPrivate',
|
||||||
'System',
|
'System',
|
||||||
'SystemDept',
|
'SystemDept',
|
||||||
'SystemDeptCreate',
|
'SystemDeptCreate',
|
||||||
|
|||||||
270
apps/web-antdv-next/src/api/qqbot/index.ts
Normal file
270
apps/web-antdv-next/src/api/qqbot/index.ts
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace QqbotApi {
|
||||||
|
export interface PageResult<T> {
|
||||||
|
list: T[];
|
||||||
|
pageNo?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardSummary {
|
||||||
|
accountTotal: number;
|
||||||
|
bus: {
|
||||||
|
connected: boolean;
|
||||||
|
mode: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
conversationTotal: number;
|
||||||
|
enabledRuleTotal: number;
|
||||||
|
messageTotal: number;
|
||||||
|
onlineTotal: number;
|
||||||
|
runtime: {
|
||||||
|
enabled: boolean;
|
||||||
|
path: string;
|
||||||
|
sessions: string[];
|
||||||
|
};
|
||||||
|
sendFailedTotal: number;
|
||||||
|
sendSuccessTotal: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Account {
|
||||||
|
clientRole?: string;
|
||||||
|
connectStatus: 'offline' | 'online';
|
||||||
|
connectionMode: 'reverse-ws';
|
||||||
|
createTime?: string;
|
||||||
|
enabled: boolean;
|
||||||
|
id: string;
|
||||||
|
lastConnectedAt?: string;
|
||||||
|
lastError?: string;
|
||||||
|
lastHeartbeatAt?: string;
|
||||||
|
name: string;
|
||||||
|
remark?: string;
|
||||||
|
selfId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccountBody {
|
||||||
|
accessToken?: string;
|
||||||
|
connectionMode?: 'reverse-ws';
|
||||||
|
enabled?: boolean;
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
remark?: string;
|
||||||
|
selfId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Rule {
|
||||||
|
cooldownMs: number;
|
||||||
|
enabled: boolean;
|
||||||
|
id: string;
|
||||||
|
keyword: string;
|
||||||
|
lastHitAt?: string;
|
||||||
|
matchType: 'equals' | 'keyword' | 'regex';
|
||||||
|
name: string;
|
||||||
|
priority: number;
|
||||||
|
remark?: string;
|
||||||
|
replyContent: string;
|
||||||
|
targetType: 'all' | 'group' | 'private';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RuleBody {
|
||||||
|
cooldownMs?: number;
|
||||||
|
enabled?: boolean;
|
||||||
|
id?: string;
|
||||||
|
keyword: string;
|
||||||
|
matchType: 'equals' | 'keyword' | 'regex';
|
||||||
|
name?: string;
|
||||||
|
priority?: number;
|
||||||
|
remark?: string;
|
||||||
|
replyContent: string;
|
||||||
|
targetType?: 'all' | 'group' | 'private';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Conversation {
|
||||||
|
createTime?: string;
|
||||||
|
id: string;
|
||||||
|
lastMessageText?: string;
|
||||||
|
lastMessageTime?: string;
|
||||||
|
messageCount: number;
|
||||||
|
selfId: string;
|
||||||
|
targetId: string;
|
||||||
|
targetName?: string;
|
||||||
|
targetType: 'group' | 'private';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Message {
|
||||||
|
direction: 'inbound' | 'outbound';
|
||||||
|
eventTime: string;
|
||||||
|
id: string;
|
||||||
|
messageText: string;
|
||||||
|
messageType: 'group' | 'private';
|
||||||
|
senderNickname?: string;
|
||||||
|
selfId: string;
|
||||||
|
targetId: string;
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SendLog {
|
||||||
|
action: string;
|
||||||
|
createTime?: string;
|
||||||
|
errorMessage?: string;
|
||||||
|
id: string;
|
||||||
|
messageText: string;
|
||||||
|
selfId: string;
|
||||||
|
status: 'failed' | 'pending' | 'success';
|
||||||
|
targetId: string;
|
||||||
|
targetType: 'group' | 'private';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Permission {
|
||||||
|
enabled: boolean;
|
||||||
|
id: string;
|
||||||
|
remark?: string;
|
||||||
|
selfId?: string;
|
||||||
|
targetId: string;
|
||||||
|
targetType: 'all' | 'group' | 'private';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PermissionBody {
|
||||||
|
enabled?: boolean;
|
||||||
|
id?: string;
|
||||||
|
remark?: string;
|
||||||
|
selfId?: string;
|
||||||
|
targetId: string;
|
||||||
|
targetType: 'all' | 'group' | 'private';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Query = Recordable<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotDashboardSummary() {
|
||||||
|
return requestClient.get<QqbotApi.DashboardSummary>(
|
||||||
|
'/qqbot/dashboard/summary',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotAccountList(params: QqbotApi.Query) {
|
||||||
|
return requestClient.get<QqbotApi.PageResult<QqbotApi.Account>>(
|
||||||
|
'/qqbot/account/list',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotEnabledAccounts() {
|
||||||
|
return requestClient.get<QqbotApi.Account[]>('/qqbot/account/enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createQqbotAccount(data: QqbotApi.AccountBody) {
|
||||||
|
return requestClient.post<string>('/qqbot/account/save', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateQqbotAccount(data: QqbotApi.AccountBody) {
|
||||||
|
return requestClient.post<boolean>('/qqbot/account/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteQqbotAccount(id: string) {
|
||||||
|
return requestClient.post<boolean>(`/qqbot/account/delete?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function kickQqbotAccount(selfId: string) {
|
||||||
|
return requestClient.post<{ count: number }>(
|
||||||
|
`/qqbot/account/kick?selfId=${selfId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotRuleList(params: QqbotApi.Query) {
|
||||||
|
return requestClient.get<QqbotApi.PageResult<QqbotApi.Rule>>(
|
||||||
|
'/qqbot/rule/list',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createQqbotRule(data: QqbotApi.RuleBody) {
|
||||||
|
return requestClient.post<string>('/qqbot/rule/save', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateQqbotRule(data: QqbotApi.RuleBody) {
|
||||||
|
return requestClient.post<boolean>('/qqbot/rule/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteQqbotRule(id: string) {
|
||||||
|
return requestClient.post<boolean>(`/qqbot/rule/delete?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggleQqbotRule(id: string, enabled: boolean) {
|
||||||
|
return requestClient.post<boolean>(
|
||||||
|
`/qqbot/rule/toggle?id=${id}&enabled=${enabled}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotConversationList(params: QqbotApi.Query) {
|
||||||
|
return requestClient.get<QqbotApi.PageResult<QqbotApi.Conversation>>(
|
||||||
|
'/qqbot/conversation/list',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotMessageList(params: QqbotApi.Query) {
|
||||||
|
return requestClient.get<QqbotApi.PageResult<QqbotApi.Message>>(
|
||||||
|
'/qqbot/message/list',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotSendLogList(params: QqbotApi.Query) {
|
||||||
|
return requestClient.get<QqbotApi.PageResult<QqbotApi.SendLog>>(
|
||||||
|
'/qqbot/send/log/list',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendQqbotPrivate(data: {
|
||||||
|
message: string;
|
||||||
|
selfId?: string;
|
||||||
|
userId: string;
|
||||||
|
}) {
|
||||||
|
return requestClient.post('/qqbot/send/private', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendQqbotGroup(data: {
|
||||||
|
groupId: string;
|
||||||
|
message: string;
|
||||||
|
selfId?: string;
|
||||||
|
}) {
|
||||||
|
return requestClient.post('/qqbot/send/group', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQqbotPermissionList(
|
||||||
|
kind: 'allowlist' | 'blocklist',
|
||||||
|
params: QqbotApi.Query,
|
||||||
|
) {
|
||||||
|
return requestClient.get<QqbotApi.PageResult<QqbotApi.Permission>>(
|
||||||
|
`/qqbot/permission/${kind}`,
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createQqbotPermission(
|
||||||
|
kind: 'allowlist' | 'blocklist',
|
||||||
|
data: QqbotApi.PermissionBody,
|
||||||
|
) {
|
||||||
|
return requestClient.post<string>(`/qqbot/permission/${kind}/save`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateQqbotPermission(
|
||||||
|
kind: 'allowlist' | 'blocklist',
|
||||||
|
data: QqbotApi.PermissionBody,
|
||||||
|
) {
|
||||||
|
return requestClient.post<boolean>(`/qqbot/permission/${kind}/update`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteQqbotPermission(
|
||||||
|
kind: 'allowlist' | 'blocklist',
|
||||||
|
id: string,
|
||||||
|
) {
|
||||||
|
return requestClient.post<boolean>(
|
||||||
|
`/qqbot/permission/${kind}/delete?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -44,6 +44,11 @@ const componentKeys: string[] = Object.keys({
|
|||||||
const path = v.replace('../../views/', '/');
|
const path = v.replace('../../views/', '/');
|
||||||
return path.replace(/\.(tsx|vue)$/, '');
|
return path.replace(/\.(tsx|vue)$/, '');
|
||||||
})
|
})
|
||||||
.filter((path) => path.startsWith('/blog/') || path.startsWith('/system/'));
|
.filter(
|
||||||
|
(path) =>
|
||||||
|
path.startsWith('/blog/') ||
|
||||||
|
path.startsWith('/qqbot/') ||
|
||||||
|
path.startsWith('/system/'),
|
||||||
|
);
|
||||||
|
|
||||||
export { accessRoutes, componentKeys, coreRouteNames, routes };
|
export { accessRoutes, componentKeys, coreRouteNames, routes };
|
||||||
|
|||||||
81
apps/web-antdv-next/src/router/routes/modules/qqbot.ts
Normal file
81
apps/web-antdv-next/src/router/routes/modules/qqbot.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:bot',
|
||||||
|
order: 110,
|
||||||
|
title: 'QQBot 管理',
|
||||||
|
},
|
||||||
|
name: 'QqBot',
|
||||||
|
path: '/qqbot',
|
||||||
|
redirect: '/qqbot/dashboard',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
component: () => import('#/views/qqbot/dashboard/list'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:gauge',
|
||||||
|
title: '工作台',
|
||||||
|
},
|
||||||
|
name: 'QqBotDashboard',
|
||||||
|
path: '/qqbot/dashboard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: () => import('#/views/qqbot/account/list'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:radio-receiver',
|
||||||
|
title: '账号连接',
|
||||||
|
},
|
||||||
|
name: 'QqBotAccount',
|
||||||
|
path: '/qqbot/account',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: () => import('#/views/qqbot/rule/list'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:workflow',
|
||||||
|
title: '自动回复规则',
|
||||||
|
},
|
||||||
|
name: 'QqBotRule',
|
||||||
|
path: '/qqbot/rule',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: () => import('#/views/qqbot/conversation/list'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:messages-square',
|
||||||
|
title: '会话管理',
|
||||||
|
},
|
||||||
|
name: 'QqBotConversation',
|
||||||
|
path: '/qqbot/conversation',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: () => import('#/views/qqbot/message/list'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:message-square-text',
|
||||||
|
title: '消息日志',
|
||||||
|
},
|
||||||
|
name: 'QqBotMessage',
|
||||||
|
path: '/qqbot/message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: () => import('#/views/qqbot/sendLog/list'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:send',
|
||||||
|
title: '发送日志',
|
||||||
|
},
|
||||||
|
name: 'QqBotSendLog',
|
||||||
|
path: '/qqbot/sendLog',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: () => import('#/views/qqbot/permission/list'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:shield-check',
|
||||||
|
title: '权限名单',
|
||||||
|
},
|
||||||
|
name: 'QqBotPermission',
|
||||||
|
path: '/qqbot/permission',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
305
apps/web-antdv-next/src/views/qqbot/account/list.tsx
Normal file
305
apps/web-antdv-next/src/views/qqbot/account/list.tsx
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
import type { TableColumnType } from 'antdv-next';
|
||||||
|
|
||||||
|
import type { QqbotApi } from '#/api/qqbot';
|
||||||
|
import type {
|
||||||
|
KtTableApi,
|
||||||
|
KtTableButton,
|
||||||
|
KtTableRowAction,
|
||||||
|
} from '#/components/ktTable';
|
||||||
|
|
||||||
|
import { computed, defineComponent, reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Form, FormItem, Input, message, Modal, Switch, Tag } from 'antdv-next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createQqbotAccount,
|
||||||
|
deleteQqbotAccount,
|
||||||
|
getQqbotAccountList,
|
||||||
|
kickQqbotAccount,
|
||||||
|
updateQqbotAccount,
|
||||||
|
} from '#/api/qqbot';
|
||||||
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||||
|
|
||||||
|
const AKtTable = KtTable as any;
|
||||||
|
const AInput = Input as any;
|
||||||
|
const AModal = Modal as any;
|
||||||
|
const ASwitch = Switch as any;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QqBotAccountList',
|
||||||
|
setup() {
|
||||||
|
const saving = ref(false);
|
||||||
|
const modalOpen = ref(false);
|
||||||
|
const editingId = ref<string>();
|
||||||
|
const form = reactive<QqbotApi.AccountBody>({
|
||||||
|
accessToken: '',
|
||||||
|
connectionMode: 'reverse-ws',
|
||||||
|
enabled: true,
|
||||||
|
name: '',
|
||||||
|
remark: '',
|
||||||
|
selfId: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns: Array<TableColumnType<QqbotApi.Account>> = [
|
||||||
|
{ dataIndex: 'selfId', key: 'selfId', title: 'Self ID', width: 160 },
|
||||||
|
{ dataIndex: 'name', key: 'name', title: '账号名称', width: 180 },
|
||||||
|
{
|
||||||
|
dataIndex: 'connectStatus',
|
||||||
|
key: 'connectStatus',
|
||||||
|
title: '连接状态',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'clientRole',
|
||||||
|
key: 'clientRole',
|
||||||
|
title: '连接角色',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'lastHeartbeatAt',
|
||||||
|
key: 'lastHeartbeatAt',
|
||||||
|
title: '最后心跳',
|
||||||
|
width: 190,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'lastError',
|
||||||
|
key: 'lastError',
|
||||||
|
title: '错误信息',
|
||||||
|
width: 260,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const api: KtTableApi<QqbotApi.Account> = {
|
||||||
|
list: async (params) => await getQqbotAccountList(params),
|
||||||
|
};
|
||||||
|
const buttons: Array<KtTableButton<QqbotApi.Account>> = [
|
||||||
|
{
|
||||||
|
icon: <Plus class="kt-table__button-icon" />,
|
||||||
|
key: 'create',
|
||||||
|
label: '新建账号',
|
||||||
|
onClick: openCreate,
|
||||||
|
permissionCodes: ['QqBot:Account:Create'],
|
||||||
|
type: 'primary',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const rowActions: Array<KtTableRowAction<QqbotApi.Account>> = [
|
||||||
|
{
|
||||||
|
disabled: (row) => row.connectStatus !== 'online',
|
||||||
|
key: 'kick',
|
||||||
|
label: '断开',
|
||||||
|
onClick: async (row, context) => {
|
||||||
|
await kickQqbotAccount(row.selfId);
|
||||||
|
message.success('连接已断开');
|
||||||
|
await context.reload();
|
||||||
|
},
|
||||||
|
permissionCodes: ['QqBot:Account:Kick'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'edit',
|
||||||
|
label: '编辑',
|
||||||
|
onClick: openEdit,
|
||||||
|
permissionCodes: ['QqBot:Account:Edit'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
confirm: (row) => `确认删除账号「${row.selfId}」吗?`,
|
||||||
|
danger: true,
|
||||||
|
key: 'delete',
|
||||||
|
label: '删除',
|
||||||
|
onClick: async (row, context) => {
|
||||||
|
await deleteQqbotAccount(row.id);
|
||||||
|
message.success('账号删除成功');
|
||||||
|
await context.reload();
|
||||||
|
},
|
||||||
|
permissionCodes: ['QqBot:Account:Delete'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const [registerTable, tableApi] = useKtTable<QqbotApi.Account>({
|
||||||
|
api,
|
||||||
|
buttons,
|
||||||
|
columns,
|
||||||
|
formOptions: {
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { allowClear: true, placeholder: 'Self ID' },
|
||||||
|
fieldName: 'selfId',
|
||||||
|
label: 'Self ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { allowClear: true, placeholder: '账号名称' },
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '账号名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '在线', value: 'online' },
|
||||||
|
{ label: '离线', value: 'offline' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fieldName: 'connectStatus',
|
||||||
|
label: '连接状态',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rowActions,
|
||||||
|
tableTitle: 'QQBot 账号连接',
|
||||||
|
});
|
||||||
|
const modalTitle = computed(() =>
|
||||||
|
editingId.value ? '编辑账号' : '新建账号',
|
||||||
|
);
|
||||||
|
|
||||||
|
function openCreate() {
|
||||||
|
editingId.value = undefined;
|
||||||
|
Object.assign(form, {
|
||||||
|
accessToken: '',
|
||||||
|
connectionMode: 'reverse-ws',
|
||||||
|
enabled: true,
|
||||||
|
name: '',
|
||||||
|
remark: '',
|
||||||
|
selfId: '',
|
||||||
|
});
|
||||||
|
modalOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEdit(row: QqbotApi.Account) {
|
||||||
|
editingId.value = row.id;
|
||||||
|
Object.assign(form, {
|
||||||
|
accessToken: '',
|
||||||
|
connectionMode: row.connectionMode,
|
||||||
|
enabled: row.enabled,
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
remark: row.remark || '',
|
||||||
|
selfId: row.selfId,
|
||||||
|
});
|
||||||
|
modalOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitAccount() {
|
||||||
|
if (!form.selfId.trim()) {
|
||||||
|
message.warning('请填写 Self ID');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
saving.value = true;
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
...form,
|
||||||
|
id: editingId.value,
|
||||||
|
selfId: form.selfId.trim(),
|
||||||
|
};
|
||||||
|
if (!payload.accessToken) delete payload.accessToken;
|
||||||
|
await (editingId.value
|
||||||
|
? updateQqbotAccount(payload)
|
||||||
|
: createQqbotAccount(payload));
|
||||||
|
message.success('账号保存成功');
|
||||||
|
modalOpen.value = false;
|
||||||
|
await tableApi.reload();
|
||||||
|
} finally {
|
||||||
|
saving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Page autoContentHeight>
|
||||||
|
<AKtTable
|
||||||
|
onRegister={registerTable}
|
||||||
|
v-slots={{
|
||||||
|
bodyCell: ({ column, record }: any) => {
|
||||||
|
const row = record as QqbotApi.Account;
|
||||||
|
if (column.key === 'connectStatus') {
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
color={
|
||||||
|
row.connectStatus === 'online' ? 'success' : 'default'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{row.connectStatus === 'online' ? '在线' : '离线'}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AModal
|
||||||
|
confirmLoading={saving.value}
|
||||||
|
onOk={submitAccount}
|
||||||
|
{...{
|
||||||
|
'onUpdate:open': (value: boolean) => {
|
||||||
|
modalOpen.value = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
open={modalOpen.value}
|
||||||
|
title={modalTitle.value}
|
||||||
|
width="620px"
|
||||||
|
>
|
||||||
|
<Form labelCol={{ span: 5 }} model={form} wrapperCol={{ span: 18 }}>
|
||||||
|
<FormItem label="Self ID" required>
|
||||||
|
<AInput
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
form.selfId = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
placeholder="NapCat 当前登录 QQ"
|
||||||
|
value={form.selfId}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="账号名称">
|
||||||
|
<AInput
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
form.name = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
placeholder="便于后台识别"
|
||||||
|
value={form.name}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="Token">
|
||||||
|
<AInput.Password
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
form.accessToken = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
placeholder={
|
||||||
|
editingId.value ? '留空表示不修改' : 'OneBot 反向 WS token'
|
||||||
|
}
|
||||||
|
value={form.accessToken}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="启用">
|
||||||
|
<ASwitch
|
||||||
|
checked={form.enabled}
|
||||||
|
{...{
|
||||||
|
'onUpdate:checked': (value: boolean) => {
|
||||||
|
form.enabled = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="备注">
|
||||||
|
<AInput
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
form.remark = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={form.remark}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</AModal>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
107
apps/web-antdv-next/src/views/qqbot/conversation/list.tsx
Normal file
107
apps/web-antdv-next/src/views/qqbot/conversation/list.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import type { TableColumnType } from 'antdv-next';
|
||||||
|
|
||||||
|
import type { QqbotApi } from '#/api/qqbot';
|
||||||
|
import type { KtTableApi } from '#/components/ktTable';
|
||||||
|
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Tag } from 'antdv-next';
|
||||||
|
|
||||||
|
import { getQqbotConversationList } from '#/api/qqbot';
|
||||||
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||||
|
|
||||||
|
import { getOptionLabel, qqbotMessageTypeOptions } from '../modules/options';
|
||||||
|
|
||||||
|
const AKtTable = KtTable as any;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QqBotConversationList',
|
||||||
|
setup() {
|
||||||
|
const columns: Array<TableColumnType<QqbotApi.Conversation>> = [
|
||||||
|
{ dataIndex: 'selfId', key: 'selfId', title: 'Self ID', width: 150 },
|
||||||
|
{
|
||||||
|
dataIndex: 'targetType',
|
||||||
|
key: 'targetType',
|
||||||
|
title: '会话类型',
|
||||||
|
width: 110,
|
||||||
|
},
|
||||||
|
{ dataIndex: 'targetId', key: 'targetId', title: '目标 ID', width: 160 },
|
||||||
|
{ dataIndex: 'targetName', key: 'targetName', title: '名称', width: 160 },
|
||||||
|
{
|
||||||
|
dataIndex: 'lastMessageText',
|
||||||
|
key: 'lastMessageText',
|
||||||
|
title: '最后消息',
|
||||||
|
width: 360,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'messageCount',
|
||||||
|
key: 'messageCount',
|
||||||
|
title: '消息数',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'lastMessageTime',
|
||||||
|
key: 'lastMessageTime',
|
||||||
|
title: '最后时间',
|
||||||
|
width: 190,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const api: KtTableApi<QqbotApi.Conversation> = {
|
||||||
|
list: async (params) => await getQqbotConversationList(params),
|
||||||
|
};
|
||||||
|
const [registerTable] = useKtTable<QqbotApi.Conversation>({
|
||||||
|
api,
|
||||||
|
columns,
|
||||||
|
formOptions: {
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { allowClear: true, placeholder: 'Self ID' },
|
||||||
|
fieldName: 'selfId',
|
||||||
|
label: 'Self ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: qqbotMessageTypeOptions,
|
||||||
|
},
|
||||||
|
fieldName: 'targetType',
|
||||||
|
label: '会话类型',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { allowClear: true, placeholder: '目标 ID' },
|
||||||
|
fieldName: 'targetId',
|
||||||
|
label: '目标 ID',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rowActions: [],
|
||||||
|
tableTitle: '会话管理',
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Page autoContentHeight>
|
||||||
|
<AKtTable
|
||||||
|
onRegister={registerTable}
|
||||||
|
v-slots={{
|
||||||
|
bodyCell: ({ column, record }: any) => {
|
||||||
|
const row = record as QqbotApi.Conversation;
|
||||||
|
if (column.key === 'targetType') {
|
||||||
|
return (
|
||||||
|
<Tag color={row.targetType === 'group' ? 'blue' : 'green'}>
|
||||||
|
{getOptionLabel(qqbotMessageTypeOptions, row.targetType)}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
129
apps/web-antdv-next/src/views/qqbot/dashboard/list.tsx
Normal file
129
apps/web-antdv-next/src/views/qqbot/dashboard/list.tsx
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import type { DescriptionsItemType } from 'antdv-next';
|
||||||
|
|
||||||
|
import type { QqbotApi } from '#/api/qqbot';
|
||||||
|
|
||||||
|
import { defineComponent, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Card, Col, Descriptions, Row, Statistic, Tag } from 'antdv-next';
|
||||||
|
|
||||||
|
import { getQqbotDashboardSummary } from '#/api/qqbot';
|
||||||
|
|
||||||
|
const ACard = Card as any;
|
||||||
|
const ACol = Col as any;
|
||||||
|
const ADescriptions = Descriptions as any;
|
||||||
|
const ARow = Row as any;
|
||||||
|
const AStatistic = Statistic as any;
|
||||||
|
const ATag = Tag as any;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QqBotDashboardList',
|
||||||
|
setup() {
|
||||||
|
const loading = ref(false);
|
||||||
|
const summary = ref<QqbotApi.DashboardSummary>();
|
||||||
|
|
||||||
|
async function loadSummary() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
summary.value = await getQqbotDashboardSummary();
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadSummary);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const data = summary.value;
|
||||||
|
const runtimeItems: DescriptionsItemType[] = [
|
||||||
|
{
|
||||||
|
content: (
|
||||||
|
<ATag color={data?.runtime.enabled ? 'success' : 'default'}>
|
||||||
|
{data?.runtime.enabled ? '已启用' : '未启用'}
|
||||||
|
</ATag>
|
||||||
|
),
|
||||||
|
key: 'runtime',
|
||||||
|
label: 'QQBot Runtime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: data?.runtime.path || '-',
|
||||||
|
key: 'reverseWsPath',
|
||||||
|
label: '反向 WS 路径',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: data?.runtime.sessions?.length || 0,
|
||||||
|
key: 'sessions',
|
||||||
|
label: '在线会话',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: (
|
||||||
|
<ATag color={data?.bus.connected ? 'success' : 'default'}>
|
||||||
|
{data?.bus.mode || 'local'} /{' '}
|
||||||
|
{data?.bus.connected ? '已连接' : '未连接'}
|
||||||
|
</ATag>
|
||||||
|
),
|
||||||
|
key: 'mqtt',
|
||||||
|
label: 'MQTT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: data?.conversationTotal || 0,
|
||||||
|
key: 'conversationTotal',
|
||||||
|
label: '会话数',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: `${data?.sendSuccessTotal || 0}/${data?.sendFailedTotal || 0}`,
|
||||||
|
key: 'sendResult',
|
||||||
|
label: '发送成功/失败',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page autoContentHeight>
|
||||||
|
<div style={{ display: 'grid', gap: '16px' }}>
|
||||||
|
<ARow gutter={[16, 16]}>
|
||||||
|
<ACol span={6}>
|
||||||
|
<ACard loading={loading.value}>
|
||||||
|
<AStatistic
|
||||||
|
title="账号总数"
|
||||||
|
value={data?.accountTotal || 0}
|
||||||
|
/>
|
||||||
|
</ACard>
|
||||||
|
</ACol>
|
||||||
|
<ACol span={6}>
|
||||||
|
<ACard loading={loading.value}>
|
||||||
|
<AStatistic title="在线账号" value={data?.onlineTotal || 0} />
|
||||||
|
</ACard>
|
||||||
|
</ACol>
|
||||||
|
<ACol span={6}>
|
||||||
|
<ACard loading={loading.value}>
|
||||||
|
<AStatistic
|
||||||
|
title="启用规则"
|
||||||
|
value={data?.enabledRuleTotal || 0}
|
||||||
|
/>
|
||||||
|
</ACard>
|
||||||
|
</ACol>
|
||||||
|
<ACol span={6}>
|
||||||
|
<ACard loading={loading.value}>
|
||||||
|
<AStatistic
|
||||||
|
title="消息总数"
|
||||||
|
value={data?.messageTotal || 0}
|
||||||
|
/>
|
||||||
|
</ACard>
|
||||||
|
</ACol>
|
||||||
|
</ARow>
|
||||||
|
|
||||||
|
<ACard loading={loading.value} title="运行状态">
|
||||||
|
<ADescriptions
|
||||||
|
bordered
|
||||||
|
column={2}
|
||||||
|
items={runtimeItems}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</ACard>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
125
apps/web-antdv-next/src/views/qqbot/message/list.tsx
Normal file
125
apps/web-antdv-next/src/views/qqbot/message/list.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import type { TableColumnType } from 'antdv-next';
|
||||||
|
|
||||||
|
import type { QqbotApi } from '#/api/qqbot';
|
||||||
|
import type { KtTableApi } from '#/components/ktTable';
|
||||||
|
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Tag } from 'antdv-next';
|
||||||
|
|
||||||
|
import { getQqbotMessageList } from '#/api/qqbot';
|
||||||
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||||
|
|
||||||
|
import { getOptionLabel, qqbotMessageTypeOptions } from '../modules/options';
|
||||||
|
|
||||||
|
const AKtTable = KtTable as any;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QqBotMessageList',
|
||||||
|
setup() {
|
||||||
|
const columns: Array<TableColumnType<QqbotApi.Message>> = [
|
||||||
|
{ dataIndex: 'selfId', key: 'selfId', title: 'Self ID', width: 150 },
|
||||||
|
{
|
||||||
|
dataIndex: 'messageType',
|
||||||
|
key: 'messageType',
|
||||||
|
title: '消息类型',
|
||||||
|
width: 110,
|
||||||
|
},
|
||||||
|
{ dataIndex: 'direction', key: 'direction', title: '方向', width: 100 },
|
||||||
|
{ dataIndex: 'targetId', key: 'targetId', title: '目标 ID', width: 150 },
|
||||||
|
{ dataIndex: 'userId', key: 'userId', title: '用户 ID', width: 150 },
|
||||||
|
{
|
||||||
|
dataIndex: 'senderNickname',
|
||||||
|
key: 'senderNickname',
|
||||||
|
title: '发送人',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'messageText',
|
||||||
|
key: 'messageText',
|
||||||
|
title: '消息内容',
|
||||||
|
width: 420,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'eventTime',
|
||||||
|
key: 'eventTime',
|
||||||
|
title: '消息时间',
|
||||||
|
width: 190,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const api: KtTableApi<QqbotApi.Message> = {
|
||||||
|
list: async (params) => await getQqbotMessageList(params),
|
||||||
|
};
|
||||||
|
const [registerTable] = useKtTable<QqbotApi.Message>({
|
||||||
|
api,
|
||||||
|
columns,
|
||||||
|
formOptions: {
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { allowClear: true, placeholder: '关键词' },
|
||||||
|
fieldName: 'keyword',
|
||||||
|
label: '关键词',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { allowClear: true, placeholder: 'Self ID' },
|
||||||
|
fieldName: 'selfId',
|
||||||
|
label: 'Self ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: qqbotMessageTypeOptions,
|
||||||
|
},
|
||||||
|
fieldName: 'targetType',
|
||||||
|
label: '消息类型',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { allowClear: true, placeholder: '目标 ID' },
|
||||||
|
fieldName: 'targetId',
|
||||||
|
label: '目标 ID',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rowActions: [],
|
||||||
|
tableTitle: '消息日志',
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Page autoContentHeight>
|
||||||
|
<AKtTable
|
||||||
|
onRegister={registerTable}
|
||||||
|
v-slots={{
|
||||||
|
bodyCell: ({ column, record }: any) => {
|
||||||
|
const row = record as QqbotApi.Message;
|
||||||
|
if (column.key === 'messageType') {
|
||||||
|
return (
|
||||||
|
<Tag color={row.messageType === 'group' ? 'blue' : 'green'}>
|
||||||
|
{getOptionLabel(qqbotMessageTypeOptions, row.messageType)}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (column.key === 'direction') {
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
color={
|
||||||
|
row.direction === 'inbound' ? 'default' : 'processing'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{row.direction === 'inbound' ? '接收' : '发送'}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
44
apps/web-antdv-next/src/views/qqbot/modules/options.ts
Normal file
44
apps/web-antdv-next/src/views/qqbot/modules/options.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
export const qqbotTargetTypeOptions = [
|
||||||
|
{ label: '全部', value: 'all' },
|
||||||
|
{ label: '私聊', value: 'private' },
|
||||||
|
{ label: '群聊', value: 'group' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const qqbotMessageTypeOptions = [
|
||||||
|
{ label: '私聊', value: 'private' },
|
||||||
|
{ label: '群聊', value: 'group' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const qqbotRuleMatchOptions = [
|
||||||
|
{ label: '关键词包含', value: 'keyword' },
|
||||||
|
{ label: '完全相等', value: 'equals' },
|
||||||
|
{ label: '正则匹配', value: 'regex' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const qqbotRuleTargetOptions = qqbotTargetTypeOptions;
|
||||||
|
|
||||||
|
const qqbotDefaultSendStatusOption = {
|
||||||
|
color: 'default',
|
||||||
|
label: '等待中',
|
||||||
|
value: 'pending',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const qqbotSendStatusOptions = [
|
||||||
|
qqbotDefaultSendStatusOption,
|
||||||
|
{ color: 'success', label: '成功', value: 'success' },
|
||||||
|
{ color: 'error', label: '失败', value: 'failed' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getOptionLabel(
|
||||||
|
options: Array<{ label: string; value: string }>,
|
||||||
|
value?: string,
|
||||||
|
) {
|
||||||
|
return options.find((item) => item.value === value)?.label || value || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSendStatusOption(status?: string) {
|
||||||
|
return (
|
||||||
|
qqbotSendStatusOptions.find((item) => item.value === status) ||
|
||||||
|
qqbotDefaultSendStatusOption
|
||||||
|
);
|
||||||
|
}
|
||||||
297
apps/web-antdv-next/src/views/qqbot/permission/list.tsx
Normal file
297
apps/web-antdv-next/src/views/qqbot/permission/list.tsx
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
import type { TableColumnType } from 'antdv-next';
|
||||||
|
|
||||||
|
import type { QqbotApi } from '#/api/qqbot';
|
||||||
|
import type {
|
||||||
|
KtTableApi,
|
||||||
|
KtTableButton,
|
||||||
|
KtTableRowAction,
|
||||||
|
} from '#/components/ktTable';
|
||||||
|
|
||||||
|
import { computed, defineComponent, reactive, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
Tabs,
|
||||||
|
Tag,
|
||||||
|
} from 'antdv-next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createQqbotPermission,
|
||||||
|
deleteQqbotPermission,
|
||||||
|
getQqbotPermissionList,
|
||||||
|
updateQqbotPermission,
|
||||||
|
} from '#/api/qqbot';
|
||||||
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||||
|
|
||||||
|
import { getOptionLabel, qqbotTargetTypeOptions } from '../modules/options';
|
||||||
|
|
||||||
|
const AKtTable = KtTable as any;
|
||||||
|
const AInput = Input as any;
|
||||||
|
const AModal = Modal as any;
|
||||||
|
const ASelect = Select as any;
|
||||||
|
const ASwitch = Switch as any;
|
||||||
|
const ATabs = Tabs as any;
|
||||||
|
|
||||||
|
type PermissionKind = 'allowlist' | 'blocklist';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QqBotPermissionList',
|
||||||
|
setup() {
|
||||||
|
const activeKind = ref<PermissionKind>('allowlist');
|
||||||
|
const saving = ref(false);
|
||||||
|
const modalOpen = ref(false);
|
||||||
|
const editingId = ref<string>();
|
||||||
|
const form = reactive<QqbotApi.PermissionBody>({
|
||||||
|
enabled: true,
|
||||||
|
remark: '',
|
||||||
|
selfId: '',
|
||||||
|
targetId: '',
|
||||||
|
targetType: 'private',
|
||||||
|
});
|
||||||
|
const columns: Array<TableColumnType<QqbotApi.Permission>> = [
|
||||||
|
{ dataIndex: 'selfId', key: 'selfId', title: 'Self ID', width: 150 },
|
||||||
|
{
|
||||||
|
dataIndex: 'targetType',
|
||||||
|
key: 'targetType',
|
||||||
|
title: '目标类型',
|
||||||
|
width: 110,
|
||||||
|
},
|
||||||
|
{ dataIndex: 'targetId', key: 'targetId', title: '目标 ID', width: 160 },
|
||||||
|
{ dataIndex: 'enabled', key: 'enabled', title: '状态', width: 100 },
|
||||||
|
{ dataIndex: 'remark', key: 'remark', title: '备注', width: 260 },
|
||||||
|
];
|
||||||
|
const api: KtTableApi<QqbotApi.Permission> = {
|
||||||
|
list: async (params) =>
|
||||||
|
await getQqbotPermissionList(activeKind.value, params),
|
||||||
|
};
|
||||||
|
const buttons: Array<KtTableButton<QqbotApi.Permission>> = [
|
||||||
|
{
|
||||||
|
icon: <Plus class="kt-table__button-icon" />,
|
||||||
|
key: 'create',
|
||||||
|
label: '新增名单',
|
||||||
|
onClick: openCreate,
|
||||||
|
permissionCodes: ['QqBot:Permission:Create'],
|
||||||
|
type: 'primary',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const rowActions: Array<KtTableRowAction<QqbotApi.Permission>> = [
|
||||||
|
{
|
||||||
|
key: 'edit',
|
||||||
|
label: '编辑',
|
||||||
|
onClick: openEdit,
|
||||||
|
permissionCodes: ['QqBot:Permission:Edit'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
confirm: (row) =>
|
||||||
|
`确认删除名单「${row.targetId || row.targetType}」吗?`,
|
||||||
|
danger: true,
|
||||||
|
key: 'delete',
|
||||||
|
label: '删除',
|
||||||
|
onClick: async (row, context) => {
|
||||||
|
await deleteQqbotPermission(activeKind.value, row.id);
|
||||||
|
message.success('名单删除成功');
|
||||||
|
await context.reload();
|
||||||
|
},
|
||||||
|
permissionCodes: ['QqBot:Permission:Delete'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const [registerTable, tableApi] = useKtTable<QqbotApi.Permission>({
|
||||||
|
api,
|
||||||
|
buttons,
|
||||||
|
columns,
|
||||||
|
formOptions: {
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { allowClear: true, placeholder: 'Self ID' },
|
||||||
|
fieldName: 'selfId',
|
||||||
|
label: 'Self ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: qqbotTargetTypeOptions,
|
||||||
|
},
|
||||||
|
fieldName: 'targetType',
|
||||||
|
label: '目标类型',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { allowClear: true, placeholder: '目标 ID' },
|
||||||
|
fieldName: 'targetId',
|
||||||
|
label: '目标 ID',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rowActions,
|
||||||
|
tableTitle: '权限名单',
|
||||||
|
});
|
||||||
|
const modalTitle = computed(
|
||||||
|
() =>
|
||||||
|
`${editingId.value ? '编辑' : '新增'}${activeKind.value === 'allowlist' ? '白名单' : '黑名单'}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(activeKind, async () => {
|
||||||
|
await tableApi.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
function openCreate() {
|
||||||
|
editingId.value = undefined;
|
||||||
|
Object.assign(form, {
|
||||||
|
enabled: true,
|
||||||
|
remark: '',
|
||||||
|
selfId: '',
|
||||||
|
targetId: '',
|
||||||
|
targetType: 'private',
|
||||||
|
});
|
||||||
|
modalOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEdit(row: QqbotApi.Permission) {
|
||||||
|
editingId.value = row.id;
|
||||||
|
Object.assign(form, { ...row });
|
||||||
|
modalOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitPermission() {
|
||||||
|
if (form.targetType !== 'all' && !form.targetId.trim()) {
|
||||||
|
message.warning('请填写目标 ID');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
saving.value = true;
|
||||||
|
try {
|
||||||
|
await (editingId.value
|
||||||
|
? updateQqbotPermission(activeKind.value, {
|
||||||
|
...form,
|
||||||
|
id: editingId.value,
|
||||||
|
})
|
||||||
|
: createQqbotPermission(activeKind.value, form));
|
||||||
|
message.success('名单保存成功');
|
||||||
|
modalOpen.value = false;
|
||||||
|
await tableApi.reload();
|
||||||
|
} finally {
|
||||||
|
saving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Page autoContentHeight>
|
||||||
|
<div style={{ display: 'grid', gap: '12px' }}>
|
||||||
|
<ATabs
|
||||||
|
activeKey={activeKind.value}
|
||||||
|
items={[
|
||||||
|
{ key: 'allowlist', label: '白名单' },
|
||||||
|
{ key: 'blocklist', label: '黑名单' },
|
||||||
|
]}
|
||||||
|
{...{
|
||||||
|
'onUpdate:activeKey': (value: PermissionKind) => {
|
||||||
|
activeKind.value = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AKtTable
|
||||||
|
onRegister={registerTable}
|
||||||
|
v-slots={{
|
||||||
|
bodyCell: ({ column, record }: any) => {
|
||||||
|
const row = record as QqbotApi.Permission;
|
||||||
|
if (column.key === 'enabled') {
|
||||||
|
return (
|
||||||
|
<Tag color={row.enabled ? 'success' : 'default'}>
|
||||||
|
{row.enabled ? '启用' : '停用'}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (column.key === 'targetType') {
|
||||||
|
return getOptionLabel(qqbotTargetTypeOptions, row.targetType);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<AModal
|
||||||
|
confirmLoading={saving.value}
|
||||||
|
onOk={submitPermission}
|
||||||
|
{...{
|
||||||
|
'onUpdate:open': (value: boolean) => {
|
||||||
|
modalOpen.value = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
open={modalOpen.value}
|
||||||
|
title={modalTitle.value}
|
||||||
|
width="620px"
|
||||||
|
>
|
||||||
|
<Form labelCol={{ span: 5 }} model={form} wrapperCol={{ span: 18 }}>
|
||||||
|
<FormItem label="Self ID">
|
||||||
|
<AInput
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
form.selfId = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
placeholder="留空代表全部账号"
|
||||||
|
value={form.selfId}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="目标类型">
|
||||||
|
<ASelect
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (
|
||||||
|
value: QqbotApi.PermissionBody['targetType'],
|
||||||
|
) => {
|
||||||
|
form.targetType = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
options={qqbotTargetTypeOptions}
|
||||||
|
value={form.targetType}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="目标 ID">
|
||||||
|
<AInput
|
||||||
|
disabled={form.targetType === 'all'}
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
form.targetId = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
placeholder="私聊填 QQ 号,群聊填群号"
|
||||||
|
value={form.targetId}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="启用">
|
||||||
|
<ASwitch
|
||||||
|
checked={form.enabled}
|
||||||
|
{...{
|
||||||
|
'onUpdate:checked': (value: boolean) => {
|
||||||
|
form.enabled = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="备注">
|
||||||
|
<AInput
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
form.remark = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={form.remark}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</AModal>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
349
apps/web-antdv-next/src/views/qqbot/rule/list.tsx
Normal file
349
apps/web-antdv-next/src/views/qqbot/rule/list.tsx
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
import type { TableColumnType } from 'antdv-next';
|
||||||
|
|
||||||
|
import type { QqbotApi } from '#/api/qqbot';
|
||||||
|
import type {
|
||||||
|
KtTableApi,
|
||||||
|
KtTableButton,
|
||||||
|
KtTableRowAction,
|
||||||
|
} from '#/components/ktTable';
|
||||||
|
|
||||||
|
import { computed, defineComponent, reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
Tag,
|
||||||
|
TextArea,
|
||||||
|
} from 'antdv-next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createQqbotRule,
|
||||||
|
deleteQqbotRule,
|
||||||
|
getQqbotRuleList,
|
||||||
|
toggleQqbotRule,
|
||||||
|
updateQqbotRule,
|
||||||
|
} from '#/api/qqbot';
|
||||||
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getOptionLabel,
|
||||||
|
qqbotRuleMatchOptions,
|
||||||
|
qqbotRuleTargetOptions,
|
||||||
|
} from '../modules/options';
|
||||||
|
|
||||||
|
const AKtTable = KtTable as any;
|
||||||
|
const AInput = Input as any;
|
||||||
|
const AInputNumber = InputNumber as any;
|
||||||
|
const AModal = Modal as any;
|
||||||
|
const ASelect = Select as any;
|
||||||
|
const ASwitch = Switch as any;
|
||||||
|
const ATextArea = TextArea as any;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QqBotRuleList',
|
||||||
|
setup() {
|
||||||
|
const saving = ref(false);
|
||||||
|
const modalOpen = ref(false);
|
||||||
|
const editingId = ref<string>();
|
||||||
|
const form = reactive<QqbotApi.RuleBody>({
|
||||||
|
cooldownMs: 1500,
|
||||||
|
enabled: true,
|
||||||
|
keyword: '',
|
||||||
|
matchType: 'keyword',
|
||||||
|
name: '',
|
||||||
|
priority: 0,
|
||||||
|
replyContent: '',
|
||||||
|
targetType: 'all',
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns: Array<TableColumnType<QqbotApi.Rule>> = [
|
||||||
|
{ dataIndex: 'name', key: 'name', title: '规则名称', width: 180 },
|
||||||
|
{ dataIndex: 'keyword', key: 'keyword', title: '关键词', width: 220 },
|
||||||
|
{
|
||||||
|
dataIndex: 'matchType',
|
||||||
|
key: 'matchType',
|
||||||
|
title: '匹配方式',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'targetType',
|
||||||
|
key: 'targetType',
|
||||||
|
title: '目标范围',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{ dataIndex: 'enabled', key: 'enabled', title: '状态', width: 100 },
|
||||||
|
{ dataIndex: 'priority', key: 'priority', title: '优先级', width: 100 },
|
||||||
|
{
|
||||||
|
dataIndex: 'lastHitAt',
|
||||||
|
key: 'lastHitAt',
|
||||||
|
title: '最后命中',
|
||||||
|
width: 190,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const api: KtTableApi<QqbotApi.Rule> = {
|
||||||
|
list: async (params) => await getQqbotRuleList(params),
|
||||||
|
};
|
||||||
|
const buttons: Array<KtTableButton<QqbotApi.Rule>> = [
|
||||||
|
{
|
||||||
|
icon: <Plus class="kt-table__button-icon" />,
|
||||||
|
key: 'create',
|
||||||
|
label: '新建规则',
|
||||||
|
onClick: openCreate,
|
||||||
|
permissionCodes: ['QqBot:Rule:Create'],
|
||||||
|
type: 'primary',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const rowActions: Array<KtTableRowAction<QqbotApi.Rule>> = [
|
||||||
|
{
|
||||||
|
key: 'toggle',
|
||||||
|
label: '启停',
|
||||||
|
onClick: async (row, context) => {
|
||||||
|
await toggleQqbotRule(row.id, !row.enabled);
|
||||||
|
message.success(row.enabled ? '规则已停用' : '规则已启用');
|
||||||
|
await context.reload();
|
||||||
|
},
|
||||||
|
permissionCodes: ['QqBot:Rule:Toggle'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'edit',
|
||||||
|
label: '编辑',
|
||||||
|
onClick: openEdit,
|
||||||
|
permissionCodes: ['QqBot:Rule:Edit'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
confirm: (row) => `确认删除规则「${row.name || row.keyword}」吗?`,
|
||||||
|
danger: true,
|
||||||
|
key: 'delete',
|
||||||
|
label: '删除',
|
||||||
|
onClick: async (row, context) => {
|
||||||
|
await deleteQqbotRule(row.id);
|
||||||
|
message.success('规则删除成功');
|
||||||
|
await context.reload();
|
||||||
|
},
|
||||||
|
permissionCodes: ['QqBot:Rule:Delete'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const [registerTable, tableApi] = useKtTable<QqbotApi.Rule>({
|
||||||
|
api,
|
||||||
|
buttons,
|
||||||
|
columns,
|
||||||
|
formOptions: {
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '规则名称/关键词',
|
||||||
|
},
|
||||||
|
fieldName: 'keyword',
|
||||||
|
label: '关键词',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: qqbotRuleTargetOptions,
|
||||||
|
},
|
||||||
|
fieldName: 'targetType',
|
||||||
|
label: '目标范围',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '启用', value: true },
|
||||||
|
{ label: '停用', value: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fieldName: 'enabled',
|
||||||
|
label: '状态',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rowActions,
|
||||||
|
tableTitle: '自动回复规则',
|
||||||
|
});
|
||||||
|
const modalTitle = computed(() =>
|
||||||
|
editingId.value ? '编辑规则' : '新建规则',
|
||||||
|
);
|
||||||
|
|
||||||
|
function openCreate() {
|
||||||
|
editingId.value = undefined;
|
||||||
|
Object.assign(form, {
|
||||||
|
cooldownMs: 1500,
|
||||||
|
enabled: true,
|
||||||
|
keyword: '',
|
||||||
|
matchType: 'keyword',
|
||||||
|
name: '',
|
||||||
|
priority: 0,
|
||||||
|
replyContent: '',
|
||||||
|
targetType: 'all',
|
||||||
|
});
|
||||||
|
modalOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEdit(row: QqbotApi.Rule) {
|
||||||
|
editingId.value = row.id;
|
||||||
|
Object.assign(form, { ...row });
|
||||||
|
modalOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitRule() {
|
||||||
|
if (!form.keyword.trim() || !form.replyContent.trim()) {
|
||||||
|
message.warning('请填写关键词和回复内容');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
saving.value = true;
|
||||||
|
try {
|
||||||
|
await (editingId.value
|
||||||
|
? updateQqbotRule({ ...form, id: editingId.value })
|
||||||
|
: createQqbotRule(form));
|
||||||
|
message.success('规则保存成功');
|
||||||
|
modalOpen.value = false;
|
||||||
|
await tableApi.reload();
|
||||||
|
} finally {
|
||||||
|
saving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Page autoContentHeight>
|
||||||
|
<AKtTable
|
||||||
|
onRegister={registerTable}
|
||||||
|
v-slots={{
|
||||||
|
bodyCell: ({ column, record }: any) => {
|
||||||
|
const row = record as QqbotApi.Rule;
|
||||||
|
if (column.key === 'enabled') {
|
||||||
|
return (
|
||||||
|
<Tag color={row.enabled ? 'success' : 'default'}>
|
||||||
|
{row.enabled ? '启用' : '停用'}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (column.key === 'matchType') {
|
||||||
|
return getOptionLabel(qqbotRuleMatchOptions, row.matchType);
|
||||||
|
}
|
||||||
|
if (column.key === 'targetType') {
|
||||||
|
return getOptionLabel(qqbotRuleTargetOptions, row.targetType);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AModal
|
||||||
|
confirmLoading={saving.value}
|
||||||
|
onOk={submitRule}
|
||||||
|
{...{
|
||||||
|
'onUpdate:open': (value: boolean) => {
|
||||||
|
modalOpen.value = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
open={modalOpen.value}
|
||||||
|
title={modalTitle.value}
|
||||||
|
width="720px"
|
||||||
|
>
|
||||||
|
<Form labelCol={{ span: 5 }} model={form} wrapperCol={{ span: 18 }}>
|
||||||
|
<FormItem label="规则名称">
|
||||||
|
<AInput
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
form.name = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={form.name}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="匹配方式" required>
|
||||||
|
<ASelect
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: QqbotApi.RuleBody['matchType']) => {
|
||||||
|
form.matchType = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
options={qqbotRuleMatchOptions}
|
||||||
|
value={form.matchType}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="关键词" required>
|
||||||
|
<AInput
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
form.keyword = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={form.keyword}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="目标范围">
|
||||||
|
<ASelect
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (
|
||||||
|
value: QqbotApi.RuleBody['targetType'],
|
||||||
|
) => {
|
||||||
|
form.targetType = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
options={qqbotRuleTargetOptions}
|
||||||
|
value={form.targetType}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="回复内容" required>
|
||||||
|
<ATextArea
|
||||||
|
autoSize={{ maxRows: 6, minRows: 3 }}
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
form.replyContent = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={form.replyContent}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="优先级">
|
||||||
|
<AInputNumber
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: number) => {
|
||||||
|
form.priority = value || 0;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={form.priority}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="冷却时间">
|
||||||
|
<AInputNumber
|
||||||
|
addonAfter="ms"
|
||||||
|
min={0}
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: number) => {
|
||||||
|
form.cooldownMs = value || 0;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={form.cooldownMs}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="启用">
|
||||||
|
<ASwitch
|
||||||
|
checked={form.enabled}
|
||||||
|
{...{
|
||||||
|
'onUpdate:checked': (value: boolean) => {
|
||||||
|
form.enabled = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</AModal>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
248
apps/web-antdv-next/src/views/qqbot/sendLog/list.tsx
Normal file
248
apps/web-antdv-next/src/views/qqbot/sendLog/list.tsx
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
import type { TableColumnType } from 'antdv-next';
|
||||||
|
|
||||||
|
import type { QqbotApi } from '#/api/qqbot';
|
||||||
|
import type { KtTableApi, KtTableButton } from '#/components/ktTable';
|
||||||
|
|
||||||
|
import { computed, defineComponent, reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Form, FormItem, Input, message, Modal, Select, Tag } from 'antdv-next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getQqbotSendLogList,
|
||||||
|
sendQqbotGroup,
|
||||||
|
sendQqbotPrivate,
|
||||||
|
} from '#/api/qqbot';
|
||||||
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getOptionLabel,
|
||||||
|
getSendStatusOption,
|
||||||
|
qqbotMessageTypeOptions,
|
||||||
|
qqbotSendStatusOptions,
|
||||||
|
} from '../modules/options';
|
||||||
|
|
||||||
|
const AKtTable = KtTable as any;
|
||||||
|
const AInput = Input as any;
|
||||||
|
const AModal = Modal as any;
|
||||||
|
const ASelect = Select as any;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QqBotSendLogList',
|
||||||
|
setup() {
|
||||||
|
const saving = ref(false);
|
||||||
|
const modalOpen = ref(false);
|
||||||
|
const sendForm = reactive({
|
||||||
|
message: '',
|
||||||
|
selfId: '',
|
||||||
|
targetId: '',
|
||||||
|
targetType: 'private' as 'group' | 'private',
|
||||||
|
});
|
||||||
|
const columns: Array<TableColumnType<QqbotApi.SendLog>> = [
|
||||||
|
{ dataIndex: 'selfId', key: 'selfId', title: 'Self ID', width: 150 },
|
||||||
|
{
|
||||||
|
dataIndex: 'targetType',
|
||||||
|
key: 'targetType',
|
||||||
|
title: '目标类型',
|
||||||
|
width: 110,
|
||||||
|
},
|
||||||
|
{ dataIndex: 'targetId', key: 'targetId', title: '目标 ID', width: 160 },
|
||||||
|
{ dataIndex: 'status', key: 'status', title: '状态', width: 100 },
|
||||||
|
{
|
||||||
|
dataIndex: 'messageText',
|
||||||
|
key: 'messageText',
|
||||||
|
title: '消息内容',
|
||||||
|
width: 420,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'errorMessage',
|
||||||
|
key: 'errorMessage',
|
||||||
|
title: '错误信息',
|
||||||
|
width: 260,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
key: 'createTime',
|
||||||
|
title: '发送时间',
|
||||||
|
width: 190,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const api: KtTableApi<QqbotApi.SendLog> = {
|
||||||
|
list: async (params) => await getQqbotSendLogList(params),
|
||||||
|
};
|
||||||
|
const buttons: Array<KtTableButton<QqbotApi.SendLog>> = [
|
||||||
|
{
|
||||||
|
key: 'send',
|
||||||
|
label: '手动发送',
|
||||||
|
onClick: openSend,
|
||||||
|
permissionCodes: ['QqBot:Send:Private', 'QqBot:Send:Group'],
|
||||||
|
type: 'primary',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const [registerTable, tableApi] = useKtTable<QqbotApi.SendLog>({
|
||||||
|
api,
|
||||||
|
buttons,
|
||||||
|
columns,
|
||||||
|
formOptions: {
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { allowClear: true, placeholder: 'Self ID' },
|
||||||
|
fieldName: 'selfId',
|
||||||
|
label: 'Self ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: qqbotMessageTypeOptions,
|
||||||
|
},
|
||||||
|
fieldName: 'targetType',
|
||||||
|
label: '目标类型',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: { allowClear: true, placeholder: '目标 ID' },
|
||||||
|
fieldName: 'targetId',
|
||||||
|
label: '目标 ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: qqbotSendStatusOptions,
|
||||||
|
},
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rowActions: [],
|
||||||
|
tableTitle: '发送日志',
|
||||||
|
});
|
||||||
|
const targetLabel = computed(() =>
|
||||||
|
sendForm.targetType === 'group' ? '群号' : 'QQ 号',
|
||||||
|
);
|
||||||
|
|
||||||
|
function openSend() {
|
||||||
|
Object.assign(sendForm, {
|
||||||
|
message: '',
|
||||||
|
selfId: '',
|
||||||
|
targetId: '',
|
||||||
|
targetType: 'private',
|
||||||
|
});
|
||||||
|
modalOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitSend() {
|
||||||
|
if (!sendForm.targetId.trim() || !sendForm.message.trim()) {
|
||||||
|
message.warning('请填写目标和消息内容');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
saving.value = true;
|
||||||
|
try {
|
||||||
|
await (sendForm.targetType === 'group'
|
||||||
|
? sendQqbotGroup({
|
||||||
|
groupId: sendForm.targetId,
|
||||||
|
message: sendForm.message,
|
||||||
|
selfId: sendForm.selfId || undefined,
|
||||||
|
})
|
||||||
|
: sendQqbotPrivate({
|
||||||
|
message: sendForm.message,
|
||||||
|
selfId: sendForm.selfId || undefined,
|
||||||
|
userId: sendForm.targetId,
|
||||||
|
}));
|
||||||
|
message.success('消息已发送');
|
||||||
|
modalOpen.value = false;
|
||||||
|
await tableApi.reload();
|
||||||
|
} finally {
|
||||||
|
saving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Page autoContentHeight>
|
||||||
|
<AKtTable
|
||||||
|
onRegister={registerTable}
|
||||||
|
v-slots={{
|
||||||
|
bodyCell: ({ column, record }: any) => {
|
||||||
|
const row = record as QqbotApi.SendLog;
|
||||||
|
if (column.key === 'targetType') {
|
||||||
|
return getOptionLabel(qqbotMessageTypeOptions, row.targetType);
|
||||||
|
}
|
||||||
|
if (column.key === 'status') {
|
||||||
|
const status = getSendStatusOption(row.status);
|
||||||
|
return <Tag color={status.color}>{status.label}</Tag>;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AModal
|
||||||
|
confirmLoading={saving.value}
|
||||||
|
onOk={submitSend}
|
||||||
|
{...{
|
||||||
|
'onUpdate:open': (value: boolean) => {
|
||||||
|
modalOpen.value = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
open={modalOpen.value}
|
||||||
|
title="手动发送"
|
||||||
|
width="620px"
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
labelCol={{ span: 5 }}
|
||||||
|
model={sendForm}
|
||||||
|
wrapperCol={{ span: 18 }}
|
||||||
|
>
|
||||||
|
<FormItem label="Self ID">
|
||||||
|
<AInput
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
sendForm.selfId = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
placeholder="留空使用默认启用账号"
|
||||||
|
value={sendForm.selfId}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="目标类型">
|
||||||
|
<ASelect
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: 'group' | 'private') => {
|
||||||
|
sendForm.targetType = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
options={qqbotMessageTypeOptions}
|
||||||
|
value={sendForm.targetType}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label={targetLabel.value} required>
|
||||||
|
<AInput
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
sendForm.targetId = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={sendForm.targetId}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="消息内容" required>
|
||||||
|
<AInput.TextArea
|
||||||
|
autoSize={{ maxRows: 6, minRows: 3 }}
|
||||||
|
{...{
|
||||||
|
'onUpdate:value': (value: string) => {
|
||||||
|
sendForm.message = value;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={sendForm.message}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</AModal>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user