第6篇:地块管理系统(上)- 数据模型与列表

效果

请添加图片描述

教程目标

通过本篇教程,你将学会:

  • 设计地块数据模型
  • 实现地块服务层(单例模式)
  • 实现地块列表页面
  • 实现地块筛选与搜索
  • 展示地块统计信息
  • 实现数据持久化

完成本教程后,你将拥有一个完整的地块列表管理功能。


一、地块数据模型设计

在开发地块管理功能之前,我们需要先设计好数据模型。数据模型是应用的基础,它决定了数据的结构和关系。

1.1 创建 ProfessionalAgricultureModels.ets

操作步骤

  1. 在 DevEco Studio 中,展开项目目录 entry/src/main/ets
  2. 右键点击 models 文件夹,选择 New → ArkTS File
  3. 输入文件名 ProfessionalAgricultureModels,点击 OK
  4. 在文件中输入以下代码
/**
 * 专业农业模式数据模型
 * 包含地块、作物、农事操作、成本、销售等核心数据结构
 */

/**
 * 地块类型枚举
 * 定义四种常见的农业地块类型
 */
export enum FieldType {
  FARMLAND = 'farmland',        // 农田 - 种植粮食作物的大田
  ORCHARD = 'orchard',          // 果园 - 种植果树的区域
  VEGETABLE = 'vegetable',      // 菜地 - 种植蔬菜的区域
  GREENHOUSE = 'greenhouse'     // 温室大棚 - 人工控制的种植环境
}

/**
 * 灌溉系统枚举
 * 定义常见的农业灌溉方式
 */
export enum IrrigationSystem {
  NONE = 'none',                // 无灌溉系统
  DRIP = 'drip',               // 滴灌 - 节水灌溉方式
  SPRINKLER = 'sprinkler',     // 喷灌 - 适合大面积灌溉
  FLOOD = 'flood'              // 漫灌 - 传统灌溉方式
}

/**
 * 作物类型枚举
 * 按用途分类的作物类型
 */
export enum CropType {
  GRAIN = 'grain',             // 粮食作物 - 如水稻、小麦、玉米
  VEGETABLE = 'vegetable',     // 蔬菜 - 如白菜、萝卜、番茄
  FRUIT = 'fruit',             // 水果 - 如苹果、柑橘、葡萄
  CASH_CROP = 'cash_crop'      // 经济作物 - 如棉花、油菜、甘蔗
}

/**
 * 作物生长阶段枚举
 * 描述作物从种植到收获的各个阶段
 */
export enum CropGrowthStage {
  SEEDLING = 'seedling',       // 育苗�� - 种子发芽到幼苗期
  GROWING = 'growing',         // 生长期 - 营养生长旺盛期
  FLOWERING = 'flowering',     // 开花期 - 生殖生长阶段
  FRUITING = 'fruiting',       // 结果期 - 果实成熟期
  HARVESTING = 'harvesting',   // 收获期 - 可以采收的阶段
  FALLOW = 'fallow'           // 休耕期 - 土地休息恢复期
}

/**
 * 作物健康状态枚举
 * 用于评估作物当前的健康状况
 */
export enum CropHealthStatus {
  HEALTHY = 'healthy',         // 健康 - 生长正常
  ATTENTION = 'attention',     // 需关注 - 有轻微问题
  DISEASE = 'disease',         // 病害 - 感染病害
  PEST = 'pest'               // 虫害 - 发生虫害
}

/**
 * 作物信息接口
 * 记录单个作物的完整信息
 */
export interface CropInfo {
  id: string;                  // 作物唯一标识
  name: string;                // 作物名称,如"水稻"、"小麦"
  type: CropType;              // 作物类型
  variety: string;             // 品种,如"杂交稻"、"冬小麦"
  plantingDate: number;        // 种植日期(时间戳)
  expectedHarvestDate: number; // 预计收获日期(时间戳)
  growthStage: CropGrowthStage; // 当前生长阶段
  healthStatus: CropHealthStatus; // 健康状态
  area: number;                // 种植面积(亩)
  expectedYield: number;       // 预计产量(公斤)
  notes?: string;              // 备注信息(可选)
}

/**
 * 地块信息接口
 * 记录单个地块的完整信息
 */
export interface FieldInfo {
  id: string;                  // 地块唯一标识
  name: string;                // 地块名称,如"东区1号田"
  type: FieldType;             // 地块类型
  area: number;                // 面积(亩)
  location: string;            // 位置描述,如"湖北省武汉市东西湖区"
  latitude?: number;           // 纬度坐标(可选,用于地图定位)
  longitude?: number;          // 经度坐标(可选,用于地图定位)
  soilType: string;            // 土壤类型,如"黏土"、"沙壤土"
  irrigationSystem: IrrigationSystem; // 灌溉系统类型
  currentCrop?: CropInfo;      // 当前种植的作物(可选)
  status: string;              // 地块状态:种植中、闲置、准备中
  createdAt: number;           // 创建时间(时间戳)
  lastUpdatedAt: number;       // 最后更新时间(时间戳)
  notes?: string;              // 备注信息(可选)
}

/**
 * 农事操作类型枚举
 * 定义常见的农事操作
 */
export enum FarmOperationType {
  PLOWING = 'plowing',         // 耕地 - 翻松土壤
  SOWING = 'sowing',           // 播种 - 种植作物
  FERTILIZING = 'fertilizing', // 施肥 - 提供养分
  WATERING = 'watering',       // 浇水 - 灌溉
  WEEDING = 'weeding',         // 除草 - 清除杂草
  PEST_CONTROL = 'pest_control', // 病虫害防治
  HARVESTING = 'harvesting',   // 收获 - 采收作物
  OTHER = 'other'              // 其他操作
}

/**
 * 农事操作记录接口
 * 记录一次农事操作的详细信息
 */
export interface FarmOperation {
  id: string;                  // 操作记录唯一标识
  fieldId: string;             // 关联的地块ID
  type: FarmOperationType;     // 操作类型
  description: string;         // 操作描述
  operationDate: number;       // 操作日期(时间戳)
  operator: string;            // 操作人姓名
  cost?: number;               // 操作成本(元,可选)
  notes?: string;              // 备注信息(可选)
  images?: string[];           // 操作图片(可选)
}

/**
 * 成本记录接口
 * 记录农业生产中的各项成本
 */
export interface CostRecord {
  id: string;                  // 成本记录唯一标识
  fieldId: string;             // 关联的地块ID
  category: string;            // 成本类别:种子、肥料、农药、人工、机械等
  amount: number;              // 金额(元)
  date: number;                // 发生日期(时间戳)
  description: string;         // 描述信息
  notes?: string;              // 备注信息(可选)
}

/**
 * 销售记录接口
 * 记录农产品的销售信息
 */
export interface SalesRecord {
  id: string;                  // 销售记录唯一标识
  fieldId: string;             // 关联的地块ID
  cropName: string;            // 作物名称
  quantity: number;            // 销售数量(公斤)
  unitPrice: number;           // 单价(元/公斤)
  totalAmount: number;         // 总金额(元)
  saleDate: number;            // 销售日期(时间戳)
  buyer?: string;              // 买家信息(可选)
  notes?: string;              // 备注信息(可选)
}

设计要点说明

设计要点 说明
枚举类型 使用 enum 定义固定选项,避免输入错误,提高代码可读性
可选属性 使用 ? 标记非必填字段,如 latitude?longitude?
关联关系 通过ID建立关联,如 fieldId 关联地块,避免深层嵌套
时间存储 统一使用时间戳(number类型)存储日期时间,便于计算和排序
扩展字段 预留 notesimages 字段,方便后续扩展功能

二、创建地块服务

服务层采用单例模式,负责处理地块相关的业务逻辑和数据操作。这种设计确保了全局只有一个服务实例,便于状态管理和数据一致性。

2.1 创建 FieldService.ets

操作步骤

  1. entry/src/main/ets/services/ 目录下创建新文件
  2. 命名为 FieldService.ets
  3. 输入以下代码
import { FieldInfo, FarmOperation, CostRecord, SalesRecord } from '../models/ProfessionalAgricultureModels';
import { StorageUtil } from '../utils/StorageUtil';

/**
 * 地块统计信息接口
 * 用于展示地块的整体情况
 */
export interface FieldStats {
  totalFields: number;        // 地块总数
  totalArea: number;          // 总面积(亩)
  activeFields: number;       // 种植中的地块数量
  idleFields: number;         // 闲置的地块数量
  totalCost: number;          // 总成本(元)
  totalIncome: number;        // 总收入(元)
}

/**
 * 地块管理服务
 * 采用单例模式,提供地块和农事记录相关的业务逻辑
 */
export class FieldService {
  // 单例实例
  private static instance: FieldService;

  // 私有构造函数,防止外部直接创建实例
  private constructor() {}

  /**
   * 获取单例实例
   * @returns FieldService 单例对象
   */
  public static getInstance(): FieldService {
    if (!FieldService.instance) {
      FieldService.instance = new FieldService();
    }
    return FieldService.instance;
  }

  /**
   * 获取所有地块列表
   * @returns Promise<FieldInfo[]> 地块列表
   */
  async getAllFields(): Promise<FieldInfo[]> {
    // 从本地存储获取地块数据
    const fields = await StorageUtil.getObject<FieldInfo[]>('fields_list', []);
    return fields || [];
  }

  /**
   * 根据ID获取单个地块信息
   * @param id 地块ID
   * @returns Promise<FieldInfo | null> 地块信息,未找到返回null
   */
  async getFieldById(id: string): Promise<FieldInfo | null> {
    const fields = await this.getAllFields();
    return fields.find(f => f.id === id) || null;
  }

  /**
   * 添加新地块
   * @param field 地块信息对象
   * @returns Promise<boolean> 成功返回true,失败返回false
   */
  async addField(field: FieldInfo): Promise<boolean> {
    try {
      // 获取现有地块列表
      const fields = await this.getAllFields();
      // 添加新地块到列表末尾
      fields.push(field);
      // 保存到本地存储
      await StorageUtil.saveObject('fields_list', fields);
      console.info('[FieldService] Field added:', field.id);
      return true;
    } catch (error) {
      console.error('[FieldService] Failed to add field:', error);
      return false;
    }
  }

  /**
   * 更新地块信息
   * @param field 要更新的地块信息对象
   * @returns Promise<boolean> 成功返回true,失败返回false
   */
  async updateField(field: FieldInfo): Promise<boolean> {
    try {
      // 获取现有地块列表
      const fields = await this.getAllFields();
      // 查找要更新的地块索引
      const index = fields.findIndex(f => f.id === field.id);
      if (index !== -1) {
        // 更新地块信息
        fields[index] = field;
        // 保存到本地存储
        await StorageUtil.saveObject('fields_list', fields);
        console.info('[FieldService] Field updated:', field.id);
        return true;
      }
      // 未找到对应地块
      return false;
    } catch (error) {
      console.error('[FieldService] Failed to update field:', error);
      return false;
    }
  }

  /**
   * 删除地块
   * @param id 要删除的地块ID
   * @returns Promise<boolean> 成功返回true,失败返回false
   */
  async deleteField(id: string): Promise<boolean> {
    try {
      // 获取现有地块列表
      const fields = await this.getAllFields();
      // 过滤掉要删除的地块
      const filteredFields = fields.filter(f => f.id !== id);
      // 保存到本地存储
      await StorageUtil.saveObject('fields_list', filteredFields);
      console.info('[FieldService] Field deleted:', id);
      return true;
    } catch (error) {
      console.error('[FieldService] Failed to delete field:', error);
      return false;
    }
  }

  /**
   * 获取地块统计信息
   * @returns Promise<FieldStats> 统计信息对象
   */
  async getFieldStats(): Promise<FieldStats> {
    // 获取所有地块
    const fields = await this.getAllFields();

    // 计算统计数据
    const stats: FieldStats = {
      totalFields: fields.length,  // 地块总数
      // 总面积:累加所有地块面积
      totalArea: fields.reduce((sum, f) => sum + f.area, 0),
      // 种植中:筛选状态为"种植中"的地块数量
      activeFields: fields.filter(f => f.status === '种植中').length,
      // 闲置:筛选状态为"闲置"的地块数量
      idleFields: fields.filter(f => f.status === '闲置').length,
      totalCost: 0,  // 成本统计(后续实现)
      totalIncome: 0  // 收入统计(后续实现)
    };

    return stats;
  }

  /**
   * 搜索地块
   * 根据关键词搜索地块名称、位置或作物
   * @param keyword 搜索关键词
   * @returns Promise<FieldInfo[]> 搜索结果列表
   */
  async searchFields(keyword: string): Promise<FieldInfo[]> {
    const fields = await this.getAllFields();
    // 如果关键词为空,返回所有地块
    if (!keyword) {
      return fields;
    }

    // 过滤匹配的地块
    return fields.filter(f =>
      f.name.includes(keyword) ||                    // 匹配地块名称
      f.location.includes(keyword) ||                // 匹配位置
      (f.currentCrop && f.currentCrop.name.includes(keyword))  // 匹配作物名称
    );
  }

  /**
   * 按状态筛选地块
   * @param status 地块状态(全部、种植中、闲置、准备中)
   * @returns Promise<FieldInfo[]> 筛选结果列表
   */
  async filterFieldsByStatus(status: string): Promise<FieldInfo[]> {
    const fields = await this.getAllFields();
    // 如果状态为"全部"或为空,返回所有地块
    if (!status || status === '全部') {
      return fields;
    }
    // 返回指定状态的地块
    return fields.filter(f => f.status === status);
  }
}

服务设计要点

特性 说明
单例模式 确保全局只有一个服务实例,避免数据不一致
异步方法 所有数据操作都是异步的,使用 async/await
错误处理 try-catch 捕获异常,返回 boolean 表示操作结果
日志记录 使用 console.info/error 记录操作日志,便于调试

三、创建地块列表页面

地块列表页面是用户查看和管理地块的主要入口。我们将创建一个包含统计信息、搜索筛选和地块列表的完整页面。

3.1 创建 Management 目录

操作步骤

  1. entry/src/main/ets/pages/ 下创建 Management 文件夹
  2. 右键点击 pages 文件夹 → New → Directory
  3. 输入 Management,点击 OK

3.2 创建 FieldManagementPage.ets

操作步骤

  1. 右键点击 Management 文件夹 → New → ArkTS File
  2. 输入文件名 FieldManagementPage,点击 OK
  3. 输入以下代码
import { router } from '@kit.ArkUI';
import { FieldInfo, FieldType, IrrigationSystem, CropType, CropGrowthStage, CropHealthStatus } from '../../models/ProfessionalAgricultureModels';
import { FieldService, FieldStats } from '../../services/FieldService';
import { CommonCard, StatusBadge, InfoRow, EmptyView, LoadingView, getBadgeStyle, BadgeType } from '../../components/CommonComponents';

/**
 * 地块管理页面
 * 显示地块列表、统计信息,支持搜索和筛选功能
 */
@Entry
@ComponentV2
struct FieldManagementPage {
  // 状态变量:地块列表
  @Local fields: FieldInfo[] = [];
  // 状态变量:筛选后的地块列表
  @Local filteredFields: FieldInfo[] = [];
  // 状态变量:统计信息
  @Local stats: FieldStats | null = null;
  // 状态变量:加载状态
  @Local isLoading: boolean = true;
  // 状态变量:搜索关键词
  @Local searchKeyword: string = '';
  // 状态变量:选中的状态筛选
  @Local selectedStatus: string = '全部';

  // ���务实例
  private fieldService = FieldService.getInstance();
  // 状态选项
  private statusOptions: string[] = ['全部', '种植中', '闲置', '准备中'];

  /**
   * 页面即将出现时加载数据
   */
  async aboutToAppear(): Promise<void> {
    await this.loadData();
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('地块管理')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor($r('app.color.text_primary'))
          .layoutWeight(1)

        Button('添加地块')
          .fontSize(14)
          .backgroundColor($r('app.color.primary_professional'))
          .borderRadius(8)
          .padding({ left: 16, right: 16, top: 8, bottom: 8 })
          .onClick(() => {
            this.onAddField();
          })
      }
      .width('100%')
      .padding(16)

      // 统计卡片
      if (this.stats) {
        this.buildStatsCard()
      }

      // 搜索和筛选区域
      Column({ space: 12 }) {
        // 搜索框
        TextInput({ placeholder: '搜索地块名称、位置或作物' })
          .onChange((value: string) => {
            // 用户输入时更新关键词并筛选
            this.searchKeyword = value;
            this.filterFields();
          })

        // 状态筛选按钮组
        Row({ space: 8 }) {
          ForEach(this.statusOptions, (status: string) => {
            Button(status)
              .fontSize(14)
              // 选中状态显示主题色,未选中显示背景色
              .backgroundColor(this.selectedStatus === status ?
                $r('app.color.primary_professional') : $r('app.color.background'))
              // 选中状态文字白色,未选中显示文字色
              .fontColor(this.selectedStatus === status ?
                Color.White : $r('app.color.text_primary'))
              .borderRadius(16)
              .padding({ left: 16, right: 16, top: 6, bottom: 6 })
              .onClick(() => {
                // 点击切换筛选状态
                this.selectedStatus = status;
                this.filterFields();
              })
          })
        }
      }
      .width('100%')
      .padding({ left: 16, right: 16 })

      // 地块列表区域
      if (this.isLoading) {
        // 加载中状态
        LoadingView({ message: '加载中...' })
          .layoutWeight(1)
      } else if (this.filteredFields.length === 0) {
        // 空状态提示
        EmptyView({
          icon: '🌾',
          message: '还没有地块数据',
          actionText: '添加地块',
          onAction: () => {
            this.onAddField();
          }
        })
        .layoutWeight(1)
      } else {
        // 地块列表
        List({ space: 12 }) {
          ForEach(this.filteredFields, (field: FieldInfo) => {
            ListItem() {
              this.buildFieldCard(field)
            }
          })
        }
        .width('100%')
        .layoutWeight(1)
        .padding(16)
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.background'))
  }

  /**
   * 构建统计卡片
   * 显示地块总数、总面积、种植中数量、闲置数量
   */
  @Builder
  buildStatsCard() {
    CommonCard({ title: '统计概览', icon: '📊' }) {
      Row({ space: 16 }) {
        this.buildStatItem('地块总数', `${this.stats!.totalFields}`, '个')
        this.buildStatItem('总面积', `${this.stats!.totalArea.toFixed(1)}`, '亩')
        this.buildStatItem('种植中', `${this.stats!.activeFields}`, '个')
        this.buildStatItem('闲置', `${this.stats!.idleFields}`, '个')
      }
      .width('100%')
    }
    .margin({ left: 16, right: 16, bottom: 12 })
  }

  /**
   * 构建单个统计项
   * @param label 标签文字
   * @param value 数值
   * @param unit 单位
   */
  @Builder
  buildStatItem(label: string, value: string, unit: string) {
    Column({ space: 4 }) {
      // 标签
      Text(label)
        .fontSize(12)
        .fontColor($r('app.color.text_secondary'))

      // 数值和单位
      Row({ space: 4 }) {
        Text(value)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor($r('app.color.primary_professional'))

        Text(unit)
          .fontSize(12)
          .fontColor($r('app.color.text_secondary'))
      }
    }
    .layoutWeight(1)
  }

  /**
   * 构建地块卡片
   * @param field 地块信息
   */
  @Builder
  buildFieldCard(field: FieldInfo) {
    CommonCard({
      onClick: () => {
        this.onFieldClick(field);
      }
    }) {
      Column({ space: 12 }) {
        // 标题行:地块名称 + 状态标签
        Row() {
          Text(field.name)
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor($r('app.color.text_primary'))
            .layoutWeight(1)

          // 状态徽章
          StatusBadge({
            text: field.status,
            textColor: this.getStatusColor(field.status).textColor,
            backgroundColor: this.getStatusColor(field.status).backgroundColor
          })
        }
        .width('100%')

        // 信息行
        InfoRow({ label: '面积', value: `${field.area}`, icon: '📏' })
        InfoRow({ label: '位置', value: field.location, icon: '📍' })

        // 如果有作物,显示作物信息
        if (field.currentCrop) {
          InfoRow({
            label: '当前作物',
            value: field.currentCrop.name,
            icon: '🌱'
          })
        }
      }
    }
  }

  /**
   * 根据状态获取颜色样式
   * @param status 地块状态
   * @returns 文字颜色和背景颜色
   */
  private getStatusColor(status: string): { textColor: string, backgroundColor: string } {
    switch (status) {
      case '种植中':
        return getBadgeStyle(BadgeType.SUCCESS);  // 绿色
      case '闲置':
        return getBadgeStyle(BadgeType.INFO);     // 蓝色
      case '准备中':
        return getBadgeStyle(BadgeType.WARNING);  // 橙色
      default:
        return getBadgeStyle(BadgeType.DEFAULT);   // 灰色
    }
  }

  /**
   * 加载数据
   * 获取地块列表和统计信息
   */
  async loadData(): Promise<void> {
    try {
      this.isLoading = true;
      // 获取所有地块
      this.fields = await this.fieldService.getAllFields();
      // 获取统计信息
      this.stats = await this.fieldService.getFieldStats();
      // 应用筛选
      this.filterFields();
    } catch (error) {
      console.error('[FieldManagementPage] Load data failed:', error);
    } finally {
      this.isLoading = false;
    }
  }

  /**
   * 筛选地块
   * 根据状态和关键词进行筛选
   */
  filterFields(): void {
    let result = this.fields;

    // 按状态筛选
    if (this.selectedStatus !== '全部') {
      result = result.filter(f => f.status === this.selectedStatus);
    }

    // 按关键词搜索
    if (this.searchKeyword) {
      result = result.filter(f =>
        f.name.includes(this.searchKeyword) ||
        f.location.includes(this.searchKeyword) ||
        (f.currentCrop && f.currentCrop.name.includes(this.searchKeyword))
      );
    }

    this.filteredFields = result;
  }

  /**
   * 添加地块按钮点击事件
   */
  onAddField(): void {
    router.pushUrl({
      url: 'pages/Management/AddFieldPage'
    });
  }

  /**
   * 地块卡片点击事件
   * @param field 被点击的地块
   */
  onFieldClick(field: FieldInfo): void {
    router.pushUrl({
      url: 'pages/Management/FieldDetailPage',
      params: { fieldId: field.id }
    });
  }
}

页面结构说明

┌─────────────────────────────────────┐
│  地块管理              [+ 添加地块]  │  ← 标题栏
├─────────────────────────────────────┤
│  📊 统计概览                         │  ← 统计卡片
│  [10] 个  [120.5] 亩  [6] 种植 [4]  │
├─────────────────────────────────────┤
│  [搜索框]                           │  ← 搜索区域
│  [全部] [种植中] [闲置] [准备中]    │  ← 筛选按钮
├─────────────────────────────────────┤
│  ┌─────────────────────────────┐   │
│  │ 东区1号田        [种植中]    │   │  ← 地块卡片
│  │ 📏 面积: 10.5 亩            │   │
│  │ 📍 位置: 湖北省武汉市...   │   │
│  │ 🌱 当前作物: 水稻           │   │
│  └─────────────────────────────┘   │
│  ┌─────────────────────────────┐   │
│  │ 西区果园          [闲置]     │   │
│  │ 📏 面积: 15.0 亩            │   │
│  │ 📍 位置: 湖北省武汉市...   │   │
│  └─────────────────────────────┘   │
│                ...                 │
└─────────────────────────────────────┘

四、配置页面路由

在 HarmonyOS 中,所有可访问的页面都需要在路由配置文件中注册。

4.1 修改 main_pages.json

操作步骤

  1. 打开 entry/src/main/resources/base/profile/main_pages.json
  2. src 数组中添加新页面路由
{
  "src": [
    "pages/WelcomePage",
    "pages/Index",
    "pages/Map/FieldMapPage",
    "pages/Management/FieldManagementPage",
    "pages/OnboardingFlow/ModeSelectionPage",
    "pages/OnboardingFlow/LocationPage",
    "pages/OnboardingFlow/GoalsPage"
  ]
}

路由配置说明

  • 每个页面路径必须以 pages/ 开头
  • 路径与文件系统的目录结构对应
  • 页面按注册顺序可用于返回栈导航

五、添加测试数据

为了方便测试和演示,我们添加一些测试数据。

5.1 在 FieldManagementPage 中添加测试数据方法

操作步骤

FieldManagementPage.ets 中的 aboutToAppear 方法前添加以下代码:

/**
 * 添加测试数据
 * 仅在首次运行时添加,用于演示和测试
 */
async addTestData(): Promise<void> {
  // 检查是否已有数据
  const fields = await this.fieldService.getAllFields();
  if (fields.length === 0) {
    // 创建测试地块数据
    const testFields: FieldInfo[] = [
      {
        id: '1',
        name: '东区1号田',
        type: FieldType.FARMLAND,
        area: 10.5,
        location: '湖北省武汉市东西湖区',
        latitude: 30.6228,
        longitude: 114.1377,
        soilType: '黏土',
        irrigationSystem: IrrigationSystem.DRIP,
        status: '种植中',
        createdAt: Date.now(),
        lastUpdatedAt: Date.now(),
        currentCrop: {
          id: 'c1',
          name: '水稻',
          type: CropType.GRAIN,
          variety: '杂交稻',
          plantingDate: Date.now() - 30 * 24 * 60 * 60 * 1000,  // 30天前种植
          expectedHarvestDate: Date.now() + 90 * 24 * 60 * 60 * 1000,  // 90天后收获
          growthStage: CropGrowthStage.GROWING,
          healthStatus: CropHealthStatus.HEALTHY,
          area: 10.5,
          expectedYield: 5000
        }
      },
      {
        id: '2',
        name: '西区果园',
        type: FieldType.ORCHARD,
        area: 15.0,
        location: '湖北省武汉市江夏区',
        soilType: '沙壤土',
        irrigationSystem: IrrigationSystem.SPRINKLER,
        status: '闲置',
        createdAt: Date.now(),
        lastUpdatedAt: Date.now()
      },
      {
        id: '3',
        name: '南区蔬菜大棚',
        type: FieldType.GREENHOUSE,
        area: 3.5,
        location: '湖北省武汉市蔡甸区',
        soilType: '壤土',
        irrigationSystem: IrrigationSystem.DRIP,
        status: '种植中',
        createdAt: Date.now(),
        lastUpdatedAt: Date.now(),
        currentCrop: {
          id: 'c2',
          name: '番茄',
          type: CropType.VEGETABLE,
          variety: '大红番茄',
          plantingDate: Date.now() - 45 * 24 * 60 * 60 * 1000,
          expectedHarvestDate: Date.now() + 15 * 24 * 60 * 60 * 1000,
          growthStage: CropGrowthStage.FRUITING,
          healthStatus: CropHealthStatus.HEALTHY,
          area: 3.5,
          expectedYield: 8000
        }
      },
      {
        id: '4',
        name: '北区准备田',
        type: FieldType.FARMLAND,
        area: 8.0,
        location: '湖北省武汉市黄陂区',
        soilType: '黏土',
        irrigationSystem: IrrigationSystem.NONE,
        status: '准备中',
        createdAt: Date.now(),
        lastUpdatedAt: Date.now()
      }
    ];

    // 逐个添加地块到存储
    for (const field of testFields) {
      await this.fieldService.addField(field);
    }

    console.info('[FieldManagementPage] Test data added:', testFields.length);
  }
}

然后修改 aboutToAppear 方法,添加测试数据调用:

async aboutToAppear(): Promise<void> {
  await this.addTestData();  // 先添加测试数据
  await this.loadData();    // 再加载数据
}

六、运行与测试

6.1 测试步骤

  1. 启动应用

    • 点击 DevEco Studio 工具栏的运行按钮
    • 或按快捷键 Shift + F10
  2. 进入地块管理页面

    • 完成引导流程后,在首页找到地块管理入口
    • 或直接在地址栏输入页面路径
  3. 查看统计信息

    • 页面顶部应显示统计卡片
    • 检查地块总数、总面积等数据是否正确
  4. 测试搜索功能

    • 在搜索框输入"水稻"
    • 列表应只显示含有"水稻"的地块
    • 清空搜索框,列表应恢复显示全部
  5. 测试状态筛选

    • 点击"种植中"按钮
    • 列表应只显示状态为"种植中"的地块
    • 点击"全部"按钮,列表应显示所有地块
  6. 测试地块点击

    • 点击任意地块卡片
    • 应跳转到地块详情页面(后续实现)

6.2 预期效果

功能 预期效果
统计卡片 显示 4 个地块,37.0 亩总面积,2 个种植中,1 个闲置
搜索功能 输入关键词后实时筛选结果
筛选功能 点击状态按钮后切换显示对应地块
空状态 删除所有数据后显示空状态提示
卡片点击 点击后有响应(跳转详情页)

七、常见问题与解决方案

7.1 页面无法加载

问题:运行后页面显示白屏或报错

解决方案

  1. 检查 main_pages.json 是否正确配置页面路由
  2. 确认文件路径与路由配置一致
  3. 查看控制台日志,确认具体错误信息

7.2 组件找不到

问题:编译报错 “Cannot find module”

解决方案

  1. 确认 CommonComponents.ets 已在第3篇中创建
  2. 检查 import 路径是否正确
  3. 尝试重新构建项目:Build → Rebuild Project

7.3 数据不显示

问题:页面正常但列表为空

解决方案

  1. 确认 addTestData() 方法被调用
  2. 检查 StorageUtil 是否正确初始化
  3. 使用开发者工具查看本地存储

7.4 筛选功能不生效

问题:点击筛选按钮后列表没有变化

解决方案

  1. 检查 filterFields() 方法中的筛选逻辑
  2. 确认状态字符串匹配(注意中文字符)
  3. 在方法中添加 console.log 调试

八、总结

本篇教程完成了:

  • ✅ 地块数据模型设计(枚举、接口)
  • ✅ 地块服务实现(单例模式、CRUD操作)
  • ✅ 地块列表页面(统计、搜索、筛选)
  • ✅ 测试数据添加
  • ✅ 页面路由配置

关键技术点

技术点 说明
数据模型 使用 enum 和 interface 定义清晰的数据结构
单例模式 服务层采用单例确保全局唯一实例
组件复用 使用 @Builder 构建可复用的UI组件
状态管理 使用 @Local 管理页面状态
数据持久化 通过 StorageUtil 实现本地数据存储

九、下一步

在下一篇教程中,我们将学习:

  • 地块添加页面实现
  • 地块编辑页面实现
  • 地块详情页面实现
  • 表单验证与错误处理
  • 地图选择位置功能

准备工作

  • 熟悉 ArkTS 表单组件(TextInput、Select 等)
  • 了解表单验证的基本方法
  • 准备测试地块数据

参考资料


教程版本:v1.0
更新日期:2026-01
适用版本:DevEco Studio 5.0+, HarmonyOS API 17+

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐