feat: add digital employee sync records page

This commit is contained in:
baiyanyun
2026-06-07 00:37:15 +08:00
parent dc14418963
commit e2a91d88e8
8 changed files with 388 additions and 0 deletions

View File

@@ -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,

View File

@@ -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"),

View 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;

View File

@@ -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;

View File

@@ -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',
},
],
},
{

View File

@@ -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',
// 订阅与积分(管理员)

View File

@@ -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,

View File

@@ -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;