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

35 KiB
Raw Blame History

网站全面优化实施计划

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. 阶段一(1-2天):高优先级修复

    • 导航一致性
    • 测试覆盖
    • 错误处理
  2. 阶段二(1周):中优先级优化

    • 性能优化
    • 移动端体验
    • SEO优化
  3. 阶段三(1个月):低优先级优化

    • 代码重构
    • 视觉优化

每个任务都遵循TDD原则,包含完整的测试、实现和验证步骤。建议按阶段顺序执行,每个阶段完成后进行完整的测试验证。