在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

鸿蒙 Next 碳足迹账本 App 开发实战:碳排放记录 + 分类统计 + 环保数据可视化

作者:duluo
SDK 版本:HarmonyOS API 24 (Next)
开发工具:DevEco Studio
语言框架:ArkTS + ArkUI
字数:约 9500 字


目录

  1. 引言
  2. 产品概念与碳排模型
  3. 三 Tab 架构设计
  4. 首页仪表盘设计
  5. 碳排放记录系统
  6. 分类统计柱状图
  7. 减碳贴士系统
  8. 智能评价与反馈系统
  9. 文本类型转换问题
  10. 第三十四款 App 全景回顾
  11. 结语

1. 引言

1.1 碳足迹——每个人的环境账本

全球每年碳排放量超过 360 亿吨。这个数字大得超出个人的理解范围。但如果我们把碳排放拆解到个人日常——开车上班排放 2.4kg、吃一餐牛肉排放 3.5kg、网购退货排放 1.5kg——就变成了每个人都能感知的"小事"。

个人碳足迹计算的核心逻辑是:每一件日常小事都有看不见的环境成本。 不是让人产生负罪感,而是让人知道——“原来我做一个不同的选择,可以少排放这么多碳”。

"个人碳足迹账本"App 做的就是这件事:记录日常活动,计算碳排放,让抽象的环境问题变成具体的数字。

1.2 本 App 的定位

本 App 与系列中的"慢病管理"(App 31)共享相同的底层逻辑——记录数据

慢病管理(#31) 碳足迹账本(#34)
数据类型 健康指标 碳排放数据
记录方式 一键生成随机数据 点击活动记录碳排
统计方式 最新值卡片 分类柱状图
反馈机制 用药提醒 智能评价 + 植树换算
激励方式 月度成就 每日一树

两款 App 的代码结构也非常相似:三 Tab 架构、ForEach 列表、常数数据源。区别在于碳足迹账本增加了分类统计和智能评价系统。

1.3 三十四款 App 全景

App 数量:    34
代码总行数:  ~18,800 行
编译错误数:  ~301 个
博客总字数:  ~340,000 字
技术博客数:  34 篇

2. 产品概念与碳排模型

2.1 功能需求

用户故事 1:我想知道每天我的碳排有多少
用户故事 2:我想记录开车的碳排放
用户故事 3:我想看看出行和饮食哪个排碳多
用户故事 4:我想学习怎么减少碳排放

功能清单:
├── F1: 今日总排放 + 植树换算
├── F2: 分类统计柱状图
├── F3: 12 项活动记录
├── F4: 智能评价反馈
├── F5: 今日记录快照
├── F6: 8 条减碳贴士
└── F7: 每日一树知识

2.2 数据模型

interface Activity {
  id: number;
  name: string;       // 活动名称
  emoji: string;      // 图标
  carbon: number;     // 碳排放量(kg)
  unit: string;       // 单位
  category: string;   // 分类(出行/饮食/能源/消费)
}

每条活动记录 6 个字段。排放数据来自中国碳排放数据库的平均值:

活动 碳排(kg) 数据来源
开车 10km 2.4 私家车平均油耗 8L/100km
飞机 1h 200 国内航班人均碳排放
吃牛肉 3.5 牛肉碳排放强度 35kg/kg
开空调 2h 1.2 1.5 匹空调平均功率

单位统一使用 kg,便于用户比较不同活动的碳排放大小。

2.3 记录数据模型

// [timestamp, activityId, carbonValue]
@State records: number[][] = [];

每条记录是一个 3 元组:时间戳、活动 ID、碳排量。与"慢病管理"(App 31)的 5 元组类似,但更简洁——3 个字段足够描述一条碳排记录。


3. 三 Tab 架构设计

3.1 Tab 配置

build() {
  Stack() {
    Column().backgroundColor(C.bg)
    Column() {
      this.buildHeader()
      if (this.activeTab === 0) this.buildHomeTab()
      else if (this.activeTab === 1) this.buildActivityTab()
      else this.buildTipsTab()
      this.buildTabBar()
    }
  }
}
Tab 图标 功能 使用行为
0 🏠 首页 — 仪表盘 + 统计 + 记录 查看今日碳排
1 📋 活动 — 选择活动记录 记录碳排
2 💡 贴士 — 减碳建议 学习减碳知识

3.2 首页仪表盘布局

┌──────────────────────────────┐
│  🌍 碳足迹账本                 │
│  今日排放 6.2 kg CO₂          │
├──────────────────────────────┤
│  🌱 今日碳足迹                  │
│  6.2 kg CO₂    0.28 棵(需植树)│
│  🎉 低于全国平均水平!          │
├──────────────────────────────┤
│  📊 分类统计                   │
│  出行 ████████ 3.1kg          │
│  饮食 ████     0.5kg          │
│  能源 ██       0.3kg          │
│  消费 ██████   3.0kg          │
├──────────────────────────────┤
│  📝 今日记录                   │
│  🚗 开车10km    +2.4kg       │
│  🥗 吃蔬菜餐    +0.5kg       │
├──────────────────────────────┤
│  🌳 每日一树                   │
│  一棵树每年可吸收约 22kg CO₂   │
└──────────────────────────────┘

3.3 数据流

点击活动 → addRecord() → records 头部插入
     ↓
recalcTotal() → totalToday 更新
     ↓
首页仪表盘 ← totalToday + records
分类统计 ← getCategoryTotals(records)
今日记录 ← records 最后 4 条
智能评价 ← getComparison(totalToday)
植树换算 ← getTreeCount(totalToday)

所有数据从 recordstotalToday 两个状态变量派生,不需要额外的 @State 管理派生数据。


4. 首页仪表盘设计

4.1 总览卡片

Column() {
  Text('🌱 今日碳足迹').fontSize(16).fontColor(C.text)

  Row() {
    Column() {
      Text(this.totalToday.toFixed(1)).fontSize(40).fontColor(C.primary)
      Text('kg CO₂').fontSize(13).fontColor(C.textMuted)
    }
    Column() {
      Text(this.getTreeCount() + '').fontSize(32).fontColor(C.accent)
      Text('需植树(棵)')
    }
  }

  Text(this.getComparison()).fontSize(13).fontColor(C.textLight)
}

两个核心指标并排:今日总排放大字号(40sp)+ 需植树量。数字使用不同颜色区分——主色绿(碳排)和强调色(植树量)。

植树换算公式:Math.ceil(totalToday / 22 * 100) / 100。一棵树每年吸收 22kg CO₂,今天排放的碳需要 N 棵树用一年时间来吸收。将抽象的"kg"转换成可感知的"棵树"。

4.2 今日记录快照

ForEach(this.records.slice(-4).reverse(), (r: number[]) => {
  Row() {
    Text(this.getActEmoji(r[1])).fontSize(20)
    Text(this.getActName(r[1])).fontSize(14).margin({ left: 8 }).layoutWeight(1)
    Text('+' + r[2].toFixed(1) + 'kg').fontSize(14).fontColor(C.warm)
  }
})

取最近 4 条记录(slice(-4)),倒序展示(reverse(),因为 records 头部是最新的)。每条记录显示 emoji + 活动名 + 碳排量。

getActEmojigetActName 根据活动 ID 从 ACTIVITIES 常量中查找对应的 emoji 和名称。这是 Helper 方法模式的应用。

4.3 每日一树

Column() {
  Text('🌳 每日一树').fontSize(16).fontColor(C.text)
  Text('一棵树每年可吸收约 22kg CO₂。\n今天你的碳排放需要 '
    + this.getTreeCount() + ' 棵树吸收一年。\n减少排放,从每一个选择开始。')
}

三段式文案:知识(树吸收 22kg)→ 应用(你今天需要 N 棵树)→ 行动号召(减少排放)。


5. 碳排放记录系统

5.1 活动列表

ForEach(ACTIVITIES, (a: Activity) => {
  Column() {
    Row() {
      Text(a.emoji).fontSize(28)
      Column() {
        Text(a.name).fontSize(15).fontWeight(FontWeight.Bold)
        Text(a.carbon + a.unit + ' CO₂ · ' + a.category).fontSize(12)
      }
      Text('+' + a.carbon + a.unit).fontSize(14).fontColor(C.warm)
    }
  }.onClick(() => { this.addRecord(a.id, a.carbon); })
})

12 项活动列在 Tab 1。点击任意活动 → 记录碳排。卡片展示 emoji、活动名、碳排量、分类标签。

5.2 记录操作

addRecord(actId: number, carbon: number): void {
  const now = Date.now();
  this.records = [[now, actId, carbon], ...this.records];
  this.recalcTotal();
  promptAction.showToast({ message: '✅ 已记录 ' + this.getActName(actId) });
}

三行代码完成记录:创建记录 → 头部插入 → 重新计算总量。

5.3 总量计算

recalcTotal(): void {
  let t = 0;
  for (const r of this.records) { t += r[2]; }
  this.totalToday = t;
}

遍历所有记录,累加每条记录的碳排量(r[2])。每次新增记录后调用,确保 totalToday 始终是最新值。


6. 分类统计柱状图

6.1 分类汇总

getCategoryTotals(): number[][] {
  const cats: string[] = ['出行', '饮食', '能源', '消费'];
  const totals: number[] = [0, 0, 0, 0];
  for (const r of this.records) {
    const act = this.findAct(r[1]);
    if (act !== undefined) {
      const idx = cats.indexOf(act.category);
      if (idx >= 0) totals[idx] += r[2];
    }
  }
  return [[0, totals[0]], [1, totals[1]], [2, totals[2]], [3, totals[3]]];
}

返回 number[][],每行的格式是 [分类索引, 总排放]。索引 0-3 分别对应四个分类。

6.2 柱状图渲染

ForEach(this.getCategoryTotals(), (cat: number[]) => {
  Row() {
    Text(this.getCatName(cat[0])).fontSize(14).width(50)
    Column() {
      Column().height(16).width(this.getBarWidth(cat[1]))
        .backgroundColor(C.primary).borderRadius(4)
    }.layoutWeight(1).margin({ left: 8 })
    Text(cat[1].toFixed(1) + 'kg').fontSize(12).width(54)
  }
})

每行由三部分组成:分类名(50px 宽)→ 柱状条(弹性宽度)→ 数值(54px 宽右对齐)。

柱状条宽度通过 getBarWidth 方法计算——根据分类的最大排放值缩放。这种方法不是精确的比例缩放(不是按总排放百分比),而是按预设最大值缩放,确保不同规模的数据都能看到明显的柱状条。

6.3 柱状条的视觉效果

柱状条使用 backgroundColor(C.primary).borderRadius(4)。16px 高度 + 4px 圆角形成胶囊形状,比直角矩形更柔和。


7. 减碳贴士系统

7.1 贴士数据

getTips(): string[][] {
  return [
    ['🚲', '骑行代替开车', '3公里以内的出行…减少约 2.4kg…', '2.4kg'],
    ['🥗', '多吃蔬菜少吃肉', '每餐用蔬菜代替牛肉…减少约 3kg…', '3.0kg'],
    // ... 共 8 条
  ];
}

每条贴士 4 个字段:emoji、标题、具体建议、减碳量。使用字符串数组而非对象,保持数据结构简洁。

7.2 贴士卡片

Text(tip[0]).fontSize(24)   // emoji
Text(tip[1]).fontSize(16)   // 标题
Text(tip[2]).fontSize(13)   // 建议
Text('🌿 减碳 ' + tip[3])   // 减碳量

每条贴士卡片底部显示具体的减碳量(🌿 减碳 2.4kg),让用户知道"做到这件事可以少排多少碳"。


8. 智能评价与反馈系统

8.1 分段评价

getComparison(): string {
  if (this.totalToday < 5) return '🎉 低于全国平均水平!继续保持!';
  if (this.totalToday < 10) return '👍 接近绿色生活标准!';
  if (this.totalToday < 20) return '📊 与全国平均水平相当。';
  return '⚠️ 排放偏高,试试绿色出行吧!';
}

四档评价对应不同的排放区间:

排放区间 评价 含义
0-5 kg 🎉 优秀 低于全国平均水平
5-10 kg 👍 良好 接近绿色生活标准
10-20 kg 📊 平均 与全国平均水平相当
>20 kg ⚠️ 偏高 需要改进

分段依据:中国城镇居民日均碳排约 15-20kg。5kg 以下需要非常环保的生活方式(走路/骑车上班、素食、很少用空调),10kg 以下是较为环保的水平,20kg 以上偏高。

8.2 评价的心理学设计

评价不使用负面词汇。没有"你的碳排放太高了"——而是"试试绿色出行吧"。引导而不是指责,是环保类 App 信息传递的关键原则。


9. 文本类型转换问题

9.1 错误描述

本 App 遇到 1 个编译错误

10505001: Argument of type 'number' is not assignable to parameter of type 'string | Resource'

位置:Text(this.getTreeCount())

原因:getTreeCount() 返回 number 类型,但 Text() 组件的第一个参数需要 string | Resource 类型。

getTreeCount(): number {
  return Math.ceil(this.totalToday / 22 * 100) / 100;
}

// ❌ 错误:Text 不能直接接收数字
Text(this.getTreeCount())

// ✅ 修复:转换为字符串
Text(this.getTreeCount() + '')

9.2 为什么这个错误值得记录

这不是一个新的错误类型(10505001 在系列中已出现多次),但它的出现位置有特殊意义——它发生在"完全没有复杂语法"的代码中。

App 33(相亲防骗雷达)实现 0 错误,App 34 出现了 1 个错误。这个错误的出现不是"退步",而是因为碳足迹账本在首页展示了 this.getTreeCount() 这个数字——而相亲防骗雷达首页没有展示任何方法返回的数字。

教训:文本类型转换错误是一个容易被忽视的"边缘案例"。Text() 组件大部分时间接收字符串字面量(Text('hello')),开发者容易忘记 Text() 不能直接接收数字。this.count + ''this.count.toString() 可以快速解决。


10. 第三十四款 App 全景回顾

10.1 数据总览

指标 数值
代码行数 280 行
编译错误数 1 个
@State 变量 3 个
@Builder 方法 5 个
活动类型 12 项
减碳贴士 8 条
分类统计 4 类
外部依赖 0 个

10.2 三十四款 App 的环保类对比

App 主题 核心机制 错误数
8 🗑️ 情绪垃圾桶 情绪环保 扔掉坏情绪 4
32 🌍 临期食品救援 食物环保 救援临期食品 6
34 🌱 碳足迹账本 碳排环保 记录碳排放 1

三款环保类 App 覆盖了情绪环保、食物环保、碳排环保三个方向。错误数从 4 降到 6 再降到 1——不是线性的,但整体趋势向好。

10.3 三十四款 App 的错误数中位数

App 1-10:  错误数中位数 11.5
App 11-20: 错误数中位数 4.5
App 21-30: 错误数中位数 5.5
App 31-34: 错误数中位数 0.5

中位数从 11.5 降到了 0.5,下降了 96%。这个数字说明的不是"开发者更聪明了",而是"模式更成熟了"——同样的错误不会犯第二次。

10.4 三十四款 App 的"34 个系列之最"

类别 App 纪录
最多代码 3 冰箱剩菜 1320 行
最少代码 28 记忆时光机 188 行
最多错误 24 AI 树洞 48 个
最少错误 31 慢病管理 / 33 相亲防骗 0 个
最多 Builder 3 冰箱剩菜 17 个
最少 Builder 28 记忆时光机 2 个
最多数据 24 AI 树洞 164 条模板

11. 结语

11.1 碳足迹的日常意义

一个人一天排了多少碳?开车上下班 4.8kg、吃三餐 1-4kg、用空调 1.2kg、用手机电脑 0.4kg——加起来大约 7-15kg。

这个数字不算大,但乘以 365 天、乘以 14 亿人——就是全球碳排放总量的大头。

个人碳足迹的意义不是"我一个人减排就能拯救地球",而是让抽象的环境问题变得可感知。当你知道开车 10km 排 2.4kg 碳、一棵树一年只能吸收 22kg 碳——你做的每一个小选择,都有了可量化的环境意义。

11.2 从 34 款 App 中看到的

写了 34 款 App 之后,最清楚的发现是:代码量的多少与 App 的价值没有直接关系。

碳足迹账本 280 行代码。它解决了一个真实的问题(让碳排放可感知)。它比某些 500 行的 App 更有价值——不是因为代码写得更好,而是因为"记录碳排"这件事本身有意义。

所以如果你正在做你的第一款 App,不要担心"我的 App 代码太少了"。重要的是你的 App 解决了什么问题,而不是你的 App 有多少行代码。

11.3 给开发者的建议

  1. Text() 不能直接接收 number——Text(this.count) 会编译错误,必须 Text(this.count + '')
  2. 碳排数据要真实——不要编造碳排数据。从权威来源获取(中国产品全生命周期温室气体排放系数库)
  3. 评价要用引导代替指责——"试试绿色出行"比"你排太多碳了"更有效
  4. 34 款 App 的中位错误数降到了 0.5——零错误不是一个遥远的梦

11.4 致谢

34 款 App、34 篇博客、约 340,000 字。

从白噪音到碳足迹账本,34 款 App 的旅程见证了一个开发者的成长,也见证了 ArkUI 从一个新框架到成熟的开发工具的演变。

34 不是终点。第 35 款 App 正在路上。

现在,打开 DevEco Studio,去记录你的碳足迹吧——或者去创造属于你自己的第 34 款 App。


附录 A:核心代码速查

添加碳排记录

addRecord(actId: number, carbon: number): void {
  this.records = [[Date.now(), actId, carbon], ...this.records];
  this.recalcTotal();
}

分类统计

getCategoryTotals(): number[][] {
  const cats = ['出行', '饮食', '能源', '消费'];
  const totals = [0, 0, 0, 0];
  for (const r of this.records) {
    const act = this.findAct(r[1]);
    if (act) { const idx = cats.indexOf(act.category); totals[idx] += r[2]; }
  }
  return [[0, totals[0]], [1, totals[1]], [2, totals[2]], [3, totals[3]]];
}

智能评价

getComparison(): string {
  if (this.totalToday < 5) return '🎉 低于全国平均水平!继续保持!';
  if (this.totalToday < 10) return '👍 接近绿色生活标准!';
  if (this.totalToday < 20) return '📊 与全国平均水平相当。';
  return '⚠️ 排放偏高,试试绿色出行吧!';
}

附录 B:色板

变量 用途
C.bg #F0F7F0 主背景
C.primary #4A9B5A 碳排数据
C.accent #3D8B40 植树数据
C.warm #E8927C 排放数值
C.earth #8B6B4A 贴士点缀

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐