You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

360 lines
9.8 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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;