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 目录说明
-
api 目录
- 统一管理 API 接口
- 按模块划分接口文件
- 包含接口类型定义
-
assets 目录
- 存放静态资源
- 按类型分类管理
- 支持按需加载
-
components 目录
- base:基础组件,如按钮、输入框等
- business:业务组件,如用户卡片、商品列表等
-
config 目录
- 存放项目配置
- 环境变量配置
- 常量定义
-
hooks 目录
- 存放自定义 Hooks
- 提供可复用的逻辑
- 遵循 React Hooks 规范
-
layouts 目录
- 页面布局组件
- 处理页面结构
- 管理页面模板
-
pages 目录
- 页面级组件
- 按功能模块划分
- 包含页面逻辑
-
router 目录
- 路由配置
- 路由守卫
- 权限控制
-
stores 目录
- Zustand 状态管理
- 按模块划分状态
- 状态类型定义
-
styles 目录
- 全局样式
- 样式变量
- 样式混入
-
types 目录
- TypeScript 类型定义
- 接口定义
- 枚举定义
-
utils 目录
- 工具函数
- 请求封装
- 通用方法
二、代码规范
2.1 命名规范
- 文件命名
- 组件文件:使用 PascalCase(如
UserProfile.tsx) - 工具文件:使用 camelCase(如
formatDate.ts) - 样式文件:使用 kebab-case(如
common-style.scss) - 类型文件:使用 PascalCase(如
UserTypes.ts)
- 组件命名
- 组件名使用 PascalCase
- 基础组件以 Base 开头
- 业务组件以业务模块名开头
- 页面组件以 Page 结尾
- 变量命名
- 变量名使用 camelCase
- 常量使用 UPPER_CASE
- 组件名使用 PascalCase
- 私有变量以下划线开头
- 类型名使用 PascalCase
- 接口名使用 PascalCase
2.2 代码组织
- 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>
)
}
- 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 }
- 状态管理组织
// 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 组件设计原则
- 单一职责
- 每个组件只负责一个功能
- 避免组件过于臃肿
- 合理拆分组件
- 组件通信
- 优先使用 props 和回调函数
- 复杂状态使用 Zustand
- 避免使用 Context 进行全局状态管理
- 组件复用
- 抽取公共组件
- 使用自定义 Hook
- 避免重复代码
3.2 组件示例
- 基础组件
// 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>
)
}
- 业务组件
// 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 使用规范
- 文件命名
- 组件样式文件:
[组件名].module.scss - 全局样式文件:
global.scss - 变量和混入文件:
variables.scss、mixins.scss
- 模块化样式示例
// 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);
}
}
}
- 组件中使用
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 全局样式组织
- 变量定义
// 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;
}
- 混入定义
// 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);
}
- 全局样式
// 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 样式最佳实践
- 使用 CSS 变量
// 组件样式中使用变量
.userCard {
padding: var(--spacing-md);
background: var(--white);
border-radius: 8px;
&__name {
font-size: var(--font-size-lg);
color: var(--text-color);
}
}
- 使用混入
.userCard {
@include flex-center;
&__desc {
@include multi-ellipsis(2);
}
}
- 响应式设计
.userCard {
padding: var(--spacing-md);
@media screen and (min-width: 768px) {
padding: var(--spacing-lg);
}
}
- 暗黑模式支持
.userCard {
background: var(--white);
@media (prefers-color-scheme: dark) {
background: var(--dark-background);
color: var(--dark-text-color);
}
}
五、PC端适配
5.1 布局规范
- 固定宽度布局
.container {
width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-md);
}
- 响应式布局
.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 交互规范
- 鼠标事件
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 代码优化
- 路由懒加载
// router/index.tsx
import { lazy } from 'react'
const UserPage = lazy(() => import('@/pages/UserPage'))
export const routes = [
{
path: '/user',
element: <UserPage />
}
]
- 组件按需加载
// 按需导入 Ant Design 组件
import { Button, Card } from 'antd'
- 图片懒加载
import { Image } from 'antd'
const LazyImage: FC<{ src: string; alt: string }> = ({ src, alt }) => {
return <Image src={src} alt={alt} loading="lazy" />
}
6.2 缓存优化
- 数据缓存
// 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 数据安全
- 敏感信息加密
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()
}
- 请求签名
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 单元测试
- 组件测试
// __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()
})
})
- 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 测试
- 页面测试
// 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')
})
})