积分中心
一笔加油费到月度报表:用 ArkTS 做一个离线优先的“车账本”
头像 ccai 2026-09-18 17:38:18    发布
1048 浏览 5 点赞 0 收藏

摘要

很多车主并不是没有记账需求,而是觉得打开表格、找分类和计算里程成本太麻烦。本文以一个轻量“车账本”MVP 为例,介绍如何使用 ArkUI 搭建记录页和汇总页,以本地键值存储保存少量记录,并通过输入校验、单向状态更新和失败提示,让核心功能在无网络状态下仍然可用。本文不涉及云同步、自动识别发票等尚未实现的能力。

一、先收窄需求范围

第一版只保留三个使用场景:

  1. 加油后快速记下金额和里程;
  2. 查看本月支出和分类汇总;
  3. 没有网络时仍能查看和新增记录。

功能范围如下:

  • 加油、保养、停车和其他四类费用;
  • 日期、金额、可选里程和备注;
  • 最近记录和本月汇总;
  • 删除记录和空状态提示。

第一版明确不做账号、云同步、自动识别发票和复杂图表。对于个人工具来说,先完成闭环比一次加入很多功能更重要。

二、数据模型先统一单位

金额不建议直接以浮点数长期累加。可以在模型层保存“分”:

export type CostType = 'fuel' | 'maintenance' | 'parking' | 'other'

export interface CostRecord {
  id: string
  type: CostType
  amountFen: number
  date: string       // yyyy-MM-dd
  mileageKm?: number
  note: string
}

export function parseAmount(text: string): number | undefined {
  const value = Number(text.trim())

  if (!Number.isFinite(value) || value <= 0 || value > 100000) {
    return undefined
  }

  return Math.round(value * 100)
}

export function parseMileage(text: string): number | undefined {
  if (text.trim().length === 0) {
    return undefined
  }

  const value = Number(text.trim())
  return Number.isFinite(value) && value >= 0 ? value : undefined
}

显示时再将 amountFen / 100 格式化为金额。日期也应保存为稳定格式,不要把本地化显示文本直接当成排序字段。

三、用仓储层隔离 Preferences

Preferences 更适合少量配置和小规模记录,不适合保存图片、大型离线队列或复杂关系数据。下面是示意代码,具体 getPreferences 参数形式请以当前 SDK 为准。

import { preferences } from '@kit.ArkData'
import { common } from '@kit.AbilityKit'

export class CostRepository {
  private store?: preferences.Preferences
  private readonly key: string = 'records_v1'

  async open(context: common.UIAbilityContext): Promise<void> {
    // 具体签名可能随 SDK/API 级别变化
    this.store = await preferences.getPreferences(context, 'car_cost_book')
  }

  async list(): Promise<CostRecord[]> {
    if (this.store === undefined) {
      return []
    }

    const raw = await this.store.get(this.key, '[]') as string

    try {
      const value: Object = JSON.parse(raw)
      return Array.isArray(value) ? value as CostRecord[] : []
    } catch (_) {
      return []
    }
  }

  async save(records: CostRecord[]): Promise<void> {
    if (this.store === undefined) {
      throw new Error('repository is not opened')
    }

    await this.store.put(this.key, JSON.stringify(records))
    await this.store.flush()
  }
}

几个边界必须写清:

  • put 成功不等于已经完成可靠持久化,应在适当时机调用 flush;
  • 不要在每一次键盘输入时写入和提交;
  • 不要保存密码、长期令牌等敏感信息;
  • 数据量增大后,应迁移到关系型数据库;
  • 数据损坏时不能让页面直接白屏,应保留错误提示和恢复路径。

四、页面只维护必要状态

页面可以只维护记录数组、加载状态、错误文案和编辑器显示状态:

@Entry
@Component
struct HomePage {
  private repo: CostRepository = new CostRepository()

  @State private records: CostRecord[] = []
  @State private loading: boolean = true
  @State private errorText: string = ''
  @State private editorVisible: boolean = false

  aboutToAppear(): void {
    this.loadRecords()
  }

  private async loadRecords(): Promise<void> {
    try {
      const context = getContext(this) as common.UIAbilityContext
      await this.repo.open(context)
      this.records = await this.repo.list()
    } catch (_) {
      this.errorText = '读取本地数据失败,请稍后重试'
    } finally {
      this.loading = false
    }
  }

  private async appendRecord(record: CostRecord): Promise<void> {
    const next: CostRecord[] = [record, ...this.records]

    try {
      await this.repo.save(next)
      this.records = next
      this.editorVisible = false
    } catch (_) {
      this.errorText = '保存失败,原记录未改变'
    }
  }
}

新增流程应当是:

  1. 读取表单;
  2. 校验金额、日期和里程;
  3. 生成一条新的不可变记录;
  4. 写入仓储;
  5. 持久化成功后再更新页面状态。

如果保存失败,应保留用户已经填写的内容,不要先清空表单再显示错误。

五、测试用例比功能数量更重要

MVP 至少要验收:

  • 首次安装无数据;
  • 四种费用均可新增;
  • 金额为空、负数、超大值;
  • 里程为空或包含字母;
  • 快速连续点击保存;
  • 杀掉应用后重新打开;
  • 删除最后一条记录;
  • 无网络状态启动;
  • 数据损坏时的恢复提示;
  • 系统时间切换到下个月。

月份统计要使用本地日期逻辑。直接使用 toISOString() 可能在月末跨时区时产生归属偏差,正式版本应使用经过验证的本地日期格式化方法。

六、后续演进

当记录达到数千条时,可以迁移到关系型数据库,并按月份和类型建立索引。需要跨设备时,应重新设计同步和冲突策略,不要简单地把 Preferences 文件上传。

后续还可以增加:

  • CSV 或图片报表导出;
  • 桌面卡片只读展示月度汇总;
  • 用户确认后的 AI 分类建议;
  • 备份文件和数据恢复;
  • 无障碍标签、读屏顺序和更大的触控区域。

七、总结

我认为一个可交付的 HarmonyOS 应用不一定要功能庞大。把需求收窄,选合适的存储方式,为输入错误和写入失败预留位置,再用真实异常数据验收,才能让“从想法到落地”真正闭环。


©本站发布的所有内容,包括但不限于文字、图片、音频、视频、图表、标志、标识、广告、商标、商号、域名、软件、程序等,除特别标明外,均来源于网络或用户投稿,版权归原作者或原出处所有。我们致力于保护原作者版权,若涉及版权问题,请及时联系我们进行处理。
分类
HarmonyOS
地址:北京市朝阳区北三环东路三元桥曙光西里甲1号第三置业A座1508室 电话:13391790444或(010)62178877
版权所有:电脑商情信息服务集团 北京赢邦策略咨询有限责任公司
声明:本媒体部分图片、文章来源于网络,版权归原作者所有,我司致力于保护作者版权,如有侵权,请与我司联系删除

京ICP备:2022009079号-2

京公网安备:11010502051901号

ICP证:京B2-20230255