# Pages目录结构与开发规范 ## 1. 目录结构规范 ### 1.1 页面目录标准结构 每个页面目录采用以下标准结构: ``` pages/ModuleName/SubModuleName/ ├── 📄 index.jsx # 页面主组件 ├── 📂 components/ # 页面子组件目录 │ ├── 📂 Component1/ # 子组件1(拆分的大组件) │ │ ├── 📄 index.jsx # 子组件主文件 │ │ ├── 📄 Component1.jsx # 子组件实现 │ │ ├── 📄 index.css # 子组件样式 │ │ └── 📄 types.ts # 子组件类型定义 │ ├── 📂 Component2/ # 子组件2 │ │ └── ... │ └── 📂 common/ # 页面通用组件 │ ├── 📄 Header.jsx │ ├── 📄 Footer.jsx │ └── ... ├── 📄 index.css # 页面主样式文件 ├── 📄 index.types.ts # 页面类型定义 ├── 📄 hooks/ # 页面专用Hooks │ ├── 📄 usePageData.tsx │ └── 📄 usePageActions.tsx ├── 📄 utils/ # 页面专用工具函数 │ ├── 📄 pageHelpers.tsx │ └── 📄 formatters.tsx └── 📄 constants.tsx # 页面常量定义 ``` ### 1.2 组件拆分原则 **拆分条件:** 1. 组件代码超过100行 2. 组件承担多个职责 3. 组件需要复用 4. 组件逻辑复杂(包含多个useState或useEffect) **拆分命名:** - 主组件:保持原名称(如:MachineryEntry) - 子组件:基于功能命名(如:MachineryList、MachineryForm、MachineryFilter) - 通用组件:基于用途命名(如:TableHeader、FormActions) ## 2. 开发规范 ### 2.1 组件开发规范 #### 2.1.1 主组件规范 ```jsx // pages/AgriculturalMachinery/Archive/MachineryEntry/index.jsx import { useState, useEffect, useCallback } from 'react'; import { MachineryList } from './components/MachineryList'; import { MachineryForm } from './components/MachineryForm'; import { MachineryFilter } from './components/MachineryFilter'; import { useMachineryData } from './hooks/usePageData'; import { useMachineryActions } from './hooks/usePageActions'; import './index.css'; export function MachineryEntry() { const { machinery, loading, error, filters, pagination, handleFilterChange, handlePageChange, refreshData } = useMachineryData(); const { handleCreate, handleEdit, handleDelete, handleBatchDelete, handleExport } = useMachineryActions(refreshData); return (

农机档案管理

); } ``` #### 2.1.2 子组件规范 ```jsx // pages/AgriculturalMachinery/Archive/MachineryEntry/components/MachineryList/index.jsx import { memo } from 'react'; import { MachineryTable } from './MachineryTable'; import { TableActions } from './TableActions'; import { useMachineryList } from '../hooks/usePageData'; import { MachineryRecord } from '../types'; import './index.css'; interface MachineryListProps { machinery: MachineryRecord[]; loading: boolean; error: string | null; pagination: { current: number; pageSize: number; total: number; }; onPageChange: (page: number) => void; onEdit: (record: MachineryRecord) => void; onDelete: (id: string) => void; onBatchDelete: (ids: string[]) => void; } export const MachineryList = memo(({ machinery, loading, error, pagination, onPageChange, onEdit, onDelete, onBatchDelete }) => { const { selectedRows, handleRowSelection, handleSelectAll, handleBatchActions } = useMachineryList(machinery); if (error) { return (

加载失败:{error}

); } return (
onPageChange(pagination.current)} />
); }); MachineryList.displayName = 'MachineryList'; ``` ### 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目录的开发提供了完整的指导,确保代码的可维护性、可扩展性和团队协作效率。