吸收数字员工管理端运营台前端

This commit is contained in:
baiyanyun
2026-06-12 20:18:30 +08:00
parent 48ac9bdf19
commit be901ee112
6 changed files with 484 additions and 2 deletions

View File

@@ -0,0 +1,386 @@
import { XProTable } from '@/components/ProComponents';
import WorkspaceLayout from '@/components/WorkspaceLayout';
import { SUCCESS_CODE } from '@/constants/codes.constants';
import { apiDigitalEmployeeAdminWorkbenchList } from '@/services/systemManage';
import type {
DigitalEmployeeAdminWorkbenchRow,
DigitalEmployeeAdminWorkbenchView,
} from '@/types/interfaces/systemManage';
import type {
ActionType,
FormInstance,
ProColumns,
} from '@ant-design/pro-components';
import { Button, Drawer, message, Space, Tabs, Tag, Typography } from 'antd';
import { useMemo, useRef, useState } from 'react';
import { history, useModel } from 'umi';
const WORKBENCH_TABS: Array<{
key: DigitalEmployeeAdminWorkbenchView;
label: string;
}> = [
{ key: 'business', label: '业务视图' },
{ key: 'interventions', label: '介入台' },
{ key: 'artifact_lifecycle', label: '产物治理' },
{ key: 'notifications', label: '通知运营' },
{ key: 'audits', label: '审计诊断' },
{ key: 'daily_reports', label: '日报运营' },
{ key: 'route_governance', label: '路由治理' },
];
const ENTITY_TYPE_OPTIONS = {
plan: { text: 'Plan' },
task: { text: 'Task' },
run: { text: 'Run' },
event: { text: 'Event' },
artifact: { text: 'Artifact' },
approval: { text: 'Approval' },
};
const STATUS_TONE: Record<string, string> = {
active: 'processing',
allowed: 'success',
approved: 'success',
blocked: 'error',
completed: 'success',
deleted: 'default',
failed: 'error',
pending: 'warning',
rejected: 'error',
running: 'processing',
synced: 'success',
warn: 'warning',
};
function compactText(value?: string, maxLength = 96): string {
const text = (value || '').replace(/\s+/g, ' ').trim();
if (!text) return '--';
return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
}
function jsonText(value?: Record<string, unknown>): string {
if (!value) return '{}';
return JSON.stringify(value, null, 2);
}
function tone(value?: string): string {
return value ? STATUS_TONE[value.toLowerCase()] || 'default' : 'default';
}
function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) {
const title = compactText(record.title || record.entity_id, 72);
const summary = compactText(record.summary || record.kind || record.category);
const meta = [
record.plan_id && `Plan ${record.plan_id}`,
record.task_id && `Task ${record.task_id}`,
record.run_id && `Run ${record.run_id}`,
record.approval_id && `Approval ${record.approval_id}`,
record.artifact_id && `Artifact ${record.artifact_id}`,
record.route_decision_id && `Route ${record.route_decision_id}`,
].filter((item): item is string => Boolean(item));
return (
<div style={{ minWidth: 0 }}>
<Space size={6} wrap>
<Typography.Text strong ellipsis style={{ maxWidth: 260 }}>
{title}
</Typography.Text>
{record.status && <Tag color={tone(record.status)}>{record.status}</Tag>}
</Space>
<Typography.Text
type="secondary"
ellipsis
style={{ display: 'block', maxWidth: 360, fontSize: 12 }}
>
{summary}
</Typography.Text>
{meta.length > 0 && (
<Space size={[4, 4]} wrap style={{ marginTop: 4 }}>
{meta.slice(0, 3).map((item) => (
<Tag key={item} color="default" style={{ marginInlineEnd: 0 }}>
{item}
</Tag>
))}
</Space>
)}
</div>
);
}
const DigitalEmployeeOps: React.FC = () => {
const { hasPermission } = useModel('menuModel');
const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>();
const [activeView, setActiveView] =
useState<DigitalEmployeeAdminWorkbenchView>('business');
const [detailRecord, setDetailRecord] =
useState<DigitalEmployeeAdminWorkbenchRow | null>(null);
const businessViewJson = useMemo(
() => jsonText(detailRecord?.business_view),
[detailRecord],
);
const payloadJson = useMemo(
() => jsonText(detailRecord?.payload),
[detailRecord],
);
const columns: ProColumns<DigitalEmployeeAdminWorkbenchRow>[] = useMemo(
() => [
{
title: '客户端 Key',
dataIndex: 'client_key',
hideInTable: true,
fieldProps: { placeholder: '输入完整客户端 Key', allowClear: true },
},
{
title: '设备 ID',
dataIndex: 'device_id',
width: 150,
ellipsis: true,
fieldProps: { placeholder: '输入设备 ID', allowClear: true },
renderText: (value) => value || '--',
},
{
title: '实体类型',
dataIndex: 'entity_type',
width: 120,
valueType: 'select',
valueEnum: ENTITY_TYPE_OPTIONS,
fieldProps: { placeholder: '选择实体类型', allowClear: true },
render: (_, record) => <Tag>{record.entity_type || '--'}</Tag>,
},
{
title: '实体 ID',
dataIndex: 'entity_id',
width: 180,
ellipsis: true,
fieldProps: { placeholder: '输入实体 ID', allowClear: true },
},
{
title: '状态',
dataIndex: 'status',
width: 110,
fieldProps: { placeholder: '输入状态', allowClear: true },
render: (_, record) => (
<Tag color={tone(record.status)}>{record.status || '--'}</Tag>
),
},
{
title: '类型/分类',
dataIndex: 'kind',
width: 190,
ellipsis: true,
fieldProps: { placeholder: '输入 kind', allowClear: true },
render: (_, record) => (
<Space size={4} wrap>
{record.kind && <Tag color="blue">{record.kind}</Tag>}
{record.category && <Tag>{record.category}</Tag>}
</Space>
),
},
{
title: '分类',
dataIndex: 'category',
hideInTable: true,
fieldProps: { placeholder: '输入 category', allowClear: true },
},
{
title: '等级',
dataIndex: 'level',
width: 90,
fieldProps: { placeholder: '等级', allowClear: true },
renderText: (value) => value || '--',
},
{
title: '优先级',
dataIndex: 'priority',
width: 90,
fieldProps: { placeholder: '优先级', allowClear: true },
renderText: (value) => value || '--',
},
{
title: '业务摘要',
dataIndex: 'summary',
width: 400,
hideInSearch: true,
render: (_, record) => renderSummary(record),
},
{
title: '更新时间',
dataIndex: 'updated_range',
valueType: 'dateTimeRange',
hideInTable: true,
},
{
title: '发生时间',
dataIndex: 'occurred_at',
width: 170,
hideInSearch: true,
renderText: (value) => value || '--',
},
{
title: '同步时间',
dataIndex: 'synced_at',
width: 170,
hideInSearch: true,
valueType: 'dateTime',
},
{
title: '详情',
valueType: 'option',
fixed: 'right',
width: 120,
render: (_, record) => (
<Space size={4}>
<Button type="link" onClick={() => setDetailRecord(record)}>
</Button>
{record.sync_record_url && (
<Button
type="link"
onClick={() => history.push(record.sync_record_url || '')}
>
</Button>
)}
</Space>
),
},
],
[],
);
const request = async (params: Record<string, any>) => {
const updatedRange = params.updated_range || [];
const response = await apiDigitalEmployeeAdminWorkbenchList({
view: activeView,
pageNo: params.current || 1,
pageSize: params.pageSize || 15,
clientKey: params.client_key,
deviceId: params.device_id,
entityType: params.entity_type,
entityId: params.entity_id,
status: params.status,
kind: params.kind,
category: params.category,
level: params.level,
priority: params.priority,
updatedFrom: updatedRange[0],
updatedTo: updatedRange[1],
});
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>
<Tabs
activeKey={activeView}
items={WORKBENCH_TABS}
onChange={(key) => {
setActiveView(key as DigitalEmployeeAdminWorkbenchView);
formRef.current?.resetFields();
actionRef.current?.setPageInfo?.({ current: 1, pageSize: 15 });
actionRef.current?.reload();
}}
/>
<XProTable<DigitalEmployeeAdminWorkbenchRow>
key={activeView}
actionRef={actionRef}
formRef={formRef}
rowKey={(record) =>
`${activeView}:${record.sync_record_id || record.entity_id}`
}
columns={columns}
request={request}
showQueryButtons={hasPermission(
'content_digital_employee_ops_query_list',
)}
/>
<Drawer
width={760}
title="运营详情"
open={Boolean(detailRecord)}
onClose={() => setDetailRecord(null)}
>
{detailRecord && (
<>
<div style={{ marginBottom: 16 }}>{renderSummary(detailRecord)}</div>
<Typography.Paragraph>
<Typography.Text strong></Typography.Text>
{detailRecord.client_key_masked || '--'} /{' '}
{detailRecord.device_id || '--'}
</Typography.Paragraph>
<Typography.Paragraph>
<Typography.Text strong></Typography.Text>
{detailRecord.entity_type || '--'} / {detailRecord.entity_id || '--'}
</Typography.Paragraph>
<Typography.Paragraph>
<Typography.Text strong></Typography.Text>
{[
detailRecord.plan_id && `Plan ${detailRecord.plan_id}`,
detailRecord.task_id && `Task ${detailRecord.task_id}`,
detailRecord.run_id && `Run ${detailRecord.run_id}`,
detailRecord.approval_id && `Approval ${detailRecord.approval_id}`,
detailRecord.artifact_id && `Artifact ${detailRecord.artifact_id}`,
detailRecord.notification_id &&
`Notification ${detailRecord.notification_id}`,
detailRecord.report_id && `Report ${detailRecord.report_id}`,
detailRecord.route_decision_id &&
`Route ${detailRecord.route_decision_id}`,
]
.filter(Boolean)
.join(' / ') || '--'}
</Typography.Paragraph>
<Typography.Title level={5}>Business View</Typography.Title>
<Typography.Paragraph copyable={{ text: businessViewJson }}>
<pre
style={{
maxHeight: 260,
overflow: 'auto',
margin: 0,
padding: 16,
background: '#f6f8fa',
border: '1px solid #e5e7eb',
borderRadius: 6,
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
}}
>
{businessViewJson}
</pre>
</Typography.Paragraph>
<Typography.Title level={5}>Payload</Typography.Title>
<Typography.Paragraph copyable={{ text: payloadJson }}>
<pre
style={{
maxHeight: 260,
overflow: 'auto',
margin: 0,
padding: 16,
background: '#f6f8fa',
border: '1px solid #e5e7eb',
borderRadius: 6,
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
}}
>
{payloadJson}
</pre>
</Typography.Paragraph>
</>
)}
</Drawer>
</WorkspaceLayout>
);
};
export default DigitalEmployeeOps;

View File

@@ -357,6 +357,12 @@ const routes = [
component:
'@/pages/SystemManagement/Content/DigitalEmployeeSync',
},
{
path: 'content-digital-employee-ops',
name: '数字员工运营台',
component:
'@/pages/SystemManagement/Content/DigitalEmployeeOps',
},
],
},
{

View File

@@ -55,6 +55,8 @@ const MENU_ICON_MAP: Record<string, string> = {
content_manage: 'icons-nav-cube',
// 数字员工同步记录
content_digital_employee_sync: 'icons-nav-robot',
// 数字员工运营台
content_digital_employee_ops: 'icons-nav-robot',
// 支付与收益(开发者)
dev_payment_earnings: 'icons-nav-my-earnings',
// 订阅与积分(管理员)

View File

@@ -8,6 +8,8 @@ import type {
AccessStatsResult,
AddSystemUserParams,
ConversationStatsResult,
DigitalEmployeeAdminWorkbenchListParams,
DigitalEmployeeAdminWorkbenchPage,
DigitalEmployeeSyncRecordListParams,
DigitalEmployeeSyncRecordPage,
ModelConfigDto,
@@ -284,6 +286,30 @@ export async function apiDigitalEmployeeSyncRecordList(
});
}
// 查询数字员工正式运营台
export async function apiDigitalEmployeeAdminWorkbenchList(
data: DigitalEmployeeAdminWorkbenchListParams,
): Promise<RequestResponse<DigitalEmployeeAdminWorkbenchPage>> {
return request(`/api/digital-employee/admin/workbench/${data.view}`, {
method: 'GET',
params: {
client_key: data.clientKey,
device_id: data.deviceId,
entity_type: data.entityType,
entity_id: data.entityId,
status: data.status,
kind: data.kind,
category: data.category,
level: data.level,
priority: data.priority,
updated_from: data.updatedFrom,
updated_to: data.updatedTo,
page_no: data.pageNo,
page_size: data.pageSize,
},
});
}
// 查询网页应用列表
export async function apiSystemResourceWebappList(
data: SystemWebappListParams,

View File

@@ -556,6 +556,68 @@ export interface DigitalEmployeeSyncRecordPage {
records: DigitalEmployeeSyncRecordInfo[];
}
export type DigitalEmployeeAdminWorkbenchView =
| 'business'
| 'interventions'
| 'artifact_lifecycle'
| 'notifications'
| 'audits'
| 'daily_reports'
| 'route_governance';
export interface DigitalEmployeeAdminWorkbenchRow {
view: DigitalEmployeeAdminWorkbenchView | string;
sync_record_id: number;
sync_record_url?: string;
entity_type?: string;
entity_id?: string;
operation?: string;
title?: string;
status?: string;
kind?: string;
category?: string;
level?: string;
priority?: string;
summary?: string;
plan_id?: string;
task_id?: string;
run_id?: string;
approval_id?: string;
artifact_id?: string;
notification_id?: string;
report_id?: string;
route_decision_id?: string;
occurred_at?: string;
synced_at?: string;
client_key_masked?: string;
device_id?: string;
business_view?: Record<string, unknown>;
payload?: Record<string, unknown>;
}
export interface DigitalEmployeeAdminWorkbenchListParams
extends SystemPaginationParams {
view: DigitalEmployeeAdminWorkbenchView;
clientKey?: string;
deviceId?: string;
entityType?: string;
entityId?: string;
status?: string;
kind?: string;
category?: string;
level?: string;
priority?: string;
updatedFrom?: string;
updatedTo?: string;
}
export interface DigitalEmployeeAdminWorkbenchPage {
total: number;
page_no: number;
page_size: number;
records: DigitalEmployeeAdminWorkbenchRow[];
}
// 网页应用信息
export type SystemWebappInfo = SystemResourceInfo;

View File

@@ -609,8 +609,8 @@ GET /api/digital-employee/approvals
- 建议:下一轮围绕管理端正式审计视图和诊断修复动作,把本地可追溯审计继续推进到可运营处置。
12. **管理端业务查询模型**
- 已吸收:管理端已有通用 sync record 接收、查询、payload 摘要和深链;后端 sync record 已开始保存客户端顶层 `contract_version``entity_summary``business_view`,并基于现有 `digital_employee_sync_record` 拆出 `/api/digital-employee/plans``/plans/{plan_id}``/tasks``/runs``/events``/artifacts``/approvals` 管理端业务查询模型;列表支持客户端 Key、设备、实体 ID、状态、同步状态和同步时间范围过滤业务 row 会提炼 title/status/summary、Plan/Task/Run 关联、发生/开始/结束时间、sync record 深链和脱敏客户端 KeyPlan 视图会聚合 task/run 计数与最近事件Plan 详情会返回关联 tasks/runs/events/artifacts/approvals 摘要。embedded adapter 已拆出同名只读业务查询接口,从 qimingclaw runtime、artifact、approval 和 canonical event 投影生成统一分页视图embedded 数字员工页已新增“业务视图”入口,支持计划/任务/运行/事件/产物/审批分段、实体/状态/同步状态/时间筛选、分页和详情 JSON 查看;客户端 outbox 的 event `business_view` 已开始按 route decision、governance command、approval action、artifact lifecycle/access、skill call audit 和 ready PlanStep dispatch 细分常用字段,本地与管理端业务视图投影已开始闭环。
- 缺口:复杂组合检索、跨设备聚合图、正式外部管理端菜单整合和物化业务表仍待吸收。
- 已吸收:管理端已有通用 sync record 接收、查询、payload 摘要和深链;后端 sync record 已开始保存客户端顶层 `contract_version``entity_summary``business_view`,并基于现有 `digital_employee_sync_record` 拆出 `/api/digital-employee/plans``/plans/{plan_id}``/tasks``/runs``/events``/artifacts``/approvals` 管理端业务查询模型;列表支持客户端 Key、设备、实体 ID、状态、同步状态和同步时间范围过滤业务 row 会提炼 title/status/summary、Plan/Task/Run 关联、发生/开始/结束时间、sync record 深链和脱敏客户端 KeyPlan 视图会聚合 task/run 计数与最近事件Plan 详情会返回关联 tasks/runs/events/artifacts/approvals 摘要。embedded adapter 已拆出同名只读业务查询接口,从 qimingclaw runtime、artifact、approval 和 canonical event 投影生成统一分页视图embedded 数字员工页已新增“业务视图”入口,支持计划/任务/运行/事件/产物/审批分段、实体/状态/同步状态/时间筛选、分页和详情 JSON 查看;客户端 outbox 的 event `business_view` 已开始按 route decision、governance command、approval action、artifact lifecycle/access、skill call audit 和 ready PlanStep dispatch 细分常用字段,本地与管理端业务视图投影已开始闭环。正式外部管理端台面已开始吸收qiming-backend 新增 `/api/digital-employee/admin/workbench/{view}`qiming 管理端新增“数字员工运营台”,统一展示业务视图、介入台、产物治理、通知运营、审计诊断、日报运营和路由治理,只读消费 sync record `business_view` 并提供同步记录深链。
- 缺口:复杂组合检索、跨设备聚合图、远程裁决/策略编辑动作和物化业务表仍待吸收。
- 建议:先使用 sync record 派生业务查询模型和 embedded 业务视图预览承接联调,再按容量和查询压力决定是否拆 Plan / Task / Run / Event 物理业务表。
13. **日报实体与导出交付**