Files
novalon-website/docs/plans/2026-02-27-website-comprehensive-optimization.md
T
张翔 0cfefaa937 refactor(ui): 优化导航组件和页面布局
- 移除多个页面的面包屑导航组件
- 添加统一的返回按钮组件替代各页面独立实现
- 优化导航栏滚动检测逻辑和动画效果
- 更新常量类型定义和统计数据
- 调整动态导入的SSR配置为false
- 添加FlipClock组件展示公司运营时长
- 优化新闻列表页的类型安全和响应式设计
2026-02-28 13:09:07 +08:00

1421 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 网站全面优化实施计划
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 全面优化睿新致远网站,提升导航一致性、测试覆盖率、错误处理、性能、移动端体验和SEO。
**Architecture:** 采用渐进式优化策略,按优先级分阶段实施,每个阶段独立可验证和回滚。
**Tech Stack:** Next.js 16.1.6, TypeScript, Framer Motion, Playwright, Tailwind CSS
---
## 阶段一:高优先级修复(1-2天)
### Task 1.1: 修复导航一致性问题
**Files:**
- Create: `src/components/layout/breadcrumb.tsx`
- Modify: `src/components/layout/header.tsx:1-150`
- Modify: `src/app/(marketing)/about/page.tsx:1-15`
- Modify: `src/app/(marketing)/cases/page.tsx:1-20`
- Modify: `src/app/(marketing)/services/page.tsx:1-20`
- Modify: `src/app/(marketing)/products/page.tsx:1-20`
- Modify: `src/app/(marketing)/solutions/page.tsx:1-20`
- Modify: `src/app/(marketing)/contact/page.tsx:1-20`
- Modify: `src/app/(marketing)/news/page.tsx:1-20`
**Step 1: 创建面包屑导航组件**
```typescript
// src/components/layout/breadcrumb.tsx
'use client';
import Link from 'next/link';
import { ChevronRight, Home } from 'lucide-react';
interface BreadcrumbItem {
label: string;
href: string;
}
interface BreadcrumbProps {
items: BreadcrumbItem[];
}
export function Breadcrumb({ items }: BreadcrumbProps) {
return (
<nav className="flex items-center space-x-2 text-sm text-[#5C5C5C] py-4">
<Link href="/" className="flex items-center hover:text-[#C41E3A] transition-colors">
<Home className="w-4 h-4" />
</Link>
{items.map((item, index) => (
<div key={index} className="flex items-center">
<ChevronRight className="w-4 h-4 text-[#E5E5E5]" />
<Link
href={item.href}
className="ml-2 hover:text-[#C41E3A] transition-colors"
>
{item.label}
</Link>
</div>
))}
</nav>
);
}
```
**Step 2: 运行测试验证组件创建**
Run: `npm run build`
Expected: BUILD SUCCESS
**Step 3: 在各个页面集成面包屑导航**
```typescript
// 示例:src/app/(marketing)/about/page.tsx
import { Breadcrumb } from '@/components/layout/breadcrumb';
export default function AboutPage() {
return (
<div className="min-h-screen bg-white">
<Breadcrumb items={[{ label: '关于我们', href: '/about' }]} />
{/* 其他内容 */}
</div>
);
}
```
**Step 4: 更新 Header 组件以支持统一导航**
```typescript
// src/components/layout/header.tsx
// 修改导航逻辑,使其在首页使用锚点,在详情页使用路由
const getNavigationHref = (item: typeof NAVIGATION[number], isHomePage: boolean) => {
return isHomePage ? item.href : `/${item.id}`;
};
```
**Step 5: 运行测试验证导航一致性**
Run: `npm run lint`
Expected: No linting errors
**Step 6: 提交代码**
```bash
git add src/components/layout/breadcrumb.tsx src/components/layout/header.tsx src/app/
git commit -m "feat: implement consistent navigation with breadcrumbs"
```
---
### Task 1.2: 补充关键页面的E2E测试
**Files:**
- Create: `e2e/src/pages/AboutPage.ts`
- Create: `e2e/src/pages/CasesPage.ts`
- Create: `e2e/src/pages/ServicesPage.ts`
- Create: `e2e/src/pages/ProductsPage.ts`
- Create: `e2e/src/pages/SolutionsPage.ts`
- Create: `e2e/src/pages/NewsPage.ts`
- Create: `e2e/src/tests/smoke/about-page.smoke.spec.ts`
- Create: `e2e/src/tests/smoke/cases-page.smoke.spec.ts`
- Create: `e2e/src/tests/smoke/services-page.smoke.spec.ts`
- Create: `e2e/src/tests/smoke/products-page.smoke.spec.ts`
- Create: `e2e/src/tests/smoke/solutions-page.smoke.spec.ts`
- Create: `e2e/src/tests/smoke/news-page.smoke.spec.ts`
**Step 1: 创建 AboutPage Page Object**
```typescript
// e2e/src/pages/AboutPage.ts
import { Page, Locator, expect } from '@playwright/test';
export class AboutPage {
readonly page: Page;
readonly breadcrumb: Locator;
readonly pageHeader: Locator;
constructor(page: Page) {
this.page = page;
this.breadcrumb = page.locator('nav');
this.pageHeader = page.locator('h1');
}
async goto() {
await this.page.goto('/about');
await this.page.waitForLoadState('networkidle');
}
async waitForPageLoad() {
await this.page.waitForLoadState('networkidle');
await expect(this.pageHeader).toBeVisible();
}
async getBreadcrumbItems() {
return await this.breadcrumb.allTextContents();
}
}
```
**Step 2: 创建关于页面冒烟测试**
```typescript
// e2e/src/tests/smoke/about-page.smoke.spec.ts
import { test, expect } from '../../fixtures/base.fixture';
test.describe('关于页面冒烟测试 @smoke', () => {
test.beforeEach(async ({ aboutPage }) => {
await aboutPage.goto();
await aboutPage.waitForPageLoad();
});
test('应该成功加载关于页面', async ({ aboutPage }) => {
await expect(aboutPage.page).toHaveURL(/\/about/);
await expect(aboutPage.pageHeader).toBeVisible();
});
test('应该显示面包屑导航', async ({ aboutPage }) => {
const items = await aboutPage.getBreadcrumbItems();
expect(items.length).toBeGreaterThan(0);
});
});
```
**Step 3: 运行测试验证失败**
Run: `cd e2e && npm test --grep "关于页面冒烟测试"`
Expected: FAIL with "aboutPage is not defined"
**Step 4: 更新 fixtures 以包含新页面**
```typescript
// e2e/src/fixtures/base.fixture.ts
import { test as base } from '@playwright/test';
import { AboutPage } from '../pages/AboutPage';
import { CasesPage } from '../pages/CasesPage';
import { ServicesPage } from '../pages/ServicesPage';
import { ProductsPage } from '../pages/ProductsPage';
import { SolutionsPage } from '../pages/SolutionsPage';
import { NewsPage } from '../pages/NewsPage';
export const test = base.extend<{
aboutPage: AboutPage;
casesPage: CasesPage;
servicesPage: ServicesPage;
productsPage: ProductsPage;
solutionsPage: SolutionsPage;
newsPage: NewsPage;
}>({
aboutPage: async ({ page }, use) => {
const aboutPage = new AboutPage(page);
await use(aboutPage);
},
casesPage: async ({ page }, use) => {
const casesPage = new CasesPage(page);
await use(casesPage);
},
servicesPage: async ({ page }, use) => {
const servicesPage = new ServicesPage(page);
await use(servicesPage);
},
productsPage: async ({ page }, use) => {
const productsPage = new ProductsPage(page);
await use(productsPage);
},
solutionsPage: async ({ page }, use) => {
const solutionsPage = new SolutionsPage(page);
await use(solutionsPage);
},
newsPage: async ({ page }, use) => {
const newsPage = new NewsPage(page);
await use(newsPage);
},
});
```
**Step 5: 运行测试验证通过**
Run: `cd e2e && npm test --grep "关于页面冒烟测试"`
Expected: PASS
**Step 6: 为其他页面创建类似的测试**
重复步骤 1-5,为 CasesPage、ServicesPage、ProductsPage、SolutionsPage、NewsPage 创建 Page Objects 和测试。
**Step 7: 提交代码**
```bash
git add e2e/src/pages/ e2e/src/tests/smoke/ e2e/src/fixtures/base.fixture.ts
git commit -m "test: add smoke tests for all major pages"
```
---
### Task 1.3: 完善错误处理
**Files:**
- Create: `src/app/not-found.tsx`
- Create: `src/app/error.tsx`
- Create: `src/components/ui/error-boundary.tsx`
- Modify: `src/app/layout.tsx:1-50`
**Step 1: 创建 404 页面**
```typescript
// src/app/not-found.tsx
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Home, ArrowRight } from 'lucide-react';
export default function NotFound() {
return (
<div className="min-h-screen bg-white flex items-center justify-center">
<div className="text-center">
<h1 className="text-6xl font-bold text-[#C41E3A] mb-4">404</h1>
<h2 className="text-2xl font-semibold text-[#1C1C1C] mb-4">
</h2>
<p className="text-[#5C5C5C] mb-8">
访
</p>
<div className="flex justify-center gap-4">
<Button variant="outline" asChild>
<Link href="/">
<Home className="w-4 h-4 mr-2" />
</Link>
</Button>
<Button asChild>
<Link href="/contact">
<ArrowRight className="w-4 h-4 ml-2" />
</Link>
</Button>
</div>
</div>
</div>
);
}
```
**Step 2: 创建全局错误页面**
```typescript
// src/app/error.tsx
'use client';
import { useEffect } from 'react';
import { Button } from '@/components/ui/button';
import Link from 'next/link';
import { AlertCircle, RefreshCw } from 'lucide-react';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<div className="min-h-screen bg-white flex items-center justify-center">
<div className="text-center max-w-md">
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-6">
<AlertCircle className="w-8 h-8 text-[#C41E3A]" />
</div>
<h2 className="text-2xl font-bold text-[#1C1C1C] mb-4">
</h2>
<p className="text-[#5C5C5C] mb-8">
</p>
<div className="flex justify-center gap-4">
<Button variant="outline" onClick={reset}>
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
<Button asChild>
<Link href="/">
</Link>
</Button>
</div>
</div>
</div>
);
}
```
**Step 3: 创建错误边界组件**
```typescript
// src/components/ui/error-boundary.tsx
'use client';
import { Component, ReactNode } from 'react';
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
}
export class ErrorBoundary extends Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(_: Error): ErrorBoundaryState {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: any) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="min-h-screen bg-white flex items-center justify-center">
<div className="text-center">
<h2 className="text-2xl font-bold text-[#1C1C1C] mb-4">
</h2>
<p className="text-[#5C5C5C] mb-8">
</p>
</div>
</div>
);
}
return this.props.children;
}
}
```
**Step 4: 在根布局中集成错误边界**
```typescript
// src/app/layout.tsx
import { ErrorBoundary } from '@/components/ui/error-boundary';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN">
<body>
<ErrorBoundary>
{children}
</ErrorBoundary>
</body>
</html>
);
}
```
**Step 5: 运行测试验证错误处理**
Run: `npm run build`
Expected: BUILD SUCCESS
**Step 6: 添加错误处理的E2E测试**
```typescript
// e2e/src/tests/smoke/error-handling.smoke.spec.ts
import { test, expect } from '@playwright/test';
test.describe('错误处理冒烟测试 @smoke', () => {
test('应该显示404页面', async ({ page }) => {
await page.goto('/non-existent-page');
await expect(page.locator('h1')).toContainText('404');
await expect(page.locator('h2')).toContainText('页面未找到');
});
test('404页面应该有返回首页按钮', async ({ page }) => {
await page.goto('/non-existent-page');
const homeButton = page.getByRole('link', { name: /返回首页/ });
await expect(homeButton).toBeVisible();
await homeButton.click();
await expect(page).toHaveURL('/');
});
});
```
**Step 7: 提交代码**
```bash
git add src/app/not-found.tsx src/app/error.tsx src/components/ui/error-boundary.tsx src/app/layout.tsx e2e/src/tests/smoke/error-handling.smoke.spec.ts
git commit -m "feat: implement comprehensive error handling with 404 and error pages"
```
---
## 阶段二:中优先级优化(1周)
### Task 2.1: 优化加载性能
**Files:**
- Create: `src/components/ui/optimized-image.tsx`
- Modify: `src/components/sections/hero-section.tsx:1-100`
- Modify: `src/components/sections/services-section.tsx:1-100`
- Modify: `src/components/sections/products-section.tsx:1-100`
- Modify: `src/components/sections/cases-section.tsx:1-100`
- Modify: `src/components/sections/news-section.tsx:1-100`
- Modify: `next.config.ts:1-50`
**Step 1: 创建优化的图片组件**
```typescript
// src/components/ui/optimized-image.tsx
'use client';
import Image from 'next/image';
import { useState } from 'react';
interface OptimizedImageProps {
src: string;
alt: string;
width?: number;
height?: number;
className?: string;
priority?: boolean;
}
export function OptimizedImage({
src,
alt,
width = 800,
height = 600,
className,
priority = false,
}: OptimizedImageProps) {
const [isLoading, setIsLoading] = useState(true);
return (
<div className={`relative overflow-hidden ${className || ''}`}>
{isLoading && (
<div className="absolute inset-0 bg-[#F5F5F5] animate-pulse" />
)}
<Image
src={src}
alt={alt}
width={width}
height={height}
priority={priority}
className={`transition-opacity duration-300 ${
isLoading ? 'opacity-0' : 'opacity-100'
}`}
onLoad={() => setIsLoading(false)}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
);
}
```
**Step 2: 更新 Next.js 配置以支持图片优化**
```typescript
// next.config.ts
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
],
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
// 其他配置...
};
```
**Step 3: 在各个 section 中使用优化的图片组件**
```typescript
// 示例:src/components/sections/hero-section.tsx
import { OptimizedImage } from '@/components/ui/optimized-image';
// 替换所有 <img> 标签为 <OptimizedImage>
<OptimizedImage
src="/images/hero.jpg"
alt="Hero section"
width={1920}
height={1080}
priority
className="w-full h-full object-cover"
/>
```
**Step 4: 实现代码分割和懒加载**
```typescript
// src/app/(marketing)/page.tsx
import dynamic from 'next/dynamic';
// 使用动态导入和懒加载
const HeroSection = dynamic(
() => import('@/components/sections/hero-section').then(mod => ({ default: mod.HeroSection })),
{ loading: () => <SectionSkeleton />, ssr: true }
);
const ServicesSection = dynamic(
() => import('@/components/sections/services-section').then(mod => ({ default: mod.ServicesSection })),
{ loading: () => <SectionSkeleton />, ssr: false }
);
```
**Step 5: 添加性能测试**
```typescript
// e2e/src/tests/performance/image-loading.spec.ts
import { test, expect } from '@playwright/test';
test.describe('图片加载性能测试 @performance', () => {
test('应该使用优化的图片格式', async ({ page }) => {
await page.goto('/');
const images = await page.locator('img').all();
for (const image of images) {
const src = await image.getAttribute('src');
if (src) {
expect(src).toMatch(/\.(avif|webp|jpg|png)$/);
}
}
});
test('应该有合理的图片尺寸', async ({ page }) => {
await page.goto('/');
const images = await page.locator('img').all();
for (const image of images) {
const width = await image.getAttribute('width');
const height = await image.getAttribute('height');
expect(width).toBeTruthy();
expect(height).toBeTruthy();
}
});
});
```
**Step 6: 运行性能测试**
Run: `cd e2e && npm test --grep "图片加载性能测试"`
Expected: PASS
**Step 7: 提交代码**
```bash
git add src/components/ui/optimized-image.tsx next.config.ts src/components/sections/ e2e/src/tests/performance/
git commit -m "perf: implement image optimization and code splitting"
```
---
### Task 2.2: 改进移动端体验
**Files:**
- Modify: `src/components/layout/mobile-menu.tsx:1-200`
- Modify: `src/components/layout/mobile-tab-bar.tsx:1-100`
- Create: `e2e/src/tests/responsive/mobile-ux.spec.ts`
**Step 1: 优化移动端菜单交互**
```typescript
// src/components/layout/mobile-menu.tsx
'use client';
import { useState, useEffect, useRef } from 'react';
import { X } from 'lucide-react';
export function MobileMenu({ isOpen, onClose, children }: any) {
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
onClose();
}
};
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.body.style.overflow = 'unset';
};
}, [isOpen, onClose]);
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 z-50">
<div
ref={menuRef}
className="fixed right-0 top-0 h-full w-80 bg-white shadow-2xl transform transition-transform duration-300"
>
<button
onClick={onClose}
className="absolute top-4 right-4 p-2 hover:bg-[#F5F5F5] rounded-lg"
aria-label="关闭菜单"
>
<X className="w-6 h-6" />
</button>
<div className="p-6 pt-16">
{children}
</div>
</div>
</div>
);
}
```
**Step 2: 添加移动端触摸优化**
```typescript
// src/components/ui/touch-button.tsx
'use client';
import { Button } from './button';
import { forwardRef } from 'react';
export const TouchButton = forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>((props, ref) => {
return (
<Button
ref={ref}
{...props}
className={`${props.className || ''} min-h-11 min-w-11`}
style={{
WebkitTapHighlightColor: 'transparent',
...props.style,
}}
/>
);
});
```
**Step 3: 添加移动端UX测试**
```typescript
// e2e/src/tests/responsive/mobile-ux.spec.ts
import { test, expect } from '@playwright/test';
test.describe('移动端UX测试 @responsive', () => {
test.use({ viewport: { width: 375, height: 667 } });
test('应该能够打开和关闭移动端菜单', async ({ page }) => {
await page.goto('/');
const menuButton = page.getByRole('button', { name: /菜单/ });
await menuButton.tap();
await expect(page.locator('[role="dialog"]')).toBeVisible();
const closeButton = page.getByRole('button', { name: /关闭/ });
await closeButton.tap();
await expect(page.locator('[role="dialog"]')).not.toBeVisible();
});
test('应该能够通过触摸导航', async ({ page }) => {
await page.goto('/');
const menuButton = page.getByRole('button', { name: /菜单/ });
await menuButton.tap();
const navItem = page.getByRole('link', { name: /核心业务/ });
await navItem.tap();
await expect(page).toHaveURL(/services/);
});
test('触摸目标应该足够大', async ({ page }) => {
await page.goto('/');
const buttons = await page.locator('button, a[role="button"]').all();
for (const button of buttons) {
const box = await button.boundingBox();
expect(box?.width).toBeGreaterThanOrEqual(44);
expect(box?.height).toBeGreaterThanOrEqual(44);
}
});
});
```
**Step 4: 运行移动端测试**
Run: `cd e2e && npm test --grep "移动端UX测试"`
Expected: PASS
**Step 5: 提交代码**
```bash
git add src/components/layout/mobile-menu.tsx src/components/ui/touch-button.tsx e2e/src/tests/responsive/mobile-ux.spec.ts
git commit -m "feat: improve mobile experience with optimized menu and touch targets"
```
---
### Task 2.3: SEO优化
**Files:**
- Create: `src/components/seo/structured-data.tsx`
- Modify: `src/app/layout.tsx:1-50`
- Modify: `src/app/(marketing)/page.tsx:1-20`
- Modify: `src/app/(marketing)/about/page.tsx:1-15`
- Modify: `src/app/(marketing)/cases/page.tsx:1-20`
- Modify: `src/app/(marketing)/services/page.tsx:1-20`
- Modify: `src/app/(marketing)/products/page.tsx:1-20`
**Step 1: 创建结构化数据组件**
```typescript
// src/components/seo/structured-data.tsx
interface StructuredDataProps {
data: Record<string, any>;
}
export function StructuredData({ data }: StructuredDataProps) {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
/>
);
}
```
**Step 2: 在根布局中添加基础SEO**
```typescript
// src/app/layout.tsx
import { StructuredData } from '@/components/seo/structured-data';
import { COMPANY_INFO } from '@/lib/constants';
export const metadata = {
metadataBase: new URL('http://localhost:3000'),
title: {
default: `${COMPANY_INFO.name} - ${COMPANY_INFO.slogan}`,
template: `%s - ${COMPANY_INFO.name}`,
},
description: COMPANY_INFO.description,
keywords: ['数字化转型', '软件开发', '云服务', '数据分析', '信息安全'],
openGraph: {
title: COMPANY_INFO.name,
description: COMPANY_INFO.description,
url: 'http://localhost:3000',
siteName: COMPANY_INFO.name,
images: [
{
url: '/logo.webp',
width: 1200,
height: 630,
},
],
},
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
const organizationData = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: COMPANY_INFO.name,
url: 'http://localhost:3000',
logo: 'http://localhost:3000/logo.webp',
description: COMPANY_INFO.description,
address: {
'@type': 'PostalAddress',
streetAddress: COMPANY_INFO.address,
addressLocality: '成都市',
addressRegion: '四川省',
addressCountry: 'CN',
},
contactPoint: {
'@type': 'ContactPoint',
telephone: COMPANY_INFO.phone,
email: COMPANY_INFO.email,
},
};
return (
<html lang="zh-CN">
<head>
<StructuredData data={organizationData} />
</head>
<body>
{children}
</body>
</html>
);
}
```
**Step 3: 为各个页面添加特定的结构化数据**
```typescript
// 示例:src/app/(marketing)/services/page.tsx
import { StructuredData } from '@/components/seo/structured-data';
import { SERVICES } from '@/lib/constants';
export const metadata = {
title: '核心业务 - 睿新致远',
description: '专业技术团队,为您提供全方位的数字化解决方案',
};
export default function ServicesPage() {
const servicesData = {
'@context': 'https://schema.org',
'@type': 'ItemList',
itemListElement: SERVICES.map((service) => ({
'@type': 'Service',
name: service.title,
description: service.description,
})),
};
return (
<div className="min-h-screen bg-white">
<StructuredData data={servicesData} />
{/* 页面内容 */}
</div>
);
}
```
**Step 4: 添加SEO验证测试**
```typescript
// e2e/src/tests/smoke/seo.smoke.spec.ts
import { test, expect } from '@playwright/test';
test.describe('SEO冒烟测试 @smoke', () => {
test('应该有正确的页面标题', async ({ page }) => {
await page.goto('/');
const title = await page.title();
expect(title).toContain('睿新致远');
});
test('应该有meta描述', async ({ page }) => {
await page.goto('/');
const description = await page.locator('meta[name="description"]').getAttribute('content');
expect(description).toBeTruthy();
expect(description?.length).toBeGreaterThan(50);
});
test('应该有结构化数据', async ({ page }) => {
await page.goto('/');
const structuredData = await page.locator('script[type="application/ld+json"]').count();
expect(structuredData).toBeGreaterThan(0);
});
test('应该有Open Graph标签', async ({ page }) => {
await page.goto('/');
const ogTitle = await page.locator('meta[property="og:title"]').getAttribute('content');
const ogDescription = await page.locator('meta[property="og:description"]').getAttribute('content');
expect(ogTitle).toBeTruthy();
expect(ogDescription).toBeTruthy();
});
});
```
**Step 5: 运行SEO测试**
Run: `cd e2e && npm test --grep "SEO冒烟测试"`
Expected: PASS
**Step 6: 提交代码**
```bash
git add src/components/seo/structured-data.tsx src/app/layout.tsx src/app/ e2e/src/tests/smoke/seo.smoke.spec.ts
git commit -m "feat: implement comprehensive SEO optimization with structured data"
```
---
## 阶段三:低优先级优化(1个月)
### Task 3.1: 代码重构
**Files:**
- Create: `src/components/ui/page-header.tsx`
- Modify: `src/app/(marketing)/about/page.tsx:1-15`
- Modify: `src/app/(marketing)/cases/page.tsx:1-20`
- Modify: `src/app/(marketing)/services/page.tsx:1-20`
- Modify: `src/app/(marketing)/products/page.tsx:1-20`
- Modify: `src/app/(marketing)/solutions/page.tsx:1-20`
- Modify: `src/app/(marketing)/contact/page.tsx:1-20`
- Modify: `src/app/(marketing)/news/page.tsx:1-20`
**Step 1: 统一页面标题组件**
```typescript
// src/components/ui/page-header.tsx
import { Badge } from './badge';
interface PageHeaderProps {
badge?: string;
title: string;
description: string;
}
export function PageHeader({ badge, title, description }: PageHeaderProps) {
return (
<div className="bg-linear-to-br from-[#FFFBF5] to-white py-16">
<div className="container-wide">
{badge && (
<Badge className="mb-4">{badge}</Badge>
)}
<h1 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-4">
{title}
</h1>
<p className="text-lg text-[#5C5C5C] max-w-3xl">
{description}
</p>
</div>
</div>
);
}
```
**Step 2: 提取公共卡片组件**
```typescript
// src/components/ui/content-card.tsx
import Link from 'next/link';
import { Card, CardContent } from './card';
import { Badge } from './badge';
interface ContentCardProps {
id: string;
title: string;
description: string;
category?: string;
date?: string;
image?: string;
href: string;
}
export function ContentCard({
id,
title,
description,
category,
date,
image,
href,
}: ContentCardProps) {
return (
<Link href={href} className="group">
<Card className="h-full hover:shadow-xl transition-all duration-300 border-[#E5E5E5] hover:border-[#C41E3A]">
<CardContent className="p-0">
{image && (
<div className="aspect-video bg-linear-to-br from-[#C41E3A]/10 to-[#1C1C1C]/10 flex items-center justify-center">
<span className="text-4xl">📰</span>
</div>
)}
<div className="p-6">
{category && (
<Badge variant="secondary" className="mb-3">
{category}
</Badge>
)}
{date && (
<div className="text-sm text-[#5C5C5C] mb-3">
{date}
</div>
)}
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-3 group-hover:text-[#C41E3A] transition-colors line-clamp-2">
{title}
</h3>
<p className="text-[#5C5C5C] text-sm line-clamp-3">
{description}
</p>
</div>
</CardContent>
</Card>
</Link>
);
}
```
**Step 3: 在各个页面中使用统一组件**
```typescript
// 示例:src/app/(marketing)/cases/page.tsx
import { PageHeader } from '@/components/ui/page-header';
import { ContentCard } from '@/components/ui/content-card';
export default function CasesPage() {
return (
<div className="min-h-screen bg-white">
<PageHeader
badge="成功案例"
title="与谁同行,决定能走多远"
description="我们与优秀的企业同行,共同成长,共创未来"
/>
<div className="container-wide py-16">
<div className="grid md:grid-cols-2 gap-8">
{CASES.map((caseItem) => (
<ContentCard
key={caseItem.id}
id={caseItem.id}
title={caseItem.title}
description={caseItem.description}
category={caseItem.industry}
href={`/cases/${caseItem.id}`}
/>
))}
</div>
</div>
</div>
);
}
```
**Step 4: 运行测试验证重构**
Run: `npm run lint && npm run build`
Expected: No linting errors, BUILD SUCCESS
**Step 5: 提交代码**
```bash
git add src/components/ui/page-header.tsx src/components/ui/content-card.tsx src/app/
git commit -m "refactor: extract common components and unify page structure"
```
---
### Task 3.2: 视觉优化
**Files:**
- Create: `src/components/ui/animated-section.tsx`
- Modify: `src/components/sections/hero-section.tsx:1-100`
- Modify: `src/components/sections/services-section.tsx:1-100`
- Modify: `src/components/sections/products-section.tsx:1-100`
**Step 1: 创建优化的动画section组件**
```typescript
// src/components/ui/animated-section.tsx
'use client';
import { useRef, useEffect } from 'react';
import { motion, useInView } from 'framer-motion';
interface AnimatedSectionProps {
children: React.ReactNode;
className?: string;
delay?: number;
}
export function AnimatedSection({
children,
className = '',
delay = 0,
}: AnimatedSectionProps) {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: '-100px' });
return (
<motion.section
ref={ref}
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay }}
className={className}
>
{children}
</motion.section>
);
}
```
**Step 2: 添加微交互效果**
```typescript
// src/components/ui/interactive-card.tsx
'use client';
import { motion } from 'framer-motion';
import { Card } from './card';
interface InteractiveCardProps {
children: React.ReactNode;
className?: string;
onClick?: () => void;
}
export function InteractiveCard({
children,
className = '',
onClick,
}: InteractiveCardProps) {
return (
<motion.div
whileHover={{ scale: 1.02, y: -4 }}
whileTap={{ scale: 0.98 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
className={className}
onClick={onClick}
>
<Card>{children}</Card>
</motion.div>
);
}
```
**Step 3: 添加视觉回归测试**
```typescript
// e2e/src/tests/visual/interactions.visual.spec.ts
import { test, expect } from '@playwright/test';
test.describe('交互视觉回归测试 @visual', () => {
test('卡片悬停状态应该一致', async ({ page }) => {
await page.goto('/services');
const card = page.locator('.group').first();
await card.hover();
await expect(page).toHaveScreenshot('card-hover.png', {
maxDiffPixels: 100,
});
});
test('按钮点击状态应该一致', async ({ page }) => {
await page.goto('/');
const button = page.getByRole('button', { name: /立即咨询/ });
await button.hover();
await expect(page).toHaveScreenshot('button-hover.png', {
maxDiffPixels: 100,
});
});
});
```
**Step 4: 运行视觉测试**
Run: `cd e2e && npm test --grep "交互视觉回归测试"`
Expected: PASS
**Step 5: 提交代码**
```bash
git add src/components/ui/animated-section.tsx src/components/ui/interactive-card.tsx e2e/src/tests/visual/interactions.visual.spec.ts
git commit -m "feat: add enhanced animations and micro-interactions"
```
---
## 测试验证清单
在每个阶段完成后,运行以下测试验证:
### 阶段一验证
```bash
# 导航一致性测试
cd e2e && npm test --grep "导航"
# 页面冒烟测试
cd e2e && npm test --grep "冒烟测试"
# 错误处理测试
cd e2e && npm test --grep "错误处理"
```
### 阶段二验证
```bash
# 性能测试
cd e2e && npm test --grep "@performance"
# 移动端测试
cd e2e && npm test --grep "@responsive"
# SEO测试
cd e2e && npm test --grep "SEO"
```
### 阶段三验证
```bash
# 视觉测试
cd e2e && npm test --grep "@visual"
# 回归测试
cd e2e && npm test --grep "@regression"
# 完整测试套件
cd e2e && npm test
```
---
## 代码质量检查
在每个任务完成后,运行以下命令确保代码质量:
```bash
# Lint检查
npm run lint
# 类型检查
npx tsc --noEmit
# 构建检查
npm run build
# 格式化检查
npx prettier --check "src/**/*.{ts,tsx}"
```
---
## 文档更新
在完成每个阶段后,更新以下文档:
### README.md
- 更新项目结构
- 添加新的组件说明
- 更新测试命令
### CHANGELOG.md
```markdown
## [版本号] - YYYY-MM-DD
### 新增
- 面包屑导航组件
- 统一的错误处理页面
- 优化的图片加载组件
- 移动端触摸优化
- SEO结构化数据
### 改进
- 导航一致性
- 移动端体验
- 页面加载性能
- SEO优化
### 修复
- 修复服务详情页Link导入问题
- 修复导航不一致问题
```
---
## 部署检查清单
在部署到生产环境前,确保:
- [ ] 所有测试通过
- [ ] Lint检查通过
- [ ] 类型检查通过
- [ ] 构建成功
- [ ] 环境变量配置正确
- [ ] API端点配置正确
- [ ] 图片资源优化
- [ ] 性能指标达标(Lighthouse分数 > 90
- [ ] 无障碍检查通过(axe-core
- [ ] SEO检查通过(Google Rich Results Test
---
## 回滚计划
如果某个阶段出现问题,可以按以下步骤回滚:
```bash
# 查看提交历史
git log --oneline
# 回滚到上一个稳定版本
git revert <commit-hash>
# 或者回滚到特定提交
git reset --hard <commit-hash>
# 强制推送(谨慎使用)
git push --force
```
---
## 持续集成配置
确保以下CI/CD配置正确:
### .woodpecker.yml
```yaml
pipeline:
test:
image: node:18
commands:
- npm ci
- npm run lint
- npm run build
- cd e2e && npm ci
- cd e2e && npm test
deploy:
image: node:18
commands:
- npm run build
- # 部署命令
```
---
## 总结
本实施计划分为三个阶段,每个阶段包含多个bite-sized任务:
1. **阶段一(1-2天)**:高优先级修复
- 导航一致性
- 测试覆盖
- 错误处理
2. **阶段二(1周)**:中优先级优化
- 性能优化
- 移动端体验
- SEO优化
3. **阶段三(1个月)**:低优先级优化
- 代码重构
- 视觉优化
每个任务都遵循TDD原则,包含完整的测试、实现和验证步骤。建议按阶段顺序执行,每个阶段完成后进行完整的测试验证。