feat: 添加面包屑导航组件并优化页面布局
refactor: 重构页面结构和导航逻辑 fix: 修复移动端菜单导航和滚动行为 perf: 优化图片加载性能和资源请求 test: 添加端到端测试和性能测试用例 docs: 更新.gitignore文件 chore: 更新依赖和配置 style: 优化代码格式和类型安全 ci: 调整Playwright测试超时时间 build: 更新Next.js配置和构建选项
This commit is contained in:
@@ -4,7 +4,6 @@ import { PerformanceMetrics, PerformanceThresholds } from '../types';
|
||||
export class PerformanceMonitor {
|
||||
private page: Page;
|
||||
private metrics: PerformanceMetrics;
|
||||
private startTime: number;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
@@ -16,12 +15,9 @@ export class PerformanceMonitor {
|
||||
cumulativeLayoutShift: 0,
|
||||
firstInputDelay: 0,
|
||||
};
|
||||
this.startTime = 0;
|
||||
}
|
||||
|
||||
async startMonitoring(): Promise<void> {
|
||||
this.startTime = Date.now();
|
||||
|
||||
await this.page.evaluate(() => {
|
||||
window.performance.clearResourceTimings();
|
||||
});
|
||||
@@ -86,7 +82,7 @@ export class PerformanceMonitor {
|
||||
const entries = list.getEntries();
|
||||
const longTasks = entries.filter((e) => e.duration > 50);
|
||||
if (longTasks.length > 0) {
|
||||
resolve(longTasks[0].startTime);
|
||||
resolve(longTasks[0]?.startTime || 0);
|
||||
}
|
||||
});
|
||||
observer.observe({ entryTypes: ['longtask'] });
|
||||
@@ -103,7 +99,8 @@ export class PerformanceMonitor {
|
||||
const observer = new PerformanceObserver((list) => {
|
||||
const entries = list.getEntries();
|
||||
if (entries.length > 0) {
|
||||
resolve(entries[0].processingStart - entries[0].startTime);
|
||||
const entry = entries[0] as any;
|
||||
resolve((entry?.processingStart || 0) - (entry?.startTime || 0));
|
||||
}
|
||||
});
|
||||
observer.observe({ entryTypes: ['first-input'] });
|
||||
@@ -176,7 +173,7 @@ export class PerformanceMonitor {
|
||||
const entries = list.getEntries();
|
||||
const longTasks = entries.filter((e) => e.duration > 50);
|
||||
if (longTasks.length > 0) {
|
||||
resolve(longTasks[0].startTime);
|
||||
resolve(longTasks[0]?.startTime || 0);
|
||||
}
|
||||
});
|
||||
observer.observe({ entryTypes: ['longtask'] });
|
||||
@@ -196,7 +193,8 @@ export class PerformanceMonitor {
|
||||
const observer = new PerformanceObserver((list) => {
|
||||
const entries = list.getEntries();
|
||||
if (entries.length > 0) {
|
||||
resolve(entries[0].processingStart - entries[0].startTime);
|
||||
const entry = entries[0] as any;
|
||||
resolve((entry?.processingStart || 0) - (entry?.startTime || 0));
|
||||
}
|
||||
});
|
||||
observer.observe({ entryTypes: ['first-input'] });
|
||||
@@ -211,12 +209,15 @@ export class PerformanceMonitor {
|
||||
|
||||
async measureResourceTiming(): Promise<any[]> {
|
||||
const resources = await this.page.evaluate(() => {
|
||||
return performance.getEntriesByType('resource').map((r) => ({
|
||||
name: r.name,
|
||||
duration: r.duration,
|
||||
size: (r as any).transferSize,
|
||||
type: r.initiatorType,
|
||||
}));
|
||||
return performance.getEntriesByType('resource').map((r) => {
|
||||
const resource = r as any;
|
||||
return {
|
||||
name: resource.name,
|
||||
duration: resource.duration,
|
||||
size: resource.transferSize,
|
||||
type: resource.initiatorType,
|
||||
};
|
||||
});
|
||||
});
|
||||
return resources;
|
||||
}
|
||||
|
||||
@@ -7,32 +7,32 @@ export class TestDataGenerator {
|
||||
private static readonly SUBJECTS = ['产品咨询', '技术支持', '商务合作', '其他', '意见反馈'];
|
||||
|
||||
static generateName(): string {
|
||||
const first = this.FIRST_NAMES[Math.floor(Math.random() * this.FIRST_NAMES.length)];
|
||||
const last = this.LAST_NAMES[Math.floor(Math.random() * this.LAST_NAMES.length)];
|
||||
const first = this.FIRST_NAMES[Math.floor(Math.random() * this.FIRST_NAMES.length)]!;
|
||||
const last = this.LAST_NAMES[Math.floor(Math.random() * this.LAST_NAMES.length)]!;
|
||||
return `${first}${last}`;
|
||||
}
|
||||
|
||||
static generateEmail(name?: string): string {
|
||||
const username = name || this.generateName();
|
||||
const domains = ['example.com', 'test.com', 'demo.com'];
|
||||
const domain = domains[Math.floor(Math.random() * domains.length)];
|
||||
const domain = domains[Math.floor(Math.random() * domains.length)]!;
|
||||
return `${username}@${domain}`;
|
||||
}
|
||||
|
||||
static generatePhone(): string {
|
||||
const prefix = ['138', '139', '136', '137', '158', '159'][Math.floor(Math.random() * 6)];
|
||||
const prefix = ['138', '139', '136', '137', '158', '159'][Math.floor(Math.random() * 6)]!;
|
||||
const middle = Math.floor(Math.random() * 9000 + 1000);
|
||||
const suffix = Math.floor(Math.random() * 9000 + 1000);
|
||||
return `${prefix}${middle}${suffix}`;
|
||||
}
|
||||
|
||||
static generateCompany(): string {
|
||||
const prefix = ['创新', '未来', '智慧', '科技', '数字'][Math.floor(Math.random() * 5)];
|
||||
const suffix = this.COMPANIES[Math.floor(Math.random() * this.COMPANIES.length)];
|
||||
const prefix = ['创新', '未来', '智慧', '科技', '数字'][Math.floor(Math.random() * 5)]!;
|
||||
const suffix = this.COMPANIES[Math.floor(Math.random() * this.COMPANIES.length)]!;
|
||||
return `${prefix}${suffix}`;
|
||||
}
|
||||
|
||||
static generateMessage(minLength: number = 10, maxLength: number = 100): string {
|
||||
static generateMessage(): string {
|
||||
const messages = [
|
||||
'您好,我对贵公司的产品很感兴趣,希望能了解更多信息。',
|
||||
'请问贵公司是否有相关的技术支持服务?',
|
||||
@@ -43,11 +43,11 @@ export class TestDataGenerator {
|
||||
'我们公司正在评估相关技术方案,希望能了解贵公司的解决方案。',
|
||||
'您好,我想咨询一下贵公司的产品定制服务。',
|
||||
];
|
||||
return messages[Math.floor(Math.random() * messages.length)];
|
||||
return messages[Math.floor(Math.random() * messages.length)]!;
|
||||
}
|
||||
|
||||
static generateSubject(): string {
|
||||
return this.SUBJECTS[Math.floor(Math.random() * this.SUBJECTS.length)];
|
||||
return this.SUBJECTS[Math.floor(Math.random() * this.SUBJECTS.length)]!;
|
||||
}
|
||||
|
||||
static generateContactFormData(): ContactFormData {
|
||||
@@ -79,7 +79,7 @@ export class TestDataGenerator {
|
||||
'user@domain',
|
||||
'user domain.com',
|
||||
];
|
||||
return invalidEmails[Math.floor(Math.random() * invalidEmails.length)];
|
||||
return invalidEmails[Math.floor(Math.random() * invalidEmails.length)]!;
|
||||
}
|
||||
|
||||
static generateInvalidPhone(): string {
|
||||
@@ -89,7 +89,7 @@ export class TestDataGenerator {
|
||||
'abcdefghijk',
|
||||
'123-456-7890',
|
||||
];
|
||||
return invalidPhones[Math.floor(Math.random() * invalidPhones.length)];
|
||||
return invalidPhones[Math.floor(Math.random() * invalidPhones.length)]!;
|
||||
}
|
||||
|
||||
static generateShortMessage(): string {
|
||||
@@ -136,13 +136,13 @@ export class TestDataGenerator {
|
||||
'https://demo.com/path',
|
||||
'http://example.com/page?param=value',
|
||||
];
|
||||
return urls[Math.floor(Math.random() * urls.length)];
|
||||
return urls[Math.floor(Math.random() * urls.length)]!;
|
||||
}
|
||||
|
||||
static generateDate(): string {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + Math.floor(Math.random() * 30));
|
||||
return date.toISOString().split('T')[0];
|
||||
return date.toISOString().split('T')[0]!;
|
||||
}
|
||||
|
||||
static generateTime(): string {
|
||||
|
||||
@@ -76,7 +76,11 @@ export const tabletDevices = Object.entries(devices)
|
||||
.map(([key, config]) => ({ key, ...config }));
|
||||
|
||||
export const getDevice = (key: string): DeviceConfig => {
|
||||
return devices[key] || devices['desktop-1280x720'];
|
||||
const device = devices[key];
|
||||
if (!device) {
|
||||
return devices['desktop-1280x720']!;
|
||||
}
|
||||
return device;
|
||||
};
|
||||
|
||||
export const getAllDevices = (): DeviceConfig[] => {
|
||||
@@ -84,15 +88,15 @@ export const getAllDevices = (): DeviceConfig[] => {
|
||||
};
|
||||
|
||||
export const getDesktopDevices = (): DeviceConfig[] => {
|
||||
return desktopDevices.map(d => devices[d.key]);
|
||||
return desktopDevices.map(d => devices[d.key]!);
|
||||
};
|
||||
|
||||
export const getMobileDevices = (): DeviceConfig[] => {
|
||||
return mobileDevices.map(d => devices[d.key]);
|
||||
return mobileDevices.map(d => devices[d.key]!);
|
||||
};
|
||||
|
||||
export const getTabletDevices = (): DeviceConfig[] => {
|
||||
return tabletDevices.map(d => devices[d.key]);
|
||||
return tabletDevices.map(d => devices[d.key]!);
|
||||
};
|
||||
|
||||
export const getBreakpoints = () => {
|
||||
|
||||
Reference in New Issue
Block a user