import { memo } from 'react' import { cn } from '@/lib/utils' import type { LogEntry } from '@/types/crawler' interface TerminalLineProps { log: LogEntry } const levelConfig: Record = { info: { text: 'text-cyber-neon-cyan', bg: 'bg-cyber-neon-cyan/10', glow: 'shadow-[0_0_3px_rgba(0,255,255,0.3)]' }, success: { text: 'text-cyber-neon-green', bg: 'bg-cyber-neon-green/10', glow: 'shadow-[0_0_3px_rgba(0,255,65,0.3)]' }, warning: { text: 'text-cyber-neon-orange', bg: 'bg-cyber-neon-orange/10', glow: 'shadow-[0_0_3px_rgba(255,152,0,0.3)]' }, error: { text: 'text-cyber-neon-pink', bg: 'bg-cyber-neon-pink/10', glow: 'shadow-[0_0_3px_rgba(255,0,128,0.3)]' }, debug: { text: 'text-[#8b949e]', bg: 'bg-[#21262d]', glow: '' }, } const levelIcons: Record = { info: 'DATA', success: 'OK', warning: 'WARN', error: 'ERR', debug: 'DBG', } /** * 一行日志。 * * **必须 memo。** 日志是「来一条就 push 一条」,每来一条 store 里的数组就是新的, * 于是整个列表重渲染;而这个列表最多留 500 条 —— 不做 memo 就是每来一行重渲染 500 行, * 每行还要跑 5 次 `cn()`(tailwind-merge 不便宜)。爬虫在跑的时候日志是成片来的, * 主线程直接被占满,表现就是**点什么都没反应**(点折叠最容易注意到,因为那一下还要 * 把整份列表挂载/卸载一遍)。 * * `log` 对象在数组里是稳定引用,所以 memo 之后只有新来的那一行会渲染。 */ export const TerminalLine = memo(function TerminalLine({ log }: TerminalLineProps) { const config = levelConfig[log.level] || levelConfig.info return (
{/* Timestamp */} [{log.timestamp}] {/* Level badge */} [{levelIcons[log.level]}] {/* Message */} {log.message}
) })