85 lines
2.4 KiB
TypeScript
85 lines
2.4 KiB
TypeScript
/**
|
||
* 日志流展示组件。
|
||
*
|
||
* - 等宽字体展示日志行
|
||
* - 自动滚动到底部(useRef + scrollIntoView)
|
||
* - 提供"暂停自动滚动"按钮(toggle)
|
||
*/
|
||
|
||
import React, { useEffect, useRef, useState } from "react";
|
||
import { Button } from "antd";
|
||
import { PauseCircleOutlined, PlayCircleOutlined } from "@ant-design/icons";
|
||
|
||
export interface LogStreamProps {
|
||
/** 可选的执行 ID,用于标题展示 */
|
||
executionId?: string;
|
||
/** 日志行数组 */
|
||
lines: string[];
|
||
}
|
||
|
||
const LogStream: React.FC<LogStreamProps> = ({ lines }) => {
|
||
const [autoscroll, setAutoscroll] = useState(true);
|
||
const bottomRef = useRef<HTMLDivElement>(null);
|
||
|
||
useEffect(() => {
|
||
if (autoscroll && bottomRef.current) {
|
||
bottomRef.current.scrollIntoView({ behavior: "smooth" });
|
||
}
|
||
}, [lines, autoscroll]);
|
||
|
||
const handleToggle = () => {
|
||
const next = !autoscroll;
|
||
setAutoscroll(next);
|
||
// 恢复时立即滚动到底部
|
||
if (next && bottomRef.current) {
|
||
bottomRef.current.scrollIntoView({ behavior: "smooth" });
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
|
||
<div style={{ marginBottom: 8, textAlign: "right" }}>
|
||
<Button
|
||
size="small"
|
||
icon={autoscroll ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
|
||
onClick={handleToggle}
|
||
>
|
||
{autoscroll ? "暂停滚动" : "恢复滚动"}
|
||
</Button>
|
||
</div>
|
||
<div
|
||
style={{
|
||
flex: 1,
|
||
overflow: "auto",
|
||
background: "#1e1e1e",
|
||
color: "#d4d4d4",
|
||
fontFamily: "'Cascadia Code', 'Fira Code', 'Consolas', monospace",
|
||
fontSize: 13,
|
||
lineHeight: 1.6,
|
||
padding: 12,
|
||
borderRadius: 4,
|
||
minHeight: 300,
|
||
}}
|
||
>
|
||
{lines.length === 0 ? (
|
||
<div style={{ color: "#888" }}>暂无日志</div>
|
||
) : (
|
||
lines.map((line, i) => {
|
||
let color = "#d4d4d4";
|
||
if (/\bERROR\b/i.test(line)) color = "#f56c6c";
|
||
else if (/\bWARN(?:ING)?\b/i.test(line)) color = "#e6a23c";
|
||
return (
|
||
<div key={i} style={{ whiteSpace: "pre-wrap", wordBreak: "break-all", color }}>
|
||
{line}
|
||
</div>
|
||
);
|
||
})
|
||
)}
|
||
<div ref={bottomRef} />
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default LogStream;
|