b7092296cb
- 16 个可执行任务 - 完整的代码实现 - 明确的测试步骤 - 清晰的提交信息 - 遵循 TDD、DRY、YAGNI 原则
1342 lines
31 KiB
Markdown
1342 lines
31 KiB
Markdown
# Novalon 官网重新设计实施计划
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
||
**Goal:** 将 Novalon 官网从现有设计升级为数字未来风设计,包括配色系统、UI 组件、页面布局、动效和响应式适配的全面升级。
|
||
|
||
**Architecture:** 采用渐进式升级策略,从核心配色系统开始,逐步更新 UI 组件、页面布局、动效和响应式适配。使用 Tailwind CSS v4 的 CSS 变量系统实现主题切换,使用 Framer Motion 实现动效,使用 AntV 生态实现数据可视化。
|
||
|
||
**Tech Stack:** Next.js 16, React 19, TypeScript, Tailwind CSS v4, Framer Motion, AntV (G2/G6/L7/S2), Lucide React
|
||
|
||
---
|
||
|
||
## Phase 1: 核心配色系统
|
||
|
||
### Task 1: 更新 CSS 变量 - 深色模式配色
|
||
|
||
**Files:**
|
||
- Modify: `src/app/globals.css:1-200`
|
||
|
||
**Step 1: 备份当前配色变量**
|
||
|
||
```bash
|
||
cp src/app/globals.css src/app/globals.css.backup
|
||
```
|
||
|
||
**Step 2: 更新深色模式配色变量**
|
||
|
||
在 `.dark` 类中更新配色变量:
|
||
|
||
```css
|
||
.dark {
|
||
--color-bg-primary: #0A0A0A;
|
||
--color-bg-secondary: #141414;
|
||
--color-bg-tertiary: #1A1A1A;
|
||
--color-bg-hover: #242424;
|
||
|
||
--color-text-primary: #FAFAFA;
|
||
--color-text-secondary: #D4D4D4;
|
||
--color-text-tertiary: #A3A3A3;
|
||
--color-text-muted: #737373;
|
||
|
||
--color-border-primary: #262626;
|
||
--color-border-secondary: #333333;
|
||
--color-border-accent: #404040;
|
||
|
||
--color-accent: #FAFAFA;
|
||
--color-accent-hover: #E5E5E5;
|
||
--color-accent-light: #D4D4D4;
|
||
|
||
--color-link: #D4D4D4;
|
||
--color-link-hover: #FAFAFA;
|
||
|
||
--color-brand-primary: #C41E3A;
|
||
--color-brand-primary-hover: #A01830;
|
||
--color-brand-primary-light: #E04A68;
|
||
--color-brand-primary-lighter: #F08C9F;
|
||
--color-brand-primary-bg: #1A0F11;
|
||
|
||
--color-tech-blue: #00D9FF;
|
||
--color-tech-blue-hover: #00B8D9;
|
||
--color-tech-blue-light: #33E1FF;
|
||
--color-tech-purple: #A855F7;
|
||
--color-tech-purple-hover: #9333EA;
|
||
--color-tech-purple-light: #C084FC;
|
||
--color-tech-cyan: #06B6D4;
|
||
|
||
--color-success: #22C55E;
|
||
--color-success-bg: #052E16;
|
||
--color-warning: #F59E0B;
|
||
--color-warning-bg: #1C1917;
|
||
--color-info: #0EA5E9;
|
||
--color-info-bg: #0C2D48;
|
||
--color-error: #EF4444;
|
||
--color-error-bg: #1C1917;
|
||
}
|
||
```
|
||
|
||
**Step 3: 更新浅色模式配色变量**
|
||
|
||
在 `:root` 中更新配色变量:
|
||
|
||
```css
|
||
:root {
|
||
--color-bg-primary: #FFFFFF;
|
||
--color-bg-secondary: #FAFAFA;
|
||
--color-bg-tertiary: #F5F5F5;
|
||
--color-bg-hover: #F0F0F0;
|
||
|
||
--color-text-primary: #171717;
|
||
--color-text-secondary: #525252;
|
||
--color-text-tertiary: #737373;
|
||
--color-text-muted: #A3A3A3;
|
||
|
||
--color-border-primary: #E5E5E5;
|
||
--color-border-secondary: #F0F0F0;
|
||
--color-border-accent: #D4D4D4;
|
||
|
||
--color-accent: #171717;
|
||
--color-accent-hover: #262626;
|
||
--color-accent-light: #404040;
|
||
|
||
--color-link: #525252;
|
||
--color-link-hover: #171717;
|
||
|
||
--color-brand-primary: #C41E3A;
|
||
--color-brand-primary-hover: #A01830;
|
||
--color-brand-primary-light: #D4244A;
|
||
--color-brand-primary-lighter: #E04A68;
|
||
--color-brand-primary-bg: #FEF2F4;
|
||
|
||
--color-tech-blue: #00D9FF;
|
||
--color-tech-blue-hover: #00B8D9;
|
||
--color-tech-blue-light: #33E1FF;
|
||
--color-tech-purple: #A855F7;
|
||
--color-tech-purple-hover: #9333EA;
|
||
--color-tech-purple-light: #C084FC;
|
||
--color-tech-cyan: #06B6D4;
|
||
|
||
--color-success: #16A34A;
|
||
--color-success-bg: #F0FDF4;
|
||
--color-warning: #D97706;
|
||
--color-warning-bg: #FFFBEB;
|
||
--color-info: #0284C7;
|
||
--color-info-bg: #F0F9FF;
|
||
--color-error: #DC2626;
|
||
--color-error-bg: #FEF2F2;
|
||
}
|
||
```
|
||
|
||
**Step 4: 验证配色变量**
|
||
|
||
在浏览器中打开网站,切换深色/浅色模式,检查配色是否正确应用。
|
||
|
||
**Step 5: 提交更改**
|
||
|
||
```bash
|
||
git add src/app/globals.css
|
||
git commit -m "feat: 更新核心配色系统 - 深色/浅色模式
|
||
|
||
- 更新深色模式配色变量(科技蓝、紫色、印章红)
|
||
- 更新浅色模式配色变量
|
||
- 添加新的科技色彩变量
|
||
- 保持品牌色一致性"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2: 更新 colors.ts 文件
|
||
|
||
**Files:**
|
||
- Modify: `src/lib/colors.ts:1-69`
|
||
|
||
**Step 1: 更新品牌色彩定义**
|
||
|
||
```typescript
|
||
export const brandColors = {
|
||
primary: {
|
||
600: '#C41E3A',
|
||
700: '#A01830',
|
||
500: '#D4244A',
|
||
400: '#E04A68',
|
||
100: '#FEF2F4',
|
||
},
|
||
tech: {
|
||
blue: {
|
||
600: '#00D9FF',
|
||
700: '#00B8D9',
|
||
500: '#33E1FF',
|
||
},
|
||
purple: {
|
||
600: '#A855F7',
|
||
700: '#9333EA',
|
||
500: '#C084FC',
|
||
},
|
||
cyan: {
|
||
600: '#06B6D4',
|
||
},
|
||
},
|
||
neutral: {
|
||
900: '#0A0A0A',
|
||
800: '#141414',
|
||
700: '#1A1A1A',
|
||
600: '#242424',
|
||
500: '#333333',
|
||
400: '#404040',
|
||
300: '#737373',
|
||
200: '#A3A3A3',
|
||
100: '#D4D4D4',
|
||
50: '#FAFAFA',
|
||
0: '#FFFFFF',
|
||
},
|
||
success: {
|
||
600: '#16A34A',
|
||
100: '#F0FDF4',
|
||
},
|
||
warning: {
|
||
600: '#D97706',
|
||
100: '#FFFBEB',
|
||
},
|
||
info: {
|
||
600: '#0284C7',
|
||
100: '#F0F9FF',
|
||
},
|
||
error: {
|
||
600: '#DC2626',
|
||
100: '#FEF2F2',
|
||
},
|
||
} as const;
|
||
|
||
export const colorValues = {
|
||
primary: '#C41E3A',
|
||
primaryHover: '#A01830',
|
||
primaryLight: '#D4244A',
|
||
primaryBg: '#FEF2F4',
|
||
|
||
techBlue: '#00D9FF',
|
||
techBlueHover: '#00B8D9',
|
||
techBlueLight: '#33E1FF',
|
||
techPurple: '#A855F7',
|
||
techPurpleHover: '#9333EA',
|
||
techPurpleLight: '#C084FC',
|
||
techCyan: '#06B6D4',
|
||
|
||
textPrimary: '#FAFAFA',
|
||
textSecondary: '#D4D4D4',
|
||
textTertiary: '#A3A3A3',
|
||
textMuted: '#737373',
|
||
|
||
bgPrimary: '#0A0A0A',
|
||
bgSecondary: '#141414',
|
||
bgTertiary: '#1A1A1A',
|
||
bgHover: '#242424',
|
||
|
||
border: '#262626',
|
||
borderHover: '#333333',
|
||
|
||
success: '#16A34A',
|
||
successBg: '#F0FDF4',
|
||
warning: '#D97706',
|
||
warningBg: '#FFFBEB',
|
||
info: '#0284C7',
|
||
infoBg: '#F0F9FF',
|
||
error: '#DC2626',
|
||
errorBg: '#FEF2F2',
|
||
} as const;
|
||
|
||
export const gradients = {
|
||
primary: 'linear-gradient(135deg, #00D9FF 0%, #A855F7 100%)',
|
||
reverse: 'linear-gradient(135deg, #A855F7 0%, #00D9FF 100%)',
|
||
glow: 'radial-gradient(circle, rgba(0, 217, 255, 0.15) 0%, transparent 70%)',
|
||
glowPurple: 'radial-gradient(circle, rgba(168, 85, 247, 0.15) 0%, transparent 70%)',
|
||
} as const;
|
||
|
||
export type BrandColor = typeof brandColors;
|
||
export type ColorValue = typeof colorValues;
|
||
export type Gradient = typeof gradients;
|
||
```
|
||
|
||
**Step 2: 验证类型定义**
|
||
|
||
运行 TypeScript 编译检查:
|
||
|
||
```bash
|
||
npm run build
|
||
```
|
||
|
||
Expected: 无类型错误
|
||
|
||
**Step 3: 提交更改**
|
||
|
||
```bash
|
||
git add src/lib/colors.ts
|
||
git commit -m "feat: 更新色彩系统定义
|
||
|
||
- 添加科技蓝、紫色、青色色彩系列
|
||
- 更新中性色系列
|
||
- 添加渐变色定义
|
||
- 完善类型定义"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: 创建渐变色工具函数
|
||
|
||
**Files:**
|
||
- Create: `src/lib/gradients.ts`
|
||
|
||
**Step 1: 创建渐变色工具函数**
|
||
|
||
```typescript
|
||
import { gradients } from './colors';
|
||
|
||
export const getGradientStyle = (type: keyof typeof gradients) => {
|
||
return {
|
||
background: gradients[type],
|
||
};
|
||
};
|
||
|
||
export const getGlowStyle = (color: 'blue' | 'purple', opacity: number = 0.15) => {
|
||
const colorValue = color === 'blue' ? '0, 217, 255' : '168, 85, 247';
|
||
return {
|
||
background: `radial-gradient(circle, rgba(${colorValue}, ${opacity}) 0%, transparent 70%)`,
|
||
};
|
||
};
|
||
|
||
export const getBorderGradientStyle = () => {
|
||
return {
|
||
borderImage: `${gradients.primary} 1`,
|
||
};
|
||
};
|
||
|
||
export const getTextGradientStyle = () => {
|
||
return {
|
||
background: gradients.primary,
|
||
WebkitBackgroundClip: 'text',
|
||
WebkitTextFillColor: 'transparent',
|
||
backgroundClip: 'text',
|
||
};
|
||
};
|
||
```
|
||
|
||
**Step 2: 创建测试文件**
|
||
|
||
```typescript
|
||
import { getGradientStyle, getGlowStyle, getTextGradientStyle } from '../gradients';
|
||
|
||
describe('gradients utilities', () => {
|
||
test('getGradientStyle returns correct gradient', () => {
|
||
const style = getGradientStyle('primary');
|
||
expect(style.background).toContain('linear-gradient');
|
||
});
|
||
|
||
test('getGlowStyle returns correct glow for blue', () => {
|
||
const style = getGlowStyle('blue', 0.2);
|
||
expect(style.background).toContain('rgba(0, 217, 255, 0.2)');
|
||
});
|
||
|
||
test('getGlowStyle returns correct glow for purple', () => {
|
||
const style = getGlowStyle('purple', 0.1);
|
||
expect(style.background).toContain('rgba(168, 85, 247, 0.1)');
|
||
});
|
||
|
||
test('getTextGradientStyle returns correct text gradient', () => {
|
||
const style = getTextGradientStyle();
|
||
expect(style.WebkitBackgroundClip).toBe('text');
|
||
expect(style.WebkitTextFillColor).toBe('transparent');
|
||
});
|
||
});
|
||
```
|
||
|
||
**Step 3: 运行测试**
|
||
|
||
```bash
|
||
npm test -- src/lib/gradients.test.ts
|
||
```
|
||
|
||
Expected: 所有测试通过
|
||
|
||
**Step 4: 提交更改**
|
||
|
||
```bash
|
||
git add src/lib/gradients.ts src/lib/gradients.test.ts
|
||
git commit -m "feat: 添加渐变色工具函数
|
||
|
||
- 创建渐变色样式生成函数
|
||
- 创建光晕效果生成函数
|
||
- 创建文字渐变效果函数
|
||
- 添加单元测试"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 2: UI 组件更新
|
||
|
||
### Task 4: 更新按钮组件
|
||
|
||
**Files:**
|
||
- Modify: `src/components/ui/button.tsx`
|
||
|
||
**Step 1: 更新按钮样式变体**
|
||
|
||
```typescript
|
||
import * as React from "react"
|
||
import { Slot } from "@radix-ui/react-slot"
|
||
import { cva, type VariantProps } from "class-variance-authority"
|
||
import { cn } from "@/lib/utils"
|
||
|
||
const buttonVariants = cva(
|
||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-tech-blue)] focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||
{
|
||
variants: {
|
||
variant: {
|
||
default:
|
||
"bg-[var(--color-brand-primary)] text-white hover:bg-[var(--color-brand-primary-hover)] hover:shadow-[0_0_20px_rgba(196,30,58,0.4)] hover:-translate-y-0.5 active:scale-[0.98]",
|
||
secondary:
|
||
"bg-gradient-to-br from-[var(--color-tech-blue)] to-[var(--color-tech-purple)] text-white hover:shadow-[0_0_20px_rgba(0,217,255,0.3)] hover:-translate-y-0.5 active:scale-[0.98]",
|
||
outline:
|
||
"border border-[var(--color-tech-blue)] bg-transparent text-[var(--color-tech-blue)] hover:bg-[rgba(0,217,255,0.1)] hover:shadow-[0_0_20px_rgba(0,217,255,0.2)]",
|
||
ghost:
|
||
"text-[var(--color-text-secondary)] hover:bg-[rgba(255,255,255,0.05)] hover:text-[var(--color-text-primary)]",
|
||
link:
|
||
"text-[var(--color-tech-blue)] underline-offset-4 hover:underline",
|
||
},
|
||
size: {
|
||
default: "h-10 px-4 py-2",
|
||
sm: "h-8 rounded-md px-3 text-xs",
|
||
lg: "h-12 rounded-lg px-6 text-base",
|
||
icon: "h-10 w-10",
|
||
},
|
||
},
|
||
defaultVariants: {
|
||
variant: "default",
|
||
size: "default",
|
||
},
|
||
}
|
||
)
|
||
|
||
export interface ButtonProps
|
||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||
VariantProps<typeof buttonVariants> {
|
||
asChild?: boolean
|
||
}
|
||
|
||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||
const Comp = asChild ? Slot : "button"
|
||
return (
|
||
<Comp
|
||
className={cn(buttonVariants({ variant, size, className }))}
|
||
ref={ref}
|
||
{...props}
|
||
/>
|
||
)
|
||
}
|
||
)
|
||
Button.displayName = "Button"
|
||
|
||
export { Button, buttonVariants }
|
||
```
|
||
|
||
**Step 2: 测试按钮组件**
|
||
|
||
在浏览器中打开网站,检查按钮样式是否正确应用。
|
||
|
||
**Step 3: 提交更改**
|
||
|
||
```bash
|
||
git add src/components/ui/button.tsx
|
||
git commit -m "feat: 更新按钮组件样式
|
||
|
||
- 更新默认按钮为印章红色
|
||
- 添加科技蓝渐变次要按钮
|
||
- 更新轮廓按钮样式
|
||
- 添加悬停发光效果
|
||
- 添加点击缩放反馈"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5: 更新卡片组件
|
||
|
||
**Files:**
|
||
- Modify: `src/components/ui/card.tsx`
|
||
|
||
**Step 1: 更新卡片样式**
|
||
|
||
```typescript
|
||
import * as React from "react"
|
||
import { cn } from "@/lib/utils"
|
||
|
||
const Card = React.forwardRef<
|
||
HTMLDivElement,
|
||
React.HTMLAttributes<HTMLDivElement>
|
||
>(({ className, ...props }, ref) => (
|
||
<div
|
||
ref={ref}
|
||
className={cn(
|
||
"rounded-xl border border-[var(--color-border-primary)] bg-[var(--color-bg-tertiary)] text-[var(--color-text-primary)] transition-all duration-300 hover:border-[var(--color-tech-blue)] hover:shadow-[0_0_30px_rgba(0,217,255,0.15)] hover:-translate-y-1",
|
||
className
|
||
)}
|
||
{...props}
|
||
/>
|
||
))
|
||
Card.displayName = "Card"
|
||
|
||
const CardHeader = React.forwardRef<
|
||
HTMLDivElement,
|
||
React.HTMLAttributes<HTMLDivElement>
|
||
>(({ className, ...props }, ref) => (
|
||
<div
|
||
ref={ref}
|
||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||
{...props}
|
||
/>
|
||
))
|
||
CardHeader.displayName = "CardHeader"
|
||
|
||
const CardTitle = React.forwardRef<
|
||
HTMLParagraphElement,
|
||
React.HTMLAttributes<HTMLHeadingElement>
|
||
>(({ className, ...props }, ref) => (
|
||
<h3
|
||
ref={ref}
|
||
className={cn(
|
||
"text-2xl font-semibold leading-none tracking-tight text-[var(--color-text-primary)]",
|
||
className
|
||
)}
|
||
{...props}
|
||
/>
|
||
))
|
||
CardTitle.displayName = "CardTitle"
|
||
|
||
const CardDescription = React.forwardRef<
|
||
HTMLParagraphElement,
|
||
React.HTMLAttributes<HTMLParagraphElement>
|
||
>(({ className, ...props }, ref) => (
|
||
<p
|
||
ref={ref}
|
||
className={cn("text-sm text-[var(--color-text-secondary)]", className)}
|
||
{...props}
|
||
/>
|
||
))
|
||
CardDescription.displayName = "CardDescription"
|
||
|
||
const CardContent = React.forwardRef<
|
||
HTMLDivElement,
|
||
React.HTMLAttributes<HTMLDivElement>
|
||
>(({ className, ...props }, ref) => (
|
||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||
))
|
||
CardContent.displayName = "CardContent"
|
||
|
||
const CardFooter = React.forwardRef<
|
||
HTMLDivElement,
|
||
React.HTMLAttributes<HTMLDivElement>
|
||
>(({ className, ...props }, ref) => (
|
||
<div
|
||
ref={ref}
|
||
className={cn("flex items-center p-6 pt-0", className)}
|
||
{...props}
|
||
/>
|
||
))
|
||
CardFooter.displayName = "CardFooter"
|
||
|
||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||
```
|
||
|
||
**Step 2: 测试卡片组件**
|
||
|
||
在浏览器中打开网站,检查卡片样式是否正确应用。
|
||
|
||
**Step 3: 提交更改**
|
||
|
||
```bash
|
||
git add src/components/ui/card.tsx
|
||
git commit -m "feat: 更新卡片组件样式
|
||
|
||
- 更新卡片背景色为深灰
|
||
- 添加悬停边框发光效果
|
||
- 添加悬停上移动画
|
||
- 更新文字颜色变量"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6: 更新导航栏组件
|
||
|
||
**Files:**
|
||
- Modify: `src/components/layout/header.tsx`
|
||
|
||
**Step 1: 更新导航栏样式**
|
||
|
||
在导航栏组件中添加新的样式类:
|
||
|
||
```typescript
|
||
// 在 header.tsx 中更新导航栏样式
|
||
<header className="fixed top-0 left-0 right-0 z-50 h-16 bg-[rgba(10,10,10,0.8)] backdrop-blur-xl border-b border-[var(--color-border-primary)]">
|
||
{/* 导航内容 */}
|
||
</header>
|
||
```
|
||
|
||
**Step 2: 更新导航链接样式**
|
||
|
||
```typescript
|
||
// 更新导航链接样式
|
||
<a className="text-[var(--color-text-secondary)] hover:text-[var(--color-tech-blue)] transition-colors duration-200 relative group">
|
||
{link.name}
|
||
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-[var(--color-tech-blue)] transition-all duration-200 group-hover:w-full" />
|
||
</a>
|
||
```
|
||
|
||
**Step 3: 测试导航栏**
|
||
|
||
在浏览器中打开网站,检查导航栏样式是否正确应用。
|
||
|
||
**Step 4: 提交更改**
|
||
|
||
```bash
|
||
git add src/components/layout/header.tsx
|
||
git commit -m "feat: 更新导航栏样式
|
||
|
||
- 更新导航栏背景为半透明深色
|
||
- 添加模糊效果
|
||
- 更新导航链接悬停效果
|
||
- 添加下划线动画"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 3: 页面布局优化
|
||
|
||
### Task 7: 更新 Hero 区域
|
||
|
||
**Files:**
|
||
- Modify: `src/components/sections/hero-section.tsx`
|
||
|
||
**Step 1: 更新 Hero 区域背景**
|
||
|
||
```typescript
|
||
<section className="relative min-h-screen flex items-center justify-center bg-[var(--color-bg-primary)] overflow-hidden">
|
||
{/* 网格背景 */}
|
||
<div className="absolute inset-0 opacity-[0.03]" style={{
|
||
backgroundImage: 'linear-gradient(var(--color-tech-blue) 1px, transparent 1px), linear-gradient(90deg, var(--color-tech-blue) 1px, transparent 1px)',
|
||
backgroundSize: '50px 50px'
|
||
}} />
|
||
|
||
{/* 光晕效果 */}
|
||
<div className="absolute top-1/4 left-1/4 w-96 h-96 rounded-full opacity-10" style={{
|
||
background: 'radial-gradient(circle, rgba(0, 217, 255, 0.15) 0%, transparent 70%)'
|
||
}} />
|
||
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 rounded-full opacity-10" style={{
|
||
background: 'radial-gradient(circle, rgba(168, 85, 247, 0.15) 0%, transparent 70%)'
|
||
}} />
|
||
|
||
{/* 内容 */}
|
||
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
{/* Hero 内容 */}
|
||
</div>
|
||
</section>
|
||
```
|
||
|
||
**Step 2: 更新 Hero 文案样式**
|
||
|
||
```typescript
|
||
<h1 className="text-5xl md:text-6xl lg:text-7xl font-bold text-[var(--color-text-primary)] mb-6">
|
||
睿新致遠,智创未来
|
||
</h1>
|
||
<p className="text-xl md:text-2xl text-[var(--color-text-secondary)] mb-8 max-w-2xl">
|
||
以科技创新驱动企业数字化转型,打造智慧未来
|
||
</p>
|
||
```
|
||
|
||
**Step 3: 测试 Hero 区域**
|
||
|
||
在浏览器中打开网站,检查 Hero 区域样式是否正确应用。
|
||
|
||
**Step 4: 提交更改**
|
||
|
||
```bash
|
||
git add src/components/sections/hero-section.tsx
|
||
git commit -m "feat: 更新 Hero 区域设计
|
||
|
||
- 添加网格背景
|
||
- 添加科技蓝和紫色光晕效果
|
||
- 更新文案样式
|
||
- 优化布局结构"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 8: 创建粒子效果组件
|
||
|
||
**Files:**
|
||
- Create: `src/components/effects/particles.tsx`
|
||
|
||
**Step 1: 创建粒子效果组件**
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import { useEffect, useRef } from 'react';
|
||
import { motion } from 'framer-motion';
|
||
|
||
interface Particle {
|
||
x: number;
|
||
y: number;
|
||
size: number;
|
||
speedX: number;
|
||
speedY: number;
|
||
color: string;
|
||
}
|
||
|
||
export function Particles() {
|
||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||
const particlesRef = useRef<Particle[]>([]);
|
||
const animationRef = useRef<number>();
|
||
|
||
useEffect(() => {
|
||
const canvas = canvasRef.current;
|
||
if (!canvas) return;
|
||
|
||
const ctx = canvas.getContext('2d');
|
||
if (!ctx) return;
|
||
|
||
const resizeCanvas = () => {
|
||
canvas.width = window.innerWidth;
|
||
canvas.height = window.innerHeight;
|
||
};
|
||
|
||
resizeCanvas();
|
||
window.addEventListener('resize', resizeCanvas);
|
||
|
||
// 初始化粒子
|
||
const particleCount = 50;
|
||
const colors = ['#00D9FF', '#A855F7', '#06B6D4'];
|
||
|
||
for (let i = 0; i < particleCount; i++) {
|
||
particlesRef.current.push({
|
||
x: Math.random() * canvas.width,
|
||
y: Math.random() * canvas.height,
|
||
size: Math.random() * 3 + 1,
|
||
speedX: (Math.random() - 0.5) * 0.5,
|
||
speedY: (Math.random() - 0.5) * 0.5,
|
||
color: colors[Math.floor(Math.random() * colors.length)],
|
||
});
|
||
}
|
||
|
||
const animate = () => {
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
|
||
particlesRef.current.forEach((particle) => {
|
||
particle.x += particle.speedX;
|
||
particle.y += particle.speedY;
|
||
|
||
if (particle.x < 0 || particle.x > canvas.width) particle.speedX *= -1;
|
||
if (particle.y < 0 || particle.y > canvas.height) particle.speedY *= -1;
|
||
|
||
ctx.beginPath();
|
||
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
|
||
ctx.fillStyle = particle.color;
|
||
ctx.fill();
|
||
});
|
||
|
||
animationRef.current = requestAnimationFrame(animate);
|
||
};
|
||
|
||
animate();
|
||
|
||
return () => {
|
||
window.removeEventListener('resize', resizeCanvas);
|
||
if (animationRef.current) {
|
||
cancelAnimationFrame(animationRef.current);
|
||
}
|
||
};
|
||
}, []);
|
||
|
||
return (
|
||
<canvas
|
||
ref={canvasRef}
|
||
className="absolute inset-0 pointer-events-none"
|
||
style={{ opacity: 0.6 }}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 在 Hero 区域中使用粒子效果**
|
||
|
||
```typescript
|
||
import { Particles } from '@/components/effects/particles';
|
||
|
||
export function HeroSection() {
|
||
return (
|
||
<section className="relative min-h-screen">
|
||
<Particles />
|
||
{/* 其他内容 */}
|
||
</section>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 3: 测试粒子效果**
|
||
|
||
在浏览器中打开网站,检查粒子效果是否正常运行。
|
||
|
||
**Step 4: 提交更改**
|
||
|
||
```bash
|
||
git add src/components/effects/particles.tsx src/components/sections/hero-section.tsx
|
||
git commit -m "feat: 添加粒子效果组件
|
||
|
||
- 创建 Canvas 粒子动画
|
||
- 使用科技蓝、紫色、青色粒子
|
||
- 添加粒子漂浮动画
|
||
- 在 Hero 区域中集成"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 9: 创建数字计数动画组件
|
||
|
||
**Files:**
|
||
- Create: `src/components/effects/count-up.tsx`
|
||
|
||
**Step 1: 创建数字计数动画组件**
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import { useEffect, useRef, useState } from 'react';
|
||
import { useInView } from 'framer-motion';
|
||
|
||
interface CountUpProps {
|
||
end: number;
|
||
duration?: number;
|
||
prefix?: string;
|
||
suffix?: string;
|
||
className?: string;
|
||
}
|
||
|
||
export function CountUp({ end, duration = 2000, prefix = '', suffix = '', className }: CountUpProps) {
|
||
const ref = useRef<HTMLSpanElement>(null);
|
||
const isInView = useInView(ref, { once: true });
|
||
const [count, setCount] = useState(0);
|
||
|
||
useEffect(() => {
|
||
if (!isInView) return;
|
||
|
||
let startTime: number;
|
||
let animationFrame: number;
|
||
|
||
const animate = (currentTime: number) => {
|
||
if (!startTime) startTime = currentTime;
|
||
const progress = Math.min((currentTime - startTime) / duration, 1);
|
||
|
||
setCount(Math.floor(progress * end));
|
||
|
||
if (progress < 1) {
|
||
animationFrame = requestAnimationFrame(animate);
|
||
}
|
||
};
|
||
|
||
animationFrame = requestAnimationFrame(animate);
|
||
|
||
return () => {
|
||
if (animationFrame) {
|
||
cancelAnimationFrame(animationFrame);
|
||
}
|
||
};
|
||
}, [isInView, end, duration]);
|
||
|
||
return (
|
||
<span ref={ref} className={className}>
|
||
{prefix}{count.toLocaleString()}{suffix}
|
||
</span>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 在数据卡片中使用计数动画**
|
||
|
||
```typescript
|
||
import { CountUp } from '@/components/effects/count-up';
|
||
|
||
export function DataCard() {
|
||
return (
|
||
<div className="p-6 rounded-xl border border-[var(--color-border-primary)] bg-[var(--color-bg-tertiary)]">
|
||
<CountUp end={1000} suffix="+" className="text-4xl font-bold text-[var(--color-tech-blue)]" />
|
||
<p className="text-[var(--color-text-secondary)] mt-2">服务客户</p>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 3: 测试计数动画**
|
||
|
||
在浏览器中打开网站,检查计数动画是否正常运行。
|
||
|
||
**Step 4: 提交更改**
|
||
|
||
```bash
|
||
git add src/components/effects/count-up.tsx
|
||
git commit -m "feat: 添加数字计数动画组件
|
||
|
||
- 创建 CountUp 组件
|
||
- 支持前缀、后缀
|
||
- 支持自定义持续时间
|
||
- 使用 Intersection Observer 触发"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 4: 动效与交互
|
||
|
||
### Task 10: 创建滚动揭示 Hook
|
||
|
||
**Files:**
|
||
- Create: `src/hooks/use-scroll-reveal.ts`
|
||
|
||
**Step 1: 创建滚动揭示 Hook**
|
||
|
||
```typescript
|
||
import { useEffect, useRef, useState } from 'react';
|
||
|
||
export function useScrollReveal<T extends HTMLElement>(threshold = 0.1) {
|
||
const ref = useRef<T>(null);
|
||
const [isVisible, setIsVisible] = useState(false);
|
||
|
||
useEffect(() => {
|
||
const element = ref.current;
|
||
if (!element) return;
|
||
|
||
const observer = new IntersectionObserver(
|
||
([entry]) => {
|
||
if (entry.isIntersecting) {
|
||
setIsVisible(true);
|
||
observer.unobserve(element);
|
||
}
|
||
},
|
||
{ threshold }
|
||
);
|
||
|
||
observer.observe(element);
|
||
|
||
return () => {
|
||
observer.unobserve(element);
|
||
};
|
||
}, [threshold]);
|
||
|
||
return { ref, isVisible };
|
||
}
|
||
```
|
||
|
||
**Step 2: 创建滚动揭示组件**
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import { motion } from 'framer-motion';
|
||
import { useScrollReveal } from '@/hooks/use-scroll-reveal';
|
||
|
||
interface ScrollRevealProps {
|
||
children: React.ReactNode;
|
||
className?: string;
|
||
delay?: number;
|
||
}
|
||
|
||
export function ScrollReveal({ children, className, delay = 0 }: ScrollRevealProps) {
|
||
const { ref, isVisible } = useScrollReveal<HTMLDivElement>();
|
||
|
||
return (
|
||
<motion.div
|
||
ref={ref}
|
||
initial={{ opacity: 0, y: 30 }}
|
||
animate={isVisible ? { opacity: 1, y: 0 } : { opacity: 0, y: 30 }}
|
||
transition={{ duration: 0.6, delay, ease: [0.16, 1, 0.3, 1] }}
|
||
className={className}
|
||
>
|
||
{children}
|
||
</motion.div>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 3: 在页面中使用滚动揭示**
|
||
|
||
```typescript
|
||
import { ScrollReveal } from '@/components/effects/scroll-reveal';
|
||
|
||
export function ProductsSection() {
|
||
return (
|
||
<section>
|
||
<ScrollReveal>
|
||
<h2>产品矩阵</h2>
|
||
</ScrollReveal>
|
||
<div className="grid grid-cols-3 gap-6">
|
||
{products.map((product, index) => (
|
||
<ScrollReveal key={product.id} delay={index * 0.1}>
|
||
<ProductCard product={product} />
|
||
</ScrollReveal>
|
||
))}
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 4: 提交更改**
|
||
|
||
```bash
|
||
git add src/hooks/use-scroll-reveal.ts src/components/effects/scroll-reveal.tsx
|
||
git commit -m "feat: 添加滚动揭示动效
|
||
|
||
- 创建 useScrollReveal Hook
|
||
- 创建 ScrollReveal 组件
|
||
- 支持延迟动画
|
||
- 使用 Intersection Observer"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 11: 创建光晕脉动组件
|
||
|
||
**Files:**
|
||
- Create: `src/components/effects/glow-pulse.tsx`
|
||
|
||
**Step 1: 创建光晕脉动组件**
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import { motion } from 'framer-motion';
|
||
|
||
interface GlowPulseProps {
|
||
color: 'blue' | 'purple';
|
||
size?: number;
|
||
className?: string;
|
||
}
|
||
|
||
export function GlowPulse({ color, size = 400, className }: GlowPulseProps) {
|
||
const colorValue = color === 'blue' ? '0, 217, 255' : '168, 85, 247';
|
||
|
||
return (
|
||
<motion.div
|
||
className={`rounded-full pointer-events-none ${className}`}
|
||
style={{
|
||
width: size,
|
||
height: size,
|
||
background: `radial-gradient(circle, rgba(${colorValue}, 0.15) 0%, transparent 70%)`,
|
||
}}
|
||
animate={{
|
||
scale: [1, 1.15, 1],
|
||
opacity: [0.1, 0.15, 0.1],
|
||
}}
|
||
transition={{
|
||
duration: 4,
|
||
repeat: Infinity,
|
||
ease: 'easeInOut',
|
||
}}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 在 Hero 区域中使用光晕脉动**
|
||
|
||
```typescript
|
||
import { GlowPulse } from '@/components/effects/glow-pulse';
|
||
|
||
export function HeroSection() {
|
||
return (
|
||
<section className="relative">
|
||
<GlowPulse color="blue" size={400} className="absolute top-1/4 left-1/4" />
|
||
<GlowPulse color="purple" size={400} className="absolute bottom-1/4 right-1/4" />
|
||
{/* 其他内容 */}
|
||
</section>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 3: 提交更改**
|
||
|
||
```bash
|
||
git add src/components/effects/glow-pulse.tsx
|
||
git commit -m "feat: 添加光晕脉动组件
|
||
|
||
- 创建 GlowPulse 组件
|
||
- 支持蓝色和紫色光晕
|
||
- 添加脉动动画
|
||
- 可自定义大小"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 5: 响应式适配
|
||
|
||
### Task 12: 优化移动端导航
|
||
|
||
**Files:**
|
||
- Modify: `src/components/layout/mobile-menu.tsx`
|
||
|
||
**Step 1: 更新移动端菜单样式**
|
||
|
||
```typescript
|
||
import { motion } from 'framer-motion';
|
||
|
||
export function MobileMenu() {
|
||
return (
|
||
<motion.div
|
||
initial={{ opacity: 0, x: '100%' }}
|
||
animate={{ opacity: 1, x: 0 }}
|
||
exit={{ opacity: 0, x: '100%' }}
|
||
transition={{ duration: 0.2 }}
|
||
className="fixed inset-0 z-50 bg-[var(--color-bg-primary)]"
|
||
>
|
||
<div className="flex flex-col h-full">
|
||
{/* 菜单项 */}
|
||
<nav className="flex-1 px-6 py-8">
|
||
{links.map((link) => (
|
||
<a
|
||
key={link.href}
|
||
href={link.href}
|
||
className="block py-4 text-lg text-[var(--color-text-primary)] hover:text-[var(--color-tech-blue)] transition-colors"
|
||
>
|
||
{link.name}
|
||
</a>
|
||
))}
|
||
</nav>
|
||
</div>
|
||
</motion.div>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 测试移动端导航**
|
||
|
||
在移动设备或浏览器开发者工具中测试移动端导航。
|
||
|
||
**Step 3: 提交更改**
|
||
|
||
```bash
|
||
git add src/components/layout/mobile-menu.tsx
|
||
git commit -m "feat: 优化移动端导航
|
||
|
||
- 更新移动端菜单背景色
|
||
- 添加滑入动画
|
||
- 优化菜单项样式
|
||
- 改善触摸体验"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 13: 优化响应式布局
|
||
|
||
**Files:**
|
||
- Modify: `src/app/(marketing)/page.tsx`
|
||
|
||
**Step 1: 更新首页响应式布局**
|
||
|
||
```typescript
|
||
export default function HomePage() {
|
||
return (
|
||
<main>
|
||
{/* Hero */}
|
||
<section className="min-h-screen flex items-center">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 w-full">
|
||
{/* 内容 */}
|
||
</div>
|
||
</section>
|
||
|
||
{/* 产品矩阵 */}
|
||
<section className="py-20 md:py-24 lg:py-32">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
|
||
{/* 产品卡片 */}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 测试响应式布局**
|
||
|
||
在不同设备尺寸下测试页面布局。
|
||
|
||
**Step 3: 提交更改**
|
||
|
||
```bash
|
||
git add src/app/\(marketing\)/page.tsx
|
||
git commit -m "feat: 优化响应式布局
|
||
|
||
- 更新 Section 间距
|
||
- 优化网格布局断点
|
||
- 改善移动端体验
|
||
- 统一内边距规范"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 6: 性能优化与测试
|
||
|
||
### Task 14: 添加性能监控
|
||
|
||
**Files:**
|
||
- Create: `src/components/analytics/performance-monitor.tsx`
|
||
|
||
**Step 1: 创建性能监控组件**
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import { useEffect } from 'react';
|
||
|
||
export function PerformanceMonitor() {
|
||
useEffect(() => {
|
||
if (typeof window === 'undefined') return;
|
||
|
||
// 监控 Web Vitals
|
||
const observer = new PerformanceObserver((list) => {
|
||
list.getEntries().forEach((entry) => {
|
||
console.log('[Performance]', entry.name, entry.duration);
|
||
});
|
||
});
|
||
|
||
observer.observe({ entryTypes: ['measure', 'navigation'] });
|
||
|
||
return () => observer.disconnect();
|
||
}, []);
|
||
|
||
return null;
|
||
}
|
||
```
|
||
|
||
**Step 2: 在布局中添加性能监控**
|
||
|
||
```typescript
|
||
import { PerformanceMonitor } from '@/components/analytics/performance-monitor';
|
||
|
||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||
return (
|
||
<html>
|
||
<body>
|
||
<PerformanceMonitor />
|
||
{children}
|
||
</body>
|
||
</html>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 3: 提交更改**
|
||
|
||
```bash
|
||
git add src/components/analytics/performance-monitor.tsx src/app/layout.tsx
|
||
git commit -m "feat: 添加性能监控
|
||
|
||
- 创建性能监控组件
|
||
- 监控 Web Vitals 指标
|
||
- 输出性能日志"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 15: 运行性能测试
|
||
|
||
**Files:**
|
||
- Test: `e2e-tests/tests/test_performance.py`
|
||
|
||
**Step 1: 运行 Lighthouse 测试**
|
||
|
||
```bash
|
||
npx lighthouse http://localhost:3000 --output=json --output-path=./lighthouse-report.json
|
||
```
|
||
|
||
Expected: 性能分数 ≥ 90
|
||
|
||
**Step 2: 运行 E2E 性能测试**
|
||
|
||
```bash
|
||
pytest e2e-tests/tests/test_performance.py -v
|
||
```
|
||
|
||
Expected: 所有测试通过
|
||
|
||
**Step 3: 提交测试报告**
|
||
|
||
```bash
|
||
git add lighthouse-report.json
|
||
git commit -m "test: 添加性能测试报告
|
||
|
||
- 运行 Lighthouse 测试
|
||
- 验证性能指标
|
||
- 确保性能分数达标"
|
||
```
|
||
|
||
---
|
||
|
||
## 最终验收
|
||
|
||
### Task 16: 全面测试与验收
|
||
|
||
**Step 1: 运行所有测试**
|
||
|
||
```bash
|
||
npm run build
|
||
npm run lint
|
||
pytest e2e-tests/ -v
|
||
```
|
||
|
||
Expected: 所有测试通过,无错误
|
||
|
||
**Step 2: 视觉验收**
|
||
|
||
在浏览器中检查:
|
||
- 配色方案是否符合设计
|
||
- 组件样式是否正确
|
||
- 动效是否流畅
|
||
- 响应式布局是否正常
|
||
|
||
**Step 3: 性能验收**
|
||
|
||
检查 Lighthouse 报告:
|
||
- Performance ≥ 90
|
||
- Accessibility ≥ 95
|
||
- Best Practices ≥ 90
|
||
- SEO ≥ 90
|
||
|
||
**Step 4: 提交最终版本**
|
||
|
||
```bash
|
||
git add .
|
||
git commit -m "feat: 完成 Novalon 官网重新设计
|
||
|
||
✅ 核心配色系统升级
|
||
✅ UI 组件更新
|
||
✅ 页面布局优化
|
||
✅ 动效与交互增强
|
||
✅ 响应式适配完善
|
||
✅ 性能优化完成
|
||
|
||
通过所有测试和验收标准"
|
||
```
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
本实施计划将 Novalon 官网重新设计方案分解为 **16 个可执行任务**,每个任务都包含:
|
||
|
||
- 明确的文件路径
|
||
- 完整的代码实现
|
||
- 具体的测试步骤
|
||
- 清晰的提交信息
|
||
|
||
遵循 **TDD、DRY、YAGNI** 原则,确保高质量交付。
|
||
|
||
**预计完成时间:** 2-3 个工作日
|
||
|
||
**下一步:** 选择执行方式
|