import { useState, useEffect, useRef, useCallback } from 'react'; import styles from './DebugConsole.module.css'; interface LogEntry { id: number; timestamp: string; level: 'info' | 'warn' | 'error' | 'debug'; source: 'main' | 'renderer' | 'grpc' | 'tss' | 'account'; message: string; } interface DebugConsoleProps { isOpen: boolean; onClose: () => void; } let logIdCounter = 0; export default function DebugConsole({ isOpen, onClose }: DebugConsoleProps) { const [logs, setLogs] = useState([]); const [filter, setFilter] = useState('all'); const [autoScroll, setAutoScroll] = useState(true); const [isPaused, setIsPaused] = useState(false); const logsEndRef = useRef(null); const containerRef = useRef(null); const addLog = useCallback((entry: Omit) => { if (isPaused) return; const now = new Date(); const ms = now.getMilliseconds().toString().padStart(3, '0'); const newEntry: LogEntry = { ...entry, id: ++logIdCounter, timestamp: `${now.toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', })}.${ms}`, }; setLogs(prev => { const newLogs = [...prev, newEntry]; // 保留最近 500 条日志 if (newLogs.length > 500) { return newLogs.slice(-500); } return newLogs; }); }, [isPaused]); useEffect(() => { if (!isOpen) return; // 订阅主进程的日志 const handleMainLog = (_event: unknown, data: { level: string; source: string; message: string }) => { addLog({ level: data.level as LogEntry['level'], source: data.source as LogEntry['source'], message: data.message, }); }; // 使用 IPC 监听日志事件 window.electronAPI?.debug?.subscribeLogs(handleMainLog); // 拦截 console 方法来捕获前端日志 const originalConsole = { log: console.log, warn: console.warn, error: console.error, debug: console.debug, }; console.log = (...args) => { originalConsole.log(...args); addLog({ level: 'info', source: 'renderer', message: args.map(String).join(' ') }); }; console.warn = (...args) => { originalConsole.warn(...args); addLog({ level: 'warn', source: 'renderer', message: args.map(String).join(' ') }); }; console.error = (...args) => { originalConsole.error(...args); addLog({ level: 'error', source: 'renderer', message: args.map(String).join(' ') }); }; console.debug = (...args) => { originalConsole.debug(...args); addLog({ level: 'debug', source: 'renderer', message: args.map(String).join(' ') }); }; return () => { // 恢复原始 console console.log = originalConsole.log; console.warn = originalConsole.warn; console.error = originalConsole.error; console.debug = originalConsole.debug; window.electronAPI?.debug?.unsubscribeLogs(); }; }, [isOpen, addLog]); useEffect(() => { if (autoScroll && logsEndRef.current) { logsEndRef.current.scrollIntoView({ behavior: 'smooth' }); } }, [logs, autoScroll]); const handleScroll = () => { if (!containerRef.current) return; const { scrollTop, scrollHeight, clientHeight } = containerRef.current; // 如果用户手动滚动到顶部,暂停自动滚动 setAutoScroll(scrollHeight - scrollTop - clientHeight < 50); }; const clearLogs = () => { setLogs([]); logIdCounter = 0; }; const filteredLogs = logs.filter(log => { if (filter === 'all') return true; if (filter === 'errors') return log.level === 'error' || log.level === 'warn'; return log.source === filter; }); const getLevelClass = (level: string) => { switch (level) { case 'error': return styles.error; case 'warn': return styles.warn; case 'debug': return styles.debug; default: return styles.info; } }; const getSourceColor = (source: string) => { switch (source) { case 'main': return '#61afef'; case 'grpc': return '#c678dd'; case 'tss': return '#e5c07b'; case 'account': return '#56b6c2'; default: return '#98c379'; } }; if (!isOpen) return null; return (

🔧 Debug Console

{filteredLogs.length === 0 ? (
No logs yet...
) : ( filteredLogs.map(log => (
{log.timestamp} [{log.source.toUpperCase()}] {log.message}
)) )}
{filteredLogs.length} logs Press F12 for DevTools
); }