diff --git a/src/components/InstanceCanvas/index.module.less b/src/components/InstanceCanvas/index.module.less new file mode 100644 index 0000000..862b9bb --- /dev/null +++ b/src/components/InstanceCanvas/index.module.less @@ -0,0 +1,310 @@ +.instanceCanvas { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background-color: var(--color-fill-2); + overflow: hidden; +} + +.header { + height: 50px; + background-color: var(--color-bg-2); + border-bottom: 1px solid var(--color-border-2); + display: flex; + align-items: center; + padding: 0 20px; + flex-shrink: 0; + gap: 12px; +} + +.backButton { + flex-shrink: 0; +} + +.title { + margin: 0; + font-size: 18px; + font-weight: 600; + color: var(--color-text-1); + flex: 1; +} + +.mainContent { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + position: relative; +} + +.canvasArea { + flex: 1; + overflow: hidden; + position: relative; + background-color: var(--color-bg-1); +} + +.logBarContainer { + position: relative; + width: 100%; + flex-shrink: 0; +} + +.logBar { + width: 100%; + height: 100%; + background-color: var(--color-bg-2); + border-top: 1px solid var(--color-border-2); + overflow: hidden; + display: flex; + flex-direction: column; +} + +.logTabs { + height: 100%; + display: flex; + flex-direction: column; + + :global { + .arco-tabs-content { + flex: 1; + overflow: hidden; + padding: 0; + } + + .arco-tabs-content-list { + height: 100%; + } + + .arco-tabs-pane { + height: 100%; + overflow: auto; + } + + .arco-tabs-nav { + padding: 0 16px; + margin: 0; + } + } +} + +.collapsedTabBar { + width: 100%; + background-color: var(--color-bg-2); + border-top: 1px solid var(--color-border-2); +} + +.collapsedTabs { + :global { + .arco-tabs-content { + display: none; + } + + .arco-tabs-nav { + padding: 0 16px; + margin: 0; + } + } +} + +.logContent { + padding: 12px 16px; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 13px; + line-height: 1.6; + color: var(--color-text-2); + background-color: var(--color-bg-1); + height: 100%; + overflow-y: auto; +} + +.logSection { + margin-bottom: 16px; + padding: 12px; + background-color: var(--color-fill-2); + border-radius: 4px; + border-left: 3px solid var(--color-primary-6); +} + +.logHeader { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 8px; + padding-bottom: 8px; + border-bottom: 1px solid var(--color-border-2); +} + +.logNodeName { + font-weight: 600; + color: var(--color-text-1); + font-size: 14px; +} + +.logDuration { + color: var(--color-text-3); + font-size: 12px; +} + +.logState { + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + + &.success { + background-color: var(--color-success-light-1); + color: var(--color-success-6); + } + + &.failed { + background-color: var(--color-danger-light-1); + color: var(--color-danger-6); + } + + &.running { + background-color: var(--color-primary-light-1); + color: var(--color-primary-6); + } +} + +.logMessage { + color: var(--color-text-2); + font-size: 12px; + line-height: 1.8; + white-space: pre-wrap; + word-break: break-all; + + div { + margin: 2px 0; + } +} + +.logItem { + margin-bottom: 4px; + word-break: break-all; +} + +.logTimestamp { + color: var(--color-text-3); + margin-right: 8px; +} + +.dataContent { + padding: 12px 16px; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 13px; + line-height: 1.6; + color: var(--color-text-1); + background-color: var(--color-bg-1); + height: 100%; + overflow-y: auto; +} + +.dataSection { + margin-bottom: 20px; + padding: 16px; + background-color: var(--color-fill-2); + border-radius: 6px; + border: 1px solid var(--color-border-2); +} + +.dataHeader { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 12px; + padding-bottom: 12px; + border-bottom: 2px solid var(--color-border-3); +} + +.dataNodeName { + font-weight: 600; + color: var(--color-text-1); + font-size: 15px; +} + +.dataDuration { + color: var(--color-text-3); + font-size: 12px; +} + +.dataState { + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + + &.success { + background-color: var(--color-success-light-1); + color: var(--color-success-6); + } + + &.failed { + background-color: var(--color-danger-light-1); + color: var(--color-danger-6); + } + + &.running { + background-color: var(--color-primary-light-1); + color: var(--color-primary-6); + } +} + +.dataBlock { + margin-top: 12px; + background-color: var(--color-bg-1); + border-radius: 4px; + padding: 12px; + border: 1px solid var(--color-border-1); +} + +.dataBlockTitle { + font-weight: 600; + color: var(--color-text-1); + margin-bottom: 8px; + font-size: 13px; +} + +.dataBlockContent { + margin: 0; + padding: 8px; + background-color: var(--color-fill-1); + border-radius: 4px; + font-size: 12px; + line-height: 1.6; + color: var(--color-text-2); + white-space: pre-wrap; + word-break: break-all; + overflow-x: auto; +} + +.emptyLog { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: var(--color-text-3); + font-size: 14px; + background-color: var(--color-bg-1); +} + +.loadingContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + background-color: var(--color-bg-1); +} + +.emptyContainer { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: var(--color-text-3); + font-size: 16px; + background-color: var(--color-bg-1); +} diff --git a/src/components/InstanceCanvas/index.tsx b/src/components/InstanceCanvas/index.tsx new file mode 100644 index 0000000..4b77207 --- /dev/null +++ b/src/components/InstanceCanvas/index.tsx @@ -0,0 +1,377 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { ResizeBox, Tabs, Message, Spin, Table, TableColumnProps, Button } from '@arco-design/web-react'; +import { IconCheckCircleFill, IconLoading, IconCloseCircleFill, IconLeft } from '@arco-design/web-react/icon'; +import FlowEditor from '@/pages/flowEditor/index'; +import { getInstanceDefinition } from '@/api/appIns'; +import { formatSeconds } from '@/utils/common'; +import dayjs from 'dayjs'; +import styles from './index.module.less'; + +const TabPane = Tabs.TabPane; + +interface LogMessage { + id: number; + type: string; + message: string; + timestamp: string; +} + +interface RuntimeData { + [key: string]: any; +} + +interface InstanceCanvasProps { + instanceData: any; // 实例数据 + title?: string; // 实例标题 + onBack?: () => void; // 返回回调 +} + +const InstanceCanvas: React.FC = ({ instanceData, title, onBack }) => { + const [activeTab, setActiveTab] = useState('1'); + const [logBarExpanded, setLogBarExpanded] = useState(true); + const [logContainerHeight, setLogContainerHeight] = useState('250px'); + const [runtimeLogs, setRuntimeLogs] = useState([]); + const [runtimeData, setRuntimeData] = useState({}); + const [flowData, setFlowData] = useState(null); // 流程数据 + const [loading, setLoading] = useState(false); // 加载状态 + const resizeBoxRef = useRef(null); + + // 获取实例定义数据 + const fetchInstanceDefinition = async () => { + if (!instanceData?.id) return; + + setLoading(true); + try { + const res: any = await getInstanceDefinition(instanceData.id); + if (res.code === 200 && res.data) { + + const components = res.data.main?.flow?.components; + // 包装数据格式,使其符合 FlowEditor 的期望 + // FlowEditor 使用 useDefault=true 时,会调用 projectFlowHandle + // projectFlowHandle 期望 initialData?.main?.components 或 initialData?.components + if (components && typeof components === 'object') { + // 转换连接格式:将单 $ 转换为 $$ + const convertedComponents = Object.entries(components).reduce((acc, [nodeId, nodeConfig]: [string, any]) => { + const converted = { ...nodeConfig }; + + // 转换 apiDownstream + if (converted.apiDownstream && Array.isArray(converted.apiDownstream)) { + converted.apiDownstream = converted.apiDownstream.map((targetArray: string[]) => { + if (Array.isArray(targetArray)) { + return targetArray.map((target: string) => { + if (typeof target === 'string' && target.includes('$') && !target.includes('$$')) { + return target.replace('$', '$$'); + } + return target; + }); + } + return targetArray; + }); + } + + // 转换 apiUpstream + if (converted.apiUpstream && Array.isArray(converted.apiUpstream)) { + converted.apiUpstream = converted.apiUpstream.map((sourceArray: string[]) => { + if (Array.isArray(sourceArray)) { + return sourceArray.map((source: string) => { + if (typeof source === 'string' && source.includes('$') && !source.includes('$$')) { + return source.replace('$', '$$'); + } + return source; + }); + } + return sourceArray; + }); + } + + acc[nodeId] = converted; + return acc; + }, {} as any); + + setFlowData({ + components: convertedComponents, + main: { + components: convertedComponents + } + }); + } + + // 处理运行日志 + if (res.data.main?.nodeLogs && Array.isArray(res.data.main.nodeLogs) && res.data.main.nodeLogs.length > 0) { + const logs = res.data.main.nodeLogs.map((log: any, index: number) => ({ + id: index, + type: 'runtime', + message: log.runLog || '', + timestamp: new Date(log.startTime).toLocaleString('zh-CN'), + nodeId: log.nodeId, + nodeName: log.name, + duration: log.duration, + state: log.state + })); + setRuntimeLogs(logs); + } + + // 处理运行数据(input/output) + if (res.data.main?.nodeLogs && Array.isArray(res.data.main.nodeLogs) && res.data.main.nodeLogs.length > 0) { + const dataMap: RuntimeData = {}; + res.data.main.nodeLogs.forEach((log: any) => { + if (log.input || log.output) { + dataMap[log.nodeId] = { + nodeName: log.name, + input: log.input || {}, + output: log.output || {}, + duration: log.duration, + startTime: log.startTime, + endTime: log.endTime, + state: log.state === 1 ? '成功' : log.state === -1 ? '失败' : '运行中' + }; + } + }); + setRuntimeData(dataMap); + } + } + else { + Message.error(res.msg || '获取实例数据失败'); + } + } catch (error) { + console.error('获取实例定义失败:', error); + Message.error('获取实例数据失败'); + } finally { + setLoading(false); + } + }; + + // 当 instanceData 变化时,重新获取数据 + useEffect(() => { + fetchInstanceDefinition(); + }, [instanceData?.id]); + + // 处理 Tab 点击事件 + const handleTabClick = (key: string) => { + if (key === activeTab) { + setLogBarExpanded(!logBarExpanded); + } + else { + setActiveTab(key); + setLogBarExpanded(true); + } + }; + + // 当展开/收起状态改变时,更新元素样式 + useEffect(() => { + if (resizeBoxRef.current) { + resizeBoxRef.current.style.height = logBarExpanded ? logContainerHeight : '0px'; + } + }, [logBarExpanded, logContainerHeight]); + + // 处理 ResizeBox 手动调整大小事件 + const handleResize = (e: MouseEvent, size: { width: number; height: number }) => { + if (size.height <= 40) { + setLogBarExpanded(false); + } + else { + setLogBarExpanded(true); + setLogContainerHeight(`${size.height}px`); + } + }; + + // 渲染运行日志内容 + const renderRuntimeLogs = () => { + return ( +
+ {runtimeLogs.length === 0 ? ( +

暂无运行日志

+ ) : ( + runtimeLogs.map((log: any) => ( +
+
+ {log.timestamp} +
+
+ {log.message} +
+
+ )) + )} +
+ ); + }; + + // 定义运行数据表格列 + const dataColumns: TableColumnProps[] = [ + { + title: '节点', + dataIndex: 'nodeName', + width: 120 + }, + { + title: '时间', + width: 160, + render: (_: any, record: any) => ( + <> + 开始 {dayjs(record.startTime).format('MM-DD HH:mm:ss')} +
+ {record.endTime > 0 && 结束 {dayjs(record.endTime).format('MM-DD HH:mm:ss')}} + + ) + }, + { + title: '耗时', + dataIndex: 'duration', + width: 80, + render: (_: any, record: any) => ( + record.duration > 1000 ? + {formatSeconds((record.duration / 1000).toString())} : + {record.duration}ms + ) + }, + { + title: '状态', + dataIndex: 'state', + width: 60, + align: 'center', + render: (_: any, record: any) => ( + <> + {record.state === 1 && } + {record.state === 0 && } + {record.state === -1 && } + + ) + }, + { + title: '输入参数', + dataIndex: 'input', + width: 200, + render: (_: any, record: any) => ( +
{JSON.stringify(record.input)}
+ ) + }, + { + title: '输出参数', + dataIndex: 'output', + width: 200, + render: (_: any, record: any) => ( +
{JSON.stringify(record.output)}
+ ) + } + ]; + + // 渲染运行数据内容 + const renderRuntimeData = () => { + // 将 runtimeData 对象转换为表格数据数组 + const tableData = Object.entries(runtimeData).map(([nodeId, data]: [string, any]) => ({ + key: nodeId, + nodeId, + nodeName: data.nodeName, + startTime: data.startTime || 0, + endTime: data.endTime || 0, + duration: data.duration, + state: data.state === '成功' ? 1 : data.state === '失败' ? -1 : 0, + input: data.input, + output: data.output + })); + + return ( +
+ {tableData.length === 0 ? ( +

暂无运行数据

+ ) : ( + + )} + + ); + }; + + return ( +
+ {/* 顶部标题栏 */} + {title && ( +
+ {onBack && ( + + )} +

实例名称:{title}

+
+ )} + + {/* 主内容区域 */} +
+ {/* 流程画布 */} +
+ {loading ? ( +
+ + 加载中... +
+ ) : flowData ? ( + + ) : ( +
+ 暂无流程数据 +
+ )} +
+ + {/* 底部日志栏 */} +
+ +
+ + + {renderRuntimeLogs()} + + + {renderRuntimeData()} + + +
+
+ + {/* Tab 标签栏(始终可见) */} + {!logBarExpanded && ( +
+ + + + +
+ )} +
+
+
+ ); +}; + +export default InstanceCanvas; diff --git a/src/pages/flowEditor/FlowEditorMain.tsx b/src/pages/flowEditor/FlowEditorMain.tsx index 1e5512f..d8976cc 100644 --- a/src/pages/flowEditor/FlowEditorMain.tsx +++ b/src/pages/flowEditor/FlowEditorMain.tsx @@ -39,6 +39,7 @@ interface FlowEditorMainProps { setNodes: React.Dispatch>; setEdges: React.Dispatch>; useDefault: boolean; + readOnly?: boolean; // 新增:只读模式 reactFlowInstance: any; reactFlowWrapper: React.RefObject; menu: any; @@ -96,6 +97,7 @@ const FlowEditorMain: React.FC = (props) => { setNodes, setEdges, useDefault, + readOnly = false, // 解构 readOnly,默认为 false reactFlowInstance, reactFlowWrapper, menu, @@ -355,7 +357,7 @@ const FlowEditorMain: React.FC = (props) => { return { ...node, - draggable: !currentAppIsRunning, + draggable: readOnly ? false : !currentAppIsRunning, // readOnly 模式下禁止拖拽 style: { ...node.style, ...style @@ -387,12 +389,20 @@ const FlowEditorMain: React.FC = (props) => { snapToGrid={true} snapGrid={[2, 2]} proOptions={{ hideAttribution: true }} // 隐藏水印 - nodesConnectable={!currentAppIsRunning} // 运行时禁用节点连接 - nodesDraggable={!currentAppIsRunning} // 运行时禁用节点拖拽 - // elementsSelectable={!currentAppIsRunning} // 运行时禁用元素选择 - connectOnClick={!currentAppIsRunning} // 运行时禁用点击连接 - disableKeyboardA11y={currentAppIsRunning} // 运行时禁用键盘交互 + nodesConnectable={readOnly ? false : !currentAppIsRunning} // readOnly 或运行时禁用节点连接 + nodesDraggable={readOnly ? false : !currentAppIsRunning} // readOnly 或运行时禁用节点拖拽 + elementsSelectable={!readOnly} // readOnly 模式下禁用元素选择 + connectOnClick={readOnly ? false : !currentAppIsRunning} // readOnly 或运行时禁用点击连接 + disableKeyboardA11y={readOnly || currentAppIsRunning} // readOnly 或运行时禁用键盘交互 + edgesFocusable={!readOnly} // readOnly 模式下边不可聚焦 + nodesFocusable={!readOnly} // readOnly 模式下节点不可聚焦 + edgesReconnectable={readOnly ? false : !currentAppIsRunning} // readOnly 或运行时禁用边重连 onBeforeDelete={async ({ nodes, edges }) => { + // readOnly 模式下禁止删除 + if (readOnly) { + return false; + } + // 在应用编排模式下(useDefault为false)只允许删除边,不允许删除节点 if (!useDefault && nodes.length > 0) { console.warn('在应用编排模式下不允许删除节点'); @@ -488,21 +498,21 @@ const FlowEditorMain: React.FC = (props) => { setIsEditModalOpen(false); }} - onNodesChange={!currentAppIsRunning ? onNodesChange : undefined} // 运行时禁用节点变更 - onEdgesChange={!currentAppIsRunning ? onEdgesChange : undefined} // 运行时禁用边变更 - onConnect={!currentAppIsRunning ? onConnect : undefined} // 运行时禁用连接 - onReconnect={!currentAppIsRunning ? onReconnect : undefined} // 运行时禁用重新连接 - onDragOver={!currentAppIsRunning ? onDragOver : undefined} // 运行时禁用拖拽 - onDrop={!currentAppIsRunning ? onDrop : undefined} // 运行时禁用放置 - onNodeDrag={!currentAppIsRunning ? onNodeDrag : undefined} // 运行时禁用节点拖拽 + onNodesChange={readOnly ? undefined : (!currentAppIsRunning ? onNodesChange : undefined)} // readOnly 或运行时禁用节点变更 + onEdgesChange={readOnly ? undefined : (!currentAppIsRunning ? onEdgesChange : undefined)} // readOnly 或运行时禁用边变更 + onConnect={readOnly ? undefined : (!currentAppIsRunning ? onConnect : undefined)} // readOnly 或运行时禁用连接 + onReconnect={readOnly ? undefined : (!currentAppIsRunning ? onReconnect : undefined)} // readOnly 或运行时禁用重新连接 + onDragOver={readOnly ? undefined : (!currentAppIsRunning ? onDragOver : undefined)} // readOnly 或运行时禁用拖拽 + onDrop={readOnly ? undefined : (!currentAppIsRunning ? onDrop : undefined)} // readOnly 或运行时禁用放置 + onNodeDrag={readOnly ? undefined : (!currentAppIsRunning ? onNodeDrag : undefined)} // readOnly 或运行时禁用节点拖拽 connectionLineType={ConnectionLineType.SmoothStep} connectionLineComponent={CustomConnectionLine} - onNodeDragStop={!currentAppIsRunning ? onNodeDragStop : undefined} // 运行时禁用节点拖拽停止 - onNodeContextMenu={(useDefault || currentAppIsRunning) ? onNodeContextMenu : undefined} // 应用编排模式下禁用节点上下文菜单,运行时仍可使用 - onNodeDoubleClick={!currentAppIsRunning ? onNodeDoubleClick : undefined} // 运行时禁用节点双击 - onEdgeContextMenu={(!currentAppIsRunning && useDefault) ? onEdgeContextMenu : undefined} // 运行时或应用编排模式下禁用边上下文菜单 + onNodeDragStop={readOnly ? undefined : (!currentAppIsRunning ? onNodeDragStop : undefined)} // readOnly 或运行时禁用节点拖拽停止 + onNodeContextMenu={readOnly ? undefined : ((useDefault || currentAppIsRunning) ? onNodeContextMenu : undefined)} // readOnly 或应用编排模式下禁用节点上下文菜单 + onNodeDoubleClick={readOnly ? undefined : (!currentAppIsRunning ? onNodeDoubleClick : undefined)} // readOnly 或运行时禁用节点双击 + onEdgeContextMenu={readOnly ? undefined : ((!currentAppIsRunning && useDefault) ? onEdgeContextMenu : undefined)} // readOnly 或运行时或应用编排模式下禁用边上下文菜单 onPaneClick={(useDefault || currentAppIsRunning) ? onPaneClick : undefined} // 运行时禁用面板点击 - onPaneContextMenu={(!currentAppIsRunning && useDefault) ? onPaneContextMenu : undefined} // 运行时或应用编排模式下禁用面板上下文菜单 + onPaneContextMenu={readOnly ? undefined : ((!currentAppIsRunning && useDefault) ? onPaneContextMenu : undefined)} // readOnly 或运行时或应用编排模式下禁用面板上下文菜单 onEdgeMouseEnter={(_event, edge) => { setEdges((eds) => eds.map(e => { if (e.id === edge.id) { @@ -524,21 +534,23 @@ const FlowEditorMain: React.FC = (props) => { selectionMode={!currentAppIsRunning ? SelectionMode.Partial : undefined} // 运行时禁用选择模式 > - - - - {useDefault && + {!readOnly && ( + + + + )} + {useDefault && !readOnly && = ({ initialData, useDefault }) => { +const FlowEditorWithProvider: React.FC<{ initialData?: any, useDefault?: boolean, readOnly?: boolean }> = ({ initialData, useDefault, readOnly }) => { return (
e.preventDefault()}> - +
); }; -const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ initialData, useDefault }) => { +const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean, readOnly?: boolean }> = ({ initialData, useDefault, readOnly = false }) => { const reactFlowInstance = useReactFlow(); const reactFlowWrapper = useRef(null); const [menu, setMenu] = useState<{ @@ -302,6 +302,7 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini setNodes={setNodes} setEdges={setEdges} useDefault={useDefault} + readOnly={readOnly} reactFlowInstance={reactFlowInstance} reactFlowWrapper={reactFlowWrapper} menu={menu} diff --git a/src/pages/instance/index.tsx b/src/pages/instance/index.tsx index 21e4445..b0500d4 100644 --- a/src/pages/instance/index.tsx +++ b/src/pages/instance/index.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import styles from './style/index.module.less'; import CustomCard from '@/components/CustomCard'; import Overview from './overview'; +import InstanceCanvas from '@/components/InstanceCanvas'; import { Space, Select, Switch, Table, TableColumnProps, Progress } from '@arco-design/web-react'; import { IconLoading, @@ -19,6 +20,8 @@ import { getMyAppList } from '@/api/apps'; const Option = Select.Option; +// 将 checkInstance 函数移到组件外部,稍后会在组件内部重新定义 +let checkInstanceHandler: (instance: any) => void; const columns: TableColumnProps[] = [ { @@ -63,27 +66,23 @@ const columns: TableColumnProps[] = [ title: '运行状态', dataIndex: 'state', render: (col, record, index) => ( - - {formatInstanceStatus(record.state)} +
+
{formatInstanceStatus(record.state)}
{getIcon(record.state)} - +
) }, - // { - // title: '操作', - // dataIndex: '', - // render: (col, record, index) => ( - // - // checkInstance(record)} /> - // - // ) - // } + { + title: '操作', + dataIndex: '', + render: (col, record, index) => ( + + checkInstanceHandler(record)} /> + + ) + } ]; -function checkInstance(instance) { - console.log('instance:', instance); -} - function getIcon(type) { switch (type) { case 'RUNNING': @@ -104,6 +103,8 @@ function getIcon(type) { } const Instance = () => { + const [viewMode, setViewMode] = useState<'list' | 'canvas'>('list'); // 视图模式:列表或画布 + const [selectedInstance, setSelectedInstance] = useState(null); // 选中的实例 const [instanceData, setInstanceData] = useState({ list: [], totalCount: 0, @@ -125,6 +126,21 @@ const Instance = () => { durationDesc: false }); + // 定义 checkInstance 处理函数 + const checkInstance = (instance: any) => { + setSelectedInstance(instance); + setViewMode('canvas'); + }; + + // 将处理函数赋值给外部变量 + checkInstanceHandler = checkInstance; + + // 返回列表视图 + const backToList = () => { + setViewMode('list'); + setSelectedInstance(null); + }; + const Selector = () => { return ( @@ -288,48 +304,60 @@ const Instance = () => { return ( <> -
- - - -
- 应用: - {Selector()} -
-
- 运行状态: - {RunStatus()} -
-
- 类型: - {Type()} -
-
- 运行时长倒序: - handleSearchParamChange('durationDesc', checked)} - /> -
-
-
+ + + +
+ 应用: + {Selector()} +
+
+ 运行状态: + {RunStatus()} +
+
+ 类型: + {Type()} +
+
+ 运行时长倒序: + handleSearchParamChange('durationDesc', checked)} + /> +
+
+
+ + + ) : ( + // 画布视图 +
+ - -
+ + )} ); }; diff --git a/src/pages/instance/style/index.module.less b/src/pages/instance/style/index.module.less index 1d7c754..eaaf652 100644 --- a/src/pages/instance/style/index.module.less +++ b/src/pages/instance/style/index.module.less @@ -1,3 +1,18 @@ .instanceContainer { -} \ No newline at end of file +} + +.canvasContainer { + width: 100%; + height: calc(100vh - 60px); + display: flex; + flex-direction: column; + background-color: var(--color-fill-2); + overflow: hidden; +} + +.handleRow { + display: flex; + align-items: center; + gap: 8px; +} diff --git a/src/utils/convertFlowData.ts b/src/utils/convertFlowData.ts index 32e3588..b70ad23 100644 --- a/src/utils/convertFlowData.ts +++ b/src/utils/convertFlowData.ts @@ -676,9 +676,12 @@ const getNodeApiIns = (nodeId: string, nodeConfig: any, currentProjectCompData: return [{ name: 'end', desc: '', dataType: '', defaultValue: '' }]; } else { - const comp = currentProjectCompData.filter(item => item.id === nodeConfig?.component?.compId); + const comp = currentProjectCompData.filter(item => { + return (item.id || item?.comp?.id) === nodeConfig?.component?.compId; + }); if (comp && comp.length > 0) { - return comp[0].def?.apis.map(v => { + const apiIns = comp[0].def?.apis || comp[0]?.comp.def?.apis; + return apiIns.map(v => { return { ...v, name: v.id, @@ -854,6 +857,82 @@ const getCurrentProjectStoreData = () => { }); } + // 如果从 store 中没有获取到数据,尝试从 sessionStorage 中获取 + if (result.length === 0) { + try { + const userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '{}'); + const compLibsKey = `compLibs${userInfo.userId}`; + const compLibsData = sessionStorage.getItem(compLibsKey); + + if (compLibsData) { + const { myLibs = [], pubLibs = [], teamLibs = [], myFlow = [], pubFlow = [] } = JSON.parse(compLibsData); + + // 处理 myLibs(我的组件库) + if (Array.isArray(myLibs)) { + myLibs.forEach((lib: any) => { + if (lib.children && Array.isArray(lib.children)) { + lib.children.forEach((item: any) => { + result.push({ + ...item, + type: 'mineComp' + }); + }); + } + }); + } + + // 处理 pubLibs(公共组件库) + if (Array.isArray(pubLibs)) { + pubLibs.forEach((lib: any) => { + if (lib.children && Array.isArray(lib.children)) { + lib.children.forEach((item: any) => { + result.push({ + ...item, + type: 'pubComp' + }); + }); + } + }); + } + + // 处理 teamLibs(协同组件库) + if (Array.isArray(teamLibs)) { + teamLibs.forEach((lib: any) => { + if (lib.children && Array.isArray(lib.children)) { + lib.children.forEach((item: any) => { + result.push({ + ...item, + type: 'teamWorkComp' + }); + }); + } + }); + } + + // 处理 myFlow(我的流程) + if (Array.isArray(myFlow)) { + myFlow.forEach((item: any) => { + result.push({ + ...item, + type: 'mineFlow' + }); + }); + } + + // 处理 pubFlow(公共流程) + if (Array.isArray(pubFlow)) { + pubFlow.forEach((item: any) => { + result.push({ + ...item, + type: 'pubFlow' + }); + }); + } + } + } catch (error) { + console.error('从 sessionStorage 获取组件数据失败:', error); + } + } return result; };