HarmonyOS智慧农业管理应用开发教程--高高种地--第14篇:数据分析与可视化

第14篇:数据分析与可视化

📚 本篇导读

在前面的教程中,我们已经实现了地块管理、农事记录、成本核算、销售管理等核心功能。这些功能产生了大量的数据,如何将这些数据整合、分析并以可视化的方式呈现给用户,帮助用户做出更好的决策,是本篇要解决的问题。

本篇将实现:

  • 📊 数据总览模块(地块、成本、收入、利润等核心指标)
  • 💰 经济效益分析(成本、收入、利润率、投入产出比)
  • 🌾 生产分析(单位面积产出、单位面积成本)
  • ✅ 任务完成情况分析
  • 🎯 双模式支持(专业农业模式 vs 家庭园艺模式)

🎯 学习目标

完成本篇教程后,你将掌握:

  1. 如何整合多个Service的数据进行综合分析
  2. 如何设计和实现数据统计功能
  3. 如何使用卡片式布局展示统计数据
  4. 如何实现双模式的数据分析页面
  5. 如何计算和展示经济效益指标

📐 页面设计要点

页面结构

复制代码
┌─────────────────────────────────┐
│  < 返回    数据分析      📊    │  ← 导航栏
├─────────────────────────────────┤
│  数据总览                       │
│  ┌─────────┬─────────┐         │
│  │地块总数 │种植面积 │         │  ← 统计卡片
│  │  10块   │ 150.5亩 │         │
│  ├─────────┼─────────┤         │
│  │ 总成本  │ 总收入  │         │
│  │ 12.5万  │ 18.3万  │         │
│  └─────────┴─────────┘         │
├─────────────────────────────────┤
│  经济效益分析                   │
│  总成本: ¥125,000.00           │
│  总收入: ¥183,000.00           │  ← 经济指标
│  净利润: ¥58,000.00            │
│  利润率: 46.40%                │
├─────────────────────────────────┤
│  生产分析                       │
│  📊 单位面积产出               │  ← 生产指标
│      1,216.31 元/亩            │
│  💰 单位面积成本               │
│      830.56 元/亩              │
├─────────────────────────────────┤
│  任务完成情况                   │
│  任务完成率        85%         │  ← 任务分析
│  ██████████░░░  不错!继续保持  │
└─────────────────────────────────┘

双模式设计

特性 专业农业模式 家庭园艺模式
核心指标 地块、面积、成本、收入、利润 植物数量、任务完成率
经济分析 完整的成本收入分析 不展示
生产分析 单位面积产出/成本 不展示
任务分析 通用展示 通用展示
植物分析 不展示 植物养护分析

💾 数据模型

数据总览接口

typescript 复制代码
// 数据总览统计
interface DataSummary {
  // 地块统计(专业农业模式)
  totalFields: number;      // 地块总数
  totalArea: number;        // 总面积(亩)
  
  // 财务统计(专业农业模式)
  totalCost: number;        // 总成本(元)
  totalIncome: number;      // 总收入(元)
  profit: number;           // 净利润(元)
  
  // 植物统计(家庭园艺模式)
  totalPlants: number;      // 植物总数
  
  // 任务统计(通用)
  taskCompletionRate: number;  // 任务完成率(0-100)
}

相关Service说明

本页面需要调用以下Service:

  • FieldService: 获取地块数据和统计信息
  • TaskService: 获取任务完成情况
  • PlantService: 获取植物数据(家庭园艺模式)
  • 通过FieldService.getFieldStats()间接获取成本和收入数据

🛠️ 核心功能实现

1. 数据分析页面 - DataAnalysisPage

完整页面代码
typescript 复制代码
/**
 * 数据分析页面
 * 为专业农业模式提供数据统计和分析功能
 */

import { router } from '@kit.ArkUI';
import { FieldService } from '../../services/FieldService';
import { PlantService } from '../../services/PlantService';
import { TaskService } from '../../services/TaskService';
import { StorageUtil } from '../../utils/StorageUtil';
import { AppMode } from '../../models/CommonModels';

/**
 * 数据总览统计接口
 */
interface DataSummary {
  totalFields: number;           // 地块总数
  totalArea: number;             // 总面积(亩)
  totalPlants: number;           // 植物总数
  totalCost: number;             // 总成本(元)
  totalIncome: number;           // 总收入(元)
  profit: number;                // 净利润(元)
  taskCompletionRate: number;    // 任务完成率(0-100)
}

@Entry
@ComponentV2
export struct DataAnalysisPage {
  // ========== 状态变量 ==========
  
  @Local userMode: AppMode = AppMode.HOME_GARDENING;  // 用户模式
  @Local summary: DataSummary = {                      // 统计数据
    totalFields: 0,
    totalArea: 0,
    totalPlants: 0,
    totalCost: 0,
    totalIncome: 0,
    profit: 0,
    taskCompletionRate: 0
  };
  @Local isLoading: boolean = true;                    // 加载状态

  // ========== Service实例 ==========
  
  private fieldService = FieldService.getInstance();
  private plantService = PlantService.getInstance();
  private taskService = TaskService.getInstance();

  // ========== 生命周期 ==========
  
  /**
   * 页面即将显示时调用
   */
  async aboutToAppear(): Promise<void> {
    // 读取用户模式
    const mode = await StorageUtil.getString('user_mode', AppMode.HOME_GARDENING);
    this.userMode = mode as AppMode;
    
    // 加载数据
    await this.loadData();
  }

  /**
   * 加载统计数据
   */
  async loadData(): Promise<void> {
    try {
      if (this.userMode === AppMode.PROFESSIONAL_AGRICULTURE) {
        // 专业农业模式数据
        const fields = await this.fieldService.getAllFields();
        const stats = await this.fieldService.getFieldStats();

        this.summary.totalFields = fields.length;
        this.summary.totalArea = stats.totalArea;
        this.summary.totalCost = stats.totalCost;
        this.summary.totalIncome = stats.totalIncome;
        this.summary.profit = stats.totalIncome - stats.totalCost;
      } else {
        // 家庭园艺模式数据
        const plants = await this.plantService.getAllPlants();
        this.summary.totalPlants = plants.length;
      }

      // 任务完成率(通用)
      const taskStats = await this.taskService.getTaskStats();
      this.summary.taskCompletionRate = taskStats.total > 0 ?
        Math.round((taskStats.completed / taskStats.total) * 100) : 0;

      this.isLoading = false;
    } catch (error) {
      console.error('Failed to load data:', error);
      this.isLoading = false;
    }
  }

  // ========== 页面构建 ==========
  
  build() {
    Column() {
      // 顶部导航栏
      this.buildHeader()

      // 内容区域
      if (this.isLoading) {
        this.buildLoading()
      } else {
        Scroll() {
          Column({ space: 16 }) {
            // 数据总览
            this.buildOverview()
            
            // 根据模式显示不同内容
            if (this.userMode === AppMode.PROFESSIONAL_AGRICULTURE) {
              // 专业农业模式
              this.buildEconomicAnalysis()   // 经济效益分析
              this.buildProductionAnalysis() // 生产分析
            } else {
              // 家庭园艺模式
              this.buildPlantAnalysis()      // 植物养护分析
            }
            
            // 任务分析(通用)
            this.buildTaskAnalysis()
          }
          .padding({ left: 16, right: 16, top: 16, bottom: 16 })
        }
        .layoutWeight(1)
        .scrollBar(BarState.Auto)
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.background'))
  }

  // ========== 导航栏 ==========
  
  @Builder
  buildHeader() {
    Row() {
      // 返回按钮
      Button('< 返回')
        .backgroundColor(Color.Transparent)
        .fontColor($r('app.color.text_primary'))
        .onClick(() => {
          router.back();
        })

      // 标题
      Text('数据分析')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .layoutWeight(1)
        .textAlign(TextAlign.Center)

      // 占位按钮(保持布局平衡)
      Button('📊')
        .backgroundColor(Color.Transparent)
        .fontColor($r('app.color.text_primary'))
    }
    .width('100%')
    .height(56)
    .padding({ left: 16, right: 16 })
    .backgroundColor($r('app.color.card_background'))
    .shadow({ radius: 4, color: $r('app.color.shadow_light'), offsetY: 2 })
  }

  // ========== 加载状态 ==========
  
  @Builder
  buildLoading() {
    Column() {
      Text('加载中...')
        .fontSize(16)
        .fontColor($r('app.color.text_secondary'))
    }
    .width('100%')
    .layoutWeight(1)
    .justifyContent(FlexAlign.Center)
  }

  // ========== 数据总览 ==========
  
  @Builder
  buildOverview() {
    Column({ space: 12 }) {
      // 标题
      Text('数据总览')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .width('100%')

      // 统计卡片网格
      if (this.userMode === AppMode.PROFESSIONAL_AGRICULTURE) {
        // 专业农业模式: 2x2网格
        Grid() {
          GridItem() {
            this.buildStatCard('地块总数', this.summary.totalFields.toString(), '块', '#2196F3')
          }
          GridItem() {
            this.buildStatCard('种植面积', this.summary.totalArea.toFixed(1), '亩', '#4CAF50')
          }
          GridItem() {
            this.buildStatCard('总成本', (this.summary.totalCost / 10000).toFixed(2), '万元', '#FF9800')
          }
          GridItem() {
            this.buildStatCard('总收入', (this.summary.totalIncome / 10000).toFixed(2), '万元', '#9C27B0')
          }
        }
        .columnsTemplate('1fr 1fr')
        .rowsTemplate('1fr 1fr')
        .columnsGap(12)
        .rowsGap(12)
        .width('100%')
        .height(200)
      } else {
        // 家庭园艺模式: 1x2网格
        Grid() {
          GridItem() {
            this.buildStatCard('植物总数', this.summary.totalPlants.toString(), '株', '#4CAF50')
          }
          GridItem() {
            this.buildStatCard('任务完成率', this.summary.taskCompletionRate.toString(), '%', '#2196F3')
          }
        }
        .columnsTemplate('1fr 1fr')
        .columnsGap(12)
        .width('100%')
        .height(100)
      }
    }
    .width('100%')
    .padding(16)
    .backgroundColor($r('app.color.card_background'))
    .borderRadius(12)
  }

  /**
   * 统计卡片组件
   * @param label 标签文字
   * @param value 数值
   * @param unit 单位
   * @param color 颜色
   */
  @Builder
  buildStatCard(label: string, value: string, unit: string, color: string) {
    Column() {
      // 数值
      Text(value)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor(color)
      
      // 单位
      Text(unit)
        .fontSize(12)
        .fontColor($r('app.color.text_secondary'))
        .margin({ top: 4 })
      
      // 标签
      Text(label)
        .fontSize(13)
        .fontColor($r('app.color.text_primary'))
        .margin({ top: 8 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor($r('app.color.background'))
    .borderRadius(8)
  }

  // ========== 经济效益分析 ==========
  
  @Builder
  buildEconomicAnalysis() {
    Column({ space: 12 }) {
      // 标题
      Text('经济效益分析')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .width('100%')

      // 经济指标列表
      Column({ space: 16 }) {
        // 总成本
        Row() {
          Text('总成本')
            .fontSize(14)
            .fontColor($r('app.color.text_secondary'))
            .width(100)
          Text('¥' + this.summary.totalCost.toFixed(2))
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FF9800')
            .layoutWeight(1)
        }
        .width('100%')

        // 总收入
        Row() {
          Text('总收入')
            .fontSize(14)
            .fontColor($r('app.color.text_secondary'))
            .width(100)
          Text('¥' + this.summary.totalIncome.toFixed(2))
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#4CAF50')
            .layoutWeight(1)
        }
        .width('100%')

        // 净利润
        Row() {
          Text('净利润')
            .fontSize(14)
            .fontColor($r('app.color.text_secondary'))
            .width(100)
          Text('¥' + this.summary.profit.toFixed(2))
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor(this.summary.profit >= 0 ? '#2196F3' : '#F44336')
            .layoutWeight(1)
        }
        .width('100%')

        // 利润率
        Row() {
          Text('利润率')
            .fontSize(14)
            .fontColor($r('app.color.text_secondary'))
            .width(100)
          Text(this.getProfitRate() + '%')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor(this.summary.profit >= 0 ? '#2196F3' : '#F44336')
            .layoutWeight(1)
        }
        .width('100%')
      }
      .width('100%')
      .padding(16)
      .backgroundColor($r('app.color.background'))
      .borderRadius(8)
    }
    .width('100%')
    .padding(16)
    .backgroundColor($r('app.color.card_background'))
    .borderRadius(12)
  }

  // ========== 生产分析 ==========
  
  @Builder
  buildProductionAnalysis() {
    Column({ space: 12 }) {
      // 标题
      Text('生产分析')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .width('100%')

      Column({ space: 12 }) {
        // 单位面积产出
        Row() {
          Text('📊')
            .fontSize(32)
            .width(60)
            .textAlign(TextAlign.Center)

          Column({ space: 4 }) {
            Text('单位面积产出')
              .fontSize(14)
              .fontColor($r('app.color.text_primary'))
            Text(this.getOutputPerUnit() + ' 元/亩')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor($r('app.color.primary_professional'))
          }
          .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)
        }
        .width('100%')
        .padding(12)
        .backgroundColor($r('app.color.background'))
        .borderRadius(8)

        // 单位面积成本
        Row() {
          Text('💰')
            .fontSize(32)
            .width(60)
            .textAlign(TextAlign.Center)

          Column({ space: 4 }) {
            Text('单位面积成本')
              .fontSize(14)
              .fontColor($r('app.color.text_primary'))
            Text(this.getCostPerUnit() + ' 元/亩')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor('#FF9800')
          }
          .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)
        }
        .width('100%')
        .padding(12)
        .backgroundColor($r('app.color.background'))
        .borderRadius(8)
      }
    }
    .width('100%')
    .padding(16)
    .backgroundColor($r('app.color.card_background'))
    .borderRadius(12)
  }

  // ========== 植物养护分析 ==========
  
  @Builder
  buildPlantAnalysis() {
    Column({ space: 12 }) {
      // 标题
      Text('植物养护分析')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .width('100%')

      Column({ space: 12 }) {
        // 植物总数
        Row() {
          Text('🌱')
            .fontSize(32)
            .width(60)
            .textAlign(TextAlign.Center)

          Column({ space: 4 }) {
            Text('植物总数')
              .fontSize(14)
              .fontColor($r('app.color.text_primary'))
            Text(this.summary.totalPlants + ' 株')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor($r('app.color.primary_home_gardening'))
          }
          .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)
        }
        .width('100%')
        .padding(12)
        .backgroundColor($r('app.color.background'))
        .borderRadius(8)

        // 养护任务完成率
        Row() {
          Text('✅')
            .fontSize(32)
            .width(60)
            .textAlign(TextAlign.Center)

          Column({ space: 4 }) {
            Text('养护任务完成率')
              .fontSize(14)
              .fontColor($r('app.color.text_primary'))
            Text(this.summary.taskCompletionRate + '%')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor('#4CAF50')
          }
          .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)
        }
        .width('100%')
        .padding(12)
        .backgroundColor($r('app.color.background'))
        .borderRadius(8)
      }
    }
    .width('100%')
    .padding(16)
    .backgroundColor($r('app.color.card_background'))
    .borderRadius(12)
  }

  // ========== 任务完成情况 ==========
  
  @Builder
  buildTaskAnalysis() {
    Column({ space: 12 }) {
      // 标题
      Text('任务完成情况')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .width('100%')

      Column({ space: 8 }) {
        // 完成率标题和数值
        Row() {
          Text('任务完成率')
            .fontSize(14)
            .fontColor($r('app.color.text_secondary'))
            .layoutWeight(1)
          Text(this.summary.taskCompletionRate + '%')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#4CAF50')
        }
        .width('100%')

        // 进度条
        Progress({
          value: this.summary.taskCompletionRate,
          total: 100,
          type: ProgressType.Linear
        })
          .width('100%')
          .height(8)
          .color('#4CAF50')

        // 评价文字
        Text(this.getTaskCompletionComment())
          .fontSize(13)
          .fontColor($r('app.color.text_secondary'))
          .margin({ top: 4 })
      }
      .width('100%')
      .padding(16)
      .backgroundColor($r('app.color.background'))
      .borderRadius(8)
    }
    .width('100%')
    .padding(16)
    .backgroundColor($r('app.color.card_background'))
    .borderRadius(12)
  }

  // ========== 辅助方法 ==========

  /**
   * 计算利润率
   * @returns 利润率百分比字符串
   */
  private getProfitRate(): string {
    if (this.summary.totalCost === 0) {
      return '0.00';
    }
    const rate = (this.summary.profit / this.summary.totalCost) * 100;
    return rate.toFixed(2);
  }

  /**
   * 计算单位面积产出
   * @returns 单位面积产出字符串
   */
  private getOutputPerUnit(): string {
    if (this.summary.totalArea === 0) {
      return '0.00';
    }
    const output = this.summary.totalIncome / this.summary.totalArea;
    return output.toFixed(2);
  }

  /**
   * 计算单位面积成本
   * @returns 单位面积成本字符串
   */
  private getCostPerUnit(): string {
    if (this.summary.totalArea === 0) {
      return '0.00';
    }
    const cost = this.summary.totalCost / this.summary.totalArea;
    return cost.toFixed(2);
  }

  /**
   * 获取任务完成率评价
   * @returns 评价文字
   */
  private getTaskCompletionComment(): string {
    if (this.summary.taskCompletionRate >= 90) {
      return '太棒了!任务完成度非常高';
    } else if (this.summary.taskCompletionRate >= 70) {
      return '不错!继续保持';
    } else if (this.summary.taskCompletionRate >= 50) {
      return '还有提升空间,加油!';
    } else {
      return '需要加强任务管理哦';
    }
  }
}

🔗 路由配置

EntryAbility.ets 中配置路由

确保路由配置中包含数据分析页面:

typescript 复制代码
// 在初始化路由时添加
{
  name: 'DataAnalysisPage',
  pageSourceFile: 'src/main/ets/pages/Services/DataAnalysisPage.ets',
  buildFunction: 'DataAnalysisPageBuilder'
}

从其他页面跳转到数据分析

typescript 复制代码
import { router } from '@kit.ArkUI';

// 跳转到数据分析页面
router.pushUrl({
  url: 'pages/Services/DataAnalysisPage'
});

🧪 测试步骤

1. 准备测试数据

在测试之前,确保已有以下数据:

  • 地块数据: 至少添加2-3个地块
  • 成本记录: 添加若干成本记录(不同分类)
  • 销售记录: 添加若干销售记录
  • 任务数据: 创建一些任务并完成部分任务

2. 测试专业农业模式

  1. 启动应用:确保当前为专业农业模式
  2. 进入数据分析页面:从首页或服务列表进入
  3. 检查数据总览 :
    • 地块总数是否正确
    • 种植面积是否正确
    • 总成本是否正确(所有成本记录的总和)
    • 总收入是否正确(所有销售记录的总和)
  4. 检查经济效益分析 :
    • 净利润 = 总收入 - 总成本
    • 利润率 = (净利润 / 总成本) × 100%
    • 利润为正时显示蓝色,为负时显示红色
  5. 检查生产分析 :
    • 单位面积产出 = 总收入 / 总面积
    • 单位面积成本 = 总成本 / 总面积
  6. 检查任务分析 :
    • 任务完成率 = (已完成任务数 / 总任务数) × 100%
    • 进度条显示正确
    • 评价文字根据完成率显示

3. 测试家庭园艺模式

  1. 切换到家庭园艺模式 :
    • 在设置中切换模式
    • 重新启动应用
  2. 进入数据分析页面
  3. 检查页面内容 :
    • 只显示植物总数和任务完成率
    • 不显示经济效益分析
    • 不显示生产分析
    • 显示植物养护分析
    • 显示任务分析

4. 测试边界情况

  1. 无数据情况 :
    • 删除所有地块、成本、销售记录
    • 数值应显示为0
    • 计算的指标(如利润率)应显示0.00
  2. 零除问题 :
    • 总面积为0时,单位面积产出/成本应显示0.00
    • 总成本为0时,利润率应显示0.00
  3. 加载状态 :
    • 页面打开时应显示"加载中..."
    • 数据加载完成后切换到内容

5. 测试UI响应

  1. 返回按钮:点击返回,应回到上一页
  2. 滚动:内容较多时,应能流畅滚动
  3. 布局:在不同屏幕尺寸下,卡片布局应正常显示

💡 核心知识点

1. 数据整合策略

typescript 复制代码
// 从多个Service获取数据并整合
async loadData(): Promise<void> {
  // 1. 获取地块数据
  const fields = await this.fieldService.getAllFields();
  const stats = await this.fieldService.getFieldStats();
  
  // 2. 提取所需字段
  this.summary.totalFields = fields.length;
  this.summary.totalArea = stats.totalArea;
  this.summary.totalCost = stats.totalCost;
  this.summary.totalIncome = stats.totalIncome;
  
  // 3. 计算派生字段
  this.summary.profit = stats.totalIncome - stats.totalCost;
}

要点:

  • 统一的数据加载流程
  • 错误处理
  • 加载状态管理

2. 条件渲染

typescript 复制代码
// 根据用户模式显示不同内容
if (this.userMode === AppMode.PROFESSIONAL_AGRICULTURE) {
  this.buildEconomicAnalysis()   // 专业农业模式特有
  this.buildProductionAnalysis()
} else {
  this.buildPlantAnalysis()      // 家庭园艺模式特有
}

要点:

  • 使用条件语句控制组件显示
  • 保持代码结构清晰
  • 避免重复代码

3. Grid布局

typescript 复制代码
// 2x2网格布局
Grid() {
  GridItem() { this.buildStatCard('地块总数', ...) }
  GridItem() { this.buildStatCard('种植面积', ...) }
  GridItem() { this.buildStatCard('总成本', ...) }
  GridItem() { this.buildStatCard('总收入', ...) }
}
.columnsTemplate('1fr 1fr')  // 2列,等宽
.rowsTemplate('1fr 1fr')     // 2行,等高
.columnsGap(12)              // 列间距
.rowsGap(12)                 // 行间距

要点:

  • 1fr表示占用1份可用空间
  • columnsGaprowsGap设置间距
  • Grid适合规则的卡片布局

4. 安全的数值计算

typescript 复制代码
// 避免除零错误
private getProfitRate(): string {
  if (this.summary.totalCost === 0) {
    return '0.00';  // 防止除零
  }
  const rate = (this.summary.profit / this.summary.totalCost) * 100;
  return rate.toFixed(2);  // 保留两位小数
}

要点:

  • 检查分母是否为0
  • 使用toFixed()格式化数字
  • 返回字符串便于显示

5. 数据格式化

typescript 复制代码
// 大数值以万元显示
(this.summary.totalCost / 10000).toFixed(2) + '万元'

// 保留一位小数
this.summary.totalArea.toFixed(1) + '亩'

// 整数显示
this.summary.totalFields.toString() + '块'

要点:

  • 根据业务需求选择精度
  • 添加单位便于理解
  • 统一的格式化风格

❓ 常见问题

1. 数据不准确?

问题: 显示的成本、收入数据与实际不符

解决方案:

typescript 复制代码
// 检查FieldService.getFieldStats()的实现
async getFieldStats(): Promise<any> {
  const fields = await this.getAllFields();
  
  // 确保正确累加成本和收入
  let totalCost = 0;
  let totalIncome = 0;
  
  fields.forEach(field => {
    totalCost += field.totalCost || 0;
    totalIncome += field.totalIncome || 0;
  });
  
  return { totalCost, totalIncome, ... };
}

2. 页面加载慢?

问题: 打开页面后长时间显示"加载中..."

解决方案:

  • 检查网络请求是否超时
  • 优化Service的查询效率
  • 考虑使用缓存机制
typescript 复制代码
// 添加超时处理
async loadData(): Promise<void> {
  try {
    const timeout = new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Timeout')), 5000)
    );
    
    await Promise.race([
      this.fetchData(),
      timeout
    ]);
  } catch (error) {
    console.error('Load timeout:', error);
    this.isLoading = false;
  }
}

3. 模式切换后数据不更新?

问题: 从专业农业模式切换到家庭园艺模式后,数据仍显示旧的

解决方案:

typescript 复制代码
// 在aboutToAppear中重新加载
async aboutToAppear(): Promise<void> {
  // 每次进入都重新读取模式
  const mode = await StorageUtil.getString('user_mode', AppMode.HOME_GARDENING);
  this.userMode = mode as AppMode;
  
  // 清空旧数据
  this.summary = {
    totalFields: 0,
    totalArea: 0,
    totalPlants: 0,
    totalCost: 0,
    totalIncome: 0,
    profit: 0,
    taskCompletionRate: 0
  };
  
  // 重新加载
  await this.loadData();
}

4. 利润率显示NaN或Infinity?

问题: 利润率显示为NaN或Infinity

解决方案:

typescript 复制代码
private getProfitRate(): string {
  // 检查分母
  if (this.summary.totalCost === 0 || !isFinite(this.summary.totalCost)) {
    return '0.00';
  }
  
  // 检查分子
  if (!isFinite(this.summary.profit)) {
    return '0.00';
  }
  
  const rate = (this.summary.profit / this.summary.totalCost) * 100;
  
  // 检查结果
  if (!isFinite(rate)) {
    return '0.00';
  }
  
  return rate.toFixed(2);
}

🎓 扩展功能

完成基础功能后,可以尝试以下扩展:

1. 添加图表可视化

使用图表库(如ECharts for HarmonyOS)展示:

  • 成本分类饼图
  • 月度收入趋势折线图
  • 地块面积对比柱状图

2. 导出报表

添加导出功能,将统计数据导出为:

  • PDF报表
  • Excel表格
  • CSV文件

3. 时间范围筛选

添加时间筛选器:

  • 本月数据
  • 本季度数据
  • 本年数据
  • 自定义时间范围

4. 对比分析

添加同比、环比分析:

  • 与上月对比
  • 与去年同期对比
  • 显示增长率

5. 预测功能

基于历史数据预测:

  • 预计收入
  • 预计成本
  • 预计利润

📝 本篇小结

通过本篇教程,我们学习了:

  1. 数据整合: 从多个Service获取数据并整合分析
  2. 双模式设计: 根据用户模式显示不同的分析内容
  3. 经济指标计算: 利润、利润率、单位面积产出等
  4. 卡片式布局: 使用Grid组件实现统计卡片
  5. 安全的数值计算: 避免除零错误,正确格式化数字
  6. 条件渲染: 根据条件动态显示不同组件

数据分析是决策支持的基础,通过可视化的方式展示数据,能帮助用户更好地了解经营状况,做出合理的决策。

下一篇预告 : 我们将实现知识学堂系统,为用户提供丰富的农业知识内容,包括基础养护、病虫害防治、种植技巧等,让应用不仅是管理工具,更是学习平台。


📚 相关教程

  • 第12篇: 成本核算系统
  • 第13篇: 销售管理与助手
  • 第15篇: 知识学堂系统(下一篇)
相关推荐
zilikew2 小时前
Flutter框架跨平台鸿蒙开发——高尔夫计分器APP的开发流程
flutter·华为·harmonyos·鸿蒙
wang_yb2 小时前
面积图的奇妙变形:流图与地平线图
数据分析·databook
鸣弦artha2 小时前
Flutter框架跨平台鸿蒙开发——GridView基础入门
flutter·华为·harmonyos
芒鸽3 小时前
macos上Rust 命令行工具鸿蒙化适配完全攻略
macos·rust·harmonyos
御承扬3 小时前
鸿蒙原生系列之懒加载瀑布流组件
c++·harmonyos·懒加载·鸿蒙ndk ui·瀑布流布局
相思难忘成疾3 小时前
通向HCIP之路:第一步,全面回顾HCIA
华为·hcip
Easonmax3 小时前
基础入门 React Native 鸿蒙跨平台开发:实现一个红绿灯
react native·react.js·harmonyos
qq_22589174663 小时前
基于Python+Django豆瓣图书数据可视化分析推荐系统 可视化 协同过滤算法 情感分析 爬虫
爬虫·python·算法·信息可视化·数据分析·django
Volunteer Technology3 小时前
文本数据分析(二)
python·数据挖掘·数据分析