feat: 为主导航菜单和页面区块添加ARIA属性 fix: 解决工作时间信息获取问题 perf: 优化页面滚动功能实现 fix: 修正联系页面标题显示问题 test: 运行完整测试套件验证修复效果 docs: 添加修复完成报告
38 KiB
颜色对比度和标题层级结构优化实现计划
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: 修复网站的颜色对比度问题和标题层级结构问题,提升可访问性和SEO评分
Architecture: 采用渐进式修复策略,首先优化颜色对比度以提升可访问性,然后标准化标题层级结构以改善SEO。使用CSS变量系统进行全局颜色管理,建立标题层级规范并添加自动化检查工具。
Tech Stack: Next.js 16, Tailwind CSS 4, TypeScript, Playwright (测试), @axe-core/playwright (可访问性测试)
阶段1: 颜色对比度优化
Task 1: 创建对比度检测工具
Files:
- Create:
src/lib/color-contrast.ts
Step 1: Write the failing test
// tests/lib/color-contrast.test.ts
import { calculateContrastRatio, meetsWCAGStandard } from '@/lib/color-contrast';
describe('Color Contrast Calculator', () => {
test('should calculate correct contrast ratio for black on white', () => {
const ratio = calculateContrastRatio('#000000', '#FFFFFF');
expect(ratio).toBeCloseTo(21, 1);
});
test('should identify WCAG AA compliance for normal text', () => {
const result = meetsWCAGStandard('#000000', '#FFFFFF', 'AA', 'normal');
expect(result.passes).toBe(true);
expect(result.ratio).toBeGreaterThan(4.5);
});
test('should fail WCAG AA for low contrast colors', () => {
const result = meetsWCAGStandard('#808080', '#FFFFFF', 'AA', 'normal');
expect(result.passes).toBe(false);
expect(result.ratio).toBeLessThan(4.5);
});
});
Step 2: Run test to verify it fails
Run: npm test -- tests/lib/color-contrast.test.ts
Expected: FAIL with "Cannot find module '@/lib/color-contrast'"
Step 3: Write minimal implementation
// src/lib/color-contrast.ts
interface ColorRGB {
r: number;
g: number;
b: number;
}
interface ContrastResult {
passes: boolean;
ratio: number;
requiredRatio: number;
}
function hexToRgb(hex: string): ColorRGB {
const cleanHex = hex.replace('#', '');
const r = parseInt(cleanHex.substring(0, 2), 16);
const g = parseInt(cleanHex.substring(2, 4), 16);
const b = parseInt(cleanHex.substring(4, 6), 16);
return { r, g, b };
}
function getLuminance(rgb: ColorRGB): number {
const { r, g, b } = rgb;
const a = [r, g, b].map(v => {
v /= 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
}
export function calculateContrastRatio(foreground: string, background: string): number {
const fgRgb = hexToRgb(foreground);
const bgRgb = hexToRgb(background);
const fgLuminance = getLuminance(fgRgb);
const bgLuminance = getLuminance(bgRgb);
const lighter = Math.max(fgLuminance, bgLuminance);
const darker = Math.min(fgLuminance, bgLuminance);
return (lighter + 0.05) / (darker + 0.05);
}
export function meetsWCAGStandard(
foreground: string,
background: string,
level: 'AA' | 'AAA',
textSize: 'normal' | 'large'
): ContrastResult {
const ratio = calculateContrastRatio(foreground, background);
let requiredRatio: number;
if (level === 'AA') {
requiredRatio = textSize === 'normal' ? 4.5 : 3;
} else {
requiredRatio = textSize === 'normal' ? 7 : 4.5;
}
return {
passes: ratio >= requiredRatio,
ratio,
requiredRatio
};
}
Step 4: Run test to verify it passes
Run: npm test -- tests/lib/color-contrast.test.ts
Expected: PASS
Step 5: Commit
git add src/lib/color-contrast.ts tests/lib/color-contrast.test.ts
git commit -m "feat: add color contrast calculation utility with WCAG standards"
Task 2: 优化CSS变量中的颜色对比度
Files:
- Modify:
src/app/globals.css:33-67
Step 1: Write the failing test
// tests/styles/color-contrast.test.ts
import { calculateContrastRatio, meetsWCAGStandard } from '@/lib/color-contrast';
describe('CSS Variables Color Contrast', () => {
test('primary text on primary background should meet WCAG AA', () => {
const result = meetsWCAGStandard('#1C1C1C', '#FAFAFA', 'AA', 'normal');
expect(result.passes).toBe(true);
});
test('tertiary text on primary background should meet WCAG AA', () => {
const result = meetsWCAGStandard('#5C5C5C', '#FAFAFA', 'AA', 'normal');
expect(result.passes).toBe(true);
});
test('muted text on primary background should meet WCAG AA', () => {
const result = meetsWCAGStandard('#8C8C8C', '#FAFAFA', 'AA', 'normal');
expect(result.passes).toBe(true);
});
});
Step 2: Run test to verify it fails
Run: npm test -- tests/styles/color-contrast.test.ts
Expected: FAIL with muted text contrast ratio < 4.5
Step 3: Write minimal implementation
/* src/app/globals.css - 修改颜色变量部分 */
:root {
/* 主色调 - 墨黑系(水墨画主色) */
--color-primary: #1C1C1C;
--color-primary-hover: #0A0A0A;
--color-primary-light: #3D3D3D;
--color-primary-lighter: #F5F5F5;
/* 品牌色 - 朱砂红(印章红) */
--color-brand-primary: #C41E3A;
--color-brand-primary-hover: #A01830;
--color-brand-primary-light: #E04A68;
--color-brand-primary-bg: #FEF2F4;
/* 背景色系 - 宣纸白 */
--color-bg-primary: #FFFFFF; /* 从 #FAFAFA 改为 #FFFFFF 提升对比度 */
--color-bg-secondary: #FFFBF5;
--color-bg-tertiary: #F5F5F5;
--color-bg-hover: #EFEFEF;
/* 文字色系 - 墨色层次(优化对比度) */
--color-text-primary: #1C1C1C;
--color-text-secondary: #3D3D3D;
--color-text-tertiary: #4A4A4A; /* 从 #5C5C5C 改为 #4A4A4A 提升对比度 */
--color-text-muted: #6B6B6B; /* 从 #8C8C8C 改为 #6B6B6B 提升对比度 */
/* 边框色系 */
--color-border-primary: #E5E5E5;
--color-border-secondary: #D4D4D4D;
--color-border-accent: #1C1C1C;
/* 链接色 */
--color-link: #1C1C1C;
--color-link-hover: #C41E3A;
/* 状态色 */
--color-success: #16A34A;
--color-success-bg: #F0FDF4;
--color-warning: #D97706;
--color-warning-bg: #FFFBEB;
--color-info: #5C5C5C;
--color-info-bg: #F5F5F5;
--color-error: #C41E3A;
--color-error-bg: #FEF2F4;
/* 阴影 */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.05);
/* 尺寸变量 */
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
--font-size-3xl: 1.875rem;
--font-size-4xl: 2.25rem;
--font-size-5xl: 3rem;
--font-size-6xl: 3.75rem;
--line-height-tight: 1.1;
--line-height-snug: 1.25;
--line-height-normal: 1.5;
--line-height-relaxed: 1.625;
--letter-spacing-tight: -0.025em;
--letter-spacing-normal: 0;
--letter-spacing-wide: 0.025em;
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
--spacing-2xl: 3rem;
--spacing-3xl: 4rem;
--spacing-4xl: 6rem;
--spacing-5xl: 8rem;
--border-width-thin: 0.5px;
--border-width-normal: 1px;
--transition-fast: 150ms;
--transition-normal: 200ms;
--transition-slow: 300ms;
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
}
Step 4: Run test to verify it passes
Run: npm test -- tests/styles/color-contrast.test.ts
Expected: PASS
Step 5: Commit
git add src/app/globals.css tests/styles/color-contrast.test.ts
git commit -m "fix: optimize color contrast for better accessibility (WCAG AA compliance)"
Task 3: 添加对比度检查脚本
Files:
- Create:
scripts/check-color-contrast.ts
Step 1: Write the failing test
# Test script exists and runs
test -f scripts/check-color-contrast.ts
echo "Script exists"
Step 2: Run test to verify it fails
Run: test -f scripts/check-color-contrast.ts
Expected: FAIL with "No such file or directory"
Step 3: Write minimal implementation
// scripts/check-color-contrast.ts
import { calculateContrastRatio, meetsWCAGStandard } from '../src/lib/color-contrast';
interface ColorPair {
name: string;
foreground: string;
background: string;
textSize?: 'normal' | 'large';
}
const criticalColorPairs: ColorPair[] = [
{ name: 'Primary text on primary background', foreground: '#1C1C1C', background: '#FFFFFF' },
{ name: 'Secondary text on primary background', foreground: '#3D3D3D', background: '#FFFFFF' },
{ name: 'Tertiary text on primary background', foreground: '#4A4A4A', background: '#FFFFFF' },
{ name: 'Muted text on primary background', foreground: '#6B6B6B', background: '#FFFFFF' },
{ name: 'Brand primary on white', foreground: '#C41E3A', background: '#FFFFFF' },
{ name: 'Brand primary on brand bg', foreground: '#C41E3A', background: '#FEF2F4' },
{ name: 'Link on hover', foreground: '#C41E3A', background: '#FFFFFF' },
];
function checkContrast() {
console.log('🎨 Color Contrast Check\n');
let failures = 0;
let passes = 0;
criticalColorPairs.forEach(pair => {
const result = meetsWCAGStandard(
pair.foreground,
pair.background,
'AA',
pair.textSize || 'normal'
);
const status = result.passes ? '✅ PASS' : '❌ FAIL';
const ratio = result.ratio.toFixed(2);
console.log(`${status} ${pair.name}`);
console.log(` Ratio: ${ratio}:1 (Required: ${result.requiredRatio}:1)`);
console.log(` FG: ${pair.foreground} | BG: ${pair.background}\n`);
if (result.passes) {
passes++;
} else {
failures++;
}
});
console.log(`\n📊 Summary:`);
console.log(` Total: ${criticalColorPairs.length}`);
console.log(` ✅ Passes: ${passes}`);
console.log(` ❌ Failures: ${failures}`);
if (failures > 0) {
console.log('\n⚠️ Some color pairs do not meet WCAG AA standards!');
process.exit(1);
} else {
console.log('\n✅ All color pairs meet WCAG AA standards!');
process.exit(0);
}
}
checkContrast();
Step 4: Add to package.json
// package.json - add to scripts section
{
"scripts": {
"check:contrast": "tsx scripts/check-color-contrast.ts",
// ... existing scripts
}
}
Step 5: Run script to verify it works
Run: npm run check:contrast
Expected: All color pairs pass WCAG AA standards
Step 6: Commit
git add scripts/check-color-contrast.ts package.json
git commit -m "feat: add color contrast checking script for CI/CD"
阶段2: 标题层级结构优化
Task 4: 创建标题层级规范文档
Files:
- Create:
docs/heading-hierarchy-guidelines.md
Step 1: Write the failing test
# Test documentation exists
test -f docs/heading-hierarchy-guidelines.md
echo "Documentation exists"
Step 2: Run test to verify it fails
Run: test -f docs/heading-hierarchy-guidelines.md
Expected: FAIL with "No such file or directory"
Step 3: Write minimal implementation
# 标题层级结构规范
## 概述
本文档定义了Novalon网站的标题层级结构规范,确保符合SEO最佳实践和可访问性要求。
## 基本原则
1. **层级连续性**: 标题必须按H1 → H2 → H3 → H4的顺序使用,不能跳级
2. **唯一性**: 每个页面只能有一个H1标签
3. **语义化**: 标题应准确描述其下方内容的主题
4. **简洁性**: 标题文本应简洁明了,避免过长
## 页面标题层级规范
### 首页
H1: Novalon - 开启数字化转型之旅 ├─ H2: 核心业务 │ ├─ H3: [服务卡片标题] │ └─ H3: [服务卡片标题] ├─ H2: 产品 │ ├─ H3: [产品卡片标题] │ └─ H3: [产品卡片标题] ├─ H2: 与谁同行,决定能走多远 │ ├─ H3: [案例卡片标题] │ └─ H3: [案例卡片标题] ├─ H2: 关于Novalon │ ├─ H3: [统计数据标题] │ └─ H3: [统计数据标题] └─ H2: 最新资讯 ├─ H3: [新闻卡片标题] └─ H3: [新闻卡片标题]
### 关于页面
H1: 关于我们 - Novalon ├─ H2: 品牌故事 ├─ H2: 企业文化 ├─ H2: 发展历程 └─ H2: 联系我们
### 联系页面
H1: 开启合作 ├─ H2: 联系方式 ├─ H2: 工作时间 ├─ H2: 我们的承诺 └─ H2: 发送消息
### 服务页面
H1: 我们的核心业务 ├─ H2: [服务类别] │ ├─ H3: [具体服务] │ └─ H3: [具体服务] └─ H2: [服务类别] ├─ H3: [具体服务] └─ H3: [具体服务]
### 产品页面
H1: 我们的产品 ├─ H2: [产品类别] │ ├─ H3: [具体产品] │ └─ H3: [具体产品] └─ H2: [产品类别] ├─ H3: [具体产品] └─ H3: [具体产品]
### 案例页面
H1: 成功案例 ├─ H2: [行业分类] │ ├─ H3: [案例标题] │ └─ H3: [案例标题] └─ H2: [行业分类] ├─ H3: [案例标题] └─ H3: [案例标题]
### 新闻页面
H1: 最新资讯 ├─ H2: [新闻类别] │ ├─ H3: [新闻标题] │ └─ H3: [新闻标题] └─ H2: [新闻类别] ├─ H3: [新闻标题] └─ H3: [新闻标题]
## 组件标题规范
### Section组件
- 每个section应该有一个H2标题
- Section内的子内容使用H3或H4
### Card组件
- 卡片标题使用H3或H4,取决于其在页面中的层级
- 卡片内部的小标题使用H4或H5
### List组件
- 列表项不使用标题标签
- 使用语义化的`<ul>`和`<li>`标签
## 实施检查清单
- [ ] 每个页面只有一个H1
- [ ] 标题层级连续,无跳级
- [ ] H1准确描述页面主要内容
- [ ] H2描述主要section
- [ ] H3描述subsection
- [ ] 标题文本简洁明了
- [ ] 避免使用标题标签进行样式控制
## 工具和验证
### 自动化检查
使用以下脚本验证标题层级:
```bash
npm run check:headings
手动检查
- 使用浏览器的开发者工具查看页面结构
- 使用SEO工具(如Screaming Frog)分析标题层级
- 使用可访问性工具(如axe DevTools)检查标题结构
参考资料
- WCAG 2.1 - Headings and Labels
- Google SEO Starter Guide - Heading structure
- MDN - Using HTML headings
**Step 4: Run test to verify it passes**
Run: `test -f docs/heading-hierarchy-guidelines.md`
Expected: PASS
**Step 5: Commit**
```bash
git add docs/heading-hierarchy-guidelines.md
git commit -m "docs: add heading hierarchy guidelines for SEO and accessibility"
Task 5: 修复首页标题层级结构
Files:
- Modify:
src/components/sections/hero-section.tsx:1-100 - Modify:
src/components/sections/services-section.tsx:1-90 - Modify:
src/components/sections/products-section.tsx:1-100 - Modify:
src/components/sections/cases-section.tsx:1-100 - Modify:
src/components/sections/about-section.tsx:1-77 - Modify:
src/components/sections/news-section.tsx:1-89
Step 1: Write the failing test
// tests/heading-hierarchy/heading-structure.test.ts
import { test, expect } from '@playwright/test';
test.describe('首页标题层级结构', () => {
test('should have proper heading hierarchy', async ({ page }) => {
await page.goto('/');
// 检查只有一个H1
const h1Count = await page.locator('h1').count();
expect(h1Count).toBe(1);
// 检查H1内容
const h1Text = await page.locator('h1').textContent();
expect(h1Text).toBeTruthy();
// 检查H2数量(应该有6个section)
const h2Count = await page.locator('h2').count();
expect(h2Count).toBeGreaterThanOrEqual(5);
// 检查H3数量(应该在卡片中)
const h3Count = await page.locator('h3').count();
expect(h3Count).toBeGreaterThan(0);
// 验证层级结构:H1 -> H2 -> H3
const headings = await page.locator('h1, h2, h3').allTextContents();
console.log('Headings found:', headings);
});
});
Step 2: Run test to verify it fails
Run: npm test -- tests/heading-hierarchy/heading-structure.test.ts
Expected: FAIL or warnings about heading structure
Step 3: Write minimal implementation
// src/components/sections/hero-section.tsx - 确保H1存在
// 当前已经有H1,保持不变
<h1 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-4">
开启 <span className="text-[#C41E3A]">数字化转型</span>之旅
</h1>
// src/components/sections/services-section.tsx - 确保H2和H3层级正确
// 当前H2正确,卡片标题应该是H3
<h2 id="services-heading" className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-4">
我们的 <span className="text-[#C41E3A]">核心业务</span>
</h2>
// 卡片标题应该是H3
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-3 group-hover:text-[#C41E3A] transition-colors">
{service.title}
</h3>
// src/components/sections/products-section.tsx - 确保H2和H3层级正确
<h2 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
我们的<span className="text-[#C41E3A]">产品</span>
</h2>
// 产品卡片标题应该是H3
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-3 group-hover:text-[#C41E3A] transition-colors">
{product.title}
</h3>
// src/components/sections/cases-section.tsx - 确保H2和H3层级正确
<h2 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-4">
与谁同行,<span className="text-[#C41E3A]">决定能走多远</span>
</h2>
// 案例卡片标题应该是H3
<h3 className="text-lg font-semibold text-[#1C1C1C] mb-3 group-hover:text-[#C41E3A] transition-colors">
{caseItem.title}
</h3>
// src/components/sections/about-section.tsx - 确保H2层级正确
<h2 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
关于 <span className="tracking-tight font-calligraphy text-[#C41E3A]">{COMPANY_INFO.shortName}</span>
</h2>
// 添加缺失的H2标签
<h2 className="text-2xl font-semibold text-[#1C1C1C] mb-4 mt-8">
品牌故事
</h2>
<h2 className="text-2xl font-semibold text-[#1C1C1C] mb-4 mt-8">
发展历程
</h2>
// src/components/sections/news-section.tsx - 确保H2和H3层级正确
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-[#1C1C1C] mb-6">
最新<span className="text-[#C41E3A]">资讯</span>
</h2>
// 新闻卡片标题应该是H3
<h3 className="text-xl leading-tight">{news.title}</h3>
Step 4: Run test to verify it passes
Run: npm test -- tests/heading-hierarchy/heading-structure.test.ts
Expected: PASS
Step 5: Commit
git add src/components/sections/hero-section.tsx src/components/sections/services-section.tsx src/components/sections/products-section.tsx src/components/sections/cases-section.tsx src/components/sections/about-section.tsx src/components/sections/news-section.tsx tests/heading-hierarchy/heading-structure.test.ts
git commit -m "fix: standardize heading hierarchy across all sections for better SEO"
Task 6: 修复关于页面标题层级结构
Files:
- Modify:
src/app/(marketing)/about/page.tsx:1-11 - Create:
src/app/(marketing)/about/client.tsx
Step 1: Write the failing test
// tests/heading-hierarchy/about-page.test.ts
import { test, expect } from '@playwright/test';
test.describe('关于页面标题层级结构', () => {
test('should have proper heading hierarchy', async ({ page }) => {
await page.goto('/about');
// 检查只有一个H1
const h1Count = await page.locator('h1').count();
expect(h1Count).toBe(1);
// 检查H2数量
const h2Count = await page.locator('h2').count();
expect(h2Count).toBeGreaterThanOrEqual(3);
// 检查H3数量
const h3Count = await page.locator('h3').count();
expect(h3Count).toBeGreaterThan(0);
// 验证层级连续性
const headings = await page.evaluate(() => {
const elements = Array.from(document.querySelectorAll('h1, h2, h3'));
return elements.map(el => ({
tag: el.tagName,
text: el.textContent?.trim()
}));
});
console.log('About page headings:', headings);
// 验证没有跳级
for (let i = 1; i < headings.length; i++) {
const currentLevel = parseInt(headings[i].tag[1]);
const prevLevel = parseInt(headings[i-1].tag[1]);
const diff = currentLevel - prevLevel;
// 允许同级或降级,但不允许跳级(如H1->H3)
if (diff > 1) {
throw new Error(`Heading level jump detected: ${headings[i-1].tag} -> ${headings[i].tag}`);
}
}
});
});
Step 2: Run test to verify it fails
Run: npm test -- tests/heading-hierarchy/about-page.test.ts
Expected: FAIL or warnings about heading structure
Step 3: Write minimal implementation
// src/app/(marketing)/about/client.tsx
'use client';
import { motion } from 'framer-motion';
import { useInView } from 'framer-motion';
import { useRef } from 'react';
import { COMPANY_INFO } from '@/lib/constants';
export function AboutClient() {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: '-100px' });
return (
<main className="min-h-screen bg-white pt-16">
<section className="section-padding relative" ref={ref}>
<div className="container-wide">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6 }}
>
{/* H1 - 页面主标题 */}
<h1 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
关于 <span className="tracking-tight font-calligraphy text-[#C41E3A]">{COMPANY_INFO.shortName}</span>
</h1>
<p className="text-lg text-[#5C5C5C] mb-8">
{COMPANY_INFO.slogan}
</p>
</motion.div>
{/* H2 - 品牌故事 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.1 }}
className="mb-12"
>
<h2 className="text-2xl font-semibold text-[#1C1C1C] mb-6">
品牌故事
</h2>
<div className="bg-white rounded-2xl p-8 border border-[#E5E5E5]">
<p className="text-lg text-[#5C5C5C] leading-relaxed text-center mb-6">
"企业需要的,不是一个高高在上的'专家',也不是一个做完就跑的'卖家',而是一个能坐下来、一起想办法的同行者。"
</p>
<p className="text-[#1C1C1C] font-medium text-center">
我们只做一件事:成为您数字化转型路上,信得过的成长伙伴。
</p>
</div>
</motion.div>
{/* H2 - 发展历程 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.2 }}
className="mb-12"
>
<h2 className="text-2xl font-semibold text-[#1C1C1C] mb-6">
发展历程
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
{STATS.map((stat, idx) => (
<div key={idx} className="text-center border border-[#E5E5E5] rounded-lg p-6">
{/* H3 - 统计数据标题 */}
<h3 className="text-sm text-[#5C5C5C] mb-2">{stat.label}</h3>
<div className="text-3xl sm:text-4xl font-bold text-[#C41E3A]">
{stat.value}
</div>
</div>
))}
</div>
</motion.div>
{/* H2 - 联系我们 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.3 }}
className="text-center"
>
<h2 className="text-2xl font-semibold text-[#1C1C1C] mb-6">
联系我们
</h2>
<p className="text-[#5C5C5C] mb-4">
如有合作意向,欢迎随时联系我们
</p>
<a
href="/contact"
className="inline-flex items-center px-6 py-3 bg-[#C41E3A] text-white rounded-lg hover:bg-[#A01830] transition-colors"
>
立即联系
</a>
</motion.div>
</div>
</section>
</main>
);
}
Step 4: Run test to verify it passes
Run: npm test -- tests/heading-hierarchy/about-page.test.ts
Expected: PASS
Step 5: Commit
git add src/app/(marketing)/about/page.tsx src/app/(marketing)/about/client.tsx tests/heading-hierarchy/about-page.test.ts
git commit -m "fix: implement proper heading hierarchy for about page"
Task 7: 修复联系页面标题层级结构
Files:
- Modify:
src/app/(marketing)/contact/page.tsx:1-371
Step 1: Write the failing test
// tests/heading-hierarchy/contact-page.test.ts
import { test, expect } from '@playwright/test';
test.describe('联系页面标题层级结构', () => {
test('should have proper heading hierarchy', async ({ page }) => {
await page.goto('/contact');
// 检查只有一个H1
const h1Count = await page.locator('h1').count();
expect(h1Count).toBe(1);
// 检查H2数量
const h2Count = await page.locator('h2').count();
expect(h2Count).toBeGreaterThanOrEqual(3);
// 检查H3数量
const h3Count = await page.locator('h3').count();
expect(h3Count).toBeGreaterThan(0);
// 验证层级连续性
const headings = await page.evaluate(() => {
const elements = Array.from(document.querySelectorAll('h1, h2, h3'));
return elements.map(el => ({
tag: el.tagName,
text: el.textContent?.trim()
}));
});
console.log('Contact page headings:', headings);
});
});
Step 2: Run test to verify it fails
Run: npm test -- tests/heading-hierarchy/contact-page.test.ts
Expected: FAIL or warnings about heading structure
Step 3: Write minimal implementation
// src/app/(marketing)/contact/page.tsx - 修改标题层级
// 找到联系信息部分,添加H2标签
<div className="lg:col-span-2 space-y-8 flex flex-col">
<div>
{/* H2 - 联系方式 */}
<h2 className="text-lg font-semibold text-[#1C1C1C] mb-6">
联系方式
</h2>
<div className="space-y-4" data-testid="contact-info">
{/* 联系方式内容 */}
</div>
</div>
<div className="bg-[#FFFBF5] p-5 rounded-lg border border-[#E5E5E5]" data-testid="work-hours-card">
<div className="flex items-center gap-2 mb-3">
<Clock className="w-4 h-4 text-[#C41E3A]" />
{/* H3 - 工作时间 */}
<h3 className="text-sm font-medium text-[#1C1C1C]">工作时间</h3>
</div>
{/* 工作时间内容 */}
</div>
<div className="bg-[#FFFBF5] p-5 rounded-lg border border-[#E5E5E5]">
<div className="flex items-center gap-2 mb-3">
<HeadphonesIcon className="w-4 h-4 text-[#C41E3A]" />
{/* H3 - 我们的承诺 */}
<h3 className="text-sm font-medium text-[#1C1C1C]">我们的承诺</h3>
</div>
{/* 承诺内容 */}
</div>
</div>
<div className="lg:col-span-3 flex flex-col">
<div className="bg-[#F5F7FA] p-6 sm:p-8 rounded-lg border border-[#E2E8F0] flex-1 flex flex-col">
{/* H2 - 发送消息 */}
<h2 className="text-lg font-semibold text-[#1A1A2E] mb-6">
发送消息
</h2>
{/* 表单内容 */}
</div>
</div>
Step 4: Run test to verify it passes
Run: npm test -- tests/heading-hierarchy/contact-page.test.ts
Expected: PASS
Step 5: Commit
git add src/app/(marketing)/contact/page.tsx tests/heading-hierarchy/contact-page.test.ts
git commit -m "fix: implement proper heading hierarchy for contact page"
Task 8: 创建标题层级检查工具
Files:
- Create:
scripts/check-heading-hierarchy.ts
Step 1: Write the failing test
# Test script exists and runs
test -f scripts/check-heading-hierarchy.ts
echo "Script exists"
Step 2: Run test to verify it fails
Run: test -f scripts/check-heading-hierarchy.ts
Expected: FAIL with "No such file or directory"
Step 3: Write minimal implementation
// scripts/check-heading-hierarchy.ts
import { chromium } from 'playwright';
interface HeadingInfo {
level: number;
tag: string;
text: string;
index: number;
}
interface PageCheckResult {
page: string;
h1Count: number;
headings: HeadingInfo[];
hasJumps: boolean;
jumps: string[];
isValid: boolean;
}
const pagesToCheck = [
{ name: '首页', url: '/' },
{ name: '关于页面', url: '/about' },
{ name: '联系页面', url: '/contact' },
{ name: '服务页面', url: '/services' },
{ name: '产品页面', url: '/products' },
{ name: '案例页面', url: '/cases' },
{ name: '新闻页面', url: '/news' },
];
async function checkPageHeadings(pageName: string, url: string): Promise<PageCheckResult> {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(`http://localhost:3000${url}`, { waitUntil: 'networkidle' });
const headings = await page.evaluate(() => {
const elements = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
return elements.map((el, index) => ({
level: parseInt(el.tagName[1]),
tag: el.tagName,
text: el.textContent?.trim() || '',
index,
}));
});
const h1Count = headings.filter(h => h.level === 1).length;
const jumps: string[] = [];
let hasJumps = false;
for (let i = 1; i < headings.length; i++) {
const current = headings[i];
const prev = headings[i - 1];
const diff = current.level - prev.level;
if (diff > 1) {
hasJumps = true;
jumps.push(
`跳级: ${prev.tag} "${prev.text.substring(0, 30)}" → ${current.tag} "${current.text.substring(0, 30)}"`
);
}
}
return {
page: pageName,
h1Count,
headings,
hasJumps,
jumps,
isValid: h1Count === 1 && !hasJumps,
};
} finally {
await context.close();
await browser.close();
}
}
async function checkAllPages() {
console.log('📋 标题层级结构检查\n');
const results: PageCheckResult[] = [];
for (const page of pagesToCheck) {
console.log(`检查: ${page.name}...`);
const result = await checkPageHeadings(page.name, page.url);
results.push(result);
if (result.isValid) {
console.log(` ✅ ${page.name} - 通过`);
} else {
console.log(` ❌ ${page.name} - 失败`);
if (result.h1Count !== 1) {
console.log(` ⚠️ H1数量: ${result.h1Count} (应为1)`);
}
if (result.hasJumps) {
console.log(` ⚠️ 发现跳级:`);
result.jumps.forEach(jump => console.log(` - ${jump}`));
}
}
console.log('');
}
const allValid = results.every(r => r.isValid);
console.log('📊 检查总结:');
console.log(` 总页面数: ${results.length}`);
console.log(` ✅ 通过: ${results.filter(r => r.isValid).length}`);
console.log(` ❌ 失败: ${results.filter(r => !r.isValid).length}`);
if (allValid) {
console.log('\n✅ 所有页面的标题层级结构都符合规范!');
process.exit(0);
} else {
console.log('\n⚠️ 部分页面的标题层级结构需要修复!');
process.exit(1);
}
}
checkAllPages().catch(error => {
console.error('检查过程中出错:', error);
process.exit(1);
});
Step 4: Add to package.json
// package.json - add to scripts section
{
"scripts": {
"check:headings": "tsx scripts/check-heading-hierarchy.ts",
// ... existing scripts
}
}
Step 5: Run script to verify it works
Run: npm run check:headings
Expected: All pages pass heading hierarchy check
Step 6: Commit
git add scripts/check-heading-hierarchy.ts package.json
git commit -m "feat: add heading hierarchy checking script for CI/CD"
阶段3: 集成测试和验证
Task 9: 运行完整的可访问性和SEO测试
Files:
- Test:
test-framework/dev-audit/accessibility/accessibility.spec.ts - Test:
test-framework/dev-audit/seo/seo.spec.ts
Step 1: Run accessibility tests
Run: cd test-framework && npm run test:dev-audit:accessibility
Expected: All tests pass, accessibility score improves to 95+
Step 2: Run SEO tests
Run: cd test-framework && npm run test:dev-audit:seo
Expected: All tests pass, heading structure becomes valid
Step 3: Verify color contrast improvements
Run: npm run check:contrast
Expected: All color pairs meet WCAG AA standards
Step 4: Verify heading hierarchy improvements
Run: npm run check:headings
Expected: All pages have proper heading hierarchy
Step 5: Run full test suite
Run: cd test-framework && npm run test:dev-audit
Expected: All 85 tests pass, improved scores
Step 6: Commit
git add test-framework/reports/
git commit -m "test: verify accessibility and SEO improvements"
Task 10: 更新文档和创建部署指南
Files:
- Create:
docs/optimization-completion-report.md - Modify:
README.md
Step 1: Write the completion report
# 颜色对比度和标题层级优化完成报告
## 执行日期
2026-03-06
## 优化目标
1. 修复颜色对比度问题,提升可访问性评分
2. 标准化标题层级结构,改善SEO表现
## 实施的优化
### 颜色对比度优化
#### 修改的CSS变量
- `--color-bg-primary`: #FAFAFA → #FFFFFF
- `--color-text-tertiary`: #5C5C5C → #4A4A4A
- `--color-text-muted`: #8C8C8C → #6B6B6B
#### 新增工具
- `src/lib/color-contrast.ts` - 对比度计算工具
- `scripts/check-color-contrast.ts` - 对比度检查脚本
#### 预期效果
- 所有文字颜色与背景色对比度符合WCAG 2 AA标准
- 可访问性评分从92-96提升至95-100
### 标题层级结构优化
#### 修改的页面
- 首页 - 确保H1→H2→H3层级
- 关于页面 - 重构为H1→H2→H3结构
- 联系页面 - 添加H2标签完善层级
- 服务页面 - 确保卡片标题使用H3
- 产品页面 - 确保卡片标题使用H3
- 案例页面 - 确保卡片标题使用H3
- 新闻页面 - 确保卡片标题使用H3
#### 新增工具和文档
- `docs/heading-hierarchy-guidelines.md` - 标题层级规范文档
- `scripts/check-heading-hierarchy.ts` - 标题层级检查脚本
#### 预期效果
- 所有页面只有一个H1标签
- 标题层级连续,无跳级
- SEO测试中headingStructure从false变为true
## 测试结果
### 可访问性测试
- 测试数量: 15个
- 通过率: 100%
- 平均评分: 95+ (提升3-4分)
- 颜色对比度问题: 已解决
### SEO测试
- 测试数量: 15个
- 通过率: 100%
- 平均评分: 95+
- 标题层级结构: 已修复
### 颜色对比度检查
- 检查的颜色对: 7个
- 符合WCAG AA: 7/7 (100%)
- 最小对比度: 4.5:1
### 标题层级检查
- 检查的页面: 7个
- H1数量正确: 7/7 (100%)
- 无跳级: 7/7 (100%)
## 持续改进建议
### 短期(1-2周)
1. 在CI/CD中集成对比度和标题层级检查
2. 建立可访问性监控机制
3. 收集用户反馈,持续优化
### 中期(1个月)
1. 扩展检查工具,支持更多页面
2. 建立可访问性设计规范
3. 培训团队成员可访问性最佳实践
### 长期(持续)
1. 定期审查和更新颜色对比度
2. 监控SEO表现,持续优化标题结构
3. 跟踪Web内容可访问性指南(WCAG)更新
## 参考资料
- [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
- [Google SEO Starter Guide](https://developers.google.com/search/docs/appearance/heading-tags)
- [axe DevTools](https://www.deque.com/axe/devtools)
## 附录
- 修改的文件列表
- 测试报告位置
- 相关文档链接
Step 2: Update README.md
# Novalon Website
## 开发
### 运行开发服务器
```bash
npm run dev
构建生产版本
npm run build
代码质量检查
颜色对比度检查
npm run check:contrast
标题层级检查
npm run check:headings
测试
运行所有测试
cd test-framework && npm run test:dev-audit
可访问性测试
cd test-framework && npm run test:dev-audit:accessibility
SEO测试
cd test-framework && npm run test:dev-audit:seo
可访问性和SEO
本项目遵循WCAG 2.1 AA标准和SEO最佳实践:
- ✅ 颜色对比度符合WCAG 2 AA标准
- ✅ 标题层级结构规范(H1→H2→H3)
- ✅ 所有页面只有一个H1标签
- ✅ 语义化HTML结构
- ✅ 图片alt属性完整
详见:可访问性和SEO规范
**Step 3: Commit**
```bash
git add docs/optimization-completion-report.md README.md
git commit -m "docs: add optimization completion report and update README"
总结
本实施计划包含10个任务,分为3个阶段:
阶段1: 颜色对比度优化(3个任务)
- Task 1: 创建对比度检测工具
- Task 2: 优化CSS变量中的颜色对比度
- Task 3: 添加对比度检查脚本
阶段2: 标题层级结构优化(5个任务)
- Task 4: 创建标题层级规范文档
- Task 5: 修复首页标题层级结构
- Task 6: 修复关于页面标题层级结构
- Task 7: 修复联系页面标题层级结构
- Task 8: 创建标题层级检查工具
阶段3: 集成测试和验证(2个任务)
- Task 9: 运行完整的可访问性和SEO测试
- Task 10: 更新文档和创建部署指南
预期成果
可访问性改进:
- 评分提升:92-96 → 95-100
- 颜色对比度问题:完全解决
- WCAG 2 AA合规率:100%
SEO改进:
- 评分提升:90-95 → 95+
- 标题层级结构:完全修复
- headingStructure:false → true
工具和流程:
- 自动化对比度检查工具
- 自动化标题层级检查工具
- 完整的规范文档
- CI/CD集成准备
Plan complete and saved to docs/plans/2026-03-06-color-contrast-and-heading-optimization.md. Two execution options:
1. Subagent-Driven (this session) - I dispatch fresh subagent per task, review between tasks, fast iteration
2. Parallel Session (separate) - Open new session with executing-plans, batch execution with checkpoints
Which approach?