- 移除多个页面的面包屑导航组件 - 添加统一的返回按钮组件替代各页面独立实现 - 优化导航栏滚动检测逻辑和动画效果 - 更新常量类型定义和统计数据 - 调整动态导入的SSR配置为false - 添加FlipClock组件展示公司运营时长 - 优化新闻列表页的类型安全和响应式设计
35 KiB
网站全面优化实施计划
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: 创建面包屑导航组件
// 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: 在各个页面集成面包屑导航
// 示例: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 组件以支持统一导航
// 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: 提交代码
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
// 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: 创建关于页面冒烟测试
// 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 以包含新页面
// 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: 提交代码
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 页面
// 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: 创建全局错误页面
// 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: 创建错误边界组件
// 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: 在根布局中集成错误边界
// 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测试
// 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: 提交代码
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: 创建优化的图片组件
// 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 配置以支持图片优化
// 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 中使用优化的图片组件
// 示例: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: 实现代码分割和懒加载
// 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: 添加性能测试
// 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: 提交代码
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: 优化移动端菜单交互
// 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: 添加移动端触摸优化
// 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测试
// 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: 提交代码
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: 创建结构化数据组件
// 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
// 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: 为各个页面添加特定的结构化数据
// 示例: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验证测试
// 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: 提交代码
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: 统一页面标题组件
// 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: 提取公共卡片组件
// 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: 在各个页面中使用统一组件
// 示例: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: 提交代码
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组件
// 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: 添加微交互效果
// 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: 添加视觉回归测试
// 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: 提交代码
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"
测试验证清单
在每个阶段完成后,运行以下测试验证:
阶段一验证
# 导航一致性测试
cd e2e && npm test --grep "导航"
# 页面冒烟测试
cd e2e && npm test --grep "冒烟测试"
# 错误处理测试
cd e2e && npm test --grep "错误处理"
阶段二验证
# 性能测试
cd e2e && npm test --grep "@performance"
# 移动端测试
cd e2e && npm test --grep "@responsive"
# SEO测试
cd e2e && npm test --grep "SEO"
阶段三验证
# 视觉测试
cd e2e && npm test --grep "@visual"
# 回归测试
cd e2e && npm test --grep "@regression"
# 完整测试套件
cd e2e && npm test
代码质量检查
在每个任务完成后,运行以下命令确保代码质量:
# Lint检查
npm run lint
# 类型检查
npx tsc --noEmit
# 构建检查
npm run build
# 格式化检查
npx prettier --check "src/**/*.{ts,tsx}"
文档更新
在完成每个阶段后,更新以下文档:
README.md
- 更新项目结构
- 添加新的组件说明
- 更新测试命令
CHANGELOG.md
## [版本号] - YYYY-MM-DD
### 新增
- 面包屑导航组件
- 统一的错误处理页面
- 优化的图片加载组件
- 移动端触摸优化
- SEO结构化数据
### 改进
- 导航一致性
- 移动端体验
- 页面加载性能
- SEO优化
### 修复
- 修复服务详情页Link导入问题
- 修复导航不一致问题
部署检查清单
在部署到生产环境前,确保:
- 所有测试通过
- Lint检查通过
- 类型检查通过
- 构建成功
- 环境变量配置正确
- API端点配置正确
- 图片资源优化
- 性能指标达标(Lighthouse分数 > 90)
- 无障碍检查通过(axe-core)
- SEO检查通过(Google Rich Results Test)
回滚计划
如果某个阶段出现问题,可以按以下步骤回滚:
# 查看提交历史
git log --oneline
# 回滚到上一个稳定版本
git revert <commit-hash>
# 或者回滚到特定提交
git reset --hard <commit-hash>
# 强制推送(谨慎使用)
git push --force
持续集成配置
确保以下CI/CD配置正确:
.woodpecker.yml
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-2天):高优先级修复
- 导航一致性
- 测试覆盖
- 错误处理
-
阶段二(1周):中优先级优化
- 性能优化
- 移动端体验
- SEO优化
-
阶段三(1个月):低优先级优化
- 代码重构
- 视觉优化
每个任务都遵循TDD原则,包含完整的测试、实现和验证步骤。建议按阶段顺序执行,每个阶段完成后进行完整的测试验证。