feat: add digital employee sync records page
This commit is contained in:
@@ -4,6 +4,7 @@ import com.xspaceagi.agent.core.adapter.application.DigitalEmployeeSyncApplicati
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRecordPageDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRequestDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncResponseDto;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -12,6 +13,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/digital-employee/sync")
|
||||
public class DigitalEmployeeSyncController {
|
||||
@@ -24,6 +27,7 @@ public class DigitalEmployeeSyncController {
|
||||
return ReqResult.success(digitalEmployeeSyncApplicationService.syncOutbox(request));
|
||||
}
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST)
|
||||
@RequestMapping(path = "/records", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeeSyncRecordPageDto> queryRecords(
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
|
||||
@@ -417,6 +417,11 @@ public enum ResourceEnum {
|
||||
CONTENT_SKILL_QUERY_DETAIL(ResourceTypeEnum.OPERATION, "content_skill_query_detail", "查询详情", "content_skill"),
|
||||
CONTENT_SKILL_DELETE(ResourceTypeEnum.OPERATION, "content_skill_delete", "删除", "content_skill"),
|
||||
|
||||
// 2级模块 - 数字员工同步记录
|
||||
CONTENT_DIGITAL_EMPLOYEE_SYNC(ResourceTypeEnum.MODULE, "content_digital_employee_sync", "数字员工同步记录", "content_manage"),
|
||||
|
||||
CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST(ResourceTypeEnum.OPERATION, "content_digital_employee_sync_query_list", "查询列表", "content_digital_employee_sync"),
|
||||
|
||||
// ================== 多语言管理模块 ==================
|
||||
I18N_LANG_MANAGE(ResourceTypeEnum.MODULE, "i18n_lang_manage", "多语言管理模块", "root"),
|
||||
I18N_LANG_QUERY(ResourceTypeEnum.OPERATION, "i18n_lang_query", "查询", "i18n_lang_manage"),
|
||||
|
||||
75
qiming-backend/sql/update-20260606.sql
Normal file
75
qiming-backend/sql/update-20260606.sql
Normal file
@@ -0,0 +1,75 @@
|
||||
-- Add digital employee sync records menu and resource.
|
||||
-- Idempotent for existing tenants: creates menu/resource rows, binds menu to resource,
|
||||
-- and grants super_admin default access. Other roles can be granted this menu
|
||||
-- from the menu permission management UI.
|
||||
|
||||
INSERT INTO sys_resource (`parent_id`, `code`, `name`, `description`, `source`, `type`, `sort_index`, `status`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT parent.id, 'content_digital_employee_sync', '数字员工同步记录', '数字员工同步记录', 1, 1, 90, 1, parent.`_tenant_id`, 0, 'system', 1
|
||||
FROM sys_resource parent
|
||||
WHERE parent.`code` = 'content_manage' AND parent.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_resource r
|
||||
WHERE r.`_tenant_id` = parent.`_tenant_id` AND r.`code` = 'content_digital_employee_sync' AND r.`yn` = 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_resource (`parent_id`, `code`, `name`, `description`, `source`, `type`, `sort_index`, `status`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT parent.id, 'content_digital_employee_sync_query_list', '查询列表', '查询数字员工同步记录', 1, 2, 1, 1, parent.`_tenant_id`, 0, 'system', 1
|
||||
FROM sys_resource parent
|
||||
WHERE parent.`code` = 'content_digital_employee_sync' AND parent.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_resource r
|
||||
WHERE r.`_tenant_id` = parent.`_tenant_id` AND r.`code` = 'content_digital_employee_sync_query_list' AND r.`yn` = 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu (`parent_id`, `code`, `name`, `description`, `source`, `path`, `open_type`, `icon`, `sort_index`, `status`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT parent.id, 'content_digital_employee_sync', '数字员工同步记录', '数字员工同步记录', 1, '/system/content/content-digital-employee-sync', 1, '', 90, 1, parent.`_tenant_id`, 0, 'system', 1
|
||||
FROM sys_menu parent
|
||||
WHERE parent.`code` = 'content_manage' AND parent.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_menu m
|
||||
WHERE m.`_tenant_id` = parent.`_tenant_id` AND m.`code` = 'content_digital_employee_sync' AND m.`yn` = 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu_resource (`menu_id`, `resource_id`, `resource_bind_type`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT m.id, r.id, 1, m.`_tenant_id`, 0, 'system', 1
|
||||
FROM sys_menu m
|
||||
JOIN sys_resource r ON r.`_tenant_id` = m.`_tenant_id` AND r.`code` = 'content_digital_employee_sync' AND r.`yn` = 1
|
||||
WHERE m.`code` = 'content_digital_employee_sync' AND m.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_menu_resource mr
|
||||
WHERE mr.`_tenant_id` = m.`_tenant_id` AND mr.`menu_id` = m.id AND mr.`resource_id` = r.id AND mr.`yn` = 1
|
||||
);
|
||||
|
||||
UPDATE sys_menu_resource mr
|
||||
JOIN sys_menu m ON m.id = mr.menu_id AND m.`_tenant_id` = mr.`_tenant_id` AND m.`code` = 'content_digital_employee_sync' AND m.`yn` = 1
|
||||
JOIN sys_resource r ON r.id = mr.resource_id AND r.`_tenant_id` = mr.`_tenant_id` AND r.`code` = 'content_digital_employee_sync' AND r.`yn` = 1
|
||||
SET mr.`resource_bind_type` = 1,
|
||||
mr.`modified` = CURRENT_TIMESTAMP
|
||||
WHERE mr.`yn` = 1;
|
||||
|
||||
INSERT INTO sys_role_menu (`role_id`, `menu_id`, `menu_bind_type`, `resource_tree_json`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT role.id,
|
||||
menu.id,
|
||||
1,
|
||||
JSON_ARRAY(JSON_OBJECT('id', resource.id, 'code', resource.`code`, 'resourceBindType', 1)),
|
||||
menu.`_tenant_id`,
|
||||
0,
|
||||
'system',
|
||||
1
|
||||
FROM sys_role role
|
||||
JOIN sys_menu menu ON menu.`_tenant_id` = role.`_tenant_id` AND menu.`code` = 'content_digital_employee_sync' AND menu.`yn` = 1
|
||||
JOIN sys_resource resource ON resource.`_tenant_id` = role.`_tenant_id` AND resource.`code` = 'content_digital_employee_sync' AND resource.`yn` = 1
|
||||
WHERE role.`code` = 'super_admin' AND role.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_role_menu rm
|
||||
WHERE rm.`_tenant_id` = role.`_tenant_id` AND rm.`role_id` = role.id AND rm.`menu_id` = menu.id AND rm.`yn` = 1
|
||||
);
|
||||
|
||||
UPDATE sys_role_menu rm
|
||||
JOIN sys_role role ON role.id = rm.role_id AND role.`_tenant_id` = rm.`_tenant_id` AND role.`code` = 'super_admin' AND role.`yn` = 1
|
||||
JOIN sys_menu menu ON menu.id = rm.menu_id AND menu.`_tenant_id` = rm.`_tenant_id` AND menu.`code` = 'content_digital_employee_sync' AND menu.`yn` = 1
|
||||
JOIN sys_resource resource ON resource.`_tenant_id` = rm.`_tenant_id` AND resource.`code` = 'content_digital_employee_sync' AND resource.`yn` = 1
|
||||
SET rm.`menu_bind_type` = 1,
|
||||
rm.`resource_tree_json` = JSON_ARRAY(JSON_OBJECT('id', resource.id, 'code', resource.`code`, 'resourceBindType', 1)),
|
||||
rm.`modified` = CURRENT_TIMESTAMP
|
||||
WHERE rm.`yn` = 1;
|
||||
@@ -0,0 +1,240 @@
|
||||
import { XProTable } from '@/components/ProComponents';
|
||||
import WorkspaceLayout from '@/components/WorkspaceLayout';
|
||||
import { SUCCESS_CODE } from '@/constants/codes.constants';
|
||||
import { apiDigitalEmployeeSyncRecordList } from '@/services/systemManage';
|
||||
import type { DigitalEmployeeSyncRecordInfo } from '@/types/interfaces/systemManage';
|
||||
import type {
|
||||
ActionType,
|
||||
FormInstance,
|
||||
ProColumns,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Button, Drawer, message, Tag, Typography } from 'antd';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useLocation, useModel } from 'umi';
|
||||
|
||||
const ENTITY_TYPE_OPTIONS = {
|
||||
plan: { text: 'Plan' },
|
||||
task: { text: 'Task' },
|
||||
run: { text: 'Run' },
|
||||
event: { text: 'Event' },
|
||||
artifact: { text: 'Artifact' },
|
||||
approval: { text: 'Approval' },
|
||||
};
|
||||
|
||||
const ENTITY_TYPE_TONE: Record<string, string> = {
|
||||
plan: 'blue',
|
||||
task: 'geekblue',
|
||||
run: 'purple',
|
||||
event: 'cyan',
|
||||
artifact: 'green',
|
||||
approval: 'orange',
|
||||
};
|
||||
|
||||
function formatPayload(payload?: string): string {
|
||||
if (!payload) return '{}';
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(payload), null, 2);
|
||||
} catch {
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
const DigitalEmployeeSync: React.FC = () => {
|
||||
const { hasPermission } = useModel('menuModel');
|
||||
const actionRef = useRef<ActionType>();
|
||||
const formRef = useRef<FormInstance>();
|
||||
const location = useLocation();
|
||||
const [payloadRecord, setPayloadRecord] =
|
||||
useState<DigitalEmployeeSyncRecordInfo | null>(null);
|
||||
const formattedPayload = useMemo(
|
||||
() => (payloadRecord ? formatPayload(payloadRecord.payload) : ''),
|
||||
[payloadRecord],
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
formRef.current?.resetFields();
|
||||
actionRef.current?.reset?.();
|
||||
actionRef.current?.setPageInfo?.({ current: 1, pageSize: 15 });
|
||||
actionRef.current?.reload();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const state = location.state as any;
|
||||
if (state?._t) {
|
||||
handleReset();
|
||||
}
|
||||
}, [location.state, handleReset]);
|
||||
|
||||
const columns: ProColumns<DigitalEmployeeSyncRecordInfo>[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: '客户端 Key',
|
||||
dataIndex: 'client_key',
|
||||
width: 190,
|
||||
ellipsis: true,
|
||||
fieldProps: {
|
||||
placeholder: '输入客户端 Key',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '设备 ID',
|
||||
dataIndex: 'device_id',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
hideInSearch: true,
|
||||
renderText: (value) => value || '--',
|
||||
},
|
||||
{
|
||||
title: '实体类型',
|
||||
dataIndex: 'entity_type',
|
||||
width: 120,
|
||||
valueType: 'select',
|
||||
valueEnum: ENTITY_TYPE_OPTIONS,
|
||||
fieldProps: {
|
||||
placeholder: '选择实体类型',
|
||||
allowClear: true,
|
||||
},
|
||||
render: (_, record) => (
|
||||
<Tag color={ENTITY_TYPE_TONE[record.entity_type] || 'default'}>
|
||||
{record.entity_type || '--'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '实体 ID',
|
||||
dataIndex: 'entity_id',
|
||||
width: 220,
|
||||
ellipsis: true,
|
||||
fieldProps: {
|
||||
placeholder: '输入实体 ID',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Outbox ID',
|
||||
dataIndex: 'outbox_id',
|
||||
width: 220,
|
||||
ellipsis: true,
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
hideInSearch: true,
|
||||
renderText: (value) => value || '--',
|
||||
},
|
||||
{
|
||||
title: '同步时间',
|
||||
dataIndex: 'synced_at',
|
||||
align: 'center',
|
||||
width: 170,
|
||||
hideInSearch: true,
|
||||
valueType: 'dateTime',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created',
|
||||
align: 'center',
|
||||
width: 170,
|
||||
hideInSearch: true,
|
||||
valueType: 'dateTime',
|
||||
},
|
||||
{
|
||||
title: '详情',
|
||||
valueType: 'option',
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
render: (_, record) => (
|
||||
<Button type="link" onClick={() => setPayloadRecord(record)}>
|
||||
查看
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
const request = async (params: {
|
||||
pageSize?: number;
|
||||
current?: number;
|
||||
client_key?: string;
|
||||
entity_type?: string;
|
||||
entity_id?: string;
|
||||
}) => {
|
||||
const response = await apiDigitalEmployeeSyncRecordList({
|
||||
pageNo: params.current || 1,
|
||||
pageSize: params.pageSize || 15,
|
||||
clientKey: params.client_key,
|
||||
entityType: params.entity_type,
|
||||
entityId: params.entity_id,
|
||||
});
|
||||
|
||||
if (response.code !== SUCCESS_CODE) {
|
||||
message.error(response.message || '查询数字员工同步记录失败');
|
||||
}
|
||||
|
||||
return {
|
||||
data: response.data?.records || [],
|
||||
total: response.data?.total || 0,
|
||||
success: response.code === SUCCESS_CODE,
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<WorkspaceLayout title="数字员工同步记录" hideScroll>
|
||||
<XProTable<DigitalEmployeeSyncRecordInfo>
|
||||
actionRef={actionRef}
|
||||
formRef={formRef}
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
request={request}
|
||||
onReset={handleReset}
|
||||
showQueryButtons={hasPermission(
|
||||
'content_digital_employee_sync_query_list',
|
||||
)}
|
||||
/>
|
||||
<Drawer
|
||||
width={720}
|
||||
title="同步 Payload"
|
||||
open={Boolean(payloadRecord)}
|
||||
onClose={() => setPayloadRecord(null)}
|
||||
>
|
||||
{payloadRecord && (
|
||||
<>
|
||||
<Typography.Paragraph>
|
||||
<Typography.Text strong>实体:</Typography.Text>
|
||||
{payloadRecord.entity_type} / {payloadRecord.entity_id}
|
||||
</Typography.Paragraph>
|
||||
<Typography.Paragraph>
|
||||
<Typography.Text strong>Outbox:</Typography.Text>
|
||||
{payloadRecord.outbox_id}
|
||||
</Typography.Paragraph>
|
||||
<Typography.Paragraph copyable={{ text: formattedPayload }}>
|
||||
<pre
|
||||
style={{
|
||||
maxHeight: 'calc(100vh - 260px)',
|
||||
overflow: 'auto',
|
||||
margin: 0,
|
||||
padding: 16,
|
||||
background: '#f6f8fa',
|
||||
border: '1px solid #e5e7eb',
|
||||
borderRadius: 6,
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{formattedPayload}
|
||||
</pre>
|
||||
</Typography.Paragraph>
|
||||
</>
|
||||
)}
|
||||
</Drawer>
|
||||
</WorkspaceLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default DigitalEmployeeSync;
|
||||
@@ -351,6 +351,12 @@ const routes = [
|
||||
name: getRouteLabel('PC.Routes.contentSkill'),
|
||||
component: '@/pages/SystemManagement/Content/Skill',
|
||||
},
|
||||
{
|
||||
path: 'content-digital-employee-sync',
|
||||
name: '数字员工同步记录',
|
||||
component:
|
||||
'@/pages/SystemManagement/Content/DigitalEmployeeSync',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -53,6 +53,8 @@ const MENU_ICON_MAP: Record<string, string> = {
|
||||
system_log_query: 'icons-nav-log',
|
||||
// 内容管理
|
||||
content_manage: 'icons-nav-cube',
|
||||
// 数字员工同步记录
|
||||
content_digital_employee_sync: 'icons-nav-robot',
|
||||
// 支付与收益(开发者)
|
||||
dev_payment_earnings: 'icons-nav-my-earnings',
|
||||
// 订阅与积分(管理员)
|
||||
|
||||
@@ -8,6 +8,8 @@ import type {
|
||||
AccessStatsResult,
|
||||
AddSystemUserParams,
|
||||
ConversationStatsResult,
|
||||
DigitalEmployeeSyncRecordListParams,
|
||||
DigitalEmployeeSyncRecordPage,
|
||||
ModelConfigDto,
|
||||
NotifyMessageSendParams,
|
||||
OnlineMonitorInfo,
|
||||
@@ -264,6 +266,22 @@ export async function apiSystemResourceAgentDelete(data: {
|
||||
});
|
||||
}
|
||||
|
||||
// 查询数字员工同步记录
|
||||
export async function apiDigitalEmployeeSyncRecordList(
|
||||
data: DigitalEmployeeSyncRecordListParams,
|
||||
): Promise<RequestResponse<DigitalEmployeeSyncRecordPage>> {
|
||||
return request('/api/digital-employee/sync/records', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
client_key: data.clientKey,
|
||||
entity_type: data.entityType,
|
||||
entity_id: data.entityId,
|
||||
page_no: data.pageNo,
|
||||
page_size: data.pageSize,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 查询网页应用列表
|
||||
export async function apiSystemResourceWebappList(
|
||||
data: SystemWebappListParams,
|
||||
|
||||
@@ -516,6 +516,44 @@ export interface SystemAgentListParams extends SystemPaginationParams {
|
||||
// 智能体列表分页响应
|
||||
export type SystemAgentPage = SystemPageResult<SystemAgentInfo>;
|
||||
|
||||
// 数字员工同步记录
|
||||
export interface DigitalEmployeeSyncRecordInfo {
|
||||
id: number;
|
||||
client_key: string;
|
||||
device_id?: string;
|
||||
outbox_id: string;
|
||||
entity_type:
|
||||
| 'plan'
|
||||
| 'task'
|
||||
| 'run'
|
||||
| 'event'
|
||||
| 'artifact'
|
||||
| 'approval'
|
||||
| string;
|
||||
entity_id: string;
|
||||
operation: string;
|
||||
payload: string;
|
||||
synced_at: string;
|
||||
modified: string;
|
||||
created: string;
|
||||
}
|
||||
|
||||
// 数字员工同步记录查询参数
|
||||
export interface DigitalEmployeeSyncRecordListParams
|
||||
extends SystemPaginationParams {
|
||||
clientKey?: string;
|
||||
entityType?: string;
|
||||
entityId?: string;
|
||||
}
|
||||
|
||||
// 数字员工同步记录分页响应
|
||||
export interface DigitalEmployeeSyncRecordPage {
|
||||
total: number;
|
||||
page_no: number;
|
||||
page_size: number;
|
||||
records: DigitalEmployeeSyncRecordInfo[];
|
||||
}
|
||||
|
||||
// 网页应用信息
|
||||
export type SystemWebappInfo = SystemResourceInfo;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user