08ea5fbe98
添加用户管理视图、API和状态管理文件
270 lines
8.3 KiB
TypeScript
270 lines
8.3 KiB
TypeScript
export interface PerformanceThresholds {
|
|
pageLoadTime: number;
|
|
firstContentfulPaint: number;
|
|
largestContentfulPaint: number;
|
|
timeToInteractive: number;
|
|
cumulativeLayoutShift: number;
|
|
firstInputDelay: number;
|
|
fps: number;
|
|
frameTime: number;
|
|
animationDuration: number;
|
|
}
|
|
|
|
export const PERFORMANCE_THRESHOLDS: PerformanceThresholds = {
|
|
pageLoadTime: 2000,
|
|
firstContentfulPaint: 1000,
|
|
largestContentfulPaint: 1500,
|
|
timeToInteractive: 2000,
|
|
cumulativeLayoutShift: 0.1,
|
|
firstInputDelay: 100,
|
|
fps: 30,
|
|
frameTime: 33.33,
|
|
animationDuration: 500,
|
|
};
|
|
|
|
export interface PerformanceMetrics {
|
|
pageLoadTime: number;
|
|
firstContentfulPaint: number;
|
|
largestContentfulPaint: number;
|
|
timeToInteractive: number;
|
|
cumulativeLayoutShift: number;
|
|
firstInputDelay: number;
|
|
fps: number;
|
|
frameTime: number;
|
|
droppedFrames: number;
|
|
totalFrames: number;
|
|
animationDuration: number;
|
|
}
|
|
|
|
export interface PerformanceReport {
|
|
timestamp: string;
|
|
url: string;
|
|
platform: string;
|
|
metrics: PerformanceMetrics;
|
|
thresholds: PerformanceThresholds;
|
|
passed: boolean;
|
|
issues: PerformanceIssue[];
|
|
}
|
|
|
|
export interface PerformanceIssue {
|
|
type: 'page-load' | 'animation' | 'rendering';
|
|
metric: string;
|
|
actual: number;
|
|
expected: number;
|
|
severity: 'critical' | 'warning' | 'info';
|
|
message: string;
|
|
}
|
|
|
|
export class PerformanceMonitor {
|
|
private metrics: PerformanceMetrics;
|
|
private thresholds: PerformanceThresholds;
|
|
private issues: PerformanceIssue[];
|
|
|
|
constructor(thresholds?: Partial<PerformanceThresholds>) {
|
|
this.thresholds = { ...PERFORMANCE_THRESHOLDS, ...thresholds };
|
|
this.metrics = this.initializeMetrics();
|
|
this.issues = [];
|
|
}
|
|
|
|
private initializeMetrics(): PerformanceMetrics {
|
|
return {
|
|
pageLoadTime: 0,
|
|
firstContentfulPaint: 0,
|
|
largestContentfulPaint: 0,
|
|
timeToInteractive: 0,
|
|
cumulativeLayoutShift: 0,
|
|
firstInputDelay: 0,
|
|
fps: 0,
|
|
frameTime: 0,
|
|
droppedFrames: 0,
|
|
totalFrames: 0,
|
|
animationDuration: 0,
|
|
};
|
|
}
|
|
|
|
public setMetric(key: keyof PerformanceMetrics, value: number): void {
|
|
this.metrics[key] = value;
|
|
}
|
|
|
|
public getMetric(key: keyof PerformanceMetrics): number {
|
|
return this.metrics[key];
|
|
}
|
|
|
|
public getMetrics(): PerformanceMetrics {
|
|
return { ...this.metrics };
|
|
}
|
|
|
|
public checkThresholds(): void {
|
|
this.issues = [];
|
|
|
|
if (this.metrics.pageLoadTime > this.thresholds.pageLoadTime) {
|
|
this.issues.push({
|
|
type: 'page-load',
|
|
metric: 'pageLoadTime',
|
|
actual: this.metrics.pageLoadTime,
|
|
expected: this.thresholds.pageLoadTime,
|
|
severity: 'critical',
|
|
message: `页面加载时间 ${this.metrics.pageLoadTime}ms 超过阈值 ${this.thresholds.pageLoadTime}ms`,
|
|
});
|
|
}
|
|
|
|
if (this.metrics.firstContentfulPaint > this.thresholds.firstContentfulPaint) {
|
|
this.issues.push({
|
|
type: 'page-load',
|
|
metric: 'firstContentfulPaint',
|
|
actual: this.metrics.firstContentfulPaint,
|
|
expected: this.thresholds.firstContentfulPaint,
|
|
severity: 'warning',
|
|
message: `首次内容绘制 ${this.metrics.firstContentfulPaint}ms 超过阈值 ${this.thresholds.firstContentfulPaint}ms`,
|
|
});
|
|
}
|
|
|
|
if (this.metrics.largestContentfulPaint > this.thresholds.largestContentfulPaint) {
|
|
this.issues.push({
|
|
type: 'page-load',
|
|
metric: 'largestContentfulPaint',
|
|
actual: this.metrics.largestContentfulPaint,
|
|
expected: this.thresholds.largestContentfulPaint,
|
|
severity: 'warning',
|
|
message: `最大内容绘制 ${this.metrics.largestContentfulPaint}ms 超过阈值 ${this.thresholds.largestContentfulPaint}ms`,
|
|
});
|
|
}
|
|
|
|
if (this.metrics.timeToInteractive > this.thresholds.timeToInteractive) {
|
|
this.issues.push({
|
|
type: 'page-load',
|
|
metric: 'timeToInteractive',
|
|
actual: this.metrics.timeToInteractive,
|
|
expected: this.thresholds.timeToInteractive,
|
|
severity: 'warning',
|
|
message: `可交互时间 ${this.metrics.timeToInteractive}ms 超过阈值 ${this.thresholds.timeToInteractive}ms`,
|
|
});
|
|
}
|
|
|
|
if (this.metrics.cumulativeLayoutShift > this.thresholds.cumulativeLayoutShift) {
|
|
this.issues.push({
|
|
type: 'rendering',
|
|
metric: 'cumulativeLayoutShift',
|
|
actual: this.metrics.cumulativeLayoutShift,
|
|
expected: this.thresholds.cumulativeLayoutShift,
|
|
severity: 'warning',
|
|
message: `累积布局偏移 ${this.metrics.cumulativeLayoutShift} 超过阈值 ${this.thresholds.cumulativeLayoutShift}`,
|
|
});
|
|
}
|
|
|
|
if (this.metrics.firstInputDelay > this.thresholds.firstInputDelay) {
|
|
this.issues.push({
|
|
type: 'page-load',
|
|
metric: 'firstInputDelay',
|
|
actual: this.metrics.firstInputDelay,
|
|
expected: this.thresholds.firstInputDelay,
|
|
severity: 'warning',
|
|
message: `首次输入延迟 ${this.metrics.firstInputDelay}ms 超过阈值 ${this.thresholds.firstInputDelay}ms`,
|
|
});
|
|
}
|
|
|
|
if (this.metrics.fps < this.thresholds.fps) {
|
|
this.issues.push({
|
|
type: 'animation',
|
|
metric: 'fps',
|
|
actual: this.metrics.fps,
|
|
expected: this.thresholds.fps,
|
|
severity: 'critical',
|
|
message: `动画帧率 ${this.metrics.fps}fps 低于阈值 ${this.thresholds.fps}fps`,
|
|
});
|
|
}
|
|
|
|
if (this.metrics.frameTime > this.thresholds.frameTime) {
|
|
this.issues.push({
|
|
type: 'animation',
|
|
metric: 'frameTime',
|
|
actual: this.metrics.frameTime,
|
|
expected: this.thresholds.frameTime,
|
|
severity: 'warning',
|
|
message: `帧时间 ${this.metrics.frameTime}ms 超过阈值 ${this.thresholds.frameTime}ms`,
|
|
});
|
|
}
|
|
}
|
|
|
|
public getIssues(): PerformanceIssue[] {
|
|
return [...this.issues];
|
|
}
|
|
|
|
public hasCriticalIssues(): boolean {
|
|
return this.issues.some(issue => issue.severity === 'critical');
|
|
}
|
|
|
|
public hasWarnings(): boolean {
|
|
return this.issues.some(issue => issue.severity === 'warning');
|
|
}
|
|
|
|
public generateReport(url: string, platform: string): PerformanceReport {
|
|
this.checkThresholds();
|
|
|
|
return {
|
|
timestamp: new Date().toISOString(),
|
|
url,
|
|
platform,
|
|
metrics: this.getMetrics(),
|
|
thresholds: this.thresholds,
|
|
passed: !this.hasCriticalIssues(),
|
|
issues: this.getIssues(),
|
|
};
|
|
}
|
|
|
|
public reset(): void {
|
|
this.metrics = this.initializeMetrics();
|
|
this.issues = [];
|
|
}
|
|
}
|
|
|
|
export const createPerformanceMonitor = (thresholds?: Partial<PerformanceThresholds>): PerformanceMonitor => {
|
|
return new PerformanceMonitor(thresholds);
|
|
};
|
|
|
|
export const generatePerformanceReportSummary = (reports: PerformanceReport[]): string => {
|
|
const totalReports = reports.length;
|
|
const passedReports = reports.filter(report => report.passed).length;
|
|
const failedReports = totalReports - passedReports;
|
|
|
|
const totalIssues = reports.reduce((sum, report) => sum + report.issues.length, 0);
|
|
const criticalIssues = reports.reduce((sum, report) => sum + report.issues.filter(issue => issue.severity === 'critical').length, 0);
|
|
const warningIssues = reports.reduce((sum, report) => sum + report.issues.filter(issue => issue.severity === 'warning').length, 0);
|
|
|
|
const avgPageLoadTime = reports.reduce((sum, report) => sum + report.metrics.pageLoadTime, 0) / totalReports;
|
|
const avgFPS = reports.reduce((sum, report) => sum + report.metrics.fps, 0) / totalReports;
|
|
|
|
return `
|
|
性能测试报告摘要
|
|
================
|
|
测试时间: ${new Date().toISOString()}
|
|
测试数量: ${totalReports}
|
|
通过数量: ${passedReports}
|
|
失败数量: ${failedReports}
|
|
通过率: ${((passedReports / totalReports) * 100).toFixed(2)}%
|
|
|
|
问题统计
|
|
--------
|
|
总问题数: ${totalIssues}
|
|
严重问题: ${criticalIssues}
|
|
警告问题: ${warningIssues}
|
|
|
|
性能指标
|
|
--------
|
|
平均页面加载时间: ${avgPageLoadTime.toFixed(2)}ms
|
|
平均动画帧率: ${avgFPS.toFixed(2)}fps
|
|
|
|
详细信息
|
|
--------
|
|
${reports.map((report, index) => `
|
|
报告 ${index + 1}: ${report.url}
|
|
- 平台: ${report.platform}
|
|
- 状态: ${report.passed ? '通过' : '失败'}
|
|
- 页面加载时间: ${report.metrics.pageLoadTime}ms
|
|
- 动画帧率: ${report.metrics.fps}fps
|
|
- 问题数: ${report.issues.length}
|
|
${report.issues.length > 0 ? ` 问题详情:\n${report.issues.map(issue => ` - [${issue.severity.toUpperCase()}] ${issue.message}`).join('\n')}` : ''}
|
|
`).join('\n')}
|
|
`;
|
|
};
|