19 KiB
19 KiB
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 组件拆分原则
拆分条件:
- 组件代码超过100行
- 组件承担多个职责
- 组件需要复用
- 组件逻辑复杂(包含多个useState或useEffect)
拆分命名:
- 主组件:保持原名称(如:MachineryEntry)
- 子组件:基于功能命名(如:MachineryList、MachineryForm、MachineryFilter)
- 通用组件:基于用途命名(如:TableHeader、FormActions)
2. 开发规范
2.1 组件开发规范
2.1.1 主组件规范
// 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 (
<div className="machinery-entry">
<div className="page-header">
<h1>农机档案管理</h1>
<div className="page-actions">
<button onClick={handleCreate}>新增农机</button>
<button onClick={handleExport}>导出数据</button>
</div>
</div>
<MachineryFilter
filters={filters}
onFilterChange={handleFilterChange}
/>
<MachineryList
machinery={machinery}
loading={loading}
error={error}
pagination={pagination}
onPageChange={handlePageChange}
onEdit={handleEdit}
onDelete={handleDelete}
onBatchDelete={handleBatchDelete}
/>
<MachineryForm />
</div>
);
}
2.1.2 子组件规范
// 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<MachineryListProps>(({
machinery,
loading,
error,
pagination,
onPageChange,
onEdit,
onDelete,
onBatchDelete
}) => {
const {
selectedRows,
handleRowSelection,
handleSelectAll,
handleBatchActions
} = useMachineryList(machinery);
if (error) {
return (
<div className="error-state">
<p>加载失败:{error}</p>
<button onClick={() => window.location.reload()}>
重新加载
</button>
</div>
);
}
return (
<div className="machinery-list">
<TableActions
selectedRows={selectedRows}
onBatchDelete={handleBatchDelete}
onRefresh={() => onPageChange(pagination.current)}
/>
<MachineryTable
data={machinery}
loading={loading}
selectedRows={selectedRows}
onRowSelection={handleRowSelection}
onSelectAll={handleSelectAll}
onEdit={onEdit}
onDelete={onDelete}
pagination={pagination}
onPageChange={onPageChange}
/>
</div>
);
});
MachineryList.displayName = 'MachineryList';
2.2 类型定义规范
2.2.1 页面类型定义
// 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 组件类型定义
// 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
// 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
// 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 页面主样式
/* 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 组件样式
/* 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 页面工具函数
// 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 格式化工具
// 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 常量定义规范
// 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 新页面开发流程
- 创建页面目录结构
- 定义类型接口 (index.types.ts)
- 开发主组件 (index.jsx)
- 拆分子组件 (components/)
- 创建Hooks (hooks/)
- 编写工具函数 (utils/)
- 定义常量 (constants.js)
- 编写样式 (index.css)
- 单元测试
5.2 组件拆分流程
- 识别拆分点: 组件代码复杂度、复用性、职责单一性
- 创建组件目录: 创建
components/ComponentName/目录 - 提取组件逻辑: 将相关代码移动到新组件
- 定义Props接口: 创建类型定义文件
- 处理状态管理: 使用Hooks或状态提升
- 更新主组件: 修改主组件使用新组件
- 样式分离: 创建独立的样式文件
- 测试验证: 确保功能正常
6. 质量保证
6.1 代码检查
- 使用ESLint检查代码规范
- 使用Prettier格式化代码
- 使用TypeScript进行类型检查
- 组件必须有PropTypes或TypeScript接口
6.2 性能优化
- 使用React.memo包装组件避免不必要的重渲染
- 使用useCallback和useMemo优化性能
- 实现虚拟滚动处理大数据量
- 使用懒加载和代码分割
6.3 可维护性
- 保持组件单一职责
- 提取可复用的通用组件
- 编写清晰的注释和文档
- 保持一致的代码风格
这个规范为pages目录的开发提供了完整的指导,确保代码的可维护性、可扩展性和团队协作效率。