644 lines
13 KiB
Markdown
644 lines
13 KiB
Markdown
# 开发指南
|
||
|
||
本文档为智慧农业生产管理系统的开发指南,帮助开发者快速了解项目结构、开发规范和最佳实践。
|
||
|
||
## 📋 目录
|
||
|
||
- [环境准备](#环境准备)
|
||
- [项目结构](#项目结构)
|
||
- [开发规范](#开发规范)
|
||
- [组件开发](#组件开发)
|
||
- [状态管理](#状态管理)
|
||
- [样式指南](#样式指南)
|
||
- [API集成](#api集成)
|
||
- [测试指南](#测试指南)
|
||
- [调试技巧](#调试技巧)
|
||
- [常见问题](#常见问题)
|
||
|
||
## 🛠️ 环境准备
|
||
|
||
### 必需软件
|
||
|
||
- **Node.js**: >= 18.0.0
|
||
- **npm**: >= 8.0.0 或 **yarn**: >= 1.22.0
|
||
- **Git**: 最新版本
|
||
|
||
### 推荐工具
|
||
|
||
- **IDE**: Visual Studio Code
|
||
- **浏览器**: Chrome/Firefox (最新版本)
|
||
- **Node管理**: nvm (可选)
|
||
|
||
### VSCode扩展推荐
|
||
|
||
项目已配置 `.vscode/extensions.json`,安装以下扩展获得最佳开发体验:
|
||
|
||
```json
|
||
{
|
||
"recommendations": [
|
||
"esbenp.prettier-vscode",
|
||
"dbaeumer.vscode-eslint",
|
||
"bradlc.vscode-tailwindcss",
|
||
"ms-vscode.vscode-typescript-next",
|
||
"formulahendry.auto-rename-tag",
|
||
"christian-kohler.path-intellisense"
|
||
]
|
||
}
|
||
```
|
||
|
||
## 📁 项目结构详解
|
||
|
||
### 核心目录
|
||
|
||
```
|
||
src/
|
||
├── components/ # 组件库
|
||
│ ├── ui/ # shadcn/ui基础组件
|
||
│ ├── common/ # 通用业务组件
|
||
│ └── layouts/ # 布局组件
|
||
├── pages/ # 页面组件
|
||
├── hooks/ # 自定义Hooks
|
||
├── lib/ # 工具库
|
||
├── config/ # 配置文件
|
||
├── types/ # TypeScript类型
|
||
├── utils/ # 工具函数
|
||
├── styles/ # 样式文件
|
||
└── assets/ # 静态资源
|
||
```
|
||
|
||
### 组件组织原则
|
||
|
||
1. **UI组件** (`components/ui/`): 纯UI组件,无业务逻辑
|
||
2. **业务组件** (`components/common/`): 包含业务逻辑的复用组件
|
||
3. **页面组件** (`pages/`): 具体页面实现,组合业务组件
|
||
4. **布局组件** (`components/layouts/`): 页面布局结构
|
||
|
||
## 📏 开发规范
|
||
|
||
### 代码规范
|
||
|
||
#### TypeScript规范
|
||
|
||
```typescript
|
||
// ✅ 使用类型注解
|
||
interface UserData {
|
||
id: string
|
||
name: string
|
||
email: string
|
||
}
|
||
|
||
const getUser = async (id: string): Promise<UserData> => {
|
||
// 实现
|
||
}
|
||
|
||
// ✅ 使用泛型
|
||
interface ApiResponse<T> {
|
||
data: T
|
||
success: boolean
|
||
}
|
||
|
||
// ✅ 枚举使用
|
||
enum UserRole {
|
||
ADMIN = 'admin',
|
||
USER = 'user'
|
||
}
|
||
```
|
||
|
||
#### React组件规范
|
||
|
||
```typescript
|
||
// ✅ 函数式组件 + Hooks
|
||
interface UserCardProps {
|
||
user: UserData
|
||
onEdit?: (user: UserData) => void
|
||
className?: string
|
||
}
|
||
|
||
export const UserCard: React.FC<UserCardProps> = ({
|
||
user,
|
||
onEdit,
|
||
className
|
||
}) => {
|
||
const [isEditing, setIsEditing] = useState(false)
|
||
|
||
const handleEdit = useCallback(() => {
|
||
onEdit?.(user)
|
||
setIsEditing(true)
|
||
}, [user, onEdit])
|
||
|
||
return (
|
||
<div className={cn('user-card', className)}>
|
||
{/* 组件内容 */}
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
#### 命名规范
|
||
|
||
- **文件名**: kebab-case (`user-card.tsx`)
|
||
- **组件名**: PascalCase (`UserCard`)
|
||
- **变量名**: camelCase (`userName`)
|
||
- **常量名**: UPPER_SNAKE_CASE (`API_BASE_URL`)
|
||
- **类型名**: PascalCase (`UserData`)
|
||
|
||
### Git提交规范
|
||
|
||
使用 [Conventional Commits](https://conventionalcommits.org/) 规范:
|
||
|
||
```
|
||
<type>(<scope>): <subject>
|
||
|
||
<body>
|
||
|
||
<footer>
|
||
```
|
||
|
||
#### 提交类型
|
||
|
||
- `feat`: 新功能
|
||
- `fix`: 修复bug
|
||
- `docs`: 文档更新
|
||
- `style`: 代码格式化
|
||
- `refactor`: 重构代码
|
||
- `test`: 测试相关
|
||
- `chore`: 构建工具、依赖更新
|
||
|
||
#### 示例
|
||
|
||
```bash
|
||
feat(machinery): add machinery status monitoring
|
||
fix(auth): resolve login validation issue
|
||
docs(readme): update installation guide
|
||
```
|
||
|
||
## 🧩 组件开发
|
||
|
||
### 组件结构模板
|
||
|
||
```typescript
|
||
// src/components/example/ExampleComponent.tsx
|
||
import React, { useState, useCallback } from 'react'
|
||
import { cn } from '@/lib/utils'
|
||
|
||
interface ExampleComponentProps {
|
||
title: string
|
||
onAction?: () => void
|
||
className?: string
|
||
}
|
||
|
||
export const ExampleComponent: React.FC<ExampleComponentProps> = ({
|
||
title,
|
||
onAction,
|
||
className
|
||
}) => {
|
||
const [state, setState] = useState(false)
|
||
|
||
const handleClick = useCallback(() => {
|
||
setState(prev => !prev)
|
||
onAction?.()
|
||
}, [onAction])
|
||
|
||
return (
|
||
<div className={cn('example-component', className)}>
|
||
<h3 className="example-title">{title}</h3>
|
||
<button onClick={handleClick}>
|
||
Click me
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 组件样式规范
|
||
|
||
```css
|
||
/* 优先使用Tailwind CSS类名 */
|
||
.example-component {
|
||
@apply rounded-lg border border-gray-200 p-4 bg-white shadow-sm;
|
||
}
|
||
|
||
.example-title {
|
||
@apply text-lg font-semibold text-gray-900 mb-2;
|
||
}
|
||
|
||
/* 必要时使用传统CSS */
|
||
.example-component:hover {
|
||
transform: translateY(-1px);
|
||
transition: transform 0.2s ease;
|
||
}
|
||
```
|
||
|
||
### shadcn/ui组件使用
|
||
|
||
```typescript
|
||
import { Button } from '@/components/ui/button'
|
||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||
import { Badge } from '@/components/ui/badge'
|
||
|
||
export const MachineryCard = ({ machinery }) => {
|
||
return (
|
||
<Card className="machinery-card">
|
||
<CardHeader>
|
||
<CardTitle>{machinery.name}</CardTitle>
|
||
<Badge variant={machinery.status === 'running' ? 'default' : 'secondary'}>
|
||
{machinery.status}
|
||
</Badge>
|
||
</CardHeader>
|
||
<CardContent>
|
||
{/* 内容 */}
|
||
</CardContent>
|
||
</Card>
|
||
)
|
||
}
|
||
```
|
||
|
||
## 🔄 状态管理
|
||
|
||
### useState使用
|
||
|
||
```typescript
|
||
const [formData, setFormData] = useState({
|
||
name: '',
|
||
email: '',
|
||
phone: ''
|
||
})
|
||
|
||
// 更新对象状态
|
||
const handleChange = (field: string, value: string) => {
|
||
setFormData(prev => ({
|
||
...prev,
|
||
[field]: value
|
||
}))
|
||
}
|
||
```
|
||
|
||
### useReducer使用
|
||
|
||
```typescript
|
||
type State = {
|
||
count: number
|
||
loading: boolean
|
||
}
|
||
|
||
type Action =
|
||
| { type: 'increment' }
|
||
| { type: 'decrement' }
|
||
| { type: 'setLoading'; payload: boolean }
|
||
|
||
const reducer = (state: State, action: Action): State => {
|
||
switch (action.type) {
|
||
case 'increment':
|
||
return { ...state, count: state.count + 1 }
|
||
case 'decrement':
|
||
return { ...state, count: state.count - 1 }
|
||
case 'setLoading':
|
||
return { ...state, loading: action.payload }
|
||
default:
|
||
return state
|
||
}
|
||
}
|
||
|
||
const [state, dispatch] = useReducer(reducer, {
|
||
count: 0,
|
||
loading: false
|
||
})
|
||
```
|
||
|
||
### 自定义Hooks
|
||
|
||
```typescript
|
||
// src/hooks/useApi.ts
|
||
import { useState, useEffect } from 'react'
|
||
|
||
export function useApi<T>(url: string) {
|
||
const [data, setData] = useState<T | null>(null)
|
||
const [loading, setLoading] = useState(true)
|
||
const [error, setError] = useState<string | null>(null)
|
||
|
||
useEffect(() => {
|
||
const fetchData = async () => {
|
||
try {
|
||
setLoading(true)
|
||
const response = await fetch(url)
|
||
const result = await response.json()
|
||
setData(result)
|
||
} catch (err) {
|
||
setError(err.message)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
fetchData()
|
||
}, [url])
|
||
|
||
return { data, loading, error }
|
||
}
|
||
```
|
||
|
||
## 🎨 样式指南
|
||
|
||
### Tailwind CSS最佳实践
|
||
|
||
```typescript
|
||
// ✅ 使用cn工具函数合并类名
|
||
import { cn } from '@/lib/utils'
|
||
|
||
const Button = ({ variant = 'primary', className, ...props }) => {
|
||
return (
|
||
<button
|
||
className={cn(
|
||
'px-4 py-2 rounded-md font-medium transition-colors',
|
||
{
|
||
'bg-blue-600 text-white hover:bg-blue-700': variant === 'primary',
|
||
'bg-gray-200 text-gray-900 hover:bg-gray-300': variant === 'secondary'
|
||
},
|
||
className
|
||
)}
|
||
{...props}
|
||
/>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 响应式设计
|
||
|
||
```typescript
|
||
// 移动优先的响应式设计
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
{/* 内容 */}
|
||
</div>
|
||
|
||
// 响应式间距
|
||
<div className="px-4 sm:px-6 lg:px-8">
|
||
{/* 内容 */}
|
||
</div>
|
||
```
|
||
|
||
### 深色模式支持
|
||
|
||
```typescript
|
||
import { useTheme } from '@/hooks/useTheme'
|
||
|
||
export const ThemedComponent = () => {
|
||
const { theme, setTheme } = useTheme()
|
||
|
||
return (
|
||
<div className="bg-background text-foreground">
|
||
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
|
||
切换主题
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
## 🌐 API集成
|
||
|
||
### API配置
|
||
|
||
```typescript
|
||
// src/config/api.ts
|
||
export const API_CONFIG = {
|
||
baseUrl: import.meta.env.VITE_API_BASE_URL,
|
||
timeout: 10000
|
||
}
|
||
|
||
export const apiClient = {
|
||
get: <T>(url: string): Promise<T> => {
|
||
return fetch(`${API_CONFIG.baseUrl}${url}`).then(res => res.json())
|
||
},
|
||
|
||
post: <T>(url: string, data: any): Promise<T> => {
|
||
return fetch(`${API_CONFIG.baseUrl}${url}`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
}).then(res => res.json())
|
||
}
|
||
}
|
||
```
|
||
|
||
### 数据获取Hook
|
||
|
||
```typescript
|
||
// src/hooks/useMachinery.ts
|
||
import { useState, useEffect } from 'react'
|
||
import { apiClient } from '@/config/api'
|
||
|
||
export function useMachinery() {
|
||
const [machinery, setMachinery] = useState([])
|
||
const [loading, setLoading] = useState(true)
|
||
const [error, setError] = useState(null)
|
||
|
||
useEffect(() => {
|
||
apiClient.get('/machinery')
|
||
.then(setMachinery)
|
||
.catch(setError)
|
||
.finally(() => setLoading(false))
|
||
}, [])
|
||
|
||
return { machinery, loading, error }
|
||
}
|
||
```
|
||
|
||
## 🧪 测试指南
|
||
|
||
### 单元测试示例
|
||
|
||
```typescript
|
||
// src/components/__tests__/Button.test.tsx
|
||
import { render, screen, fireEvent } from '@testing-library/react'
|
||
import { Button } from '../Button'
|
||
|
||
describe('Button', () => {
|
||
it('renders correctly', () => {
|
||
render(<Button>Click me</Button>)
|
||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||
})
|
||
|
||
it('calls onClick when clicked', () => {
|
||
const handleClick = jest.fn()
|
||
render(<Button onClick={handleClick}>Click me</Button>)
|
||
|
||
fireEvent.click(screen.getByRole('button'))
|
||
expect(handleClick).toHaveBeenCalledTimes(1)
|
||
})
|
||
})
|
||
```
|
||
|
||
### Hook测试
|
||
|
||
```typescript
|
||
// src/hooks/__tests__/useCounter.test.ts
|
||
import { renderHook, act } from '@testing-library/react'
|
||
import { useCounter } from '../useCounter'
|
||
|
||
describe('useCounter', () => {
|
||
it('initializes with default value', () => {
|
||
const { result } = renderHook(() => useCounter())
|
||
expect(result.current.count).toBe(0)
|
||
})
|
||
|
||
it('increments count', () => {
|
||
const { result } = renderHook(() => useCounter())
|
||
|
||
act(() => {
|
||
result.current.increment()
|
||
})
|
||
|
||
expect(result.current.count).toBe(1)
|
||
})
|
||
})
|
||
```
|
||
|
||
## 🐛 调试技巧
|
||
|
||
### React DevTools
|
||
|
||
安装 React Developer Tools 浏览器扩展:
|
||
|
||
```bash
|
||
# 检查组件状态
|
||
# 查看组件层次结构
|
||
# 性能分析
|
||
```
|
||
|
||
### VSCode调试配置
|
||
|
||
```json
|
||
// .vscode/launch.json
|
||
{
|
||
"version": "0.2.0",
|
||
"configurations": [
|
||
{
|
||
"name": "Debug React",
|
||
"type": "node",
|
||
"request": "launch",
|
||
"program": "${workspaceFolder}/node_modules/.bin/vite",
|
||
"args": ["--mode", "development"],
|
||
"env": {
|
||
"NODE_ENV": "development"
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 常用调试代码
|
||
|
||
```typescript
|
||
// 开发环境调试
|
||
if (import.meta.env.DEV) {
|
||
console.log('Debug info:', data)
|
||
}
|
||
|
||
// 性能监控
|
||
console.time('component-render')
|
||
// ... 组件渲染逻辑
|
||
console.timeEnd('component-render')
|
||
|
||
// 网络请求调试
|
||
const debugFetch = async (url: string) => {
|
||
console.log(`Fetching: ${url}`)
|
||
const start = performance.now()
|
||
|
||
try {
|
||
const response = await fetch(url)
|
||
const data = await response.json()
|
||
console.log(`Fetched in ${performance.now() - start}ms`, data)
|
||
return data
|
||
} catch (error) {
|
||
console.error(`Fetch failed after ${performance.now() - start}ms`, error)
|
||
throw error
|
||
}
|
||
}
|
||
```
|
||
|
||
## ❓ 常见问题
|
||
|
||
### Q: 如何添加新的UI组件?
|
||
|
||
A:
|
||
1. 在 `src/components/ui/` 下创建组件文件
|
||
2. 使用 shadcn/ui 设计规范
|
||
3. 添加 TypeScript 类型定义
|
||
4. 编写组件文档
|
||
|
||
### Q: 如何处理表单验证?
|
||
|
||
A: 推荐使用 react-hook-form + zod:
|
||
|
||
```typescript
|
||
import { useForm } from 'react-hook-form'
|
||
import { zodResolver } from '@hookform/resolvers/zod'
|
||
import { z } from 'zod'
|
||
|
||
const schema = z.object({
|
||
name: z.string().min(1, '名称不能为空'),
|
||
email: z.string().email('邮箱格式不正确')
|
||
})
|
||
|
||
const { register, handleSubmit, formState: { errors } } = useForm({
|
||
resolver: zodResolver(schema)
|
||
})
|
||
```
|
||
|
||
### Q: 如何优化性能?
|
||
|
||
A:
|
||
1. 使用 React.memo 避免不必要的重渲染
|
||
2. 使用 useMemo 和 useCallback 缓存计算结果
|
||
3. 实现虚拟列表处理大数据
|
||
4. 使用代码分割减少初始加载时间
|
||
|
||
### Q: 如何处理国际化?
|
||
|
||
A: 项目支持多语言配置:
|
||
|
||
```typescript
|
||
// src/config/i18n.ts
|
||
export const locales = {
|
||
'zh-CN': '简体中文',
|
||
'en-US': 'English'
|
||
}
|
||
|
||
export const translations = {
|
||
'zh-CN': {
|
||
'machinery.title': '农机管理',
|
||
'machinery.status.running': '运行中'
|
||
},
|
||
'en-US': {
|
||
'machinery.title': 'Machinery Management',
|
||
'machinery.status.running': 'Running'
|
||
}
|
||
}
|
||
```
|
||
|
||
### Q: 如何配置开发工具?
|
||
|
||
A: 使用项目提供的脚本:
|
||
|
||
```bash
|
||
# 查看状态
|
||
npm run scripts:setup
|
||
|
||
# 启用工具
|
||
npm run scripts:enable
|
||
|
||
# 禁用工具
|
||
npm run scripts:disable
|
||
```
|
||
|
||
## 📚 更多资源
|
||
|
||
- [React 官方文档](https://react.dev/)
|
||
- [TypeScript 手册](https://www.typescriptlang.org/docs/)
|
||
- [Tailwind CSS 文档](https://tailwindcss.com/docs)
|
||
- [shadcn/ui 组件库](https://ui.shadcn.com/)
|
||
- [Vite 构建工具](https://vitejs.dev/)
|
||
|
||
---
|
||
|
||
💡 **提示**: 如果遇到问题,请先查看常见问题部分,或联系项目维护者获取帮助。 |