# Next.js App Router 开发规范 ## 1. 目录结构规范 ### 1.1 Next.js App Router 标准结构 基于 Next.js 14 App Router 的文件系统路由,每个页面目录采用以下标准结构: ``` src/app/ModuleName/ ├── 📄 page.tsx # 页面主组件(服务端组件) ├── 📄 layout.tsx # 模块专属布局 ├── 📄 loading.tsx # 加载状态组件 ├── 📄 error.tsx # 错误边界组件 ├── 📄 not-found.tsx # 404页面组件 ├── 📂 SubModuleName/ # 子模块目录 │ ├── 📄 page.tsx # 子模块页面 │ └── 📂 [id]/ # 动态路由 │ └── 📄 page.tsx # 动态参数页面 ├── 📂 components/ # 页面专用组件 │ ├── 📂 Component1/ # 子组件1 │ │ ├── 📄 index.tsx # 子组件实现 │ │ ├── 📄 index.module.css # 组件样式(CSS Modules) │ │ └── 📄 types.ts # 类型定义 │ └── 📂 common/ # 通用组件 │ ├── 📄 PageHeader.tsx │ └── 📄 LoadingSpinner.tsx ├── 📂 lib/ # 页面专用工具和逻辑 │ ├── 📄 actions.ts # 服务端操作 │ ├── 📄 hooks/ # 客户端Hooks │ │ ├── 📄 usePageData.tsx │ │ └── 📄 usePageActions.tsx │ ├── 📄 utils.tsx # 工具函数 │ └── 📄 constants.tsx # 常量定义 └── 📄 types.ts # 页面类型定义 ``` ### 1.2 动态路由结构 #### 1.2.1 基础动态路由 ``` src/app/machinery/archive/[id]/ ├── 📄 page.tsx # 详情页面 /machinery/archive/[id] ├── 📄 loading.tsx # 加载状态 ├── 📄 error.tsx # 错误处理 └── 📂 components/ ├── 📄 MachineryDetail.tsx └── 📄 RelatedInfo.tsx ``` #### 1.2.2 嵌套动态路由 ``` src/app/config/tenant/[enterpriseId]/ ├── 📄 page.tsx # 企业详情页 ├── 📂 edit/ # 编辑功能 │ └── 📄 page.tsx # /config/tenant/[enterpriseId]/edit ├── 📂 users/ # 用户管理 │ ├── 📄 page.tsx # 用户列表 │ └── 📂 [userId]/ # 用户详情 │ └── 📄 page.tsx # /config/tenant/[enterpriseId]/users/[userId] ``` #### 1.2.3 路由组结构 ``` src/app/ ├── 📂 (auth)/ # 认证路由组(不显示在URL中) │ ├── 📄 layout.tsx # 认证布局 │ ├── 📂 login/ │ │ └── 📄 page.tsx # /login │ └── 📂 register/ │ └── 📄 page.tsx # /register ├── 📂 (dashboard)/ # 仪表板路由组 │ ├── 📄 layout.tsx # 仪表板布局 │ ├── 📂 machinery/ # 农机管理 │ ├── 📂 field/ # 地块管理 │ └── 📂 config/ # 配置管理 └── 📂 api/ # API路由 └── 📂 machinery/ └── 📄 route.ts # /api/machinery ``` ### 1.2 组件拆分原则 **拆分条件:** 1. 组件代码超过100行 2. 组件承担多个职责 3. 组件需要复用 4. 组件逻辑复杂(包含多个useState或useEffect) **拆分命名:** - 主组件:保持原名称(如:MachineryEntry) - 子组件:基于功能命名(如:MachineryList、MachineryForm、MachineryFilter) - 通用组件:基于用途命名(如:TableHeader、FormActions) ## 2. Next.js 开发规范 ### 2.1 页面组件开发规范 #### 2.1.1 服务端组件规范(推荐) ```typescript // src/app/machinery/archive/entry/page.tsx import { Suspense } from 'react'; import { getMachineryList } from '@/lib/services/api/machineryApi'; import { MachineryListClient } from './components/MachineryListClient'; import { MachineryPageHeader } from '@/components/layout/MachineryPageHeader'; import { MachineryFilters } from './components/MachineryFilters'; import { LoadingSkeleton } from '@/components/ui/loading-skeleton'; export default async function MachineryArchivePage({ searchParams, }: { searchParams?: { [key: string]: string | string[] | undefined }; }) { // 服务端数据获取 const initialData = await getMachineryList(searchParams); return (
}>
); } // 元数据配置 export async function generateMetadata() { return { title: '农机档案管理 - 智慧农业系统', description: '管理农机档案信息,包括录入、编辑、删除等功能', }; } // 静态生成参数(可选) export async function generateStaticParams() { return [ { category: 'tractor' }, { category: 'harvester' }, { category: 'seeder' }, ]; } ``` #### 2.1.2 客户端组件规范 ```typescript // src/app/machinery/archive/entry/components/MachineryListClient.tsx 'use client'; import { useState, useCallback } from 'react'; import { useMachineryStore } from '@/lib/stores/machineryStore'; import { MachineryGrid } from '@/components/business/machinery/MachineryGrid'; import { MachineryTable } from '@/components/business/machinery/MachineryTable'; import { CreateMachineryModal } from '@/components/business/machinery/CreateMachineryModal'; interface Props { initialData: MachineryResponse; } export function MachineryListClient({ initialData }: Props) { const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [viewMode, setViewMode] = useState<'grid' | 'table'>('grid'); const { machinery, loading, filters, updateMachinery, deleteMachinery, refetch, } = useMachineryStore(); const handleCreate = useCallback(async (data: CreateMachineryDto) => { await updateMachinery(data); setIsCreateModalOpen(false); refetch(); }, [updateMachinery, refetch]); const handleEdit = useCallback(async (id: string, data: UpdateMachineryDto) => { await updateMachinery({ ...data, id }); refetch(); }, [updateMachinery, refetch]); const handleDelete = useCallback(async (id: string) => { await deleteMachinery(id); refetch(); }, [deleteMachinery, refetch]); return (
{loading ? (
) : viewMode === 'grid' ? ( ) : ( )} setIsCreateModalOpen(false)} onSave={handleCreate} />
); } ### 2.2 动态路由开发规范 #### 2.2.1 动态参数处理 ```typescript // src/app/machinery/archive/[id]/page.tsx import { notFound } from 'next/navigation'; import { Metadata } from 'next'; import { getMachineryById } from '@/lib/services/api/machineryApi'; import { MachineryDetailPage } from '@/components/pages/machinery/MachineryDetailPage'; interface Props { params: { id: string }; searchParams: { [key: string]: string | string[] | undefined }; } // 生成页面元数据 export async function generateMetadata({ params }: Props): Promise { try { const machinery = await getMachineryById(params.id); return { title: `${machinery.name} - 农机详情`, description: `查看农机 ${machinery.name} 的详细信息`, }; } catch { return { title: '农机详情 - 未找到', }; } } // 服务端组件 - 获取数据并渲染 export default async function MachineryDetail({ params }: Props) { const machinery = await getMachineryById(params.id); if (!machinery) { notFound(); } return (
); } // 静态生成常用农机详情页(提升性能) export async function generateStaticParams() { // 这里可以预生成一些常用的农机详情页 // 实际项目中可以从数据库获取 return [ { id: 'machinery-001' }, { id: 'machinery-002' }, { id: 'machinery-003' }, ]; } ``` #### 2.2.2 布局组件规范 ```typescript // src/app/machinery/layout.tsx import { ReactNode } from 'react'; import { MachinerySidebar } from '@/components/layout/MachinerySidebar'; import { MachineryBreadcrumb } from '@/components/navigation/MachineryBreadcrumb'; export default function MachineryLayout({ children, }: { children: ReactNode; }) { return (
{/* 农机模块侧边栏 */}
{/* 主内容区域 */}
{/* 面包屑导航 */}
{/* 页面内容 */}
{children}
); } ``` #### 2.2.3 加载和错误状态 ```typescript // src/app/machinery/loading.tsx import { LoadingSkeleton } from '@/components/ui/loading-skeleton'; export default function MachineryLoading() { return (
{[...Array(6)].map((_, i) => (
))}
); } // src/app/machinery/error.tsx 'use client'; import { useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { AlertTriangle } from 'lucide-react'; export default function Error({ error, reset, }: { error: Error; reset: () => void; }) { useEffect(() => { console.error('农机模块错误:', error); }, [error]); return (

页面加载失败

{error.message || '加载农机模块时发生了错误,请稍后重试。'}

); } ``` ### 2.3 服务端操作规范 #### 2.3.1 Server Actions ```typescript // src/lib/actions/machinery.ts 'use server'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; import { z } from 'zod'; import { createMachinery, updateMachinery, deleteMachinery } from '@/lib/services/api/machineryApi'; // 表单验证Schema const createMachinerySchema = z.object({ name: z.string().min(1, '农机名称不能为空'), model: z.string().min(1, '型号不能为空'), category: z.enum(['拖拉机', '收割机', '播种机', '植保机械']), manufacturer: z.string().min(1, '制造商不能为空'), }); export async function createMachineryAction(formData: FormData) { try { // 验证表单数据 const validatedData = createMachinerySchema.parse({ name: formData.get('name'), model: formData.get('model'), category: formData.get('category'), manufacturer: formData.get('manufacturer'), }); // 创建农机 await createMachinery(validatedData); // 重新验证缓存 revalidatePath('/machinery/archive'); revalidatePath('/machinery'); return { success: true, message: '农机创建成功' }; } catch (error) { if (error instanceof z.ZodError) { return { success: false, message: error.errors[0].message }; } return { success: false, message: '创建失败,请重试' }; } } export async function updateMachineryAction(id: string, formData: FormData) { try { const validatedData = createMachinerySchema.parse({ name: formData.get('name'), model: formData.get('model'), category: formData.get('category'), manufacturer: formData.get('manufacturer'), }); await updateMachinery(id, validatedData); revalidatePath('/machinery/archive'); revalidatePath(`/machinery/archive/${id}`); return { success: true, message: '农机更新成功' }; } catch (error) { if (error instanceof z.ZodError) { return { success: false, message: error.errors[0].message }; } return { success: false, message: '更新失败,请重试' }; } } export async function deleteMachineryAction(id: string) { try { await deleteMachinery(id); revalidatePath('/machinery/archive'); redirect('/machinery/archive'); } catch (error) { throw new Error('删除农机失败'); } } ### 2.4 Next.js API 路由规范 #### 2.4.1 API 路由结构 ```typescript // src/app/api/machinery/route.ts - 农机API路由 import { NextRequest, NextResponse } from 'next/server'; import { getMachineryList, createMachinery } from '@/lib/services/machineryService'; import { machinerySchema } from '@/lib/validations/machinery'; // GET - 获取农机列表 export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); // 解析查询参数 const filters = { page: parseInt(searchParams.get('page') || '1'), limit: parseInt(searchParams.get('limit') || '10'), category: searchParams.get('category'), status: searchParams.get('status'), }; const data = await getMachineryList(filters); return NextResponse.json({ success: true, data, message: '获取农机列表成功' }); } catch (error) { console.error('获取农机列表失败:', error); return NextResponse.json( { success: false, message: '获取农机列表失败' }, { status: 500 } ); } } // POST - 创建农机 export async function POST(request: NextRequest) { try { const body = await request.json(); // 验证数据 const validatedData = machinerySchema.parse(body); const machinery = await createMachinery(validatedData); return NextResponse.json({ success: true, data: machinery, message: '创建农机成功' }, { status: 201 }); } catch (error) { if (error.name === 'ZodError') { return NextResponse.json({ success: false, message: '数据验证失败', errors: error.errors }, { status: 400 }); } console.error('创建农机失败:', error); return NextResponse.json({ success: false, message: '创建农机失败' }, { status: 500 }); } } ``` #### 2.4.2 动态API路由 ```typescript // src/app/api/machinery/[id]/route.ts - 农机详情API import { NextRequest, NextResponse } from 'next/server'; import { getMachineryById, updateMachinery, deleteMachinery } from '@/lib/services/machineryService'; interface Context { params: { id: string }; } // GET - 获取农机详情 export async function GET(request: NextRequest, context: Context) { try { const { id } = context.params; const machinery = await getMachineryById(id); if (!machinery) { return NextResponse.json({ success: false, message: '农机不存在' }, { status: 404 }); } return NextResponse.json({ success: true, data: machinery }); } catch (error) { console.error('获取农机详情失败:', error); return NextResponse.json({ success: false, message: '获取农机详情失败' }, { status: 500 }); } } // PUT - 更新农机 export async function PUT(request: NextRequest, context: Context) { try { const { id } = context.params; const body = await request.json(); const validatedData = machinerySchema.partial().parse(body); const machinery = await updateMachinery(id, validatedData); return NextResponse.json({ success: true, data: machinery, message: '更新农机成功' }); } catch (error) { if (error.name === 'ZodError') { return NextResponse.json({ success: false, message: '数据验证失败', errors: error.errors }, { status: 400 }); } console.error('更新农机失败:', error); return NextResponse.json({ success: false, message: '更新农机失败' }, { status: 500 }); } } // DELETE - 删除农机 export async function DELETE(request: NextRequest, context: Context) { try { const { id } = context.params; await deleteMachinery(id); return NextResponse.json({ success: true, message: '删除农机成功' }); } catch (error) { console.error('删除农机失败:', error); return NextResponse.json({ success: false, message: '删除农机失败' }, { status: 500 }); } } ``` ### 2.5 路由中间件规范 #### 2.5.1 认证中间件 ```typescript // middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import { getToken } from 'next-auth/jwt'; // 需要认证的路由 const protectedRoutes = [ '/machinery', '/field', '/operation', '/asset', '/ai-model', '/irrigation', '/config' ]; // 公开路由 const publicRoutes = [ '/', '/login', '/register', '/api/auth' ]; export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET }); // 检查是否访问需要认证的路由 const isProtectedRoute = protectedRoutes.some(route => pathname.startsWith(route) ); // 检查是否访问公开路由 const isPublicRoute = publicRoutes.some(route => pathname.startsWith(route) ); // API路由处理 if (pathname.startsWith('/api/')) { // 认证相关API允许访问 if (pathname.startsWith('/api/auth/')) { return NextResponse.next(); } // 其他API需要认证 if (!token) { return NextResponse.json( { error: '未授权访问' }, { status: 401 } ); } // 添加用户信息到请求头 const requestHeaders = new Headers(request.headers); requestHeaders.set('user-id', token.sub as string); return NextResponse.next({ request: { headers: requestHeaders, }, }); } // 未登录用户访问受保护路由 if (isProtectedRoute && !token) { const loginUrl = new URL('/login', request.url); loginUrl.searchParams.set('callbackUrl', pathname); return NextResponse.redirect(loginUrl); } // 已登录用户访问登录页 if (token && (pathname === '/login' || pathname === '/register')) { return NextResponse.redirect(new URL('/machinery', request.url)); } return NextResponse.next(); } // 中间件匹配配置 export const config = { matcher: [ /* * 匹配所有路径除了: * - _next/static (静态文件) * - _next/image (图片优化) * - favicon.ico (favicon文件) * - public文件夹中的文件 */ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', ], }; ``` ### 2.6 路由性能优化规范 #### 2.6.1 数据获取优化 ```typescript // src/lib/services/api/machineryApi.ts import { cache } from 'react'; // 使用cache缓存数据获取 export const getMachineryById = cache(async (id: string) => { const res = await fetch(`${process.env.API_BASE_URL}/machinery/${id}`, { next: { tags: [`machinery-${id}`], // 缓存标签 revalidate: 300, // 5分钟重新验证 }, }); if (!res.ok) { throw new Error('Failed to fetch machinery'); } return res.json(); }); export const getMachineryList = cache(async (params?: any) => { const searchParams = new URLSearchParams(params); const res = await fetch(`${process.env.API_BASE_URL}/machinery?${searchParams}`, { next: { tags: ['machinery-list'], // 缓存标签 revalidate: 60, // 1分钟重新验证 }, }); if (!res.ok) { throw new Error('Failed to fetch machinery list'); } return res.json(); }); // 数据重新验证 export async function revalidateMachineryCache(id?: string) { const { revalidateTag } = await import('next/cache'); if (id) { revalidateTag(`machinery-${id}`); } else { revalidateTag('machinery-list'); } } ``` #### 2.6.2 静态生成和增量静态生成 ```typescript // src/app/machinery/archive/page.tsx import { getMachineryCategories } from '@/lib/services/api/machineryApi'; // 增量静态生成(ISR) export async function generateStaticParams() { try { const categories = await getMachineryCategories(); return categories.map((category) => ({ category: category.name, })); } catch { // 如果获取失败,返回空数组 return []; } } // 设置页面为静态生成 export const dynamic = 'force-static'; export const revalidate = 3600; // 1小时重新生成 ``` ### 2.7 类型定义规范 #### 2.7.1 Next.js 路由类型 ```typescript // src/types/nextjs.ts import { Metadata, ResolvingMetadata } from 'next'; // 页面Props类型 export interface PageProps { params: Record; searchParams: Record; } // 动态路由Props类型 export interface DynamicPageProps = Record> { params: T; searchParams: Record; } // 布局Props类型 export interface LayoutProps { children: React.ReactNode; params?: Record; } // 元数据生成函数类型 export type MetadataGenerator = (props: PageProps) => Promise; // 服务端组件类型 export type ServerComponent

= (props: P) => Promise; // 客户端组件类型 export type ClientComponent

= (props: P) => React.ReactElement; ``` #### 2.7.2 API 响应类型 ```typescript // src/types/api.ts export interface ApiResponse { success: boolean; data?: T; message: string; errors?: any[]; } export interface PaginatedResponse { items: T[]; total: number; page: number; pageSize: number; totalPages: number; } export interface ApiError { success: false; message: string; code?: string; details?: any; } ``` ### 2.2 类型定义规范 #### 2.2.1 页面类型定义 ```typescript // pages/AgriculturalMachinery/Archive/MachineryEntry/index.types.ts export interface MachineryRecord { id: string; name: string; model: string; category: MachineryCategory; status: MachineryStatus; manufacturer: string; purchaseDate: string; price: number; // ... 其他字段 } export type MachineryCategory = | '耕地机械' | '播种机械' | '收获机械' | '植保机械'; export type MachineryStatus = | '运行中' | '空闲中' | '待维护' | '已报废'; export interface MachineryFilters { category?: MachineryCategory; status?: MachineryStatus; manufacturer?: string; dateRange?: [string, string]; } export interface PaginationState { current: number; pageSize: number; total: number; } ``` #### 2.2.2 组件类型定义 ```typescript // pages/AgriculturalMachinery/Archive/MachineryEntry/components/MachineryList/types.ts import { MachineryRecord, MachineryFilters, PaginationState } from '../../index.types'; export interface MachineryListProps { machinery: MachineryRecord[]; loading: boolean; error: string | null; filters: MachineryFilters; pagination: PaginationState; onPageChange: (page: number) => void; onEdit: (record: MachineryRecord) => void; onDelete: (id: string) => void; onBatchDelete: (ids: string[]) => void; } export interface SelectedRow { id: string; name: string; // ... 其他需要显示在选中行的字段 } ``` ### 2.3 Hooks规范 #### 2.3.1 页面数据Hook ```javascript // pages/AgriculturalMachinery/Archive/MachineryEntry/hooks/usePageData.js import { useState, useEffect, useCallback } from 'react'; import { getMachineryList, deleteMachinery } from '@/apis/subModules/agriculturalMachinery'; import { showMessage } from '@/utils/message'; export function useMachineryData() { const [machinery, setMachinery] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [filters, setFilters] = useState({}); const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 }); // 获取数据 const fetchMachinery = useCallback(async () => { setLoading(true); setError(null); try { const params = { ...filters, page: pagination.current, pageSize: pagination.pageSize }; const response = await getMachineryList(params); setMachinery(response.data.list); setPagination(prev => ({ ...prev, total: response.data.total })); } catch (err) { setError(err.message); showMessage('error', '获取农机数据失败'); } finally { setLoading(false); } }, [filters, pagination.current, pagination.pageSize]); // 初始加载 useEffect(() => { fetchMachinery(); }, [fetchMachinery]); // 处理筛选变化 const handleFilterChange = useCallback((newFilters) => { setFilters(newFilters); setPagination(prev => ({ ...prev, current: 1 })); }, []); // 处理分页变化 const handlePageChange = useCallback((page) => { setPagination(prev => ({ ...prev, current: page })); }, []); return { machinery, loading, error, filters, pagination, handleFilterChange, handlePageChange, refreshData: fetchMachinery }; } ``` #### 2.3.2 页面操作Hook ```javascript // pages/AgriculturalMachinery/Archive/MachineryEntry/hooks/usePageActions.js import { useCallback } from 'react'; import { deleteMachinery, updateMachinery } from '@/apis/subModules/agriculturalMachinery'; import { showMessage } from '@/utils/message'; export function useMachineryActions(refreshData) { // 删除农机 const handleDelete = useCallback(async (id) => { try { await deleteMachinery(id); showMessage('success', '删除成功'); refreshData(); } catch (error) { showMessage('error', '删除失败'); } }, [refreshData]); // 编辑农机 const handleEdit = useCallback(async (data) => { try { await updateMachinery(data.id, data); showMessage('success', '更新成功'); refreshData(); } catch (error) { showMessage('error', '更新失败'); } }, [refreshData]); // 批量删除 const handleBatchDelete = useCallback(async (ids) => { try { await Promise.all(ids.map(id => deleteMachinery(id))); showMessage('success', '批量删除成功'); refreshData(); } catch (error) { showMessage('error', '批量删除失败'); } }, [refreshData]); // 导出数据 const handleExport = useCallback(() => { // 实现导出逻辑 console.log('导出数据'); }, []); return { handleDelete, handleEdit, handleBatchDelete, handleExport }; } ``` ### 2.4 样式规范 #### 2.4.1 页面主样式 ```css /* pages/AgriculturalMachinery/Archive/MachineryEntry/index.css */ .machinery-entry { padding: 24px; background: #f5f5f5; min-height: 100vh; } .page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; padding: 16px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .page-header h1 { font-size: 24px; font-weight: 600; color: #333; margin: 0; } .page-actions { display: flex; gap: 12px; } .page-actions button { padding: 8px 16px; border-radius: 4px; border: 1px solid #d9d9d9; background: white; cursor: pointer; transition: all 0.2s; } .page-actions button:hover { background: #f0f0f0; border-color: #40a9ff; } .page-actions button.primary { background: #40a9ff; color: white; border-color: #40a9ff; } .page-actions button.primary:hover { background: #1890ff; border-color: #1890ff; } /* 响应式设计 */ @media (max-width: 768px) { .page-header { flex-direction: column; align-items: flex-start; gap: 16px; } .page-actions { width: 100%; justify-content: flex-end; } } ``` #### 2.4.2 组件样式 ```css /* pages/AgriculturalMachinery/Archive/MachineryEntry/components/MachineryList/index.css */ .machinery-list { background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .error-state { padding: 48px; text-align: center; color: #666; } .error-state p { margin-bottom: 16px; font-size: 16px; } .error-state button { padding: 8px 16px; background: #ff4d4f; color: white; border: none; border-radius: 4px; cursor: pointer; transition: background 0.2s; } .error-state button:hover { background: #ff7875; } .table-actions { padding: 16px; border-bottom: 1px solid #f0f0f0; display: flex; justify-content: space-between; align-items: center; } .selected-info { color: #666; font-size: 14px; } .batch-actions { display: flex; gap: 8px; } .batch-actions button { padding: 4px 8px; font-size: 12px; border-radius: 4px; border: 1px solid #d9d9d9; background: white; cursor: pointer; transition: all 0.2s; } .batch-actions button:hover { background: #f0f0f0; } .batch-actions button.danger { color: #ff4d4f; border-color: #ff4d4f; } .batch-actions button.danger:hover { background: #ff4d4f; color: white; } ``` ### 2.5 工具函数规范 #### 2.5.1 页面工具函数 ```javascript // pages/AgriculturalMachinery/Archive/MachineryEntry/utils/pageHelpers.js import dayjs from 'dayjs'; import { formatCurrency } from '@/utils/formatters'; /** * 格式化农机状态 */ export function formatMachineryStatus(status) { const statusMap = { '运行中': { text: '运行中', color: '#52c41a' }, '空闲中': { text: '空闲中', color: '#d9d9d9' }, '待维护': { text: '待维护', color: '#faad14' }, '已报废': { text: '已报废', color: '#ff4d4f' } }; return statusMap[status] || { text: '未知', color: '#d9d9d9' }; } /** * 格式化日期 */ export function formatDate(date) { return dayjs(date).format('YYYY-MM-DD HH:mm'); } /** * 格式化价格 */ export function formatPrice(price) { return formatCurrency(price); } /** * 生成农机二维码内容 */ export function generateQRCode(machinery) { return JSON.stringify({ id: machinery.id, name: machinery.name, model: machinery.model, category: machinery.category, manufacturer: machinery.manufacturer }); } ``` #### 2.5.2 格式化工具 ```javascript // pages/AgriculturalMachinery/Archive/MachineryEntry/utils/formatters.js /** * 格式化货币 */ export function formatCurrency(amount) { return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(amount); } /** * 格式化数字 */ export function formatNumber(num, precision = 2) { return new Intl.NumberFormat('zh-CN', { minimumFractionDigits: precision, maximumFractionDigits: precision }).format(num); } /** * 格式化百分比 */ export function formatPercent(value) { return `${(value * 100).toFixed(1)}%`; } ``` ### 2.6 常量定义规范 ```javascript // pages/AgriculturalMachinery/Archive/MachineryEntry/constants.js export const MACHINERY_CATEGORIES = [ { value: '耕地机械', label: '耕地机械' }, { value: '播种机械', label: '播种机械' }, { value: '收获机械', label: '收获机械' }, { value: '植保机械', label: '植保机械' } ]; export const MACHINERY_STATUS = [ { value: '运行中', label: '运行中', color: '#52c41a' }, { value: '空闲中', label: '空闲中', color: '#d9d9d9' }, { value: '待维护', label: '待维护', color: '#faad14' }, { value: '已报废', label: '已报废', color: '#ff4d4f' } ]; export const MANUFACTURERS = [ '约翰迪尔', '久保田', '福田雷沃', '中国一拖', '时风集团' ]; export const PAGE_SIZE_OPTIONS = [10, 20, 50, 100]; export const DEFAULT_PAGE_SIZE = 10; ``` ## 3. 组件拆分示例 ### 3.1 MachineryEntry 组件拆分 ``` MachineryEntry/ ├── index.jsx # 主组件 ├── index.css # 主样式 ├── index.types.ts # 主类型定义 ├── hooks/ # 页面Hooks │ ├── usePageData.js # 数据管理Hook │ └── usePageActions.js # 操作Hook ├── utils/ # 工具函数 │ ├── pageHelpers.js # 页面工具函数 │ └── formatters.js # 格式化函数 ├── constants.js # 常量定义 └── components/ # 子组件 ├── MachineryFilter/ # 筛选组件 │ ├── index.jsx │ ├── index.css │ └── types.ts ├── MachineryList/ # 列表组件 │ ├── index.jsx │ ├── index.css │ ├── types.ts │ └── components/ │ ├── MachineryTable/ # 表格组件 │ │ ├── index.jsx │ │ ├── index.css │ │ └── types.ts │ └── TableActions/ # 表格操作组件 │ ├── index.jsx │ ├── index.css │ └── types.ts ├── MachineryForm/ # 表单组件 │ ├── index.jsx │ ├── index.css │ ├── types.ts │ └── components/ │ ├── BasicInfo/ # 基本信息表单 │ ├── TechnicalInfo/ # 技术参数表单 │ ├── PurchaseInfo/ # 购买信息表单 │ └── InsuranceInfo/ # 保险信息表单 └── common/ # 通用组件 ├── PageHeader/ # 页面头部 ├── LoadingSpinner/ # 加载动画 └── EmptyState/ # 空状态 ``` ## 4. 文件命名规范 ### 4.1 组件文件命名 - **主组件**: 使用功能名称,如 `MachineryEntry.jsx` - **子组件**: 使用功能描述,如 `MachineryTable.jsx` - **通用组件**: 使用通用描述,如 `TableHeader.jsx` - **Hooks**: 以 `use` 开头,如 `usePageData.js` - **工具函数**: 使用动词或名词,如 `pageHelpers.js`, `formatters.js` - **类型文件**: 以 `.types.ts` 结尾,如 `index.types.ts` - **样式文件**: 与组件同名,如 `index.css` ### 4.2 目录命名 - **页面目录**: 使用业务模块名,如 `AgriculturalMachinery` - **组件目录**: 使用功能描述,如 `MachineryList` - **通用目录**: 使用通用描述,如 `common` ## 5. 开发流程 ### 5.1 新页面开发流程 1. **创建页面目录结构** 2. **定义类型接口** (index.types.ts) 3. **开发主组件** (index.jsx) 4. **拆分子组件** (components/) 5. **创建Hooks** (hooks/) 6. **编写工具函数** (utils/) 7. **定义常量** (constants.js) 8. **编写样式** (index.css) 9. **单元测试** ### 5.2 组件拆分流程 1. **识别拆分点**: 组件代码复杂度、复用性、职责单一性 2. **创建组件目录**: 创建 `components/ComponentName/` 目录 3. **提取组件逻辑**: 将相关代码移动到新组件 4. **定义Props接口**: 创建类型定义文件 5. **处理状态管理**: 使用Hooks或状态提升 6. **更新主组件**: 修改主组件使用新组件 7. **样式分离**: 创建独立的样式文件 8. **测试验证**: 确保功能正常 ## 6. 质量保证 ### 6.1 代码检查 - 使用ESLint检查代码规范 - 使用Prettier格式化代码 - 使用TypeScript进行类型检查 - 组件必须有PropTypes或TypeScript接口 ### 6.2 性能优化 - 使用React.memo包装组件避免不必要的重渲染 - 使用useCallback和useMemo优化性能 - 实现虚拟滚动处理大数据量 - 使用懒加载和代码分割 ### 6.3 可维护性 - 保持组件单一职责 - 提取可复用的通用组件 - 编写清晰的注释和文档 - 保持一致的代码风格 这个规范为pages目录的开发提供了完整的指导,确保代码的可维护性、可扩展性和团队协作效率。