Files
novalon-website/docs/plans/2026-04-21-project-optimization-plan.md
T

19 KiB
Raw Blame History

项目优化实施计划

面向 AI 代理的工作者: 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(- [ ])语法来跟踪进度。

目标: 根据代码审查报告,修复所有必须修复的问题,完成建议的优化项,提升项目整体质量。

架构: 纯静态 Next.js 网站,修复 TypeScript 配置问题,清理无效配置,优化测试环境,改进代码组织结构。

技术栈: Next.js 16, React 19, TypeScript, Jest, Playwright


文件结构

将要修改的文件

文件 职责 变更类型
tsconfig.json TypeScript 配置 修改 - 添加 Jest 类型支持
next.config.ts Next.js 配置 修改 - 移除无效 headers 配置
src/contexts/theme-context.tsx 主题上下文 修改 - 简化为纯静态版本
src/app/layout.tsx 根布局 修改 - 移除暗色主题脚本
package.json 项目配置 修改 - 清理无效脚本
e2e/website-acceptance.spec.ts E2E 测试 修改 - 支持环境变量 URL
e2e/playwright.config.ts Playwright 配置 修改 - 添加 baseURL 配置
nginx-static.conf Nginx 配置 修改 - 添加安全头

将要创建的文件

文件 职责
src/lib/constants/index.ts 常量导出入口
src/lib/constants/company.ts 公司信息常量
src/lib/constants/navigation.ts 导航配置
src/lib/constants/services.ts 服务数据
src/lib/constants/products.ts 产品数据
src/lib/constants/news.ts 新闻数据
src/lib/constants/stats.ts 统计数据

将要删除的文件

文件 原因
本次优化不删除文件

阶段一:P0 必须修复(立即处理)

任务 1:修复 TypeScript 测试类型错误

文件:

  • 修改:tsconfig.json

问题: 测试文件中 describeitexpectbeforeEach 等全局类型未定义,导致 npm run type-check 失败。

  • 步骤 1:修改 tsconfig.json 添加 Jest 类型支持
{
  "compilerOptions": {
    "target": "ES2017",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "incremental": true,
    "types": ["jest", "node"],
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": [
        "./src/*"
      ]
    }
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts",
    ".next/dev/types/**/*.ts",
    "**/*.mts",
    "dist/types/**/*.ts",
    "dist/dev/types/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "e2e"
  ]
}
  • 步骤 2:运行类型检查验证修复

运行:npm run type-check 预期:无测试相关的类型错误

  • 步骤 3Commit
git add tsconfig.json
git commit -m "fix: 添加 Jest 类型支持,修复测试文件类型错误"

阶段二:P1 建议修改(短期优化)

任务 2:移除无效的 headers 配置

文件:

  • 修改:next.config.ts
  • 修改:nginx-static.conf

问题: output: 'export' 静态导出模式下,headers 配置不会生效。应将安全头配置移至 nginx。

  • 步骤 1:简化 next.config.ts,移除 headers 配置
import type { NextConfig } from "next";

const cdnDomain = process.env.CDN_DOMAIN || '';

const nextConfig: NextConfig = {
  distDir: 'dist',
  output: 'export',
  assetPrefix: cdnDomain || undefined,
  images: {
    unoptimized: true,
  },
  compress: true,
  poweredByHeader: false,
  reactStrictMode: true,
  experimental: {
    optimizePackageImports: ['lucide-react'],
  },
  compiler: {
    removeConsole: process.env.NODE_ENV === 'production',
  },
};

export default nextConfig;
  • 步骤 2:更新 nginx-static.conf 添加安全头

读取当前 nginx-static.conf 内容后追加安全头配置。

  • 步骤 3:运行构建验证配置正确

运行:npm run build 预期:构建成功,无错误

  • 步骤 4Commit
git add next.config.ts nginx-static.conf
git commit -m "refactor: 移除无效的 headers 配置,安全头移至 nginx"

任务 3:简化 ThemeContext 为纯静态版本

文件:

  • 修改:src/contexts/theme-context.tsx
  • 修改:src/app/layout.tsx

问题: ThemeProvider 硬编码只返回 'light',但 layout.tsx 中还有暗色主题切换脚本,两者不一致。纯静态网站不需要主题切换。

  • 步骤 1:简化 theme-context.tsx
'use client';

import { createContext, useContext, type ReactNode } from 'react';

interface ThemeContextType {
  theme: 'light';
  resolvedTheme: 'light';
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export function ThemeProvider({ children }: { children: ReactNode }) {
  return (
    <ThemeContext.Provider value={{ theme: 'light', resolvedTheme: 'light' }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (context === undefined) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}
  • 步骤 2:修改 layout.tsx 移除暗色主题脚本

移除 <head> 中的暗色主题检测脚本:

// 移除以下代码块:
<script
  dangerouslySetInnerHTML={{
    __html: `
      try {
        const theme = localStorage.getItem('ruixin-theme') || 'system';
        if (theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
          document.documentElement.classList.add('dark');
        }
      } catch {}
    `,
  }}
/>
  • 步骤 3:运行类型检查验证修改

运行:npm run type-check 预期:无错误

  • 步骤 4Commit
git add src/contexts/theme-context.tsx src/app/layout.tsx
git commit -m "refactor: 简化主题上下文为纯静态版本,移除暗色主题切换逻辑"

任务 4:清理无效的 npm 脚本

文件:

  • 修改:package.json

问题: 部分脚本引用不存在的文件,如 audit:alltest:performancetest:stress 等。

  • 步骤 1:检查哪些脚本文件存在
ls -la scripts/run-all-tests.sh 2>/dev/null || echo "不存在"
ls -la tests/performance/ 2>/dev/null || echo "不存在"
  • 步骤 2:移除无效脚本,保留有效的脚本

修改 package.json 的 scripts 部分:

{
  "scripts": {
    "dev": "next dev -p 3000",
    "build": "next build",
    "start": "npx serve dist -p 3000",
    "lint": "eslint",
    "type-check": "tsc --noEmit",
    "preview": "npx serve dist -p 3000",
    "test": "cd e2e && npx playwright test --config=playwright.config.ts",
    "test:unit": "jest",
    "test:coverage": "jest --coverage",
    "test:coverage:check": "jest --coverage --ci",
    "coverage:report": "open coverage/lcov-report/index.html",
    "test:e2e": "cd e2e && npm test",
    "test:smoke": "cd e2e && npx playwright test --grep @smoke",
    "check:contrast": "tsx scripts/utils/check-color-contrast.ts",
    "check:headings": "tsx scripts/utils/check-heading-hierarchy.ts",
    "lighthouse": "lhci autorun",
    "lighthouse:collect": "lhci collect",
    "lighthouse:assert": "lhci assert",
    "lighthouse:upload": "lhci upload",
    "lighthouse:desktop": "lhci autorun --settings.preset=desktop",
    "lighthouse:mobile": "lhci autorun --settings.preset=mobile",
    "deploy:cdn": "bash scripts/deploy-cdn.sh",
    "deploy:cdn:refresh": "bash scripts/refresh-cdn.sh",
    "prepare": "husky"
  }
}
  • 步骤 3:运行 npm run 验证脚本有效

运行:npm run 预期:列出所有可用脚本,无错误

  • 步骤 4Commit
git add package.json
git commit -m "chore: 清理无效的 npm 脚本命令"

任务 5:修复 E2E 测试硬编码 URL

文件:

  • 修改:e2e/playwright.config.ts
  • 修改:e2e/website-acceptance.spec.ts

问题: E2E 测试硬编码 https://novalon.cn,无法在本地或测试环境运行。

  • 步骤 1:检查 playwright.config.ts 当前配置

读取 e2e/playwright.config.ts 内容。

  • 步骤 2:修改 playwright.config.ts 添加 baseURL
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: process.env.E2E_BASE_URL || 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
  webServer: {
    command: 'npm run preview',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120000,
  },
});
  • 步骤 3:修改 website-acceptance.spec.ts 使用相对路径

将所有硬编码 URL 改为相对路径:

import { test, expect } from '@playwright/test';

test.describe('网站全面测试验收', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });

  test('首页加载正常', async ({ page }) => {
    await expect(page).toHaveTitle(/四川睿新致远科技有限公司/);
    await expect(page.locator('header')).toBeVisible();
    await expect(page.locator('footer')).toBeVisible();
  });

  // ... 其他测试用例,将 https://novalon.cn 替换为空或相对路径

  test('联系我们页面没有显示公司电话', async ({ page }) => {
    await page.goto('/contact');
    await page.waitForLoadState('networkidle');
    // ...
  });

  test('关于我们页面没有显示公司电话', async ({ page }) => {
    await page.goto('/about');
    await page.waitForLoadState('networkidle');
    // ...
  });

  test('联系我们页面表单正常显示', async ({ page }) => {
    await page.goto('/contact');
    await page.waitForLoadState('networkidle');
    // ...
  });

  test('页面跳转功能正常', async ({ page }) => {
    await page.click('text=联系我们');
    await page.waitForLoadState('networkidle');
    expect(page.url()).toContain('/contact');
    
    await page.click('text=首页');
    await page.waitForLoadState('networkidle');
    expect(page.url()).toBe(process.env.E2E_BASE_URL || 'http://localhost:3000/');
  });

  // ...
});
  • 步骤 4:运行 E2E 测试验证修改

运行:npm run test 预期:测试可以正常运行(需要先构建)

  • 步骤 5Commit
git add e2e/playwright.config.ts e2e/website-acceptance.spec.ts
git commit -m "fix: E2E 测试支持环境变量 URL,可在本地运行"

任务 6:更新 Jest 配置路径

文件:

  • 修改:config/test/jest.config.js

问题: setupFilesAfterEnv 指向 <rootDir>/jest.setup.js,但实际文件在根目录。

  • 步骤 1:检查 jest.setup.js 位置

确认 jest.setup.js 在项目根目录。

  • 步骤 2:修改 jest.config.js 路径
const path = require('path');

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  roots: ['<rootDir>/../src'],
  testMatch: ['**/__tests__/**/*.test.{ts,tsx}', '**/*.test.{ts,tsx}'],
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.stories.{ts,tsx}',
    '!src/**/__tests__/**',
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
  coverageReporters: ['text', 'lcov', 'html', 'json'],
  coverageDirectory: '<rootDir>/../coverage',
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/../src/$1',
  },
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
  },
  transformIgnorePatterns: [
    'node_modules/(?!(nanoid|next-auth|@auth)/)',
  ],
  setupFilesAfterEnv: ['<rootDir>/../jest.setup.js'],
  testTimeout: 10000,
  verbose: true,
  maxWorkers: '50%',
};
  • 步骤 3:运行单元测试验证配置

运行:npm run test:unit 预期:测试正常运行

  • 步骤 4Commit
git add config/test/jest.config.js
git commit -m "fix: 修正 Jest 配置中的文件路径"

阶段三:P2 仅供参考(长期改进)

任务 7:拆分 constants.ts 文件

文件:

  • 创建:src/lib/constants/index.ts
  • 创建:src/lib/constants/company.ts
  • 创建:src/lib/constants/navigation.ts
  • 创建:src/lib/constants/services.ts
  • 创建:src/lib/constants/products.ts
  • 创建:src/lib/constants/news.ts
  • 创建:src/lib/constants/stats.ts
  • 删除:src/lib/constants.ts
  • 修改:所有导入 constants 的文件

问题: constants.ts 文件过大(20KB+),包含所有业务数据,不利于维护。

  • 步骤 1:创建 company.ts
export const COMPANY_INFO = {
  name: '四川睿新致远科技有限公司',
  shortName: '睿新致遠',
  slogan: '智连未来,成长伙伴',
  description: '以智慧连接数字趋势,以伙伴身份陪您成长——您的数字化转型同行者',
  founded: '2026',
  location: '四川省成都市',
  email: 'contact@novalon.cn',
  address: '中国四川省成都市龙泉驿区幸福路12号',
  icp: '蜀ICP备2026013658号',
  police: '川公网安备51010602003285号',
} as const;
  • 步骤 2:创建 navigation.ts
export interface NavigationItem {
  id: string;
  label: string;
  href: string;
}

export const NAVIGATION: NavigationItem[] = [
  { id: 'home', label: '首页', href: '/' },
  { id: 'services', label: '核心业务', href: '/' },
  { id: 'products', label: '产品服务', href: '/' },
  { id: 'cases', label: '成功案例', href: '/' },
  { id: 'about', label: '关于我们', href: '/' },
  { id: 'news', label: '新闻动态', href: '/' },
  { id: 'contact', label: '联系我们', href: '/contact' },
];
  • 步骤 3:创建 stats.ts
export interface StatItem {
  value: string;
  label: string;
}

export const STATS: StatItem[] = [
  { value: '10+', label: '企业客户' },
  { value: '20+', label: '成功案例' },
  { value: '30+', label: '项目交付' },
  { value: '12+', label: '年行业经验' },
];
  • 步骤 4:创建 services.ts

从原 constants.ts 提取 SERVICES 数据。

  • 步骤 5:创建 products.ts

从原 constants.ts 提取 PRODUCTS 数据。

  • 步骤 6:创建 news.ts
export type NewsCategory = '公司新闻' | '产品发布' | '合作动态' | '行业资讯';

export interface NewsItem {
  id: string;
  title: string;
  excerpt: string;
  date: string;
  category: NewsCategory;
  image: string;
  content: string;
}

export const NEWS: NewsItem[] = [
  // 从原 constants.ts 提取
];
  • 步骤 7:创建 index.ts 统一导出
export * from './company';
export * from './navigation';
export * from './stats';
export * from './services';
export * from './products';
export * from './news';
  • 步骤 8:删除原 constants.ts
rm src/lib/constants.ts
  • 步骤 9:运行类型检查验证重构

运行:npm run type-check 预期:无错误

  • 步骤 10:运行测试验证功能正常

运行:npm run test:unit 预期:所有测试通过

  • 步骤 11Commit
git add src/lib/constants/
git rm src/lib/constants.ts
git commit -m "refactor: 拆分 constants.ts 为模块化结构"

任务 8:清理过时的计划文档

文件:

  • 删除:docs/plans/ 目录下已完成的计划文档

问题: docs/plans/ 目录下有大量计划文档,部分已过时。

  • 步骤 1:列出所有计划文档
ls -la docs/plans/
  • 步骤 2:评估每个文档是否需要保留

保留:

  • 与当前项目架构相关的文档

删除:

  • 已完成的临时计划

  • 与 CMS 相关的计划

  • 已过时的测试优化计划

  • 步骤 3:删除过时文档

rm docs/plans/2025-03-13-intelligent-tiered-test-optimization.md
rm docs/plans/2026-03-09-production-readiness-plan.md
rm docs/plans/2026-03-09-test-coverage-improvement-plan.md
rm docs/plans/2026-03-10-full-module-test-coverage-plan.md
rm docs/plans/2026-03-10-gradual-coverage-improvement.md
rm docs/plans/2026-03-10-phased-launch-implementation-plan.md
rm docs/plans/2026-03-10-production-readiness-execution-plan.md
rm docs/plans/2026-03-10-test-coverage-improvement-plan.md
rm docs/plans/2026-03-20-quality-improvement-iteration.md
rm docs/plans/2026-03-24-code-quality-tools-integration.md
rm docs/plans/2026-03-24-contact-form-security-enhancement.md
rm docs/plans/2026-03-28-monorepo-multi-site-architecture.md
  • 步骤 4Commit
git add docs/plans/
git commit -m "docs: 清理过时的计划文档"

阶段四:验证与收尾

任务 9:全面验证

  • 步骤 1:运行类型检查
npm run type-check

预期:无错误

  • 步骤 2:运行 lint 检查
npm run lint

预期:无错误

  • 步骤 3:运行单元测试
npm run test:unit

预期:所有测试通过

  • 步骤 4:运行构建
npm run build

预期:构建成功

  • 步骤 5:本地预览验证
npm run preview

手动验证:

  • 首页加载正常

  • 导航功能正常

  • 各页面链接正常

  • 样式显示正确

  • 步骤 6:最终 Commit

git add -A
git commit -m "chore: 完成项目优化,通过全面验证"

执行顺序总结

阶段 任务 优先级 预计时间
阶段一 任务 1:修复 TypeScript 类型错误 P0 10 分钟
阶段二 任务 2:移除无效 headers 配置 P1 15 分钟
阶段二 任务 3:简化 ThemeContext P1 10 分钟
阶段二 任务 4:清理无效脚本 P1 10 分钟
阶段二 任务 5:修复 E2E 测试 URL P1 20 分钟
阶段二 任务 6:更新 Jest 配置 P1 10 分钟
阶段三 任务 7:拆分 constants.ts P2 30 分钟
阶段三 任务 8:清理过时文档 P2 10 分钟
阶段四 任务 9:全面验证 必须 15 分钟

总预计时间: 约 2 小时


风险与注意事项

  1. 任务 7(拆分 constants 可能影响较多文件,建议最后执行
  2. 任务 5E2E 测试) 需要先构建才能运行
  3. 每个任务完成后立即 commit,便于回滚
  4. 如遇阻塞,可跳过 P2 任务,优先完成 P0 和 P1