HarmonyOS ArkTS 实战:实现一个校园卡充值与消费记录应用
HarmonyOS ArkTS 实战:实现一个校园卡充值与消费记录应用
HarmonyOS ArkTS 实战:校园卡充值与消费记录应用完整实现
摘要
本文基于HarmonyOS Next + ArkTS技术栈,实现一个功能完整的校园卡应用,涵盖余额展示、在线充值、消费记录、挂失解挂、消费统计、限额设置等核心功能。项目完整覆盖金融类应用的状态管理、金额计算、交易流水排序等关键开发要点。
项目难度:⭐⭐⭐ 中级实战
适用人群:有 ArkTS 基础、希望学习金融类应用开发的 HarmonyOS 开发者
开发环境:DevEco Studio Next / API 24+
代码规模:单页面约 500 行,可直接运行
通过本项目,你将掌握:
- 金融类应用的数据模型设计与金额精度处理
- 交易流水的增删查改与分类筛选
- 卡片挂失/解挂的状态机管理
- 消费数据的统计聚合计算
- 砖红色主题的金融类 UI 视觉设计
目录
- 一、项目概述与功能架构
- 1.1 应用场景与项目价值
- 1.2 完整功能清单
- 1.3 技术选型说明
- 二、数据结构设计
- 2.1 交易记录模型(Transaction)
- 2.2 设计思路与字段说明
- 2.3 为什么用整数存储金额
- 三、核心功能实现详解
- 3.1 页面状态初始化
- 3.2 在线充值功能实现
- 3.3 消费扣款逻辑
- 3.4 挂失与解挂状态管理
- 3.5 消费记录筛选与查询
- 四、统计功能实现
- 4.1 本月消费/充值统计
- 4.2 消费分类聚合
- 4.3 限额设置与校验
- 五、UI 设计与交互体验
- 5.1 整体布局结构
- 5.2 主题色与视觉规范
- 5.3 交易记录的视觉区分
- 六、关键技术点解析
- 6.1 金额精度问题与解决方案
- 6.2 交易流水的时间倒序排列
- 6.3 挂失状态的全局影响
- 七、常见问题与优化建议
- 八、项目总结与扩展方向
- 附录:运行方式
一、项目概述与功能架构
1.1 应用场景与项目价值
校园卡是每个大学生日常生活中最常用的支付工具,食堂吃饭、超市购物、图书馆打印、宿舍缴电费都离不开它。本项目模拟真实校园卡应用的核心功能,是学习金融类应用开发非常好的实战案例。
金融类应用的开发难点不在于 UI 复杂度,而在于数据准确性和状态一致性:余额计算不能出错、交易记录不能重复、挂失状态要全局生效。这些细节处理能力是区分初级和中级开发者的重要标志。
1.2 完整功能清单
| 功能模块 | 具体功能 | 实现难度 |
|---|---|---|
| 余额展示 | 大卡片展示当前余额、卡状态、今日消费 | ⭐ |
| 充值功能 | 固定金额选择、自定义金额、充值确认 | ⭐⭐ |
| 交易记录 | 消费/充值混合流水、时间倒序、分类筛选 | ⭐⭐⭐ |
| 挂失解挂 | 一键挂失、解挂验证、挂失后功能禁用 | ⭐⭐ |
| 数据统计 | 本月消费总额、充值总额、消费笔数、分类占比 | ⭐⭐⭐ |
| 辅助功能 | 付款码展示、限额设置、账单明细导出 | ⭐⭐ |
1.3 技术选型说明
本项目采用纯 ArkTS 声明式开发:
- ArkUI 声明式范式:
@Component+@State构建响应式界面 - 本地数据存储:交易记录使用内存数组模拟,可扩展为关系型数据库
- 状态管理:单页面场景下
@State完全满足需求 - 金额计算:使用浮点数简化 Demo 实现,生产环境建议改用整数分
为什么选择单页面实现?校园卡的核心功能都围绕余额和交易记录展开,单页面集中展示可以让用户操作路径最短。真实项目中可以将充值、记录、设置拆分为 Tab 页面。
二、数据结构设计
2.1 交易记录模型(Transaction)
统一的数据模型是整个应用的核心。无论是充值还是消费,都用同一张交易流水表记录:
/** * 交易流水数据模型 * 充值和消费统一使用该结构,通过 type 字段区分 */interfaceTransaction{id:number;// 交易流水号type:'充值'|'消费';// 交易类型amount:number;// 交易金额(正数)location:string;// 交易地点time:string;// 交易时间category:string;// 交易分类(餐饮/超市/充值等)balance:number;// 交易后余额}2.2 设计思路与字段说明
统一交易模型的好处:
- 列表展示简单,不需要合并两个数组
- 统计计算方便,按 type 过滤即可分别统计
- 扩展性好,后续增加"退款""转账"等类型只需新增枚举值
关键字段作用:
balance:记录交易后余额,便于回溯对账,不需要反向计算category:消费分类字段,支持后续做分类统计和图表展示id:自增流水号,唯一标识每一笔交易
2.3 为什么用整数存储金额
⚠️ 重要提示:本 Demo 为了简化代码使用了浮点数,但真实金融应用绝对不能用浮点数存金额。
浮点数精度问题示例:
console.log(0.1+0.2);// 输出 0.30000000000000004,不是 0.3生产环境推荐方案:
- 方案一:金额以分为单位用整数存储(如 12850 分 = 128.50 元)
- 方案二:使用专门的高精度计算库
- 方案三:所有计算后统一
toFixed(2)保留两位小数
本项目作为教学 Demo 简化处理,但实际开发中务必注意金额精度。
三、核心功能实现详解
3.1 页面状态初始化
@Componentexportstruct Index{// 当前校园卡余额@Stateprivatebalance:number=128.5;// 卡片是否已挂失@StateprivateisLost:boolean=false;// 选中的充值金额@StateprivaterechargeAmount:number=50;// 下一条交易记录ID@StateprivatenextTransId:number=20;// 交易记录列表(最新在前)@Stateprivatetransactions:Transaction[]=[{id:19,type:'消费',amount:12.5,location:'第一食堂',time:'2024/5/20 12:30',category:'餐饮',balance:128.5},{id:18,type:'充值',amount:100,location:'线上充值',time:'2024/5/20 09:00',category:'充值',balance:141},{id:17,type:'消费',amount:8.5,location:'校园超市',time:'2024/5/19 18:20',category:'超市',balance:41},];// 单日消费限额(0表示不限制)@StateprivatedailyLimit:number=0;}状态设计原则:
balance作为唯一真值来源,所有交易都更新这个值isLost是全局状态,挂失后所有消费、充值按钮都要禁用- 交易记录只追加、不修改,保证流水可追溯
3.2 在线充值功能实现
/** * 充值操作 * @param amount 充值金额 */privaterecharge(amount:number):void{// 1. 状态校验:挂失状态不能充值if(this.isLost){console.warn('卡片已挂失,无法充值');return;}// 2. 金额校验if(amount<=0){console.warn('充值金额必须大于0');return;}// 3. 创建充值交易记录consttrans:Transaction={id:this.nextTransId,type:'充值',amount:amount,location:'线上充值',time:newDate().toLocaleString(),category:'充值',balance:this.balance+amount};// 4. 更新余额和交易列表this.transactions=[trans,...this.transactions];this.balance+=amount;this.nextTransId++;console.log(`充值成功,金额:${amount}元,当前余额:${this.balance}元`);}充值功能的防御性校验:
- 挂失状态拦截
- 金额合法性校验(大于0、不超过上限)
- 实际项目还需要增加支付密码、短信验证等安全环节
3.3 消费扣款逻辑
/** * 消费扣款 * @param amount 消费金额 * @param location 消费地点 * @param category 消费分类 */privateconsume(amount:number,location:string,category:string):boolean{// 1. 挂失状态禁止消费if(this.isLost){console.warn('卡片已挂失,禁止消费');returnfalse;}// 2. 余额校验if(this.balance<amount){console.warn('余额不足');returnfalse;}// 3. 单日限额校验(如果设置了限额)if(this.dailyLimit>0){consttodayConsume=this.getTodayTotalConsume();if(todayConsume+amount>this.dailyLimit){console.warn('超出单日消费限额');returnfalse;}}// 4. 创建消费记录consttrans:Transaction={id:this.nextTransId,type:'消费',amount:amount,location:location,time:newDate().toLocaleString(),category:category,balance:this.balance-amount};// 5. 更新数据this.transactions=[trans,...this.transactions];this.balance-=amount;this.nextTransId++;returntrue;}消费比充值多了两层校验:
- 余额是否足够
- 是否超出单日消费限额
这是金融类应用的典型特点——扣款路径的校验永远比充值路径严格。
3.4 挂失与解挂状态管理
/** * 挂失校园卡 */privatereportLost():void{if(this.isLost)return;this.isLost=true;console.log('校园卡已挂失,所有消费功能已冻结');}/** * 解挂校园卡 */privatereactivate():void{if(!this.isLost)return;this.isLost=false;console.log('校园卡已解挂,恢复正常使用');}挂失状态的全局影响:
- 所有消费按钮禁用
- 充值功能通常也暂停(避免资金风险)
- 付款码页面显示"已挂失"遮罩
- 交易记录仍可查看,但不能进行任何资金操作
真实场景说明:实际项目中挂失需要验证身份(密码/短信/人脸),且挂失状态要与服务端同步,不能仅本地修改。
3.5 消费记录筛选与查询
/** * 获取指定类型的交易记录 * @param type '全部' | '消费' | '充值' */privategetFilteredTransactions(type:string):Transaction[]{if(type==='全部'){returnthis.transactions;}returnthis.transactions.filter(t=>t.type===type);}/** * 按分类筛选消费记录 */privategetTransactionsByCategory(category:string):Transaction[]{returnthis.transactions.filter(t=>t.type==='消费'&&t.category===category);}筛选功能是交易列表的常用交互,一般会提供:全部/消费/充值三个 Tab 切换,以及按时间范围、分类的高级筛选。
四、统计功能实现
4.1 本月消费/充值统计
/** * 计算本月总消费 */privategetMonthlyConsume():number{constnow=newDate();constcurrentMonth=now.getMonth();constcurrentYear=now.getFullYear();returnthis.transactions.filter(t=>{consttransDate=newDate(t.time);returnt.type==='消费'&&transDate.getMonth()===currentMonth&&transDate.getFullYear()===currentYear;}).reduce((sum,t)=>sum+t.amount,0);}/** * 计算本月总充值 */privategetMonthlyRecharge():number{// 逻辑同上,过滤 type === '充值'// ...}统计功能的核心是filter + reduce的组合:先按条件筛选出目标记录,再累加金额。这是数据处理中非常经典的模式。
4.2 消费分类聚合
/** * 获取各分类消费金额 * 返回 {餐饮: 85.5, 超市: 32, ...} */privategetCategoryStats():Record<string,number>{conststats:Record<string,number>={};this.transactions.filter(t=>t.type==='消费').forEach(t=>{if(stats[t.category]){stats[t.category]+=t.amount;}else{stats[t.category]=t.amount;}});returnstats;}分类统计数据可以直接用来绘制饼图、柱状图等可视化图表,是消费分析功能的基础。
4.3 限额设置与校验
/** * 设置单日消费限额 * @param limit 限额金额,0表示不限 */privatesetDailyLimit(limit:number):void{if(limit<0)return;this.dailyLimit=limit;}/** * 计算今日已消费金额 */privategetTodayTotalConsume():number{consttoday=newDate().toDateString();returnthis.transactions.filter(t=>t.type==='消费'&&newDate(t.time).toDateString()===today).reduce((sum,t)=>sum+t.amount,0);}限额功能是校园卡的常见安全措施,防止卡片丢失后被盗刷造成大额损失。配合挂失功能形成双重保障。
五、UI 设计与交互体验
5.1 整体布局结构
页面采用经典的卡片式布局,从上到下依次为:
- 顶部余额大卡片(砖红色渐变背景):突出显示当前余额、卡状态、今日消费
- 快捷功能区:充值、付款码、挂失、账单四个圆形入口
- 充值金额选择区:20/50/100/200 四个固定金额 + 自定义输入
- 统计概览区:本月消费、本月充值、消费笔数三个数据卡片
- 交易记录列表:支持 Tab 切换(全部/消费/充值),可滚动
5.2 主题色与视觉规范
主色调:砖红色#B91C1C
选择砖红色作为校园卡主题色的原因:
- 契合金融、支付、校园卡的产品属性
- 红色代表"金额减少"的语义,与消费场景匹配
- 视觉上有稳重、可信的感觉,符合金融类产品的安全感
辅助色体系:
- 消费金额:红色
#DC2626(减号前缀) - 充值金额:绿色
#16A34A(加号前缀) - 挂失状态:灰色
#6B7280遮罩 - 警告提示:橙色
#F59E0B
5.3 交易记录的视觉区分
列表中的每一条交易记录,通过颜色和图标快速区分类型:
| 交易类型 | 金额颜色 | 前缀符号 | 图标 | 视觉感受 |
|---|---|---|---|---|
| 消费 | 红色 | - | 购物/餐具图标 | 支出 |
| 充值 | 绿色 | + | 钱包/箭头图标 | 收入 |
| 退款 | 绿色 | + | 退回箭头图标 | 返还 |
这种设计符合用户的心理模型——红色=花钱,绿色=进钱,不需要额外的学习成本。
六、关键技术点解析
6.1 金额精度问题与解决方案
这是所有金融类应用的第一课:永远不要相信浮点数计算。
常见的精度坑:
0.1+0.2=0.300000000000000041.0-0.9=0.09999999999999998三种解决方案对比:
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 整数分 | 所有金额以分为单位存储 | 精度绝对准确,计算快 | 展示时需要格式化 |
| toFixed | 每次计算后保留两位小数 | 实现简单 | 仍有微小精度风险 |
| 高精度库 | 使用 decimal.js 等第三方库 | 专业可靠 | 增加包体积 |
校园卡场景推荐:金额不大的场景用"整数分"方案最稳妥。
6.2 交易流水的时间倒序排列
// 新记录永远插入数组头部this.transactions=[newTrans,...this.transactions];为什么不使用 unshift?
unshift是原地修改,@State可能检测不到变化- 展开运算符创建新数组,触发完整的状态更新
- 符合不可变数据的最佳实践
更严谨的方案:按时间戳排序,而不是依赖插入顺序。当数据来自服务端时,必须以时间排序为准。
6.3 挂失状态的全局影响
挂失是一个典型的"全局状态影响多个功能"的场景。正确的做法是:
- 状态源唯一(只有一个
isLost变量) - 所有按钮、功能入口都绑定同一个状态做禁用判断
- 不要每个功能各自维护挂失状态,容易出现不一致
七、常见问题与优化建议
开发中容易踩的坑
- 金额浮点数精度:直接加减导致余额出现多位小数,展示异常
- 交易记录顺序错乱:误用
push而不是头部插入,最新记录不在最前面 - 挂失状态不同步:某个功能入口忘记加挂失判断,出现逻辑漏洞
- 余额负数:消费校验和扣款之间有竞态条件,极端情况余额变负
可优化方向
- 数据持久化:交易记录存入关系型数据库,重启不丢失
- 数据加密:敏感金额数据本地加密存储
- 图表可视化:接入图表组件展示消费趋势、分类占比
- 支付对接:接入真实支付 SDK 完成充值流程
- 多卡管理:支持绑定多张校园卡、公交卡
八、项目总结与扩展方向
核心收获
通过这个校园卡项目,你应该掌握了:
- 金融类应用的数据模型设计:统一交易流水、余额唯一真值
- 防御性编程思维:每一步操作前的状态、金额、权限校验
- 状态一致性管理:挂失等全局状态对所有功能的统一影响
- 数据统计计算:filter + reduce 处理聚合统计的经典模式
后续功能扩展建议
如果想继续深化这个项目,可以增加:
- 💳NFC 刷卡:调用 HarmonyOS NFC 能力模拟刷卡消费
- 📊消费分析:月度账单、消费趋势图、分类占比饼图
- 🔄转账功能:校园卡之间的余额转账
- 📱校园码合一:付款码、门禁码、借书码一码通用
- 💡生活缴费:电费、水费、网费在线缴纳
- 🔐生物识别:指纹/人脸验证充值、大额消费确认
项目效果
本文使用 HarmonyOS 和 ArkTS 实现一个校园卡充值与消费记录应用。
应用可以查看校园卡余额,在线充值,查看消费记录,挂失补卡,并提供消费统计、充值记录、限额设置等功能。
项目使用 DevEco Studio 开发,适配 API 23 及以上版本。