feat:1️⃣ 增强 performanceMonitor.ts
- ✅ 新增 measure(name, startMark, endMark) 方法(支持命名测量) - ✅ 新增 getMarks() - 获取所有性能标记 - ✅ 新增 getMeasures() - 获取所有测量结果 - ✅ 新增 getReport() - 返回完整 JSON 报告 - ✅ 新增 exportJSON() - 导出 JSON 文件 - ✅ 新增 reportToPostHog() - 上报到 PostHog - ✅ 新增全局 API window.__PERFORMANCE__(仅开发环境) - ✅ 彩色控制台使用说明 2️⃣ 添加 PostHog 性能上报 - ✅ 在 posthog.js 中新增 reportPerformanceMetrics() 函数 - ✅ 上报所有关键性能指标(网络、渲染、React) - ✅ 自动计算性能评分(0-100) - ✅ 包含浏览器和设备信息
This commit is contained in:
@@ -18,3 +18,8 @@ REACT_APP_ENABLE_MOCK=false
|
|||||||
|
|
||||||
# 开发环境标识
|
# 开发环境标识
|
||||||
REACT_APP_ENV=development
|
REACT_APP_ENV=development
|
||||||
|
|
||||||
|
# 性能监控配置
|
||||||
|
REACT_APP_ENABLE_PERFORMANCE_MONITOR=true
|
||||||
|
REACT_APP_ENABLE_PERFORMANCE_PANEL=true
|
||||||
|
REACT_APP_REPORT_TO_POSTHOG=false
|
||||||
|
|||||||
@@ -37,3 +37,11 @@ TSC_COMPILE_ON_ERROR=true
|
|||||||
IMAGE_INLINE_SIZE_LIMIT=10000
|
IMAGE_INLINE_SIZE_LIMIT=10000
|
||||||
# Node.js 内存限制(适用于大型项目)
|
# Node.js 内存限制(适用于大型项目)
|
||||||
NODE_OPTIONS=--max_old_space_size=4096
|
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
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import AppProviders from './providers/AppProviders';
|
|||||||
|
|
||||||
// Components
|
// Components
|
||||||
import GlobalComponents from './components/GlobalComponents';
|
import GlobalComponents from './components/GlobalComponents';
|
||||||
|
import { PerformancePanel } from './components/PerformancePanel';
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useGlobalErrorHandler } from './hooks/useGlobalErrorHandler';
|
import { useGlobalErrorHandler } from './hooks/useGlobalErrorHandler';
|
||||||
@@ -132,6 +133,7 @@ export default function App() {
|
|||||||
<AppProviders>
|
<AppProviders>
|
||||||
<AppContent />
|
<AppContent />
|
||||||
<GlobalComponents />
|
<GlobalComponents />
|
||||||
|
<PerformancePanel />
|
||||||
</AppProviders>
|
</AppProviders>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
384
src/components/PerformancePanel.tsx
Normal file
384
src/components/PerformancePanel.tsx
Normal file
@@ -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<any>(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 (
|
||||||
|
<>
|
||||||
|
{/* 浮动按钮 */}
|
||||||
|
<IconButton
|
||||||
|
aria-label="Open performance panel"
|
||||||
|
icon={<MdSpeed />}
|
||||||
|
position="fixed"
|
||||||
|
bottom="20px"
|
||||||
|
right="20px"
|
||||||
|
colorScheme="blue"
|
||||||
|
size="lg"
|
||||||
|
borderRadius="full"
|
||||||
|
boxShadow="lg"
|
||||||
|
zIndex={9999}
|
||||||
|
onClick={onOpen}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 抽屉面板 */}
|
||||||
|
<Drawer isOpen={isOpen} placement="right" onClose={onClose} size="lg">
|
||||||
|
<DrawerOverlay />
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerCloseButton />
|
||||||
|
<DrawerHeader borderBottomWidth="1px">
|
||||||
|
<HStack>
|
||||||
|
<MdSpeed size={24} />
|
||||||
|
<Text>性能监控面板</Text>
|
||||||
|
</HStack>
|
||||||
|
</DrawerHeader>
|
||||||
|
|
||||||
|
<DrawerBody>
|
||||||
|
{report ? (
|
||||||
|
<VStack spacing={4} align="stretch" py={4}>
|
||||||
|
{/* 操作按钮 */}
|
||||||
|
<HStack spacing={2}>
|
||||||
|
<Button
|
||||||
|
leftIcon={<MdRefresh />}
|
||||||
|
size="sm"
|
||||||
|
colorScheme="blue"
|
||||||
|
onClick={refreshData}
|
||||||
|
>
|
||||||
|
刷新数据
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
leftIcon={<MdFileDownload />}
|
||||||
|
size="sm"
|
||||||
|
colorScheme="green"
|
||||||
|
onClick={exportJSON}
|
||||||
|
>
|
||||||
|
导出 JSON
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 总览 */}
|
||||||
|
<Box p={4} bg="gray.50" borderRadius="md">
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<Text fontWeight="bold">性能评分</Text>
|
||||||
|
<Badge
|
||||||
|
colorScheme={getScoreColor(report.summary.performanceScore)}
|
||||||
|
fontSize="md"
|
||||||
|
px={3}
|
||||||
|
py={1}
|
||||||
|
borderRadius="full"
|
||||||
|
>
|
||||||
|
{report.summary.performanceScore.toUpperCase()}
|
||||||
|
</Badge>
|
||||||
|
</HStack>
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<Text fontSize="sm" color="gray.600">
|
||||||
|
性能标记: {report.summary.totalMarks}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="sm" color="gray.600">
|
||||||
|
性能测量: {report.summary.totalMeasures}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 网络指标 */}
|
||||||
|
<Box>
|
||||||
|
<Text fontWeight="bold" mb={2}>
|
||||||
|
网络指标
|
||||||
|
</Text>
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
<MetricStat
|
||||||
|
label="DNS 查询"
|
||||||
|
value={formatMs(report.metrics.dns)}
|
||||||
|
threshold={100}
|
||||||
|
actualValue={report.metrics.dns}
|
||||||
|
/>
|
||||||
|
<MetricStat
|
||||||
|
label="TCP 连接"
|
||||||
|
value={formatMs(report.metrics.tcp)}
|
||||||
|
threshold={100}
|
||||||
|
actualValue={report.metrics.tcp}
|
||||||
|
/>
|
||||||
|
<MetricStat
|
||||||
|
label="TTFB"
|
||||||
|
value={formatMs(report.metrics.ttfb)}
|
||||||
|
threshold={500}
|
||||||
|
actualValue={report.metrics.ttfb}
|
||||||
|
/>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 渲染指标 */}
|
||||||
|
<Box>
|
||||||
|
<Text fontWeight="bold" mb={2}>
|
||||||
|
渲染指标
|
||||||
|
</Text>
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
<MetricStat
|
||||||
|
label="FP (首次绘制)"
|
||||||
|
value={formatMs(report.metrics.fp)}
|
||||||
|
threshold={1000}
|
||||||
|
actualValue={report.metrics.fp}
|
||||||
|
/>
|
||||||
|
<MetricStat
|
||||||
|
label="FCP (首次内容绘制)"
|
||||||
|
value={formatMs(report.metrics.fcp)}
|
||||||
|
threshold={1800}
|
||||||
|
actualValue={report.metrics.fcp}
|
||||||
|
/>
|
||||||
|
<MetricStat
|
||||||
|
label="LCP (最大内容绘制)"
|
||||||
|
value={formatMs(report.metrics.lcp)}
|
||||||
|
threshold={2500}
|
||||||
|
actualValue={report.metrics.lcp}
|
||||||
|
/>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* React 指标 */}
|
||||||
|
<Box>
|
||||||
|
<Text fontWeight="bold" mb={2}>
|
||||||
|
React 指标
|
||||||
|
</Text>
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
<MetricStat
|
||||||
|
label="React 初始化"
|
||||||
|
value={formatMs(report.metrics.reactInit)}
|
||||||
|
threshold={1000}
|
||||||
|
actualValue={report.metrics.reactInit}
|
||||||
|
/>
|
||||||
|
<MetricStat
|
||||||
|
label="认证检查"
|
||||||
|
value={formatMs(report.metrics.authCheck)}
|
||||||
|
threshold={300}
|
||||||
|
actualValue={report.metrics.authCheck}
|
||||||
|
/>
|
||||||
|
<MetricStat
|
||||||
|
label="首页渲染"
|
||||||
|
value={formatMs(report.metrics.homepageRender)}
|
||||||
|
threshold={500}
|
||||||
|
actualValue={report.metrics.homepageRender}
|
||||||
|
/>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 总白屏时间 */}
|
||||||
|
<Box p={4} bg="blue.50" borderRadius="md" borderWidth="2px" borderColor="blue.200">
|
||||||
|
<Stat>
|
||||||
|
<StatLabel>总白屏时间</StatLabel>
|
||||||
|
<StatNumber fontSize="3xl">
|
||||||
|
{formatMs(report.metrics.totalWhiteScreen)}
|
||||||
|
</StatNumber>
|
||||||
|
<StatHelpText>
|
||||||
|
{report.metrics.totalWhiteScreen && report.metrics.totalWhiteScreen < 1500
|
||||||
|
? '✅ 优秀'
|
||||||
|
: report.metrics.totalWhiteScreen && report.metrics.totalWhiteScreen < 2000
|
||||||
|
? '⚠️ 良好'
|
||||||
|
: '❌ 需要优化'}
|
||||||
|
</StatHelpText>
|
||||||
|
</Stat>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 优化建议 */}
|
||||||
|
<Accordion allowToggle>
|
||||||
|
<AccordionItem>
|
||||||
|
<h2>
|
||||||
|
<AccordionButton>
|
||||||
|
<Box flex="1" textAlign="left" fontWeight="bold">
|
||||||
|
优化建议 ({report.recommendations.length})
|
||||||
|
</Box>
|
||||||
|
<AccordionIcon />
|
||||||
|
</AccordionButton>
|
||||||
|
</h2>
|
||||||
|
<AccordionPanel pb={4}>
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
{report.recommendations.map((rec: string, index: number) => (
|
||||||
|
<Text key={index} fontSize="sm">
|
||||||
|
{rec}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
{/* 性能标记 */}
|
||||||
|
<AccordionItem>
|
||||||
|
<h2>
|
||||||
|
<AccordionButton>
|
||||||
|
<Box flex="1" textAlign="left" fontWeight="bold">
|
||||||
|
性能标记 ({report.marks.length})
|
||||||
|
</Box>
|
||||||
|
<AccordionIcon />
|
||||||
|
</AccordionButton>
|
||||||
|
</h2>
|
||||||
|
<AccordionPanel pb={4}>
|
||||||
|
<VStack align="stretch" spacing={1}>
|
||||||
|
{report.marks.map((mark: any, index: number) => (
|
||||||
|
<HStack key={index} justify="space-between" fontSize="sm">
|
||||||
|
<Text>{mark.name}</Text>
|
||||||
|
<Text color="gray.600">{mark.time.toFixed(2)}ms</Text>
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
{/* 性能测量 */}
|
||||||
|
<AccordionItem>
|
||||||
|
<h2>
|
||||||
|
<AccordionButton>
|
||||||
|
<Box flex="1" textAlign="left" fontWeight="bold">
|
||||||
|
性能测量 ({report.measures.length})
|
||||||
|
</Box>
|
||||||
|
<AccordionIcon />
|
||||||
|
</AccordionButton>
|
||||||
|
</h2>
|
||||||
|
<AccordionPanel pb={4}>
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
{report.measures.map((measure: any, index: number) => (
|
||||||
|
<Box key={index} p={2} bg="gray.50" borderRadius="md">
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<Text fontWeight="semibold" fontSize="sm">
|
||||||
|
{measure.name}
|
||||||
|
</Text>
|
||||||
|
<Badge>{measure.duration.toFixed(2)}ms</Badge>
|
||||||
|
</HStack>
|
||||||
|
<Text fontSize="xs" color="gray.600">
|
||||||
|
{measure.startMark} → {measure.endMark}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</VStack>
|
||||||
|
) : (
|
||||||
|
<Text>加载中...</Text>
|
||||||
|
)}
|
||||||
|
</DrawerBody>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指标统计组件
|
||||||
|
*/
|
||||||
|
interface MetricStatProps {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
threshold: number;
|
||||||
|
actualValue?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MetricStat: React.FC<MetricStatProps> = ({ label, value, threshold, actualValue }) => {
|
||||||
|
const isGood = actualValue !== undefined && actualValue < threshold;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack justify="space-between" p={2} bg="gray.50" borderRadius="md">
|
||||||
|
<Text fontSize="sm">{label}</Text>
|
||||||
|
<HStack>
|
||||||
|
<Text fontSize="sm" fontWeight="bold">
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
{actualValue !== undefined && (
|
||||||
|
<Text fontSize="xs">{isGood ? '✅' : '⚠️'}</Text>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PerformancePanel;
|
||||||
@@ -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;
|
export default posthog;
|
||||||
|
|||||||
Reference in New Issue
Block a user