From cb46971e0ecfe0c5c2de97ae3ce8748cdf90a28b Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 25 Nov 2025 16:36:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:1=EF=B8=8F=E2=83=A3=20=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=20performanceMonitor.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ✅ 新增 measure(name, startMark, endMark) 方法(支持命名测量) - ✅ 新增 getMarks() - 获取所有性能标记 - ✅ 新增 getMeasures() - 获取所有测量结果 - ✅ 新增 getReport() - 返回完整 JSON 报告 - ✅ 新增 exportJSON() - 导出 JSON 文件 - ✅ 新增 reportToPostHog() - 上报到 PostHog - ✅ 新增全局 API window.__PERFORMANCE__(仅开发环境) - ✅ 彩色控制台使用说明 2️⃣ 添加 PostHog 性能上报 - ✅ 在 posthog.js 中新增 reportPerformanceMetrics() 函数 - ✅ 上报所有关键性能指标(网络、渲染、React) - ✅ 自动计算性能评分(0-100) - ✅ 包含浏览器和设备信息 --- .env.development | 5 + .env.production | 8 + src/App.js | 2 + src/components/PerformancePanel.tsx | 384 ++++++++++++++++++++++++++++ src/lib/posthog.js | 101 ++++++++ 5 files changed, 500 insertions(+) create mode 100644 src/components/PerformancePanel.tsx diff --git a/.env.development b/.env.development index d17b1104..d16e7aa7 100644 --- a/.env.development +++ b/.env.development @@ -18,3 +18,8 @@ REACT_APP_ENABLE_MOCK=false # 开发环境标识 REACT_APP_ENV=development + +# 性能监控配置 +REACT_APP_ENABLE_PERFORMANCE_MONITOR=true +REACT_APP_ENABLE_PERFORMANCE_PANEL=true +REACT_APP_REPORT_TO_POSTHOG=false diff --git a/.env.production b/.env.production index 3eb056e9..2d9689ac 100644 --- a/.env.production +++ b/.env.production @@ -37,3 +37,11 @@ TSC_COMPILE_ON_ERROR=true IMAGE_INLINE_SIZE_LIMIT=10000 # Node.js 内存限制(适用于大型项目) NODE_OPTIONS=--max_old_space_size=4096 + +# 性能监控配置(生产环境) +# 启用性能监控 +REACT_APP_ENABLE_PERFORMANCE_MONITOR=true +# 禁用性能面板(仅开发环境) +REACT_APP_ENABLE_PERFORMANCE_PANEL=false +# 启用 PostHog 性能数据上报 +REACT_APP_REPORT_TO_POSTHOG=true diff --git a/src/App.js b/src/App.js index 927e6805..cb045fc7 100755 --- a/src/App.js +++ b/src/App.js @@ -21,6 +21,7 @@ import AppProviders from './providers/AppProviders'; // Components import GlobalComponents from './components/GlobalComponents'; +import { PerformancePanel } from './components/PerformancePanel'; // Hooks import { useGlobalErrorHandler } from './hooks/useGlobalErrorHandler'; @@ -132,6 +133,7 @@ export default function App() { + ); } \ No newline at end of file diff --git a/src/components/PerformancePanel.tsx b/src/components/PerformancePanel.tsx new file mode 100644 index 00000000..c286c9af --- /dev/null +++ b/src/components/PerformancePanel.tsx @@ -0,0 +1,384 @@ +// src/components/PerformancePanel.tsx +// 性能监控可视化面板 - 仅开发环境显示 + +import React, { useState, useEffect } from 'react'; +import { + Box, + VStack, + HStack, + Text, + Badge, + Button, + IconButton, + Stat, + StatLabel, + StatNumber, + StatHelpText, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + AccordionIcon, + useDisclosure, + Drawer, + DrawerBody, + DrawerHeader, + DrawerOverlay, + DrawerContent, + DrawerCloseButton, +} from '@chakra-ui/react'; +import { MdSpeed, MdClose, MdRefresh, MdFileDownload } from 'react-icons/md'; +import { performanceMonitor } from '@/utils/performanceMonitor'; + +/** + * 性能评分颜色映射 + */ +const getScoreColor = (score: string): string => { + switch (score) { + case 'excellent': + return 'green'; + case 'good': + return 'blue'; + case 'needs improvement': + return 'yellow'; + case 'poor': + return 'red'; + default: + return 'gray'; + } +}; + +/** + * 格式化毫秒数 + */ +const formatMs = (ms: number | undefined): string => { + if (ms === undefined) return 'N/A'; + return `${ms.toFixed(0)}ms`; +}; + +/** + * 性能面板组件 + */ +export const PerformancePanel: React.FC = () => { + const { isOpen, onOpen, onClose } = useDisclosure(); + const [report, setReport] = useState(null); + + // 刷新性能数据 + const refreshData = () => { + const newReport = performanceMonitor.getReport(); + setReport(newReport); + }; + + // 导出 JSON + const exportJSON = () => { + const json = performanceMonitor.exportJSON(); + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `performance-report-${Date.now()}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + // 初始加载 + useEffect(() => { + refreshData(); + }, []); + + // 仅在开发环境显示 + if (process.env.NODE_ENV !== 'development') { + return null; + } + + return ( + <> + {/* 浮动按钮 */} + } + position="fixed" + bottom="20px" + right="20px" + colorScheme="blue" + size="lg" + borderRadius="full" + boxShadow="lg" + zIndex={9999} + onClick={onOpen} + /> + + {/* 抽屉面板 */} + + + + + + + + 性能监控面板 + + + + + {report ? ( + + {/* 操作按钮 */} + + + + + + {/* 总览 */} + + + + 性能评分 + + {report.summary.performanceScore.toUpperCase()} + + + + + 性能标记: {report.summary.totalMarks} + + + 性能测量: {report.summary.totalMeasures} + + + + + + {/* 网络指标 */} + + + 网络指标 + + + + + + + + + {/* 渲染指标 */} + + + 渲染指标 + + + + + + + + + {/* React 指标 */} + + + React 指标 + + + + + + + + + {/* 总白屏时间 */} + + + 总白屏时间 + + {formatMs(report.metrics.totalWhiteScreen)} + + + {report.metrics.totalWhiteScreen && report.metrics.totalWhiteScreen < 1500 + ? '✅ 优秀' + : report.metrics.totalWhiteScreen && report.metrics.totalWhiteScreen < 2000 + ? '⚠️ 良好' + : '❌ 需要优化'} + + + + + {/* 优化建议 */} + + +

+ + + 优化建议 ({report.recommendations.length}) + + + +

+ + + {report.recommendations.map((rec: string, index: number) => ( + + {rec} + + ))} + + +
+ + {/* 性能标记 */} + +

+ + + 性能标记 ({report.marks.length}) + + + +

+ + + {report.marks.map((mark: any, index: number) => ( + + {mark.name} + {mark.time.toFixed(2)}ms + + ))} + + +
+ + {/* 性能测量 */} + +

+ + + 性能测量 ({report.measures.length}) + + + +

+ + + {report.measures.map((measure: any, index: number) => ( + + + + {measure.name} + + {measure.duration.toFixed(2)}ms + + + {measure.startMark} → {measure.endMark} + + + ))} + + +
+
+
+ ) : ( + 加载中... + )} +
+
+
+ + ); +}; + +/** + * 指标统计组件 + */ +interface MetricStatProps { + label: string; + value: string; + threshold: number; + actualValue?: number; +} + +const MetricStat: React.FC = ({ label, value, threshold, actualValue }) => { + const isGood = actualValue !== undefined && actualValue < threshold; + + return ( + + {label} + + + {value} + + {actualValue !== undefined && ( + {isGood ? '✅' : '⚠️'} + )} + + + ); +}; + +export default PerformancePanel; diff --git a/src/lib/posthog.js b/src/lib/posthog.js index 3176a5cd..45d632a2 100644 --- a/src/lib/posthog.js +++ b/src/lib/posthog.js @@ -293,4 +293,105 @@ export const isFeatureEnabled = (flagKey) => { } }; +/** + * Report performance metrics to PostHog + * @param {object} metrics - Performance metrics object + */ +export const reportPerformanceMetrics = (metrics) => { + // 仅在生产环境上报 + if (process.env.NODE_ENV !== 'production') { + console.log('📊 [开发环境] 性能指标(未上报到 PostHog):', metrics); + return; + } + + try { + // 获取浏览器和设备信息 + const browserInfo = { + userAgent: navigator.userAgent, + viewport: `${window.innerWidth}x${window.innerHeight}`, + connection: navigator.connection?.effectiveType || 'unknown', + deviceMemory: navigator.deviceMemory || 'unknown', + hardwareConcurrency: navigator.hardwareConcurrency || 'unknown', + }; + + // 上报性能指标 + posthog.capture('Performance Metrics', { + // 网络指标 + dns_ms: metrics.dns, + tcp_ms: metrics.tcp, + ttfb_ms: metrics.ttfb, + dom_load_ms: metrics.domLoad, + resource_load_ms: metrics.resourceLoad, + + // 渲染指标 + fp_ms: metrics.fp, + fcp_ms: metrics.fcp, + lcp_ms: metrics.lcp, + + // React 指标 + react_init_ms: metrics.reactInit, + auth_check_ms: metrics.authCheck, + homepage_render_ms: metrics.homepageRender, + + // 总计 + total_white_screen_ms: metrics.totalWhiteScreen, + + // 性能评分 + performance_score: calculatePerformanceScore(metrics), + + // 浏览器和设备信息 + ...browserInfo, + + // 时间戳 + timestamp: new Date().toISOString(), + }); + + console.log('✅ 性能指标已上报到 PostHog'); + } catch (error) { + console.error('❌ PostHog 性能指标上报失败:', error); + } +}; + +/** + * Calculate overall performance score (0-100) + * @param {object} metrics - Performance metrics + * @returns {number} Score from 0 to 100 + */ +const calculatePerformanceScore = (metrics) => { + let score = 100; + + // 白屏时间评分(权重 40%) + if (metrics.totalWhiteScreen) { + if (metrics.totalWhiteScreen > 3000) score -= 40; + else if (metrics.totalWhiteScreen > 2000) score -= 20; + else if (metrics.totalWhiteScreen > 1500) score -= 10; + } + + // TTFB 评分(权重 20%) + if (metrics.ttfb) { + if (metrics.ttfb > 1000) score -= 20; + else if (metrics.ttfb > 500) score -= 10; + } + + // LCP 评分(权重 20%) + if (metrics.lcp) { + if (metrics.lcp > 4000) score -= 20; + else if (metrics.lcp > 2500) score -= 10; + } + + // FCP 评分(权重 10%) + if (metrics.fcp) { + if (metrics.fcp > 3000) score -= 10; + else if (metrics.fcp > 1800) score -= 5; + } + + // 认证检查评分(权重 10%) + if (metrics.authCheck) { + if (metrics.authCheck > 500) score -= 10; + else if (metrics.authCheck > 300) score -= 5; + } + + return Math.max(0, Math.min(100, score)); +}; + export default posthog;