From d6c7d64e5924c2121886afbdf76e8eb4ace9adcf Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 21 Nov 2025 18:11:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BA=E6=80=A7=E8=83=BD?= =?UTF-8?q?=E9=98=88=E5=80=BC=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/performanceThresholds.js | 306 +++++++++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 src/constants/performanceThresholds.js diff --git a/src/constants/performanceThresholds.js b/src/constants/performanceThresholds.js new file mode 100644 index 00000000..28f2ee23 --- /dev/null +++ b/src/constants/performanceThresholds.js @@ -0,0 +1,306 @@ +/** + * 性能指标阈值配置 + * 基于 Google Web Vitals 标准和项目实际情况 + * + * 评级标准: + * - good: 绿色,性能优秀 + * - needs-improvement: 黄色,需要改进 + * - poor: 红色,性能较差 + * + * @see https://web.dev/defining-core-web-vitals-thresholds/ + */ + +// ============================================================ +// Web Vitals 官方阈值(Google 标准) +// ============================================================ + +/** + * Largest Contentful Paint (LCP) - 最大内容绘制 + * 衡量加载性能,理想情况下应在 2.5 秒内完成 + */ +export const LCP_THRESHOLDS = { + good: 2500, // < 2.5s 为优秀 + needsImprovement: 4000, // 2.5s - 4s 需要改进 + // > 4s 为较差 +}; + +/** + * First Contentful Paint (FCP) - 首次内容绘制 + * 衡量首次渲染任何内容的速度 + */ +export const FCP_THRESHOLDS = { + good: 1800, // < 1.8s 为优秀 + needsImprovement: 3000, // 1.8s - 3s 需要改进 + // > 3s 为较差 +}; + +/** + * Cumulative Layout Shift (CLS) - 累积布局偏移 + * 衡量视觉稳定性(无单位,分数值) + */ +export const CLS_THRESHOLDS = { + good: 0.1, // < 0.1 为优秀 + needsImprovement: 0.25, // 0.1 - 0.25 需要改进 + // > 0.25 为较差 +}; + +/** + * First Input Delay (FID) - 首次输入延迟 + * 衡量交互性能 + */ +export const FID_THRESHOLDS = { + good: 100, // < 100ms 为优秀 + needsImprovement: 300, // 100ms - 300ms 需要改进 + // > 300ms 为较差 +}; + +/** + * Time to First Byte (TTFB) - 首字节时间 + * 衡量服务器响应速度 + */ +export const TTFB_THRESHOLDS = { + good: 800, // < 0.8s 为优秀 + needsImprovement: 1800, // 0.8s - 1.8s 需要改进 + // > 1.8s 为较差 +}; + +// ============================================================ +// 自定义指标阈值(项目特定) +// ============================================================ + +/** + * Time to Interactive (TTI) - 首屏可交互时间 + * 自定义指标:从页面加载到用户可以交互的时间 + */ +export const TTI_THRESHOLDS = { + good: 3500, // < 3.5s 为优秀 + needsImprovement: 7300, // 3.5s - 7.3s 需要改进 + // > 7.3s 为较差 +}; + +/** + * 骨架屏展示时长阈值 + */ +export const SKELETON_DURATION_THRESHOLDS = { + good: 300, // < 0.3s 为优秀(骨架屏展示时间短) + needsImprovement: 1000, // 0.3s - 1s 需要改进 + // > 1s 为较差(骨架屏展示太久) +}; + +/** + * API 响应时间阈值 + */ +export const API_RESPONSE_TIME_THRESHOLDS = { + good: 500, // < 500ms 为优秀 + needsImprovement: 1500, // 500ms - 1.5s 需要改进 + // > 1.5s 为较差 +}; + +/** + * 资源加载时间阈值 + */ +export const RESOURCE_LOAD_TIME_THRESHOLDS = { + good: 2000, // < 2s 为优秀 + needsImprovement: 5000, // 2s - 5s 需要改进 + // > 5s 为较差 +}; + +/** + * Bundle 大小阈值(KB) + */ +export const BUNDLE_SIZE_THRESHOLDS = { + js: { + good: 500, // < 500KB 为优秀 + needsImprovement: 1000, // 500KB - 1MB 需要改进 + // > 1MB 为较差 + }, + css: { + good: 100, // < 100KB 为优秀 + needsImprovement: 200, // 100KB - 200KB 需要改进 + // > 200KB 为较差 + }, + image: { + good: 1500, // < 1.5MB 为优秀 + needsImprovement: 3000, // 1.5MB - 3MB 需要改进 + // > 3MB 为较差 + }, +}; + +/** + * 缓存命中率阈值(百分比) + */ +export const CACHE_HIT_RATE_THRESHOLDS = { + good: 80, // > 80% 为优秀 + needsImprovement: 50, // 50% - 80% 需要改进 + // < 50% 为较差 +}; + +// ============================================================ +// 综合阈值配置对象 +// ============================================================ + +/** + * 所有性能指标的阈值配置(用于类型化访问) + */ +export const PERFORMANCE_THRESHOLDS = { + LCP: LCP_THRESHOLDS, + FCP: FCP_THRESHOLDS, + CLS: CLS_THRESHOLDS, + FID: FID_THRESHOLDS, + TTFB: TTFB_THRESHOLDS, + TTI: TTI_THRESHOLDS, + SKELETON_DURATION: SKELETON_DURATION_THRESHOLDS, + API_RESPONSE_TIME: API_RESPONSE_TIME_THRESHOLDS, + RESOURCE_LOAD_TIME: RESOURCE_LOAD_TIME_THRESHOLDS, + BUNDLE_SIZE: BUNDLE_SIZE_THRESHOLDS, + CACHE_HIT_RATE: CACHE_HIT_RATE_THRESHOLDS, +}; + +// ============================================================ +// 工具函数 +// ============================================================ + +/** + * 根据指标值和阈值计算评级 + * @param {number} value - 指标值 + * @param {Object} thresholds - 阈值配置对象 { good, needsImprovement } + * @param {boolean} reverse - 是否反向评级(值越大越好,如缓存命中率) + * @returns {'good' | 'needs-improvement' | 'poor'} + */ +export const calculateRating = (value, thresholds, reverse = false) => { + if (!thresholds || typeof value !== 'number') { + return 'poor'; + } + + const { good, needsImprovement } = thresholds; + + if (reverse) { + // 反向评级:值越大越好(如缓存命中率) + if (value >= good) return 'good'; + if (value >= needsImprovement) return 'needs-improvement'; + return 'poor'; + } else { + // 正常评级:值越小越好(如加载时间) + if (value <= good) return 'good'; + if (value <= needsImprovement) return 'needs-improvement'; + return 'poor'; + } +}; + +/** + * 获取评级对应的颜色(Chakra UI 颜色方案) + * @param {'good' | 'needs-improvement' | 'poor'} rating + * @returns {string} Chakra UI 颜色名称 + */ +export const getRatingColor = (rating) => { + switch (rating) { + case 'good': + return 'green'; + case 'needs-improvement': + return 'yellow'; + case 'poor': + return 'red'; + default: + return 'gray'; + } +}; + +/** + * 获取评级对应的控制台颜色代码 + * @param {'good' | 'needs-improvement' | 'poor'} rating + * @returns {string} ANSI 颜色代码 + */ +export const getRatingConsoleColor = (rating) => { + switch (rating) { + case 'good': + return '\x1b[32m'; // 绿色 + case 'needs-improvement': + return '\x1b[33m'; // 黄色 + case 'poor': + return '\x1b[31m'; // 红色 + default: + return '\x1b[0m'; // 重置 + } +}; + +/** + * 获取评级对应的图标 + * @param {'good' | 'needs-improvement' | 'poor'} rating + * @returns {string} Emoji 图标 + */ +export const getRatingIcon = (rating) => { + switch (rating) { + case 'good': + return '✅'; + case 'needs-improvement': + return '⚠️'; + case 'poor': + return '❌'; + default: + return '❓'; + } +}; + +/** + * 格式化指标值(添加单位) + * @param {string} metricName - 指标名称 + * @param {number} value - 指标值 + * @returns {string} 格式化后的字符串 + */ +export const formatMetricValue = (metricName, value) => { + if (typeof value !== 'number' || isNaN(value)) { + return 'N/A'; + } + + switch (metricName) { + case 'LCP': + case 'FCP': + case 'FID': + case 'TTFB': + case 'TTI': + case 'SKELETON_DURATION': + case 'API_RESPONSE_TIME': + case 'RESOURCE_LOAD_TIME': + // 时间类指标:转换为秒或毫秒 + return value >= 1000 + ? `${(value / 1000).toFixed(2)}s` + : `${Math.round(value)}ms`; + + case 'CLS': + // CLS 是无单位的分数 + return value.toFixed(3); + + case 'CACHE_HIT_RATE': + // 百分比 + return `${value.toFixed(1)}%`; + + default: + // 默认保留两位小数 + return value.toFixed(2); + } +}; + +/** + * 批量计算所有 Web Vitals 指标的评级 + * @param {Object} metrics - 指标对象 { LCP: value, FCP: value, ... } + * @returns {Object} 评级对象 { LCP: 'good', FCP: 'needs-improvement', ... } + */ +export const calculateAllRatings = (metrics) => { + const ratings = {}; + + Object.entries(metrics).forEach(([metricName, value]) => { + const thresholds = PERFORMANCE_THRESHOLDS[metricName]; + if (thresholds) { + const isReverse = metricName === 'CACHE_HIT_RATE'; // 缓存命中率是反向评级 + ratings[metricName] = calculateRating(value, thresholds, isReverse); + } + }); + + return ratings; +}; + +// ============================================================ +// 默认导出 +// ============================================================ + +export default PERFORMANCE_THRESHOLDS;