总结cursor开发项目的经验

66 阅读8分钟

1.定义系统规范,技术栈,开发工具,目录结构,目录说明,代码规范等

具体文档(PC和app就是UI组件不太一样,其他都是一样的):

PC端开发规范

一、技术栈

1.1 核心框架

  • React 18
  • TypeScript 5
  • Vite 5
  • Zustand 4
  • React Router 6
  • Ant Design 5
  • Axios
  • SCSS

1.2 开发工具

  • ESLint
  • Prettier
  • Husky
  • Commitlint
  • Jest
  • Cypress

1.3 项目结构

src/
├── api/                # API 接口定义
│   ├── modules/       # API 模块
│   └── types.ts       # API 类型定义
├── assets/            # 静态资源
│   ├── images/       # 图片资源
│   ├── icons/        # 图标资源
│   └── fonts/        # 字体资源
├── components/        # 组件
│   ├── base/         # 基础组件
│   └── business/     # 业务组件
├── config/           # 配置文件
│   ├── routes.ts     # 路由配置
│   └── constants.ts  # 常量配置
├── hooks/            # 自定义 Hooks
│   ├── useAuth.ts    # 认证相关
│   └── useRequest.ts # 请求相关
├── layouts/          # 布局组件
│   ├── MainLayout.tsx
│   └── AuthLayout.tsx
├── pages/            # 页面组件
│   ├── home/        # 首页
│   ├── user/        # 用户相关
│   └── error/       # 错误页面
├── router/           # 路由相关
│   ├── index.tsx    # 路由配置
│   └── guards.tsx   # 路由守卫
├── stores/           # 状态管理
│   ├── modules/     # 状态模块
│   └── types.ts     # 状态类型
├── styles/           # 样式文件
│   ├── variables.scss  # 变量
│   ├── mixins.scss     # 混入
│   └── global.scss     # 全局样式
├── types/            # 类型定义
│   ├── user.ts      # 用户相关类型
│   └── common.ts    # 通用类型
├── utils/            # 工具函数
│   ├── request.ts   # 请求封装
│   ├── storage.ts   # 存储相关
│   └── format.ts    # 格式化工具
├── App.tsx           # 根组件
├── main.tsx          # 入口文件
└── vite-env.d.ts     # Vite 类型声明

public/               # 公共资源
├── favicon.ico      # 网站图标
└── index.html       # HTML 模板

tests/               # 测试文件
├── unit/           # 单元测试
└── e2e/            # E2E 测试

scripts/             # 脚本文件
├── build.js        # 构建脚本
└── deploy.js       # 部署脚本

.eslintrc.js        # ESLint 配置
.prettierrc         # Prettier 配置
tsconfig.json       # TypeScript 配置
vite.config.ts      # Vite 配置
package.json        # 项目配置

1.4 目录说明

  1. api 目录

    • 统一管理 API 接口
    • 按模块划分接口文件
    • 包含接口类型定义
  2. assets 目录

    • 存放静态资源
    • 按类型分类管理
    • 支持按需加载
  3. components 目录

    • base:基础组件,如按钮、输入框等
    • business:业务组件,如用户卡片、商品列表等
  4. config 目录

    • 存放项目配置
    • 环境变量配置
    • 常量定义
  5. hooks 目录

    • 存放自定义 Hooks
    • 提供可复用的逻辑
    • 遵循 React Hooks 规范
  6. layouts 目录

    • 页面布局组件
    • 处理页面结构
    • 管理页面模板
  7. pages 目录

    • 页面级组件
    • 按功能模块划分
    • 包含页面逻辑
  8. router 目录

    • 路由配置
    • 路由守卫
    • 权限控制
  9. stores 目录

    • Zustand 状态管理
    • 按模块划分状态
    • 状态类型定义
  10. styles 目录

    • 全局样式
    • 样式变量
    • 样式混入
  11. types 目录

    • TypeScript 类型定义
    • 接口定义
    • 枚举定义
  12. utils 目录

    • 工具函数
    • 请求封装
    • 通用方法

二、代码规范

2.1 命名规范

  1. 文件命名
  • 组件文件:使用 PascalCase(如 UserProfile.tsx
  • 工具文件:使用 camelCase(如 formatDate.ts
  • 样式文件:使用 kebab-case(如 common-style.scss
  • 类型文件:使用 PascalCase(如 UserTypes.ts
  1. 组件命名
  • 组件名使用 PascalCase
  • 基础组件以 Base 开头
  • 业务组件以业务模块名开头
  • 页面组件以 Page 结尾
  1. 变量命名
  • 变量名使用 camelCase
  • 常量使用 UPPER_CASE
  • 组件名使用 PascalCase
  • 私有变量以下划线开头
  • 类型名使用 PascalCase
  • 接口名使用 PascalCase

2.2 代码组织

  1. React 组件结构
// components/UserProfile.tsx
import { FC, useState, useEffect } from 'react'
import { useUserStore } from '@/stores/user'
import type { UserInfo } from '@/types/user'
import styles from './UserProfile.module.scss'

interface UserProfileProps {
  userId: string
}

export const UserProfile: FC<UserProfileProps> = ({ userId }) => {
  const [loading, setLoading] = useState(false)
  const { userInfo, fetchUserInfo } = useUserStore()

  useEffect(() => {
    const loadUserInfo = async () => {
      setLoading(true)
      try {
        await fetchUserInfo(userId)
      } finally {
        setLoading(false)
      }
    }
    loadUserInfo()
  }, [userId, fetchUserInfo])

  if (loading) {
    return <div>Loading...</div>
  }

  return (
    <div className={styles.userProfile}>
      <h2>{userInfo?.name}</h2>
      <p>{userInfo?.description}</p>
    </div>
  )
}
  1. API 接口组织
// api/types.ts
// 统一响应对象
export interface Rs<T = any> {
  code: string     // 响应码:"1"-成功,"0"-失败,"3"-业务异常,"-100"-系统异常
  message: string  // 响应消息
  result: T        // 响应数据
}

// 通用请求参数
export interface CommonReq<T = any> {
  channel: string      // 渠道
  platform: number     // 平台
  token: string       // 令牌
  version: string     // 版本
  sign: string        // 签名
  data: T             // 业务数据
}

// 请求工具封装
// utils/request.ts
import axios from 'axios'
import type { Rs, CommonReq } from '@/api/types'
import { getToken } from '@/utils/storage'
import { sign } from '@/utils/sign'

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
})

// 请求拦截器
request.interceptors.request.use(
  (config) => {
    const token = getToken()
    
    // 从环境变量获取版本号
    const version = import.meta.env.VITE_APP_VERSION
    
    const commonReq: CommonReq = {
      channel: import.meta.env.VITE_CHANNEL || 'web',
      platform: import.meta.env.VITE_PLATFORM || 1,
      token: token || '',
      version: version, // 直接使用环境变量中的版本号
      sign: '',
      data: config.data
    }
    
    commonReq.sign = sign(commonReq)
    config.data = commonReq
    
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 响应拦截器
request.interceptors.response.use(
  (response) => {
    const res = response.data as Rs
    
    if (res.code === '1') {
      return res
    }
    
    if (res.code === '3') {
      return Promise.reject(new Error(res.message))
    }
    
    if (res.code === '-100') {
      return Promise.reject(new Error('系统异常,请稍后重试'))
    }
    
    return Promise.reject(new Error(res.message || '请求失败'))
  },
  (error) => {
    if (error.response) {
      switch (error.response.status) {
        case 401:
          router.push('/auth/login')
          break
        case 403:
          return Promise.reject(new Error('权限不足'))
        case 404:
          return Promise.reject(new Error('请求的资源不存在'))
        case 500:
          return Promise.reject(new Error('服务器错误'))
        default:
          return Promise.reject(new Error('网络错误'))
      }
    }
    return Promise.reject(error)
  }
)

export { request }
  1. 状态管理组织
// stores/types.ts
export interface UserState {
  userInfo: UserInfo | null
  token: string
}

// stores/user.ts
import { create } from 'zustand'
import type { UserState } from './types'

export const useUserStore = create<UserState>((set) => ({
  userInfo: null,
  token: '',
  
  setUserInfo: (userInfo) => set({ userInfo }),
  setToken: (token) => set({ token }),
  
  login: async (params: LoginParams) => {
    const response = await login(params)
    set({ 
      userInfo: response.result.userInfo,
      token: response.result.token 
    })
  },
  
  logout: () => {
    set({ userInfo: null, token: '' })
  }
}))

三、组件规范

3.1 组件设计原则

  1. 单一职责
  • 每个组件只负责一个功能
  • 避免组件过于臃肿
  • 合理拆分组件
  1. 组件通信
  • 优先使用 props 和回调函数
  • 复杂状态使用 Zustand
  • 避免使用 Context 进行全局状态管理
  1. 组件复用
  • 抽取公共组件
  • 使用自定义 Hook
  • 避免重复代码

3.2 组件示例

  1. 基础组件
// components/base/BaseButton.tsx
import { FC, ButtonHTMLAttributes } from 'react'
import { Button } from 'antd'
import styles from './BaseButton.module.scss'

interface BaseButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  type?: 'default' | 'primary' | 'danger'
  disabled?: boolean
}

export const BaseButton: FC<BaseButtonProps> = ({
  type = 'default',
  disabled = false,
  className,
  children,
  ...props
}) => {
  return (
    <Button
      type={type}
      disabled={disabled}
      className={`${styles.baseButton} ${className || ''}`}
      {...props}
    >
      {children}
    </Button>
  )
}
  1. 业务组件
// components/business/UserCard.tsx
import { FC } from 'react'
import { Card, Avatar } from 'antd'
import type { UserInfo } from '@/types/user'
import styles from './UserCard.module.scss'

interface UserCardProps {
  userInfo: UserInfo
}

export const UserCard: FC<UserCardProps> = ({ userInfo }) => {
  return (
    <Card className={styles.userCard}>
      <div className={styles.userCard__content}>
        <Avatar size={60} src={userInfo.avatar} />
        <div className={styles.userCard__info}>
          <h3>{userInfo.name}</h3>
          <p>{userInfo.description}</p>
        </div>
      </div>
    </Card>
  )
}

四、样式规范

4.1 SCSS Modules 使用规范

  1. 文件命名
  • 组件样式文件:[组件名].module.scss
  • 全局样式文件:global.scss
  • 变量和混入文件:variables.scssmixins.scss
  1. 模块化样式示例
// UserCard.module.scss
.userCard {
  &__content {
    display: flex;
    align-items: center;
    gap: 16px;
  }
  
  &__info {
    flex: 1;
    
    h3 {
      margin: 0 0 8px;
      font-size: 16px;
      font-weight: bold;
    }
    
    p {
      margin: 0;
      font-size: 14px;
      color: var(--text-color-secondary);
    }
  }
}
  1. 组件中使用
import styles from './UserCard.module.scss'

const UserCard: FC<UserCardProps> = ({ userInfo }) => {
  return (
    <Card className={styles.userCard}>
      <div className={styles.userCard__content}>
        <Avatar size={60} src={userInfo.avatar} />
        <div className={styles.userCard__info}>
          <h3>{userInfo.name}</h3>
          <p>{userInfo.description}</p>
        </div>
      </div>
    </Card>
  )
}

4.2 全局样式组织

  1. 变量定义
// styles/variables.scss
:root {
  // 颜色
  --primary-color: #1890ff;
  --success-color: #52c41a;
  --warning-color: #faad14;
  --danger-color: #ff4d4f;
  
  // 文字
  --text-color: rgba(0, 0, 0, 0.85);
  --text-color-secondary: rgba(0, 0, 0, 0.45);
  
  // 背景
  --background-color: #f0f2f5;
  --white: #fff;
  
  // 边框
  --border-color: #d9d9d9;
  
  // 间距
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
  
  // 字体大小
  --font-size-xs: 12px;
  --font-size-sm: 14px;
  --font-size-md: 16px;
  --font-size-lg: 18px;
  --font-size-xl: 20px;
}
  1. 混入定义
// styles/mixins.scss
@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

@mixin text-ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@mixin multi-ellipsis($lines: 2) {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: $lines;
  overflow: hidden;
}

@mixin card-shadow {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
  1. 全局样式
// styles/global.scss
@import './variables.scss';
@import './mixins.scss';

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
    Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
    'Segoe UI Symbol', 'Noto Color Emoji';
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: var(--text-color);
  background-color: var(--background-color);
  line-height: 1.5;
}

img {
  max-width: 100%;
  height: auto;
  vertical-align: middle;
}

ul, ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

a {
  color: var(--primary-color);
  text-decoration: none;
  
  &:hover {
    color: var(--primary-color);
    text-decoration: underline;
  }
}

4.3 样式最佳实践

  1. 使用 CSS 变量
// 组件样式中使用变量
.userCard {
  padding: var(--spacing-md);
  background: var(--white);
  border-radius: 8px;
  
  &__name {
    font-size: var(--font-size-lg);
    color: var(--text-color);
  }
}
  1. 使用混入
.userCard {
  @include flex-center;
  
  &__desc {
    @include multi-ellipsis(2);
  }
}
  1. 响应式设计
.userCard {
  padding: var(--spacing-md);
  
  @media screen and (min-width: 768px) {
    padding: var(--spacing-lg);
  }
}
  1. 暗黑模式支持
.userCard {
  background: var(--white);
  
  @media (prefers-color-scheme: dark) {
    background: var(--dark-background);
    color: var(--dark-text-color);
  }
}

五、PC端适配

5.1 布局规范

  1. 固定宽度布局
.container {
  width: 1200px;
  margin: 0 auto;
  padding: 0 var(--spacing-md);
}
  1. 响应式布局
.container {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--spacing-md);
  
  @media screen and (max-width: 1200px) {
    max-width: 960px;
  }
  
  @media screen and (max-width: 992px) {
    max-width: 720px;
  }
  
  @media screen and (max-width: 768px) {
    max-width: 540px;
  }
}

5.2 交互规范

  1. 鼠标事件
const InteractiveCard: FC = () => {
  const handleMouseEnter = (event: React.MouseEvent) => {
    // 处理鼠标进入
  }

  const handleMouseLeave = (event: React.MouseEvent) => {
    // 处理鼠标离开
  }

  return (
    <div
      className={styles.card}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      {/* 内容 */}
    </div>
  )
}

六、性能优化

6.1 代码优化

  1. 路由懒加载
// router/index.tsx
import { lazy } from 'react'

const UserPage = lazy(() => import('@/pages/UserPage'))

export const routes = [
  {
    path: '/user',
    element: <UserPage />
  }
]
  1. 组件按需加载
// 按需导入 Ant Design 组件
import { Button, Card } from 'antd'
  1. 图片懒加载
import { Image } from 'antd'

const LazyImage: FC<{ src: string; alt: string }> = ({ src, alt }) => {
  return <Image src={src} alt={alt} loading="lazy" />
}

6.2 缓存优化

  1. 数据缓存
// utils/cache.ts
export const cacheData = {
  set(key: string, value: unknown): void {
    localStorage.setItem(key, JSON.stringify(value))
  },
  
  get<T>(key: string): T | null {
    const data = localStorage.getItem(key)
    return data ? JSON.parse(data) : null
  },
  
  remove(key: string): void {
    localStorage.removeItem(key)
  }
}

七、安全规范

7.1 数据安全

  1. 敏感信息加密
import CryptoJS from 'crypto-js'

export const encrypt = (data: string): string => {
  return CryptoJS.AES.encrypt(data, secretKey).toString()
}

export const decrypt = (encryptedData: string): string => {
  return CryptoJS.AES.decrypt(encryptedData, secretKey).toString()
}
  1. 请求签名
export const sign = (params: Record<string, unknown>): string => {
  const sortedParams = Object.keys(params)
    .sort()
    .map(key => `${key}=${params[key]}`)
    .join('&')
  return md5(sortedParams + secretKey)
}

八、测试规范

8.1 单元测试

  1. 组件测试
// __tests__/components/UserCard.test.tsx
import { render, screen } from '@testing-library/react'
import { UserCard } from '@/components/UserCard'

describe('UserCard', () => {
  it('renders user info correctly', () => {
    const userInfo = {
      name: 'Test User',
      avatar: 'test.jpg',
      description: 'Test Description'
    }
    
    render(<UserCard userInfo={userInfo} />)
    
    expect(screen.getByText('Test User')).toBeInTheDocument()
    expect(screen.getByText('Test Description')).toBeInTheDocument()
  })
})
  1. Hook 测试
// __tests__/hooks/useUser.test.ts
import { renderHook, act } from '@testing-library/react'
import { useUserStore } from '@/stores/user'

describe('useUserStore', () => {
  it('should update user info', () => {
    const { result } = renderHook(() => useUserStore())
    
    act(() => {
      result.current.setUserInfo({ name: 'Test User' })
    })
    
    expect(result.current.userInfo).toEqual({ name: 'Test User' })
  })
})

8.2 E2E 测试

  1. 页面测试
// cypress/e2e/login.cy.ts
describe('Login Page', () => {
  it('should login successfully', () => {
    cy.visit('/login')
    cy.get('input[name="username"]').type('test')
    cy.get('input[name="password"]').type('password')
    cy.get('button[type="submit"]').click()
    cy.url().should('include', '/home')
  })
})