diff --git a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-ui/src/main/java/com/xspaceagi/agent/web/ui/controller/DigitalEmployeeSyncController.java b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-ui/src/main/java/com/xspaceagi/agent/web/ui/controller/DigitalEmployeeSyncController.java index d9a2d8c5..4d7ac3e5 100644 --- a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-ui/src/main/java/com/xspaceagi/agent/web/ui/controller/DigitalEmployeeSyncController.java +++ b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-ui/src/main/java/com/xspaceagi/agent/web/ui/controller/DigitalEmployeeSyncController.java @@ -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 queryRecords( @RequestParam(name = "client_key", required = false) String clientKey, diff --git a/qiming-backend/app-platform-modules/platform-system/system-spec/src/main/java/com/xspaceagi/system/spec/enums/ResourceEnum.java b/qiming-backend/app-platform-modules/platform-system/system-spec/src/main/java/com/xspaceagi/system/spec/enums/ResourceEnum.java index d6269ed0..a585021d 100644 --- a/qiming-backend/app-platform-modules/platform-system/system-spec/src/main/java/com/xspaceagi/system/spec/enums/ResourceEnum.java +++ b/qiming-backend/app-platform-modules/platform-system/system-spec/src/main/java/com/xspaceagi/system/spec/enums/ResourceEnum.java @@ -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"), diff --git a/qiming-backend/sql/update-20260606.sql b/qiming-backend/sql/update-20260606.sql new file mode 100644 index 00000000..02ac9280 --- /dev/null +++ b/qiming-backend/sql/update-20260606.sql @@ -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; diff --git a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeSync/index.tsx b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeSync/index.tsx new file mode 100644 index 00000000..a9a8d4a2 --- /dev/null +++ b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeSync/index.tsx @@ -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 = { + 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(); + const formRef = useRef(); + const location = useLocation(); + const [payloadRecord, setPayloadRecord] = + useState(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[] = 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) => ( + + {record.entity_type || '--'} + + ), + }, + { + 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) => ( + + ), + }, + ], + [], + ); + + 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 ( + + + actionRef={actionRef} + formRef={formRef} + rowKey="id" + columns={columns} + request={request} + onReset={handleReset} + showQueryButtons={hasPermission( + 'content_digital_employee_sync_query_list', + )} + /> + setPayloadRecord(null)} + > + {payloadRecord && ( + <> + + 实体: + {payloadRecord.entity_type} / {payloadRecord.entity_id} + + + Outbox: + {payloadRecord.outbox_id} + + +
+                {formattedPayload}
+              
+
+ + )} +
+
+ ); +}; + +export default DigitalEmployeeSync; diff --git a/qiming/src/routes/index.ts b/qiming/src/routes/index.ts index 3cd8560f..40c68007 100644 --- a/qiming/src/routes/index.ts +++ b/qiming/src/routes/index.ts @@ -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', + }, ], }, { diff --git a/qiming/src/services/menuService.ts b/qiming/src/services/menuService.ts index 6c14e033..7aec27d5 100644 --- a/qiming/src/services/menuService.ts +++ b/qiming/src/services/menuService.ts @@ -53,6 +53,8 @@ const MENU_ICON_MAP: Record = { 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', // 订阅与积分(管理员) diff --git a/qiming/src/services/systemManage.ts b/qiming/src/services/systemManage.ts index 6b8bdaff..2d80541b 100644 --- a/qiming/src/services/systemManage.ts +++ b/qiming/src/services/systemManage.ts @@ -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> { + 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, diff --git a/qiming/src/types/interfaces/systemManage.ts b/qiming/src/types/interfaces/systemManage.ts index 67a449fc..c06f148e 100644 --- a/qiming/src/types/interfaces/systemManage.ts +++ b/qiming/src/types/interfaces/systemManage.ts @@ -516,6 +516,44 @@ export interface SystemAgentListParams extends SystemPaginationParams { // 智能体列表分页响应 export type SystemAgentPage = SystemPageResult; +// 数字员工同步记录 +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;