016b7cfb91
Phase 1: Accessibility Optimizations - Add proper label associations and ARIA attributes to form inputs - Implement aria-required, aria-invalid, aria-describedby for better form accessibility - Add role='alert' for error messages - Enhance keyboard navigation with aria-expanded, aria-controls - Add aria-label for mobile menu button - Implement aria-current for active navigation items - Add semantic HTML with aria-labelledby for sections Phase 2: UX Optimizations - Create loading skeleton components for better loading states - Add FormSkeleton, SectionSkeleton, and LoadingSkeleton components - Prepare for lazy loading implementation Files modified: - src/components/ui/input.tsx: Enhanced with ARIA attributes - src/components/ui/textarea.tsx: Enhanced with ARIA attributes - src/components/layout/header.tsx: Added navigation ARIA labels - src/components/sections/hero-section.tsx: Added section labels - src/components/sections/services-section.tsx: Added section labels - src/components/ui/loading-skeleton.tsx: New loading state components Impact: - WCAG 2.1 AA compliance improvements - Better screen reader support - Enhanced keyboard navigation - Improved user feedback during loading
1176 lines
25 KiB
Markdown
1176 lines
25 KiB
Markdown
# UI/UE/UX 全面优化实施计划
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
||
**Goal:** 提升网站的无障碍性、性能、用户体验、响应式设计、SEO 和安全性,达到金融级标准。
|
||
|
||
**Architecture:** 采用渐进式优化策略,从高优先级的无障碍性和性能优化开始,逐步推进到用户体验、响应式设计、SEO 和安全性优化。每个优化点都遵循 TDD 原则,确保质量。
|
||
|
||
**Tech Stack:** Next.js 15, React 19, TypeScript, Tailwind CSS, Framer Motion, Zod
|
||
|
||
---
|
||
|
||
## 优先级说明
|
||
|
||
- ⭐⭐⭐⭐⭐ 立即实施(第一阶段)
|
||
- ⭐⭐⭐⭐ 短期实施(第二阶段)
|
||
- ⭐⭐⭐ 长期实施(第三阶段)
|
||
|
||
---
|
||
|
||
## 第一阶段:无障碍性优化(立即实施)
|
||
|
||
### Task 1: 表单标签关联优化
|
||
|
||
**Files:**
|
||
- Modify: `src/components/sections/contact-section.tsx:1-200`
|
||
- Test: `e2e-tests/tests/test_contact_form.py`
|
||
|
||
**Step 1: 添加 useId hook**
|
||
|
||
```tsx
|
||
// src/components/sections/contact-section.tsx
|
||
import { useState, useEffect, useRef, useId } from 'react';
|
||
|
||
export function ContactSection() {
|
||
const nameInputId = useId();
|
||
const phoneInputId = useId();
|
||
const emailInputId = useId();
|
||
const messageInputId = useId();
|
||
|
||
// ... existing code
|
||
}
|
||
```
|
||
|
||
**Step 2: 关联标签和输入框**
|
||
|
||
```tsx
|
||
// 姓名字段
|
||
<div className="space-y-2">
|
||
<label
|
||
htmlFor={nameInputId}
|
||
className="text-sm font-medium text-[#1C1C1C]"
|
||
>
|
||
姓名 <span className="text-[#C41E3A]">*</span>
|
||
</label>
|
||
<Input
|
||
id={nameInputId}
|
||
name="name"
|
||
placeholder="请输入您的姓名"
|
||
value={formData.name}
|
||
onChange={handleChange}
|
||
aria-required="true"
|
||
aria-invalid={errors.name ? 'true' : 'false'}
|
||
aria-describedby={errors.name ? `${nameInputId}-error` : undefined}
|
||
className={errors.name ? 'border-red-500' : ''}
|
||
/>
|
||
{errors.name && (
|
||
<p
|
||
id={`${nameInputId}-error`}
|
||
className="text-sm text-red-500"
|
||
role="alert"
|
||
>
|
||
{errors.name}
|
||
</p>
|
||
)}
|
||
</div>
|
||
```
|
||
|
||
**Step 3: 对所有字段重复 Step 2**
|
||
|
||
为 phone、email、message 字段添加相同的优化。
|
||
|
||
**Step 4: 运行无障碍性测试**
|
||
|
||
Run: `pytest e2e-tests/tests/test_contact_form.py -v`
|
||
|
||
Expected: PASS
|
||
|
||
**Step 5: 提交**
|
||
|
||
```bash
|
||
git add src/components/sections/contact-section.tsx
|
||
git commit -m "feat(a11y): add proper label associations and ARIA attributes to contact form"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2: 图片无障碍性优化
|
||
|
||
**Files:**
|
||
- Modify: `src/components/sections/about-section.tsx:1-150`
|
||
- Modify: `src/components/sections/products-section.tsx:1-200`
|
||
- Modify: `src/components/sections/cases-section.tsx:1-200`
|
||
|
||
**Step 1: 检查所有图片元素**
|
||
|
||
Run: `grep -r "<img" src/components/sections/`
|
||
|
||
Expected: 列出所有 img 标签
|
||
|
||
**Step 2: 添加 alt 属性**
|
||
|
||
```tsx
|
||
// ❌ 之前
|
||
<img src="/image.jpg" />
|
||
|
||
// ✅ 之后
|
||
<img
|
||
src="/image.jpg"
|
||
alt="诺瓦隆公司团队合影"
|
||
loading="lazy"
|
||
/>
|
||
```
|
||
|
||
**Step 3: 使用 Next.js Image 组件**
|
||
|
||
```tsx
|
||
import Image from 'next/image';
|
||
|
||
<Image
|
||
src="/image.jpg"
|
||
alt="产品展示"
|
||
width={800}
|
||
height={600}
|
||
loading="lazy"
|
||
className="rounded-lg"
|
||
/>
|
||
```
|
||
|
||
**Step 4: 提交**
|
||
|
||
```bash
|
||
git add src/components/sections/*.tsx
|
||
git commit -m "feat(a11y): add alt attributes and optimize images with Next.js Image component"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: 键盘导航优化
|
||
|
||
**Files:**
|
||
- Modify: `src/components/layout/header.tsx:1-100`
|
||
- Modify: `src/components/layout/mobile-menu.tsx:1-80`
|
||
- Test: `e2e-tests/tests/test_navigation.py`
|
||
|
||
**Step 1: 添加键盘导航支持**
|
||
|
||
```tsx
|
||
// header.tsx
|
||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||
if (e.key === 'Enter' || e.key === ' ') {
|
||
e.preventDefault();
|
||
// 触发点击事件
|
||
}
|
||
};
|
||
|
||
<nav
|
||
role="navigation"
|
||
aria-label="主导航"
|
||
className="..."
|
||
>
|
||
<button
|
||
onKeyDown={handleKeyDown}
|
||
aria-expanded={isOpen}
|
||
aria-controls="mobile-menu"
|
||
>
|
||
菜单
|
||
</button>
|
||
</nav>
|
||
```
|
||
|
||
**Step 2: 添加焦点管理**
|
||
|
||
```tsx
|
||
import { useRef, useEffect } from 'react';
|
||
|
||
const menuRef = useRef<HTMLDivElement>(null);
|
||
|
||
useEffect(() => {
|
||
if (isOpen && menuRef.current) {
|
||
menuRef.current.focus();
|
||
}
|
||
}, [isOpen]);
|
||
```
|
||
|
||
**Step 3: 运行键盘导航测试**
|
||
|
||
Run: `pytest e2e-tests/tests/test_navigation.py -v`
|
||
|
||
Expected: PASS
|
||
|
||
**Step 4: 提交**
|
||
|
||
```bash
|
||
git add src/components/layout/*.tsx
|
||
git commit -m "feat(a11y): add keyboard navigation support and focus management"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4: ARIA 标签完善
|
||
|
||
**Files:**
|
||
- Modify: `src/components/sections/hero-section.tsx:1-200`
|
||
- Modify: `src/components/sections/services-section.tsx:1-150`
|
||
- Modify: `src/components/sections/products-section.tsx:1-200`
|
||
|
||
**Step 1: 添加语义化标签**
|
||
|
||
```tsx
|
||
// hero-section.tsx
|
||
<section
|
||
id="home"
|
||
aria-labelledby="hero-heading"
|
||
className="..."
|
||
>
|
||
<h1 id="hero-heading" className="...">
|
||
诺瓦隆金融科技
|
||
</h1>
|
||
</section>
|
||
```
|
||
|
||
**Step 2: 添加按钮标签**
|
||
|
||
```tsx
|
||
<Button
|
||
aria-label="了解更多关于诺瓦隆的信息"
|
||
className="..."
|
||
>
|
||
了解更多
|
||
</Button>
|
||
```
|
||
|
||
**Step 3: 添加区域标签**
|
||
|
||
```tsx
|
||
<section aria-labelledby="services-heading">
|
||
<h2 id="services-heading">我们的服务</h2>
|
||
</section>
|
||
```
|
||
|
||
**Step 4: 提交**
|
||
|
||
```bash
|
||
git add src/components/sections/*.tsx
|
||
git commit -m "feat(a11y): add comprehensive ARIA labels and semantic HTML"
|
||
```
|
||
|
||
---
|
||
|
||
## 第二阶段:性能优化(立即实施)
|
||
|
||
### Task 5: 图片懒加载优化
|
||
|
||
**Files:**
|
||
- Modify: `src/components/sections/about-section.tsx:1-150`
|
||
- Modify: `src/components/sections/products-section.tsx:1-200`
|
||
- Create: `src/components/ui/optimized-image.tsx`
|
||
|
||
**Step 1: 创建优化图片组件**
|
||
|
||
```tsx
|
||
// 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,
|
||
height,
|
||
className = '',
|
||
priority = false
|
||
}: OptimizedImageProps) {
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
|
||
return (
|
||
<div className={`relative overflow-hidden ${className}`}>
|
||
{isLoading && (
|
||
<div className="absolute inset-0 bg-gray-200 animate-pulse" />
|
||
)}
|
||
<Image
|
||
src={src}
|
||
alt={alt}
|
||
width={width}
|
||
height={height}
|
||
loading={priority ? 'eager' : 'lazy'}
|
||
onLoadingComplete={() => setIsLoading(false)}
|
||
className={`transition-opacity duration-300 ${
|
||
isLoading ? 'opacity-0' : 'opacity-100'
|
||
}`}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 替换现有图片**
|
||
|
||
```tsx
|
||
// about-section.tsx
|
||
import { OptimizedImage } from '@/components/ui/optimized-image';
|
||
|
||
<OptimizedImage
|
||
src="/about-image.jpg"
|
||
alt="关于诺瓦隆"
|
||
width={800}
|
||
height={600}
|
||
className="rounded-lg"
|
||
/>
|
||
```
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add src/components/ui/optimized-image.tsx src/components/sections/*.tsx
|
||
git commit -m "perf: implement optimized image component with lazy loading"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6: 组件懒加载优化
|
||
|
||
**Files:**
|
||
- Modify: `src/app/(marketing)/page.tsx:1-50`
|
||
|
||
**Step 1: 使用 dynamic import**
|
||
|
||
```tsx
|
||
import dynamic from 'next/dynamic';
|
||
import { HeroSection } from '@/components/sections/hero-section';
|
||
|
||
const AboutSection = dynamic(
|
||
() => import('@/components/sections/about-section').then(mod => ({ default: mod.AboutSection })),
|
||
{ loading: () => <SectionSkeleton /> }
|
||
);
|
||
|
||
const ServicesSection = dynamic(
|
||
() => import('@/components/sections/services-section').then(mod => ({ default: mod.ServicesSection })),
|
||
{ loading: () => <SectionSkeleton /> }
|
||
);
|
||
|
||
function SectionSkeleton() {
|
||
return (
|
||
<div className="py-24">
|
||
<div className="container-wide">
|
||
<div className="h-12 w-1/3 bg-gray-200 animate-pulse rounded mb-8" />
|
||
<div className="h-6 w-2/3 bg-gray-200 animate-pulse rounded mb-4" />
|
||
<div className="h-6 w-1/2 bg-gray-200 animate-pulse rounded" />
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 提交**
|
||
|
||
```bash
|
||
git add src/app/\(marketing\)/page.tsx
|
||
git commit -m "perf: implement dynamic imports for non-critical sections"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 7: 字体优化
|
||
|
||
**Files:**
|
||
- Modify: `src/app/layout.tsx:1-50`
|
||
|
||
**Step 1: 使用 Next.js 字体优化**
|
||
|
||
```tsx
|
||
import { Inter } from 'next/font/google';
|
||
|
||
const inter = Inter({
|
||
subsets: ['latin'],
|
||
display: 'swap',
|
||
variable: '--font-inter',
|
||
});
|
||
|
||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||
return (
|
||
<html lang="zh-CN" className={inter.variable}>
|
||
<body className={inter.className}>
|
||
{children}
|
||
</body>
|
||
</html>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 提交**
|
||
|
||
```bash
|
||
git add src/app/layout.tsx
|
||
git commit -m "perf: optimize font loading with Next.js font optimization"
|
||
```
|
||
|
||
---
|
||
|
||
## 第三阶段:用户体验优化(立即实施)
|
||
|
||
### Task 8: 加载状态优化
|
||
|
||
**Files:**
|
||
- Create: `src/components/ui/loading-skeleton.tsx`
|
||
- Modify: `src/components/sections/contact-section.tsx:1-200`
|
||
|
||
**Step 1: 创建骨架屏组件**
|
||
|
||
```tsx
|
||
// src/components/ui/loading-skeleton.tsx
|
||
export function LoadingSkeleton({ className = '' }: { className?: string }) {
|
||
return (
|
||
<div className={`animate-pulse bg-gray-200 rounded ${className}`} />
|
||
);
|
||
}
|
||
|
||
export function FormSkeleton() {
|
||
return (
|
||
<div className="space-y-4">
|
||
<LoadingSkeleton className="h-12 w-full" />
|
||
<LoadingSkeleton className="h-12 w-full" />
|
||
<LoadingSkeleton className="h-12 w-full" />
|
||
<LoadingSkeleton className="h-32 w-full" />
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 在表单中使用**
|
||
|
||
```tsx
|
||
// contact-section.tsx
|
||
import { FormSkeleton } from '@/components/ui/loading-skeleton';
|
||
|
||
{isSubmitting ? (
|
||
<FormSkeleton />
|
||
) : (
|
||
<form>...</form>
|
||
)}
|
||
```
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add src/components/ui/loading-skeleton.tsx src/components/sections/contact-section.tsx
|
||
git commit -m "feat(ux): add loading skeletons for better user experience"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 9: 错误处理优化
|
||
|
||
**Files:**
|
||
- Create: `src/components/ui/error-boundary.tsx`
|
||
- Modify: `src/app/(marketing)/layout.tsx:1-50`
|
||
|
||
**Step 1: 创建错误边界**
|
||
|
||
```tsx
|
||
// src/components/ui/error-boundary.tsx
|
||
'use client';
|
||
|
||
import { Component, ReactNode } from 'react';
|
||
|
||
interface Props {
|
||
children: ReactNode;
|
||
fallback?: ReactNode;
|
||
}
|
||
|
||
interface State {
|
||
hasError: boolean;
|
||
}
|
||
|
||
export class ErrorBoundary extends Component<Props, State> {
|
||
constructor(props: Props) {
|
||
super(props);
|
||
this.state = { hasError: false };
|
||
}
|
||
|
||
static getDerivedStateFromError() {
|
||
return { hasError: true };
|
||
}
|
||
|
||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||
console.error('Error caught by boundary:', error, errorInfo);
|
||
}
|
||
|
||
render() {
|
||
if (this.state.hasError) {
|
||
return this.props.fallback || (
|
||
<div className="flex items-center justify-center min-h-[400px]">
|
||
<div className="text-center">
|
||
<h2 className="text-2xl font-bold mb-4">出错了</h2>
|
||
<p className="text-gray-600 mb-4">抱歉,页面出现了问题</p>
|
||
<button
|
||
onClick={() => this.setState({ hasError: false })}
|
||
className="px-4 py-2 bg-[#C41E3A] text-white rounded"
|
||
>
|
||
重试
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return this.props.children;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 2: 在布局中使用**
|
||
|
||
```tsx
|
||
// layout.tsx
|
||
import { ErrorBoundary } from '@/components/ui/error-boundary';
|
||
|
||
<ErrorBoundary>
|
||
{children}
|
||
</ErrorBoundary>
|
||
```
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add src/components/ui/error-boundary.tsx src/app/\(marketing\)/layout.tsx
|
||
git commit -m "feat(ux): add error boundary for graceful error handling"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 10: 成功反馈优化
|
||
|
||
**Files:**
|
||
- Create: `src/components/ui/toast.tsx`
|
||
- Modify: `src/components/sections/contact-section.tsx:1-200`
|
||
|
||
**Step 1: 创建 Toast 组件**
|
||
|
||
```tsx
|
||
// src/components/ui/toast.tsx
|
||
'use client';
|
||
|
||
import { useEffect, useState } from 'react';
|
||
import { CheckCircle2, X } from 'lucide-react';
|
||
|
||
interface ToastProps {
|
||
message: string;
|
||
type?: 'success' | 'error' | 'info';
|
||
duration?: number;
|
||
onClose: () => void;
|
||
}
|
||
|
||
export function Toast({
|
||
message,
|
||
type = 'success',
|
||
duration = 3000,
|
||
onClose
|
||
}: ToastProps) {
|
||
const [isVisible, setIsVisible] = useState(true);
|
||
|
||
useEffect(() => {
|
||
const timer = setTimeout(() => {
|
||
setIsVisible(false);
|
||
setTimeout(onClose, 300);
|
||
}, duration);
|
||
|
||
return () => clearTimeout(timer);
|
||
}, [duration, onClose]);
|
||
|
||
const icons = {
|
||
success: <CheckCircle2 className="h-5 w-5 text-green-500" />,
|
||
error: <X className="h-5 w-5 text-red-500" />,
|
||
info: <CheckCircle2 className="h-5 w-5 text-blue-500" />
|
||
};
|
||
|
||
return (
|
||
<div
|
||
className={`fixed bottom-4 right-4 z-50 flex items-center gap-3 px-4 py-3 bg-white rounded-lg shadow-lg border transition-all duration-300 ${
|
||
isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2'
|
||
}`}
|
||
role="alert"
|
||
>
|
||
{icons[type]}
|
||
<p className="text-sm font-medium">{message}</p>
|
||
<button
|
||
onClick={() => {
|
||
setIsVisible(false);
|
||
setTimeout(onClose, 300);
|
||
}}
|
||
className="ml-2 text-gray-400 hover:text-gray-600"
|
||
aria-label="关闭提示"
|
||
>
|
||
<X className="h-4 w-4" />
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 在表单中使用**
|
||
|
||
```tsx
|
||
// contact-section.tsx
|
||
import { Toast } from '@/components/ui/toast';
|
||
|
||
const [showToast, setShowToast] = useState(false);
|
||
|
||
{showToast && (
|
||
<Toast
|
||
message="表单提交成功!我们会尽快与您联系。"
|
||
type="success"
|
||
onClose={() => setShowToast(false)}
|
||
/>
|
||
)}
|
||
```
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add src/components/ui/toast.tsx src/components/sections/contact-section.tsx
|
||
git commit -m "feat(ux): add toast notifications for user feedback"
|
||
```
|
||
|
||
---
|
||
|
||
## 第四阶段:响应式设计优化(短期实施)
|
||
|
||
### Task 11: 移动端触摸优化
|
||
|
||
**Files:**
|
||
- Modify: `src/components/ui/button.tsx:1-50`
|
||
- Modify: `src/components/ui/input.tsx:1-50`
|
||
|
||
**Step 1: 增加触摸目标大小**
|
||
|
||
```tsx
|
||
// button.tsx
|
||
<Button className="min-h-[44px] min-w-[44px] px-6 py-3">
|
||
点击
|
||
</Button>
|
||
```
|
||
|
||
**Step 2: 优化输入框大小**
|
||
|
||
```tsx
|
||
// input.tsx
|
||
<Input className="h-12 text-base" />
|
||
```
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add src/components/ui/button.tsx src/components/ui/input.tsx
|
||
git commit -m "feat(responsive): optimize touch targets for mobile devices"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 12: 响应式字体优化
|
||
|
||
**Files:**
|
||
- Modify: `src/app/globals.css:1-100`
|
||
|
||
**Step 1: 添加响应式字体**
|
||
|
||
```css
|
||
/* globals.css */
|
||
html {
|
||
font-size: 16px;
|
||
}
|
||
|
||
@media (min-width: 640px) {
|
||
html {
|
||
font-size: 17px;
|
||
}
|
||
}
|
||
|
||
@media (min-width: 1024px) {
|
||
html {
|
||
font-size: 18px;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 2: 提交**
|
||
|
||
```bash
|
||
git add src/app/globals.css
|
||
git commit -m "feat(responsive): add responsive font sizing"
|
||
```
|
||
|
||
---
|
||
|
||
## 第五阶段:SEO 优化(短期实施)
|
||
|
||
### Task 13: Meta 标签优化
|
||
|
||
**Files:**
|
||
- Modify: `src/app/layout.tsx:1-50`
|
||
- Create: `src/app/(marketing)/about/metadata.ts`
|
||
|
||
**Step 1: 添加全局 metadata**
|
||
|
||
```tsx
|
||
// layout.tsx
|
||
import { Metadata } from 'next';
|
||
|
||
export const metadata: Metadata = {
|
||
title: {
|
||
default: '诺瓦隆 - 金融科技解决方案',
|
||
template: '%s | 诺瓦隆'
|
||
},
|
||
description: '诺瓦隆是专业的金融科技服务提供商,致力于为证券、基金、银行等金融机构提供创新的技术解决方案。',
|
||
keywords: ['金融科技', '证券', '基金', '银行', '投资', '风险管理'],
|
||
authors: [{ name: '诺瓦隆' }],
|
||
creator: '诺瓦隆',
|
||
publisher: '诺瓦隆',
|
||
robots: {
|
||
index: true,
|
||
follow: true
|
||
},
|
||
openGraph: {
|
||
type: 'website',
|
||
locale: 'zh_CN',
|
||
url: 'https://novalon.com',
|
||
siteName: '诺瓦隆',
|
||
title: '诺瓦隆 - 金融科技解决方案',
|
||
description: '专业的金融科技服务提供商',
|
||
images: [
|
||
{
|
||
url: '/og-image.jpg',
|
||
width: 1200,
|
||
height: 630,
|
||
alt: '诺瓦隆'
|
||
}
|
||
]
|
||
},
|
||
twitter: {
|
||
card: 'summary_large_image',
|
||
title: '诺瓦隆 - 金融科技解决方案',
|
||
description: '专业的金融科技服务提供商',
|
||
images: ['/og-image.jpg']
|
||
}
|
||
};
|
||
```
|
||
|
||
**Step 2: 提交**
|
||
|
||
```bash
|
||
git add src/app/layout.tsx
|
||
git commit -m "feat(seo): add comprehensive metadata for SEO optimization"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 14: 结构化数据优化
|
||
|
||
**Files:**
|
||
- Create: `src/components/seo/structured-data.tsx`
|
||
- Modify: `src/app/layout.tsx:1-50`
|
||
|
||
**Step 1: 创建结构化数据组件**
|
||
|
||
```tsx
|
||
// src/components/seo/structured-data.tsx
|
||
export function OrganizationSchema() {
|
||
const schema = {
|
||
"@context": "https://schema.org",
|
||
"@type": "Organization",
|
||
"name": "诺瓦隆",
|
||
"url": "https://novalon.com",
|
||
"logo": "https://novalon.com/logo.svg",
|
||
"description": "专业的金融科技服务提供商",
|
||
"address": {
|
||
"@type": "PostalAddress",
|
||
"addressCountry": "CN",
|
||
"addressLocality": "北京"
|
||
},
|
||
"contactPoint": {
|
||
"@type": "ContactPoint",
|
||
"telephone": "+86-10-12345678",
|
||
"contactType": "customer service"
|
||
}
|
||
};
|
||
|
||
return (
|
||
<script
|
||
type="application/ld+json"
|
||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 2: 在布局中使用**
|
||
|
||
```tsx
|
||
// layout.tsx
|
||
import { OrganizationSchema } from '@/components/seo/structured-data';
|
||
|
||
<head>
|
||
<OrganizationSchema />
|
||
</head>
|
||
```
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add src/components/seo/structured-data.tsx src/app/layout.tsx
|
||
git commit -m "feat(seo): add structured data for better search visibility"
|
||
```
|
||
|
||
---
|
||
|
||
## 第六阶段:安全性优化(长期实施)
|
||
|
||
### Task 15: XSS 防护
|
||
|
||
**Files:**
|
||
- Create: `src/lib/sanitize.ts`
|
||
- Modify: `src/components/sections/contact-section.tsx:1-200`
|
||
|
||
**Step 1: 安装 DOMPurify**
|
||
|
||
Run: `npm install dompurify`
|
||
Run: `npm install --save-dev @types/dompurify`
|
||
|
||
**Step 2: 创建清理工具**
|
||
|
||
```tsx
|
||
// src/lib/sanitize.ts
|
||
import DOMPurify from 'dompurify';
|
||
|
||
export function sanitizeHTML(dirty: string): string {
|
||
return DOMPurify.sanitize(dirty, {
|
||
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
|
||
ALLOWED_ATTR: ['href']
|
||
});
|
||
}
|
||
|
||
export function sanitizeInput(input: string): string {
|
||
return DOMPurify.sanitize(input, { ALLOWED_TAGS: [] });
|
||
}
|
||
```
|
||
|
||
**Step 3: 在表单中使用**
|
||
|
||
```tsx
|
||
// contact-section.tsx
|
||
import { sanitizeInput } from '@/lib/sanitize';
|
||
|
||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const { name, value } = e.target;
|
||
setFormData(prev => ({
|
||
...prev,
|
||
[name]: sanitizeInput(value)
|
||
}));
|
||
};
|
||
```
|
||
|
||
**Step 4: 提交**
|
||
|
||
```bash
|
||
git add src/lib/sanitize.ts src/components/sections/contact-section.tsx package.json
|
||
git commit -m "feat(security): implement XSS protection with DOMPurify"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 16: CSRF 防护
|
||
|
||
**Files:**
|
||
- Create: `src/lib/csrf.ts`
|
||
- Modify: `src/components/sections/contact-section.tsx:1-200`
|
||
|
||
**Step 1: 创建 CSRF 工具**
|
||
|
||
```tsx
|
||
// src/lib/csrf.ts
|
||
export function generateCSRFToken(): string {
|
||
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
||
}
|
||
|
||
export function validateCSRFToken(token: string, storedToken: string): boolean {
|
||
return token === storedToken;
|
||
}
|
||
```
|
||
|
||
**Step 2: 在表单中使用**
|
||
|
||
```tsx
|
||
// contact-section.tsx
|
||
import { generateCSRFToken } from '@/lib/csrf';
|
||
|
||
const [csrfToken] = useState(generateCSRFToken());
|
||
|
||
<form>
|
||
<input type="hidden" name="csrf_token" value={csrfToken} />
|
||
{/* ... other fields */}
|
||
</form>
|
||
```
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add src/lib/csrf.ts src/components/sections/contact-section.tsx
|
||
git commit -m "feat(security): implement CSRF protection"
|
||
```
|
||
|
||
---
|
||
|
||
## 第七阶段:代码质量优化(长期实施)
|
||
|
||
### Task 17: TypeScript 类型安全优化
|
||
|
||
**Files:**
|
||
- Modify: `src/lib/constants.ts:1-100`
|
||
- Modify: `src/components/sections/*.tsx`
|
||
|
||
**Step 1: 定义严格的类型**
|
||
|
||
```tsx
|
||
// src/lib/constants.ts
|
||
export interface CompanyInfo {
|
||
name: string;
|
||
slogan: string;
|
||
description: string;
|
||
email: string;
|
||
phone: string;
|
||
address: string;
|
||
}
|
||
|
||
export const COMPANY_INFO: CompanyInfo = {
|
||
name: '诺瓦隆',
|
||
slogan: '智连未来 · 与客户共同成长',
|
||
description: '专业的金融科技服务提供商',
|
||
email: 'contact@novalon.com',
|
||
phone: '+86 10 1234 5678',
|
||
address: '北京市朝阳区建国路88号'
|
||
};
|
||
```
|
||
|
||
**Step 2: 提交**
|
||
|
||
```bash
|
||
git add src/lib/constants.ts
|
||
git commit -m "refactor(types): add strict TypeScript types for constants"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 18: ESLint 规则优化
|
||
|
||
**Files:**
|
||
- Modify: `eslint.config.mjs:1-50`
|
||
|
||
**Step 1: 添加严格的 ESLint 规则**
|
||
|
||
```javascript
|
||
// eslint.config.mjs
|
||
export default [
|
||
{
|
||
rules: {
|
||
'@typescript-eslint/no-explicit-any': 'error',
|
||
'@typescript-eslint/explicit-module-boundary-types': 'error',
|
||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||
'react/no-danger': 'warn',
|
||
'react/jsx-no-target-blank': 'error'
|
||
}
|
||
}
|
||
];
|
||
```
|
||
|
||
**Step 2: 运行 lint 检查**
|
||
|
||
Run: `npm run lint`
|
||
|
||
Expected: 显示所有需要修复的问题
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add eslint.config.mjs
|
||
git commit -m "refactor(lint): add stricter ESLint rules for code quality"
|
||
```
|
||
|
||
---
|
||
|
||
## 测试计划
|
||
|
||
### Task 19: 无障碍性测试
|
||
|
||
**Files:**
|
||
- Create: `e2e-tests/tests/test_accessibility.py`
|
||
|
||
**Step 1: 创建无障碍性测试**
|
||
|
||
```python
|
||
# e2e-tests/tests/test_accessibility.py
|
||
def test_form_labels(page):
|
||
"""测试表单标签关联"""
|
||
page.goto("/contact")
|
||
|
||
# 检查所有输入框都有关联的标签
|
||
inputs = page.locator("input, textarea")
|
||
for i in range(inputs.count()):
|
||
input_element = inputs.nth(i)
|
||
input_id = input_element.get_attribute("id")
|
||
|
||
if input_id:
|
||
label = page.locator(f"label[for='{input_id}']")
|
||
assert label.count() > 0, f"Input {input_id} has no associated label"
|
||
|
||
def test_keyboard_navigation(page):
|
||
"""测试键盘导航"""
|
||
page.goto("/")
|
||
|
||
# 测试 Tab 键导航
|
||
page.keyboard.press("Tab")
|
||
focused_element = page.locator(":focus")
|
||
assert focused_element.count() > 0
|
||
```
|
||
|
||
**Step 2: 运行测试**
|
||
|
||
Run: `pytest e2e-tests/tests/test_accessibility.py -v`
|
||
|
||
Expected: PASS
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add e2e-tests/tests/test_accessibility.py
|
||
git commit -m "test(a11y): add accessibility tests"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 20: 性能测试
|
||
|
||
**Files:**
|
||
- Create: `e2e-tests/tests/test_performance_optimization.py`
|
||
|
||
**Step 1: 创建性能测试**
|
||
|
||
```python
|
||
# e2e-tests/tests/test_performance_optimization.py
|
||
def test_page_load_time(page):
|
||
"""测试页面加载时间"""
|
||
import time
|
||
start_time = time.time()
|
||
|
||
page.goto("/")
|
||
page.wait_for_load_state("networkidle")
|
||
|
||
end_time = time.time()
|
||
load_time = end_time - start_time
|
||
|
||
assert load_time < 3.0, f"Page load time {load_time}s exceeds 3s threshold"
|
||
|
||
def test_image_lazy_loading(page):
|
||
"""测试图片懒加载"""
|
||
page.goto("/")
|
||
|
||
# 检查图片是否有 loading="lazy" 属性
|
||
images = page.locator("img[loading='lazy']")
|
||
assert images.count() > 0, "No lazy-loaded images found"
|
||
```
|
||
|
||
**Step 2: 运行测试**
|
||
|
||
Run: `pytest e2e-tests/tests/test_performance_optimization.py -v`
|
||
|
||
Expected: PASS
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add e2e-tests/tests/test_performance_optimization.py
|
||
git commit -m "test(perf): add performance optimization tests"
|
||
```
|
||
|
||
---
|
||
|
||
## 验收标准
|
||
|
||
### 无障碍性
|
||
- ✅ 所有表单控件都有关联的标签
|
||
- ✅ 所有图片都有 alt 属性
|
||
- ✅ 支持完整的键盘导航
|
||
- ✅ 通过 WCAG 2.1 AA 级标准
|
||
|
||
### 性能
|
||
- ✅ 首屏加载时间 < 2s
|
||
- ✅ Lighthouse 性能分数 > 90
|
||
- ✅ 所有图片都使用懒加载
|
||
- ✅ 非关键组件使用动态导入
|
||
|
||
### 用户体验
|
||
- ✅ 所有表单都有加载状态
|
||
- ✅ 所有错误都有友好提示
|
||
- ✅ 所有成功操作都有反馈
|
||
- ✅ 移动端触摸目标 ≥ 44px
|
||
|
||
### SEO
|
||
- ✅ 所有页面都有完整的 metadata
|
||
- ✅ 添加结构化数据
|
||
- ✅ 使用语义化 HTML
|
||
- ✅ Lighthouse SEO 分数 = 100
|
||
|
||
### 安全性
|
||
- ✅ 所有用户输入都经过清理
|
||
- ✅ 表单都有 CSRF 保护
|
||
- ✅ 无 XSS 漏洞
|
||
- ✅ 无安全警告
|
||
|
||
---
|
||
|
||
## 执行建议
|
||
|
||
**推荐使用 Subagent-Driven 方式执行**,每个任务由独立的 subagent 完成,任务之间进行代码审查,确保质量。
|
||
|
||
执行顺序:
|
||
1. 第一阶段(Task 1-4):无障碍性优化
|
||
2. 第二阶段(Task 5-7):性能优化
|
||
3. 第三阶段(Task 8-10):用户体验优化
|
||
4. 第四阶段(Task 11-12):响应式设计优化
|
||
5. 第五阶段(Task 13-14):SEO 优化
|
||
6. 第六阶段(Task 15-16):安全性优化
|
||
7. 第七阶段(Task 17-18):代码质量优化
|
||
8. 测试阶段(Task 19-20):测试验证
|
||
|
||
---
|
||
|
||
## 风险评估
|
||
|
||
| 风险 | 影响 | 缓解措施 |
|
||
|------|------|----------|
|
||
| 破坏现有功能 | 高 | 每个任务都有测试验证 |
|
||
| 性能下降 | 中 | 使用性能测试监控 |
|
||
| 兼容性问题 | 中 | 在多个浏览器测试 |
|
||
| 时间超预期 | 低 | 任务粒度小,易于调整 |
|
||
|
||
---
|
||
|
||
## 成功指标
|
||
|
||
- ✅ Lighthouse 综合分数 > 90
|
||
- ✅ WCAG 2.1 AA 级合规
|
||
- ✅ 所有测试通过
|
||
- ✅ 无安全漏洞
|
||
- ✅ 代码质量评分 A 级
|