|
|
import React, { useState, useRef, useEffect } from 'react';
|
|
|
import { ResizeBox, Tabs } from '@arco-design/web-react';
|
|
|
import styles from './style/logBar.module.less';
|
|
|
import { updateLogBarStatus } from '@/store/ideContainer';
|
|
|
import { useSelector, useDispatch } from 'react-redux';
|
|
|
import { getNodeData } from '@/api/appIns';
|
|
|
import RunTimeData from './components/runTimeData';
|
|
|
import { getCurrentAppKey } from '@/utils/flow/runtime';
|
|
|
|
|
|
const TabPane = Tabs.TabPane;
|
|
|
|
|
|
interface LogMessage {
|
|
|
id: number;
|
|
|
type: string;
|
|
|
message: string;
|
|
|
timestamp: string;
|
|
|
}
|
|
|
|
|
|
// 添加运行数据接口
|
|
|
interface RuntimeData {
|
|
|
[appId: string]: any;
|
|
|
}
|
|
|
|
|
|
interface LogBarProps {
|
|
|
a?: string;
|
|
|
}
|
|
|
|
|
|
const data = [
|
|
|
{
|
|
|
key: '1',
|
|
|
title: '运行日志',
|
|
|
content: '运行时日志...',
|
|
|
},
|
|
|
{
|
|
|
key: '2',
|
|
|
title: '校验日志',
|
|
|
content: '校验日志...',
|
|
|
},
|
|
|
{
|
|
|
key: '3',
|
|
|
title: '运行数据',
|
|
|
content: '运行数据日志...',
|
|
|
},
|
|
|
// {
|
|
|
// key: '4',
|
|
|
// title: '组件日志',
|
|
|
// content: '组件日志...'
|
|
|
// }
|
|
|
];
|
|
|
|
|
|
const LogBar: React.FC<LogBarProps> = () => {
|
|
|
const [tabs] = useState(data);
|
|
|
const [activeTab, setActiveTab] = useState('1');
|
|
|
const resizeBoxRef = useRef<HTMLDivElement>(null); // 引用 ResizeBox 容器
|
|
|
const [validationLogs, setValidationLogs] = useState<LogMessage[]>([]);
|
|
|
const [runtimeLogs, setruntimeLogs] = useState<LogMessage[]>([]); // 添加运行时日志状态
|
|
|
const [logContainerHeight, setLogContainerHeight] = useState('250px'); // 添加日志容器高度状态
|
|
|
const [runtimeData, setRuntimeData] = useState<RuntimeData>({}); // 添加运行数据状态
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
const { logBarStatus, appRuntimeData, currentAppData } = useSelector(
|
|
|
(state: any) => state.ideContainer
|
|
|
);
|
|
|
|
|
|
const dispatch = useDispatch();
|
|
|
|
|
|
// 处理 Tab 点击事件
|
|
|
const handleTabClick = (key: string) => {
|
|
|
// 如果点击当前激活的 tab,则切换收起状态
|
|
|
if (key === activeTab) {
|
|
|
dispatch(updateLogBarStatus(!logBarStatus));
|
|
|
} else {
|
|
|
// 如果点击的是其他 tab,则切换到该 tab 并展开
|
|
|
setActiveTab(key);
|
|
|
dispatch(updateLogBarStatus(true));
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 当 collapsed 状态改变时,直接更新元素的样式
|
|
|
useEffect(() => {
|
|
|
if (resizeBoxRef.current) {
|
|
|
resizeBoxRef.current.style.height = logBarStatus
|
|
|
? logContainerHeight
|
|
|
: '0px';
|
|
|
}
|
|
|
}, [logBarStatus, logContainerHeight]);
|
|
|
|
|
|
// 处理 ResizeBox 手动调整大小事件
|
|
|
const handleResize = (
|
|
|
e: MouseEvent,
|
|
|
size: {
|
|
|
width: number;
|
|
|
height: number;
|
|
|
}
|
|
|
) => {
|
|
|
// 当高度接近收起状态的高度时,同步更新 logBarStatus 状态
|
|
|
if (size.height <= 40) {
|
|
|
dispatch(updateLogBarStatus(false));
|
|
|
} else {
|
|
|
dispatch(updateLogBarStatus(true));
|
|
|
// 更新日志容器高度状态
|
|
|
setLogContainerHeight(`${size.height}px`);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 监听日志消息事件
|
|
|
useEffect(() => {
|
|
|
const handleLogMessage = (event: CustomEvent) => {
|
|
|
const { type, message, timestamp, appId } = event.detail;
|
|
|
|
|
|
// 如果是校验类型的消息且当前校验日志tab可见,则添加到校验日志中
|
|
|
if (type === 'validation') {
|
|
|
const newLog: LogMessage = {
|
|
|
id: Date.now(),
|
|
|
type,
|
|
|
message,
|
|
|
timestamp,
|
|
|
};
|
|
|
|
|
|
setValidationLogs((prev) => [...prev, newLog]);
|
|
|
|
|
|
// 自动切换到校验日志tab并展开logBar
|
|
|
// setActiveTab('2');
|
|
|
dispatch(updateLogBarStatus(true));
|
|
|
}
|
|
|
// 如果是运行时日志
|
|
|
else if (type === 'runtime') {
|
|
|
const newLog: LogMessage = {
|
|
|
id: Date.now(),
|
|
|
type,
|
|
|
message,
|
|
|
timestamp,
|
|
|
};
|
|
|
|
|
|
setruntimeLogs((prev) => [...prev, newLog]);
|
|
|
|
|
|
// 自动切换到运行日志tab并展开logBar
|
|
|
dispatch(updateLogBarStatus(true));
|
|
|
|
|
|
// 同时将日志添加到对应应用的运行日志中
|
|
|
// 如果提供了 appId,优先使用提供的 appId;否则使用当前激活的应用
|
|
|
const targetAppKey = appId || getCurrentAppKey(currentAppData);
|
|
|
if (targetAppKey) {
|
|
|
dispatch({
|
|
|
type: 'ideContainer/addRuntimeLog',
|
|
|
payload: {
|
|
|
log: newLog,
|
|
|
appId: targetAppKey,
|
|
|
},
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 添加事件监听器
|
|
|
document.addEventListener('logMessage', handleLogMessage as EventListener);
|
|
|
|
|
|
// 清理事件监听器
|
|
|
return () => {
|
|
|
document.removeEventListener(
|
|
|
'logMessage',
|
|
|
handleLogMessage as EventListener
|
|
|
);
|
|
|
};
|
|
|
}, [dispatch, currentAppData]);
|
|
|
|
|
|
// 获取当前应用的运行状态
|
|
|
const currentAppKey = getCurrentAppKey(currentAppData);
|
|
|
const isRunning = currentAppKey && appRuntimeData[currentAppKey]?.isRunning;
|
|
|
|
|
|
// 实现轮询获取运行数据 - 只在应用运行时轮询
|
|
|
useEffect(() => {
|
|
|
let intervalId: NodeJS.Timeout | null = null;
|
|
|
const appKey = getCurrentAppKey(currentAppData);
|
|
|
|
|
|
// 只有在应用正在运行且有 runId 时才开始轮询
|
|
|
if (
|
|
|
appKey &&
|
|
|
appRuntimeData[appKey]?.isRunning &&
|
|
|
appRuntimeData[appKey]?.runId
|
|
|
) {
|
|
|
const fetchRuntimeData = async () => {
|
|
|
try {
|
|
|
setLoading(true);
|
|
|
const response = await getNodeData(appRuntimeData[appKey].runId);
|
|
|
setRuntimeData((prev) => ({
|
|
|
...prev,
|
|
|
[appKey]: response.data,
|
|
|
}));
|
|
|
} catch (error) {
|
|
|
console.error('获取运行数据失败:', error);
|
|
|
} finally {
|
|
|
setLoading(false);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 立即获取一次数据
|
|
|
fetchRuntimeData();
|
|
|
|
|
|
// 设置轮询,每3秒获取一次数据
|
|
|
intervalId = setInterval(fetchRuntimeData, 3000);
|
|
|
}
|
|
|
|
|
|
// 清理函数,组件卸载或应用停止运行时清除定时器
|
|
|
return () => {
|
|
|
if (intervalId) {
|
|
|
clearInterval(intervalId);
|
|
|
}
|
|
|
};
|
|
|
}, [currentAppData, appRuntimeData]);
|
|
|
|
|
|
// 当应用停止运行时,清除运行数据
|
|
|
useEffect(() => {
|
|
|
const appKey = getCurrentAppKey(currentAppData);
|
|
|
if (appKey && !appRuntimeData[appKey]?.isRunning) {
|
|
|
// 清除当前应用的运行数据
|
|
|
setRuntimeData((prev) => {
|
|
|
const newData = { ...prev };
|
|
|
delete newData[appKey];
|
|
|
return newData;
|
|
|
});
|
|
|
}
|
|
|
}, [isRunning, currentAppData]);
|
|
|
|
|
|
// 渲染校验日志内容
|
|
|
const renderValidationLogs = () => {
|
|
|
return (
|
|
|
<div
|
|
|
style={{
|
|
|
padding: '10px',
|
|
|
height: 'calc(100% - 40px)',
|
|
|
overflowY: 'auto',
|
|
|
}}
|
|
|
>
|
|
|
{validationLogs.length === 0 ? (
|
|
|
<p>暂无校验日志</p>
|
|
|
) : (
|
|
|
validationLogs.map((log) => (
|
|
|
<div
|
|
|
key={log.id}
|
|
|
style={{
|
|
|
marginBottom: '8px',
|
|
|
padding: '4px',
|
|
|
borderBottom: '1px solid #eee',
|
|
|
}}
|
|
|
>
|
|
|
<div style={{ fontSize: '12px', color: '#999' }}>
|
|
|
{new Date(log.timestamp).toLocaleString()}
|
|
|
</div>
|
|
|
<div style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
|
|
{log.message}
|
|
|
</div>
|
|
|
</div>
|
|
|
))
|
|
|
)}
|
|
|
</div>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
// 渲染运行时日志内容
|
|
|
const renderRuntimeLogs = () => {
|
|
|
// 获取当前应用的运行日志
|
|
|
const currentAppKey = getCurrentAppKey(currentAppData);
|
|
|
const currentAppLogs =
|
|
|
currentAppKey && appRuntimeData[currentAppKey]
|
|
|
? appRuntimeData[currentAppKey].logs || []
|
|
|
: [];
|
|
|
|
|
|
return (
|
|
|
<div
|
|
|
style={{
|
|
|
padding: '10px',
|
|
|
height: 'calc(100% - 40px)',
|
|
|
overflowY: 'auto',
|
|
|
}}
|
|
|
>
|
|
|
{currentAppLogs.length === 0 ? (
|
|
|
<p>暂无运行时日志</p>
|
|
|
) : (
|
|
|
currentAppLogs.map((log: LogMessage) => (
|
|
|
<div
|
|
|
key={log.id}
|
|
|
style={{
|
|
|
marginBottom: '8px',
|
|
|
padding: '4px',
|
|
|
borderBottom: '1px solid #eee',
|
|
|
}}
|
|
|
>
|
|
|
<div style={{ fontSize: '12px', color: '#999' }}>
|
|
|
{new Date(log.timestamp).toLocaleString()}
|
|
|
</div>
|
|
|
<div style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
|
|
{log.message}
|
|
|
</div>
|
|
|
</div>
|
|
|
))
|
|
|
)}
|
|
|
</div>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
// 渲染运行数据内容
|
|
|
const renderRuntimeData = () => {
|
|
|
const currentAppKey = getCurrentAppKey(currentAppData);
|
|
|
const currentAppDataContent = currentAppKey
|
|
|
? runtimeData[currentAppKey]
|
|
|
: null;
|
|
|
|
|
|
return (
|
|
|
<div
|
|
|
style={{
|
|
|
padding: '10px',
|
|
|
height: 'calc(100% - 40px)',
|
|
|
overflowY: 'auto',
|
|
|
}}
|
|
|
>
|
|
|
{!currentAppDataContent ? (
|
|
|
<p>暂无运行数据</p>
|
|
|
) : (
|
|
|
<RunTimeData logData={currentAppDataContent} />
|
|
|
)}
|
|
|
</div>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
<>
|
|
|
<ResizeBox
|
|
|
ref={resizeBoxRef}
|
|
|
className={styles.logBar}
|
|
|
directions={['top']}
|
|
|
style={{
|
|
|
height: logBarStatus ? logContainerHeight : '0px',
|
|
|
}}
|
|
|
onMoving={handleResize}
|
|
|
>
|
|
|
<Tabs
|
|
|
type="card-gutter"
|
|
|
activeTab={activeTab}
|
|
|
onClickTab={handleTabClick}
|
|
|
justify={true}
|
|
|
>
|
|
|
{tabs.map((x) => (
|
|
|
<TabPane destroyOnHide key={x.key} title={x.title}>
|
|
|
{x.key === '1'
|
|
|
? renderRuntimeLogs()
|
|
|
: x.key === '2'
|
|
|
? renderValidationLogs()
|
|
|
: x.key === '3'
|
|
|
? renderRuntimeData() // 添加运行数据渲染
|
|
|
: x.content}
|
|
|
</TabPane>
|
|
|
))}
|
|
|
</Tabs>
|
|
|
</ResizeBox>
|
|
|
</>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
export default LogBar;
|