第14篇:数据分析与可视化
📚 本篇导读
在前面的教程中,我们已经实现了地块管理、农事记录、成本核算、销售管理等核心功能。这些功能产生了大量的数据,如何将这些数据整合、分析并以可视化的方式呈现给用户,帮助用户做出更好的决策,是本篇要解决的问题。
本篇将实现:
- 📊 数据总览模块(地块、成本、收入、利润等核心指标)
- 💰 经济效益分析(成本、收入、利润率、投入产出比)
- 🌾 生产分析(单位面积产出、单位面积成本)
- ✅ 任务完成情况分析
- 🎯 双模式支持(专业农业模式 vs 家庭园艺模式)
🎯 学习目标
完成本篇教程后,你将掌握:
- 如何整合多个Service的数据进行综合分析
- 如何设计和实现数据统计功能
- 如何使用卡片式布局展示统计数据
- 如何实现双模式的数据分析页面
- 如何计算和展示经济效益指标
📐 页面设计要点
页面结构
┌─────────────────────────────────┐
│ < 返回 数据分析 📊 │ ← 导航栏
├─────────────────────────────────┤
│ 数据总览 │
│ ┌─────────┬─────────┐ │
│ │地块总数 │种植面积 │ │ ← 统计卡片
│ │ 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. 测试专业农业模式
- 启动应用:确保当前为专业农业模式
- 进入数据分析页面:从首页或服务列表进入
- 检查数据总览 :
- 地块总数是否正确
- 种植面积是否正确
- 总成本是否正确(所有成本记录的总和)
- 总收入是否正确(所有销售记录的总和)
- 检查经济效益分析 :
- 净利润 = 总收入 - 总成本
- 利润率 = (净利润 / 总成本) × 100%
- 利润为正时显示蓝色,为负时显示红色
- 检查生产分析 :
- 单位面积产出 = 总收入 / 总面积
- 单位面积成本 = 总成本 / 总面积
- 检查任务分析 :
- 任务完成率 = (已完成任务数 / 总任务数) × 100%
- 进度条显示正确
- 评价文字根据完成率显示
3. 测试家庭园艺模式
- 切换到家庭园艺模式 :
- 在设置中切换模式
- 重新启动应用
- 进入数据分析页面
- 检查页面内容 :
- 只显示植物总数和任务完成率
- 不显示经济效益分析
- 不显示生产分析
- 显示植物养护分析
- 显示任务分析
4. 测试边界情况
- 无数据情况 :
- 删除所有地块、成本、销售记录
- 数值应显示为0
- 计算的指标(如利润率)应显示0.00
- 零除问题 :
- 总面积为0时,单位面积产出/成本应显示0.00
- 总成本为0时,利润率应显示0.00
- 加载状态 :
- 页面打开时应显示"加载中..."
- 数据加载完成后切换到内容
5. 测试UI响应
- 返回按钮:点击返回,应回到上一页
- 滚动:内容较多时,应能流畅滚动
- 布局:在不同屏幕尺寸下,卡片布局应正常显示
💡 核心知识点
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份可用空间columnsGap和rowsGap设置间距- 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. 预测功能
基于历史数据预测:
- 预计收入
- 预计成本
- 预计利润
📝 本篇小结
通过本篇教程,我们学习了:
- ✅ 数据整合: 从多个Service获取数据并整合分析
- ✅ 双模式设计: 根据用户模式显示不同的分析内容
- ✅ 经济指标计算: 利润、利润率、单位面积产出等
- ✅ 卡片式布局: 使用Grid组件实现统计卡片
- ✅ 安全的数值计算: 避免除零错误,正确格式化数字
- ✅ 条件渲染: 根据条件动态显示不同组件
数据分析是决策支持的基础,通过可视化的方式展示数据,能帮助用户更好地了解经营状况,做出合理的决策。
下一篇预告 : 我们将实现知识学堂系统,为用户提供丰富的农业知识内容,包括基础养护、病虫害防治、种植技巧等,让应用不仅是管理工具,更是学习平台。
📚 相关教程
- 第12篇: 成本核算系统
- 第13篇: 销售管理与助手
- 第15篇: 知识学堂系统(下一篇)