1280 lines
33 KiB
Markdown
1280 lines
33 KiB
Markdown
# 睿新致远官网重构实施计划
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
||
**Goal:** 基于优秀案例参考,全面升级睿新致远官网,融合印章文化与现代科技,提升品牌形象和转化率
|
||
|
||
**Architecture:** 采用 Next.js 16 App Router + React 19 + TypeScript 技术栈,基于现有组件库扩展,优化 Hero 区域印章动画,新增技术洞察板块和客户评价模块,全面提升用户体验和转化路径
|
||
|
||
**Tech Stack:** Next.js 16.1.6, React 19.2.3, TypeScript 5, Tailwind CSS v4, Framer Motion 12.29.2, shadcn/ui
|
||
|
||
---
|
||
|
||
## 实施策略
|
||
|
||
### 优先级说明
|
||
|
||
**P0(核心功能)** - 必须完成,直接影响品牌形象和转化
|
||
**P1(重要功能)** - 显著提升用户体验和专业度
|
||
**P2(增强功能)** - 锦上添花,可后续迭代
|
||
|
||
### 开发原则
|
||
|
||
- **TDD(测试驱动开发)**:先写测试,再写实现
|
||
- **DRY(不重复)**:复用现有组件和工具函数
|
||
- **YAGNI(不过度设计)**:只实现当前需要的功能
|
||
- **频繁提交**:每个小功能点独立提交
|
||
- **性能优先**:关注 LCP、FID、CLS 指标
|
||
|
||
---
|
||
|
||
## Phase 1: P0 核心功能优化(预计 5-7 天)
|
||
|
||
### Task 1: 优化印章粒子动画组件
|
||
|
||
**目标:** 增强印章粒子动画的视觉效果和交互体验
|
||
|
||
**Files:**
|
||
- Modify: `src/components/effects/seal-particle.tsx`
|
||
- Create: `src/components/effects/seal-animation-enhanced.tsx`
|
||
- Test: `src/components/effects/__tests__/seal-animation-enhanced.test.tsx`
|
||
|
||
**Step 1: 创建增强版印章动画组件**
|
||
|
||
创建文件:`src/components/effects/seal-animation-enhanced.tsx`
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import { useEffect, useRef, useCallback, useState } from 'react';
|
||
|
||
interface Particle {
|
||
x: number;
|
||
y: number;
|
||
targetX: number;
|
||
targetY: number;
|
||
vx: number;
|
||
vy: number;
|
||
size: number;
|
||
opacity: number;
|
||
color: string;
|
||
life: number;
|
||
maxLife: number;
|
||
stage: 'idle' | 'dispersing' | 'reforming';
|
||
}
|
||
|
||
interface SealAnimationEnhancedProps {
|
||
width?: number;
|
||
height?: number;
|
||
particleCount?: number;
|
||
colors?: string[];
|
||
sealText?: string;
|
||
animationStages?: boolean;
|
||
onStageChange?: (stage: string) => void;
|
||
className?: string;
|
||
}
|
||
|
||
export function SealAnimationEnhanced({
|
||
width = 300,
|
||
height = 300,
|
||
particleCount = 150,
|
||
colors = ['#C41E3A', '#D4A574', '#8B4513'],
|
||
sealText = '睿新',
|
||
animationStages = true,
|
||
onStageChange,
|
||
className = '',
|
||
}: SealAnimationEnhancedProps) {
|
||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||
const particlesRef = useRef<Particle[]>([]);
|
||
const animationRef = useRef<number | null>(null);
|
||
const [currentStage, setCurrentStage] = useState<'idle' | 'dispersing' | 'reforming'>('idle');
|
||
const stageTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||
|
||
const createSealShape = useCallback((width: number, height: number) => {
|
||
const centerX = width / 2;
|
||
const centerY = height / 2;
|
||
const sealSize = Math.min(width, height) * 0.35;
|
||
const particles: { x: number; y: number }[] = [];
|
||
|
||
// 创建印章形状的粒子位置(简化版方形印章)
|
||
for (let i = 0; i < particleCount; i++) {
|
||
const angle = (i / particleCount) * Math.PI * 2;
|
||
const radius = sealSize * (0.8 + Math.random() * 0.4);
|
||
particles.push({
|
||
x: centerX + Math.cos(angle) * radius * (Math.random() > 0.5 ? 1 : 0.8),
|
||
y: centerY + Math.sin(angle) * radius * (Math.random() > 0.5 ? 1 : 0.8),
|
||
});
|
||
}
|
||
|
||
return particles;
|
||
}, [particleCount]);
|
||
|
||
const createParticle = useCallback(
|
||
(x: number, y: number, targetX: number, targetY: number): Particle => {
|
||
const color = colors[Math.floor(Math.random() * colors.length)];
|
||
const size = 2 + Math.random() * 3;
|
||
const maxLife = 200 + Math.random() * 100;
|
||
|
||
return {
|
||
x,
|
||
y,
|
||
targetX,
|
||
targetY,
|
||
vx: (Math.random() - 0.5) * 2,
|
||
vy: (Math.random() - 0.5) * 2,
|
||
size,
|
||
opacity: 0.6 + Math.random() * 0.4,
|
||
color,
|
||
life: 0,
|
||
maxLife,
|
||
stage: 'idle',
|
||
};
|
||
},
|
||
[colors]
|
||
);
|
||
|
||
useEffect(() => {
|
||
const canvas = canvasRef.current;
|
||
if (!canvas) return;
|
||
|
||
const ctx = canvas.getContext('2d');
|
||
if (!ctx) return;
|
||
|
||
canvas.width = width;
|
||
canvas.height = height;
|
||
|
||
const sealPositions = createSealShape(width, height);
|
||
particlesRef.current = sealPositions.map((pos, i) =>
|
||
createParticle(pos.x, pos.y, pos.x, pos.y)
|
||
);
|
||
|
||
// 动画阶段控制
|
||
if (animationStages) {
|
||
// 3秒后开始分散
|
||
stageTimerRef.current = setTimeout(() => {
|
||
setCurrentStage('dispersing');
|
||
onStageChange?.('dispersing');
|
||
|
||
particlesRef.current.forEach(p => {
|
||
p.vx = (Math.random() - 0.5) * 4;
|
||
p.vy = (Math.random() - 0.5) * 4;
|
||
p.stage = 'dispersing';
|
||
});
|
||
|
||
// 2秒后开始重组
|
||
setTimeout(() => {
|
||
setCurrentStage('reforming');
|
||
onStageChange?.('reforming');
|
||
|
||
particlesRef.current.forEach(p => {
|
||
p.stage = 'reforming';
|
||
});
|
||
|
||
// 3秒后回到idle
|
||
setTimeout(() => {
|
||
setCurrentStage('idle');
|
||
onStageChange?.('idle');
|
||
}, 3000);
|
||
}, 2000);
|
||
}, 3000);
|
||
}
|
||
|
||
const animate = () => {
|
||
ctx.clearRect(0, 0, width, height);
|
||
|
||
particlesRef.current.forEach((particle) => {
|
||
if (particle.stage === 'reforming') {
|
||
// 向目标位置移动
|
||
const dx = particle.targetX - particle.x;
|
||
const dy = particle.targetY - particle.y;
|
||
particle.vx += dx * 0.02;
|
||
particle.vy += dy * 0.02;
|
||
particle.vx *= 0.95;
|
||
particle.vy *= 0.95;
|
||
}
|
||
|
||
particle.x += particle.vx;
|
||
particle.y += particle.vy;
|
||
particle.life++;
|
||
|
||
// 绘制粒子
|
||
ctx.beginPath();
|
||
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
|
||
ctx.fillStyle = particle.color;
|
||
ctx.globalAlpha = particle.opacity;
|
||
ctx.fill();
|
||
ctx.globalAlpha = 1;
|
||
});
|
||
|
||
animationRef.current = requestAnimationFrame(animate);
|
||
};
|
||
|
||
animate();
|
||
|
||
return () => {
|
||
if (animationRef.current) {
|
||
cancelAnimationFrame(animationRef.current);
|
||
}
|
||
if (stageTimerRef.current) {
|
||
clearTimeout(stageTimerRef.current);
|
||
}
|
||
};
|
||
}, [width, height, createSealShape, createParticle, animationStages, onStageChange]);
|
||
|
||
return (
|
||
<canvas
|
||
ref={canvasRef}
|
||
className={className}
|
||
style={{ width, height }}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 创建测试文件**
|
||
|
||
创建文件:`src/components/effects/__tests__/seal-animation-enhanced.test.tsx`
|
||
|
||
```typescript
|
||
import { render, screen } from '@testing-library/react';
|
||
import { SealAnimationEnhanced } from '../seal-animation-enhanced';
|
||
|
||
describe('SealAnimationEnhanced', () => {
|
||
it('should render canvas element', () => {
|
||
render(<SealAnimationEnhanced />);
|
||
const canvas = screen.getByRole('img');
|
||
expect(canvas).toBeInTheDocument();
|
||
});
|
||
|
||
it('should accept custom props', () => {
|
||
const { container } = render(
|
||
<SealAnimationEnhanced
|
||
width={400}
|
||
height={400}
|
||
particleCount={200}
|
||
colors={['#FF0000', '#00FF00']}
|
||
sealText="测试"
|
||
animationStages={false}
|
||
/>
|
||
);
|
||
const canvas = container.querySelector('canvas');
|
||
expect(canvas).toHaveAttribute('width', '400');
|
||
expect(canvas).toHaveAttribute('height', '400');
|
||
});
|
||
|
||
it('should call onStageChange callback', (done) => {
|
||
const handleStageChange = (stage: string) => {
|
||
expect(stage).toBe('dispersing');
|
||
done();
|
||
};
|
||
|
||
render(
|
||
<SealAnimationEnhanced
|
||
animationStages={true}
|
||
onStageChange={handleStageChange}
|
||
/>
|
||
);
|
||
});
|
||
});
|
||
```
|
||
|
||
**Step 3: 运行测试**
|
||
|
||
```bash
|
||
npm test src/components/effects/__tests__/seal-animation-enhanced.test.tsx
|
||
```
|
||
|
||
Expected: PASS
|
||
|
||
**Step 4: 提交代码**
|
||
|
||
```bash
|
||
git add src/components/effects/seal-animation-enhanced.tsx
|
||
git add src/components/effects/__tests__/seal-animation-enhanced.test.tsx
|
||
git commit -m "feat: add enhanced seal animation component with stages"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2: 更新 Hero Section 使用增强版印章动画
|
||
|
||
**目标:** 在 Hero 区域集成增强版印章动画,提升视觉冲击力
|
||
|
||
**Files:**
|
||
- Modify: `src/components/sections/hero-section.tsx`
|
||
- Create: `src/lib/constants.ts` (如果不存在)
|
||
|
||
**Step 1: 检查并创建 constants 文件**
|
||
|
||
检查文件是否存在:`src/lib/constants.ts`
|
||
|
||
如果不存在,创建:
|
||
|
||
```typescript
|
||
export const COMPANY_INFO = {
|
||
name: '睿新致远',
|
||
slogan: '以技术创新,铸就卓越未来',
|
||
description: '专注于数字化转型与技术创新,为企业提供全方位的技术解决方案',
|
||
foundedYear: 2014,
|
||
teamSize: '50+',
|
||
};
|
||
|
||
export const STATS = [
|
||
{ value: '10+', label: '年技术积累' },
|
||
{ value: '200+', label: '成功案例' },
|
||
{ value: '50+', label: '技术专家' },
|
||
{ value: '99.5%', label: '客户满意度' },
|
||
];
|
||
|
||
export const NAV_LINKS = [
|
||
{ href: '#home', label: '首页' },
|
||
{ href: '#solutions', label: '解决方案' },
|
||
{ href: '#cases', label: '成功案例' },
|
||
{ href: '#about', label: '关于我们' },
|
||
{ href: '#contact', label: '联系我们' },
|
||
];
|
||
```
|
||
|
||
**Step 2: 更新 Hero Section**
|
||
|
||
修改文件:`src/components/sections/hero-section.tsx`
|
||
|
||
在文件顶部添加导入:
|
||
|
||
```typescript
|
||
import { SealAnimationEnhanced } from '@/components/effects/seal-animation-enhanced';
|
||
```
|
||
|
||
在 Hero Section 组件中,找到现有的 `SealParticle` 组件,替换为:
|
||
|
||
```typescript
|
||
{/* 增强版印章动画 */}
|
||
<div className="absolute right-0 top-1/2 -translate-y-1/2 hidden lg:block">
|
||
<SealAnimationEnhanced
|
||
width={400}
|
||
height={400}
|
||
particleCount={150}
|
||
colors={['#C41E3A', '#D4A574', '#8B4513']}
|
||
sealText="睿新"
|
||
animationStages={true}
|
||
onStageChange={(stage) => console.log('Animation stage:', stage)}
|
||
className="opacity-80"
|
||
/>
|
||
</div>
|
||
```
|
||
|
||
**Step 3: 测试更新后的 Hero Section**
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
访问 http://localhost:3000,检查:
|
||
- 印章动画是否正常显示
|
||
- 动画阶段是否按预期切换(idle → dispersing → reforming → idle)
|
||
- 移动端是否隐藏动画(lg:block)
|
||
|
||
**Step 4: 提交代码**
|
||
|
||
```bash
|
||
git add src/components/sections/hero-section.tsx
|
||
git add src/lib/constants.ts
|
||
git commit -m "feat: integrate enhanced seal animation in hero section"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: 创建技术洞察卡片组件
|
||
|
||
**目标:** 创建可复用的技术洞察卡片组件,用于展示技术文章和白皮书
|
||
|
||
**Files:**
|
||
- Create: `src/components/ui/insight-card.tsx`
|
||
- Create: `src/components/ui/__tests__/insight-card.test.tsx`
|
||
|
||
**Step 1: 创建 InsightCard 组件**
|
||
|
||
创建文件:`src/components/ui/insight-card.tsx`
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import { Calendar, Clock, ArrowRight } from 'lucide-react';
|
||
import { Badge } from '@/components/ui/badge';
|
||
|
||
export interface InsightCardProps {
|
||
title: string;
|
||
excerpt: string;
|
||
category: string;
|
||
readTime: string;
|
||
publishedAt: string;
|
||
imageUrl?: string;
|
||
href: string;
|
||
featured?: boolean;
|
||
}
|
||
|
||
export function InsightCard({
|
||
title,
|
||
excerpt,
|
||
category,
|
||
readTime,
|
||
publishedAt,
|
||
imageUrl,
|
||
href,
|
||
featured = false,
|
||
}: InsightCardProps) {
|
||
return (
|
||
<article
|
||
className={`
|
||
group relative overflow-hidden rounded-lg border border-[#E5E5E5]/50
|
||
bg-white transition-all duration-300 hover:shadow-lg
|
||
${featured ? 'md:col-span-2' : ''}
|
||
`}
|
||
>
|
||
{imageUrl && (
|
||
<div className="relative h-48 overflow-hidden">
|
||
<img
|
||
src={imageUrl}
|
||
alt={title}
|
||
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
|
||
/>
|
||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" />
|
||
</div>
|
||
)}
|
||
|
||
<div className="p-6">
|
||
<div className="flex items-center gap-3 mb-3">
|
||
<Badge variant="secondary" className="text-xs">
|
||
{category}
|
||
</Badge>
|
||
<div className="flex items-center gap-1 text-xs text-[#737373]">
|
||
<Clock className="w-3 h-3" />
|
||
<span>{readTime}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 className="text-lg font-semibold text-[#171717] mb-2 line-clamp-2 group-hover:text-[#C41E3A] transition-colors">
|
||
{title}
|
||
</h3>
|
||
|
||
<p className="text-sm text-[#737373] mb-4 line-clamp-2">
|
||
{excerpt}
|
||
</p>
|
||
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-1 text-xs text-[#A3A3A3]">
|
||
<Calendar className="w-3 h-3" />
|
||
<span>{publishedAt}</span>
|
||
</div>
|
||
|
||
<a
|
||
href={href}
|
||
className="inline-flex items-center gap-1 text-sm font-medium text-[#C41E3A] hover:gap-2 transition-all"
|
||
>
|
||
阅读更多
|
||
<ArrowRight className="w-4 h-4" />
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 创建测试文件**
|
||
|
||
创建文件:`src/components/ui/__tests__/insight-card.test.tsx`
|
||
|
||
```typescript
|
||
import { render, screen } from '@testing-library/react';
|
||
import { InsightCard } from '../insight-card';
|
||
|
||
describe('InsightCard', () => {
|
||
const defaultProps = {
|
||
title: '测试文章标题',
|
||
excerpt: '这是一篇测试文章的摘要内容',
|
||
category: '技术洞察',
|
||
readTime: '5 分钟',
|
||
publishedAt: '2026-02-13',
|
||
href: '/insights/test-article',
|
||
};
|
||
|
||
it('should render card with all props', () => {
|
||
render(<InsightCard {...defaultProps} />);
|
||
|
||
expect(screen.getByText('测试文章标题')).toBeInTheDocument();
|
||
expect(screen.getByText('这是一篇测试文章的摘要内容')).toBeInTheDocument();
|
||
expect(screen.getByText('技术洞察')).toBeInTheDocument();
|
||
expect(screen.getByText('5 分钟')).toBeInTheDocument();
|
||
expect(screen.getByText('2026-02-13')).toBeInTheDocument();
|
||
});
|
||
|
||
it('should render image when imageUrl provided', () => {
|
||
render(
|
||
<InsightCard
|
||
{...defaultProps}
|
||
imageUrl="/test-image.jpg"
|
||
/>
|
||
);
|
||
|
||
const image = screen.getByRole('img');
|
||
expect(image).toHaveAttribute('src', '/test-image.jpg');
|
||
});
|
||
|
||
it('should have correct link href', () => {
|
||
render(<InsightCard {...defaultProps} />);
|
||
|
||
const link = screen.getByRole('link', { name: /阅读更多/i });
|
||
expect(link).toHaveAttribute('href', '/insights/test-article');
|
||
});
|
||
|
||
it('should apply featured class when featured prop is true', () => {
|
||
const { container } = render(
|
||
<InsightCard {...defaultProps} featured={true} />
|
||
);
|
||
|
||
const article = container.querySelector('article');
|
||
expect(article).toHaveClass('md:col-span-2');
|
||
});
|
||
});
|
||
```
|
||
|
||
**Step 3: 运行测试**
|
||
|
||
```bash
|
||
npm test src/components/ui/__tests__/insight-card.test.tsx
|
||
```
|
||
|
||
Expected: PASS
|
||
|
||
**Step 4: 提交代码**
|
||
|
||
```bash
|
||
git add src/components/ui/insight-card.tsx
|
||
git add src/components/ui/__tests__/insight-card.test.tsx
|
||
git commit -m "feat: add insight card component for tech insights section"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4: 创建技术洞察板块组件
|
||
|
||
**目标:** 创建完整的技术洞察板块,展示最新技术文章和白皮书
|
||
|
||
**Files:**
|
||
- Create: `src/components/sections/insights-section.tsx`
|
||
- Create: `src/components/sections/__tests__/insights-section.test.tsx`
|
||
- Modify: `src/app/(marketing)/page.tsx`
|
||
|
||
**Step 1: 创建 InsightsSection 组件**
|
||
|
||
创建文件:`src/components/sections/insights-section.tsx`
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import { useEffect, useState, useRef } from 'react';
|
||
import { InsightCard } from '@/components/ui/insight-card';
|
||
import { Button } from '@/components/ui/button';
|
||
import { ArrowRight } from 'lucide-react';
|
||
|
||
interface Insight {
|
||
id: string;
|
||
title: string;
|
||
excerpt: string;
|
||
category: string;
|
||
readTime: string;
|
||
publishedAt: string;
|
||
imageUrl?: string;
|
||
href: string;
|
||
featured?: boolean;
|
||
}
|
||
|
||
const MOCK_INSIGHTS: Insight[] = [
|
||
{
|
||
id: '1',
|
||
title: '2025年技术趋势:AI驱动的数字化转型',
|
||
excerpt: '深入探讨人工智能如何重塑企业技术架构,以及如何把握AI时代的机遇',
|
||
category: '技术趋势',
|
||
readTime: '8 分钟',
|
||
publishedAt: '2026-02-10',
|
||
imageUrl: '/insights/ai-trend.jpg',
|
||
href: '/insights/ai-trend-2025',
|
||
featured: true,
|
||
},
|
||
{
|
||
id: '2',
|
||
title: '微服务架构最佳实践指南',
|
||
excerpt: '从单体应用到微服务的演进之路,包含实战案例和避坑指南',
|
||
category: '架构设计',
|
||
readTime: '12 分钟',
|
||
publishedAt: '2026-02-08',
|
||
imageUrl: '/insights/microservices.jpg',
|
||
href: '/insights/microservices-best-practices',
|
||
},
|
||
{
|
||
id: '3',
|
||
title: '云原生技术栈选型指南',
|
||
excerpt: 'Kubernetes、Docker、Service Mesh等技术栈的深度对比与选型建议',
|
||
category: '云原生',
|
||
readTime: '10 分钟',
|
||
publishedAt: '2026-02-05',
|
||
imageUrl: '/insights/cloud-native.jpg',
|
||
href: '/insights/cloud-native-stack-guide',
|
||
},
|
||
];
|
||
|
||
export function InsightsSection() {
|
||
const [isVisible, setIsVisible] = useState(false);
|
||
const sectionRef = useRef<HTMLElement>(null);
|
||
|
||
useEffect(() => {
|
||
const observer = new IntersectionObserver(
|
||
([entry]) => {
|
||
if (entry.isIntersecting) {
|
||
setIsVisible(true);
|
||
}
|
||
},
|
||
{ threshold: 0.1 }
|
||
);
|
||
|
||
if (sectionRef.current) {
|
||
observer.observe(sectionRef.current);
|
||
}
|
||
|
||
return () => observer.disconnect();
|
||
}, []);
|
||
|
||
return (
|
||
<section
|
||
id="insights"
|
||
ref={sectionRef}
|
||
className="py-24 bg-[#FAFAFA]"
|
||
>
|
||
<div className="container-wide">
|
||
<div
|
||
className={`
|
||
text-center mb-16
|
||
opacity-0 translate-y-4
|
||
${isVisible ? 'animate-fade-in-up' : ''}
|
||
`}
|
||
>
|
||
<h2 className="text-3xl sm:text-4xl font-semibold text-[#171717] mb-4">
|
||
技术洞察
|
||
</h2>
|
||
<p className="text-lg text-[#737373] max-w-2xl mx-auto">
|
||
分享前沿技术趋势、最佳实践和深度思考,助力企业数字化转型
|
||
</p>
|
||
</div>
|
||
|
||
<div
|
||
className={`
|
||
grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-12
|
||
opacity-0 translate-y-4
|
||
${isVisible ? 'animate-fade-in-up stagger-1' : ''}
|
||
`}
|
||
>
|
||
{MOCK_INSIGHTS.map((insight) => (
|
||
<InsightCard key={insight.id} {...insight} />
|
||
))}
|
||
</div>
|
||
|
||
<div
|
||
className={`
|
||
text-center
|
||
opacity-0 translate-y-4
|
||
${isVisible ? 'animate-fade-in-up stagger-2' : ''}
|
||
`}
|
||
>
|
||
<Button
|
||
variant="outline"
|
||
size="lg"
|
||
className="group"
|
||
onClick={() => window.location.href = '/insights'}
|
||
>
|
||
查看全部洞察
|
||
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 创建测试文件**
|
||
|
||
创建文件:`src/components/sections/__tests__/insights-section.test.tsx`
|
||
|
||
```typescript
|
||
import { render, screen } from '@testing-library/react';
|
||
import { InsightsSection } from '../insights-section';
|
||
|
||
describe('InsightsSection', () => {
|
||
it('should render section with title', () => {
|
||
render(<InsightsSection />);
|
||
|
||
expect(screen.getByText('技术洞察')).toBeInTheDocument();
|
||
expect(screen.getByText(/分享前沿技术趋势/)).toBeInTheDocument();
|
||
});
|
||
|
||
it('should render insight cards', () => {
|
||
render(<InsightsSection />);
|
||
|
||
expect(screen.getByText('2025年技术趋势:AI驱动的数字化转型')).toBeInTheDocument();
|
||
expect(screen.getByText('微服务架构最佳实践指南')).toBeInTheDocument();
|
||
expect(screen.getByText('云原生技术栈选型指南')).toBeInTheDocument();
|
||
});
|
||
|
||
it('should render view all button', () => {
|
||
render(<InsightsSection />);
|
||
|
||
expect(screen.getByText('查看全部洞察')).toBeInTheDocument();
|
||
});
|
||
});
|
||
```
|
||
|
||
**Step 3: 更新首页集成技术洞察板块**
|
||
|
||
修改文件:`src/app/(marketing)/page.tsx`
|
||
|
||
在导入部分添加:
|
||
|
||
```typescript
|
||
import { InsightsSection } from '@/components/sections/insights-section';
|
||
```
|
||
|
||
在 `HomePage` 组件中,在 `<CasesSection />` 之前添加:
|
||
|
||
```typescript
|
||
<InsightsSection />
|
||
```
|
||
|
||
**Step 4: 测试集成效果**
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
访问 http://localhost:3000,检查:
|
||
- 技术洞察板块是否正确显示
|
||
- 卡片布局是否响应式
|
||
- 滚动动画是否触发
|
||
|
||
**Step 5: 提交代码**
|
||
|
||
```bash
|
||
git add src/components/sections/insights-section.tsx
|
||
git add src/components/sections/__tests__/insights-section.test.tsx
|
||
git add src/app/(marketing)/page.tsx
|
||
git commit -m "feat: add insights section to homepage"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5: 创建客户评价组件
|
||
|
||
**目标:** 创建客户评价展示组件,增强社会证明
|
||
|
||
**Files:**
|
||
- Create: `src/components/ui/testimonial-card.tsx`
|
||
- Create: `src/components/sections/testimonials-section.tsx`
|
||
- Modify: `src/app/(marketing)/page.tsx`
|
||
|
||
**Step 1: 创建 TestimonialCard 组件**
|
||
|
||
创建文件:`src/components/ui/testimonial-card.tsx`
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import { Quote } from 'lucide-react';
|
||
|
||
export interface TestimonialCardProps {
|
||
quote: string;
|
||
author: string;
|
||
position: string;
|
||
company: string;
|
||
avatarUrl?: string;
|
||
rating?: number;
|
||
}
|
||
|
||
export function TestimonialCard({
|
||
quote,
|
||
author,
|
||
position,
|
||
company,
|
||
avatarUrl,
|
||
rating = 5,
|
||
}: TestimonialCardProps) {
|
||
return (
|
||
<div className="relative p-8 rounded-lg border border-[#E5E5E5]/50 bg-white hover:shadow-md transition-shadow">
|
||
<Quote className="absolute top-6 right-6 w-8 h-8 text-[#C41E3A]/10" />
|
||
|
||
{rating > 0 && (
|
||
<div className="flex gap-1 mb-4">
|
||
{Array.from({ length: rating }).map((_, i) => (
|
||
<svg
|
||
key={i}
|
||
className="w-4 h-4 text-[#C41E3A]"
|
||
fill="currentColor"
|
||
viewBox="0 0 20 20"
|
||
>
|
||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||
</svg>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
<blockquote className="text-base text-[#171717] mb-6 leading-relaxed">
|
||
"{quote}"
|
||
</blockquote>
|
||
|
||
<div className="flex items-center gap-4">
|
||
{avatarUrl && (
|
||
<img
|
||
src={avatarUrl}
|
||
alt={author}
|
||
className="w-12 h-12 rounded-full object-cover"
|
||
/>
|
||
)}
|
||
<div>
|
||
<div className="font-semibold text-[#171717]">{author}</div>
|
||
<div className="text-sm text-[#737373]">
|
||
{position} · {company}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 创建 TestimonialsSection 组件**
|
||
|
||
创建文件:`src/components/sections/testimonials-section.tsx`
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import { useEffect, useState, useRef } from 'react';
|
||
import { TestimonialCard } from '@/components/ui/testimonial-card';
|
||
|
||
interface Testimonial {
|
||
id: string;
|
||
quote: string;
|
||
author: string;
|
||
position: string;
|
||
company: string;
|
||
avatarUrl?: string;
|
||
rating?: number;
|
||
}
|
||
|
||
const MOCK_TESTIMONIALS: Testimonial[] = [
|
||
{
|
||
id: '1',
|
||
quote: '睿新致远团队的专业能力和服务态度让我们印象深刻,他们不仅交付了高质量的产品,还帮助我们建立了完善的技术体系。',
|
||
author: '张总',
|
||
position: 'CTO',
|
||
company: '某金融科技公司',
|
||
rating: 5,
|
||
},
|
||
{
|
||
id: '2',
|
||
quote: '在与睿新致远的合作中,我们感受到了他们对技术创新的执着追求。他们提供的解决方案极大地提升了我们的业务效率。',
|
||
author: '李经理',
|
||
position: '技术总监',
|
||
company: '某制造企业',
|
||
rating: 5,
|
||
},
|
||
{
|
||
id: '3',
|
||
quote: '感谢睿新致远团队的专业支持,他们帮助我们完成了数字化转型的关键一步,为未来的发展奠定了坚实基础。',
|
||
author: '王总',
|
||
position: '总经理',
|
||
company: '某零售集团',
|
||
rating: 5,
|
||
},
|
||
];
|
||
|
||
export function TestimonialsSection() {
|
||
const [isVisible, setIsVisible] = useState(false);
|
||
const sectionRef = useRef<HTMLElement>(null);
|
||
|
||
useEffect(() => {
|
||
const observer = new IntersectionObserver(
|
||
([entry]) => {
|
||
if (entry.isIntersecting) {
|
||
setIsVisible(true);
|
||
}
|
||
},
|
||
{ threshold: 0.1 }
|
||
);
|
||
|
||
if (sectionRef.current) {
|
||
observer.observe(sectionRef.current);
|
||
}
|
||
|
||
return () => observer.disconnect();
|
||
}, []);
|
||
|
||
return (
|
||
<section
|
||
ref={sectionRef}
|
||
className="py-24 bg-white"
|
||
>
|
||
<div className="container-wide">
|
||
<div
|
||
className={`
|
||
text-center mb-16
|
||
opacity-0 translate-y-4
|
||
${isVisible ? 'animate-fade-in-up' : ''}
|
||
`}
|
||
>
|
||
<h2 className="text-3xl sm:text-4xl font-semibold text-[#171717] mb-4">
|
||
客户评价
|
||
</h2>
|
||
<p className="text-lg text-[#737373] max-w-2xl mx-auto">
|
||
听听我们的客户怎么说
|
||
</p>
|
||
</div>
|
||
|
||
<div
|
||
className={`
|
||
grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6
|
||
opacity-0 translate-y-4
|
||
${isVisible ? 'animate-fade-in-up stagger-1' : ''}
|
||
`}
|
||
>
|
||
{MOCK_TESTIMONIALS.map((testimonial) => (
|
||
<TestimonialCard key={testimonial.id} {...testimonial} />
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 3: 更新首页集成客户评价板块**
|
||
|
||
修改文件:`src/app/(marketing)/page.tsx`
|
||
|
||
在导入部分添加:
|
||
|
||
```typescript
|
||
import { TestimonialsSection } from '@/components/sections/testimonials-section';
|
||
```
|
||
|
||
在 `HomePage` 组件中,在 `<ContactSection />` 之前添加:
|
||
|
||
```typescript
|
||
<TestimonialsSection />
|
||
```
|
||
|
||
**Step 4: 测试集成效果**
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
访问 http://localhost:3000,检查客户评价板块是否正确显示
|
||
|
||
**Step 5: 提交代码**
|
||
|
||
```bash
|
||
git add src/components/ui/testimonial-card.tsx
|
||
git add src/components/sections/testimonials-section.tsx
|
||
git add src/app/(marketing)/page.tsx
|
||
git commit -m "feat: add testimonials section to homepage"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6: 优化联系表单
|
||
|
||
**目标:** 优化联系表单的用户体验和验证逻辑
|
||
|
||
**Files:**
|
||
- Modify: `src/components/sections/contact-section.tsx`
|
||
- Create: `src/lib/validations/contact.ts`
|
||
|
||
**Step 1: 创建表单验证逻辑**
|
||
|
||
创建文件:`src/lib/validations/contact.ts`
|
||
|
||
```typescript
|
||
import { z } from 'zod';
|
||
|
||
export const contactFormSchema = z.object({
|
||
name: z
|
||
.string()
|
||
.min(2, '姓名至少需要2个字符')
|
||
.max(50, '姓名不能超过50个字符'),
|
||
company: z
|
||
.string()
|
||
.min(2, '公司名称至少需要2个字符')
|
||
.max(100, '公司名称不能超过100个字符'),
|
||
email: z
|
||
.string()
|
||
.email('请输入有效的邮箱地址'),
|
||
phone: z
|
||
.string()
|
||
.regex(/^1[3-9]\d{9}$/, '请输入有效的手机号码')
|
||
.optional()
|
||
.or(z.literal('')),
|
||
message: z
|
||
.string()
|
||
.min(10, '留言内容至少需要10个字符')
|
||
.max(500, '留言内容不能超过500个字符'),
|
||
});
|
||
|
||
export type ContactFormData = z.infer<typeof contactFormSchema>;
|
||
|
||
export function validateContactForm(data: unknown) {
|
||
return contactFormSchema.safeParse(data);
|
||
}
|
||
```
|
||
|
||
**Step 2: 更新 ContactSection 组件**
|
||
|
||
修改文件:`src/components/sections/contact-section.tsx`
|
||
|
||
添加表单验证和提交逻辑(具体代码根据现有实现调整)
|
||
|
||
**Step 3: 测试表单验证**
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
测试各种表单输入场景
|
||
|
||
**Step 4: 提交代码**
|
||
|
||
```bash
|
||
git add src/lib/validations/contact.ts
|
||
git add src/components/sections/contact-section.tsx
|
||
git commit -m "feat: add form validation to contact section"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 2: P1 重要功能优化(预计 3-4 天)
|
||
|
||
### Task 7: 优化移动端体验
|
||
|
||
**目标:** 优化移动端的导航、动画和性能
|
||
|
||
**Files:**
|
||
- Modify: `src/components/layout/header.tsx`
|
||
- Modify: `src/components/effects/seal-particle.tsx`
|
||
- Create: `src/hooks/use-device-performance.ts`
|
||
|
||
**Step 1: 创建设备性能检测 Hook**
|
||
|
||
创建文件:`src/hooks/use-device-performance.ts`
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import { useState, useEffect } from 'react';
|
||
|
||
export type PerformanceLevel = 'low' | 'medium' | 'high';
|
||
|
||
export function useDevicePerformance(): PerformanceLevel {
|
||
const [performance, setPerformance] = useState<PerformanceLevel>('high');
|
||
|
||
useEffect(() => {
|
||
// 检测设备性能
|
||
const checkPerformance = () => {
|
||
// 检查 CPU 核心数
|
||
const cores = navigator.hardwareConcurrency || 4;
|
||
|
||
// 检查设备内存(如果可用)
|
||
const memory = (navigator as any).deviceMemory || 8;
|
||
|
||
// 检查是否为移动设备
|
||
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||
navigator.userAgent
|
||
);
|
||
|
||
// 检查是否启用了减少动画
|
||
const prefersReducedMotion = window.matchMedia(
|
||
'(prefers-reduced-motion: reduce)'
|
||
).matches;
|
||
|
||
if (prefersReducedMotion) {
|
||
setPerformance('low');
|
||
} else if (isMobile && (cores < 4 || memory < 4)) {
|
||
setPerformance('low');
|
||
} else if (isMobile || cores < 8) {
|
||
setPerformance('medium');
|
||
} else {
|
||
setPerformance('high');
|
||
}
|
||
};
|
||
|
||
checkPerformance();
|
||
}, []);
|
||
|
||
return performance;
|
||
}
|
||
```
|
||
|
||
**Step 2: 更新印章动画组件以支持性能降级**
|
||
|
||
修改文件:`src/components/effects/seal-particle.tsx`
|
||
|
||
添加性能检测和降级逻辑
|
||
|
||
**Step 3: 优化移动端导航**
|
||
|
||
修改文件:`src/components/layout/header.tsx`
|
||
|
||
优化移动端菜单交互
|
||
|
||
**Step 4: 测试移动端体验**
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
使用浏览器开发者工具测试移动端显示效果
|
||
|
||
**Step 5: 提交代码**
|
||
|
||
```bash
|
||
git add src/hooks/use-device-performance.ts
|
||
git add src/components/effects/seal-particle.tsx
|
||
git add src/components/layout/header.tsx
|
||
git commit -m "feat: optimize mobile experience with performance detection"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 8: 添加案例详情页优化
|
||
|
||
**目标:** 优化案例详情页的展示效果和用户体验
|
||
|
||
**Files:**
|
||
- Modify: `src/app/(marketing)/cases/[slug]/page.tsx` (如果存在)
|
||
- Create: `src/components/ui/case-detail-card.tsx`
|
||
|
||
**Step 1: 创建案例详情卡片组件**
|
||
|
||
创建文件:`src/components/ui/case-detail-card.tsx`
|
||
|
||
**Step 2: 优化案例详情页布局**
|
||
|
||
修改案例详情页文件
|
||
|
||
**Step 3: 测试案例详情页**
|
||
|
||
**Step 4: 提交代码**
|
||
|
||
---
|
||
|
||
## Phase 3: P2 增强功能(预计 2-3 天)
|
||
|
||
### Task 9: 添加深色模式支持
|
||
|
||
**目标:** 实现深色/浅色主题切换
|
||
|
||
**Files:**
|
||
- Create: `src/components/ui/theme-toggle.tsx`
|
||
- Modify: `src/app/layout.tsx`
|
||
- Modify: `src/app/globals.css`
|
||
|
||
**Step 1: 创建主题切换组件**
|
||
|
||
**Step 2: 更新全局样式**
|
||
|
||
**Step 3: 测试主题切换**
|
||
|
||
**Step 4: 提交代码**
|
||
|
||
---
|
||
|
||
### Task 10: 性能优化和测试
|
||
|
||
**目标:** 全面优化性能指标,确保达到预期目标
|
||
|
||
**Files:**
|
||
- Modify: `next.config.ts`
|
||
- Create: `tests/performance/lighthouse.test.ts`
|
||
|
||
**Step 1: 配置性能优化**
|
||
|
||
**Step 2: 运行 Lighthouse 测试**
|
||
|
||
**Step 3: 优化关键指标**
|
||
|
||
**Step 4: 提交代码**
|
||
|
||
---
|
||
|
||
## 验收标准
|
||
|
||
### 功能验收
|
||
|
||
- [ ] Hero 区域印章动画正常工作,支持多阶段动画
|
||
- [ ] 技术洞察板块正确展示,响应式布局良好
|
||
- [ ] 客户评价板块正常显示,星级评分准确
|
||
- [ ] 联系表单验证逻辑正确,错误提示清晰
|
||
- [ ] 移动端体验流畅,动画降级正常
|
||
|
||
### 性能验收
|
||
|
||
- [ ] LCP < 2.5秒
|
||
- [ ] FID < 100毫秒
|
||
- [ ] CLS < 0.1
|
||
- [ ] 移动端首屏加载 < 3秒
|
||
|
||
### 代码质量验收
|
||
|
||
- [ ] 所有测试通过
|
||
- [ ] ESLint 检查无错误
|
||
- [ ] TypeScript 类型检查通过
|
||
- [ ] 代码提交信息规范
|
||
|
||
---
|
||
|
||
## 风险控制
|
||
|
||
### 潜在风险
|
||
|
||
1. **动画性能风险**
|
||
- 缓解:提供降级方案,低端设备自动禁用复杂动画
|
||
|
||
2. **开发周期风险**
|
||
- 缓解:采用分阶段交付,优先核心功能
|
||
|
||
3. **测试覆盖风险**
|
||
- 缓解:每个组件都编写单元测试,集成测试覆盖关键路径
|
||
|
||
---
|
||
|
||
## 后续优化方向
|
||
|
||
1. **数据分析驱动优化**
|
||
- 集成 Google Analytics 4
|
||
- 用户行为热力图分析
|
||
- A/B 测试关键页面
|
||
|
||
2. **内容运营支持**
|
||
- 定期更新技术博客
|
||
- 添加新案例展示
|
||
- 优化 SEO 关键词
|
||
|
||
3. **功能扩展预留**
|
||
- 多语言支持(国际化)
|
||
- 客户案例视频展示
|
||
- 在线演示预约系统
|
||
|
||
---
|
||
|
||
**文档状态:** 已完成
|
||
**创建日期:** 2026-02-13
|
||
**预计完成时间:** 10-14 天
|