feat(web): wire ProcessViewer and delegate UI into workflow pages

- InstanceMonitor: add '流程图' button that opens ProcessViewer modal
  with active node highlighting, loading flow definition via API
- PendingTasks: add '委派' button with delegate modal (UUID input),
  wired to the existing delegateTask API function
- Both ProcessViewer component and delegateTask API were previously
  dead code (never imported/called)
This commit is contained in:
iven
2026-04-11 16:26:47 +08:00
parent 6a08b99ed8
commit 1fec5e2cf2
2 changed files with 91 additions and 13 deletions

View File

@@ -6,6 +6,8 @@ import {
terminateInstance,
type ProcessInstanceInfo,
} from '../../api/workflowInstances';
import { getProcessDefinition, type NodeDef, type EdgeDef } from '../../api/workflowDefinitions';
import ProcessViewer from './ProcessViewer';
const statusColors: Record<string, string> = {
running: 'processing',
@@ -20,6 +22,13 @@ export default function InstanceMonitor() {
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
// ProcessViewer state
const [viewerOpen, setViewerOpen] = useState(false);
const [viewerNodes, setViewerNodes] = useState<NodeDef[]>([]);
const [viewerEdges, setViewerEdges] = useState<EdgeDef[]>([]);
const [activeNodeIds, setActiveNodeIds] = useState<string[]>([]);
const [viewerLoading, setViewerLoading] = useState(false);
const fetchData = useCallback(async () => {
setLoading(true);
try {
@@ -33,6 +42,22 @@ export default function InstanceMonitor() {
useEffect(() => { fetchData(); }, [fetchData]);
const handleViewFlow = async (record: ProcessInstanceInfo) => {
setViewerLoading(true);
setViewerOpen(true);
try {
const def = await getProcessDefinition(record.definition_id);
setViewerNodes(def.nodes);
setViewerEdges(def.edges);
setActiveNodeIds(record.active_tokens.map((t) => t.node_id));
} catch {
message.error('加载流程图失败');
setViewerOpen(false);
} finally {
setViewerLoading(false);
}
};
const handleTerminate = async (id: string) => {
Modal.confirm({
title: '确认终止',
@@ -66,22 +91,39 @@ export default function InstanceMonitor() {
render: (v: string) => new Date(v).toLocaleString(),
},
{
title: '操作', key: 'action', width: 100,
title: '操作', key: 'action', width: 150,
render: (_, record) => (
record.status === 'running' ? (
<Button size="small" danger onClick={() => handleTerminate(record.id)}></Button>
) : null
<>
<Button size="small" onClick={() => handleViewFlow(record)} style={{ marginRight: 8 }}>
</Button>
{record.status === 'running' && (
<Button size="small" danger onClick={() => handleTerminate(record.id)}></Button>
)}
</>
),
},
];
return (
<Table
rowKey="id"
columns={columns}
dataSource={data}
loading={loading}
pagination={{ current: page, total, pageSize: 20, onChange: setPage }}
/>
<>
<Table
rowKey="id"
columns={columns}
dataSource={data}
loading={loading}
pagination={{ current: page, total, pageSize: 20, onChange: setPage }}
/>
<Modal
title="流程图查看"
open={viewerOpen}
onCancel={() => setViewerOpen(false)}
footer={null}
width={720}
loading={viewerLoading}
>
<ProcessViewer nodes={viewerNodes} edges={viewerEdges} activeNodeIds={activeNodeIds} />
</Modal>
</>
);
}