Files
smart-crop-ui/docs/Pages目录结构与开发规范.md

19 KiB
Raw Blame History

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 主组件规范

// 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 新页面开发流程

  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目录的开发提供了完整的指导确保代码的可维护性、可扩展性和团队协作效率。