13 KiB
13 KiB
开发指南
本文档为智慧农业生产管理系统的开发指南,帮助开发者快速了解项目结构、开发规范和最佳实践。
📋 目录
🛠️ 环境准备
必需软件
- 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,安装以下扩展获得最佳开发体验:
{
"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/ # 静态资源
组件组织原则
- UI组件 (
components/ui/): 纯UI组件,无业务逻辑 - 业务组件 (
components/common/): 包含业务逻辑的复用组件 - 页面组件 (
pages/): 具体页面实现,组合业务组件 - 布局组件 (
components/layouts/): 页面布局结构
📏 开发规范
代码规范
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组件规范
// ✅ 函数式组件 + 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 规范:
<type>(<scope>): <subject>
<body>
<footer>
提交类型
feat: 新功能fix: 修复bugdocs: 文档更新style: 代码格式化refactor: 重构代码test: 测试相关chore: 构建工具、依赖更新
示例
feat(machinery): add machinery status monitoring
fix(auth): resolve login validation issue
docs(readme): update installation guide
🧩 组件开发
组件结构模板
// 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>
)
}
组件样式规范
/* 优先使用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组件使用
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使用
const [formData, setFormData] = useState({
name: '',
email: '',
phone: ''
})
// 更新对象状态
const handleChange = (field: string, value: string) => {
setFormData(prev => ({
...prev,
[field]: value
}))
}
useReducer使用
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
// 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最佳实践
// ✅ 使用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}
/>
)
}
响应式设计
// 移动优先的响应式设计
<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>
深色模式支持
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配置
// 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
// 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 }
}
🧪 测试指南
单元测试示例
// 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测试
// 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 浏览器扩展:
# 检查组件状态
# 查看组件层次结构
# 性能分析
VSCode调试配置
// .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"
}
}
]
}
常用调试代码
// 开发环境调试
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:
- 在
src/components/ui/下创建组件文件 - 使用 shadcn/ui 设计规范
- 添加 TypeScript 类型定义
- 编写组件文档
Q: 如何处理表单验证?
A: 推荐使用 react-hook-form + zod:
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:
- 使用 React.memo 避免不必要的重渲染
- 使用 useMemo 和 useCallback 缓存计算结果
- 实现虚拟列表处理大数据
- 使用代码分割减少初始加载时间
Q: 如何处理国际化?
A: 项目支持多语言配置:
// 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: 使用项目提供的脚本:
# 查看状态
npm run scripts:setup
# 启用工具
npm run scripts:enable
# 禁用工具
npm run scripts:disable
📚 更多资源
💡 提示: 如果遇到问题,请先查看常见问题部分,或联系项目维护者获取帮助。