☰
HbuilderX+uni-app实战:通用生活记账App开发与跨端适配
2026/10/27 15:11:09 网站建设 项目流程

1. 为什么我选择用HbuilderX做一款通用生活记账App

记账这件事,说大不大,说小不小。我身边不少朋友都动过"自己写一个记账工具"的念头,理由五花八门:有人嫌市面上的App广告太多,有人担心数据传到云端不放心,还有人纯粹是觉得现成的工具功能太臃肿,自己只想记个流水,结果被一堆理财、报表、社区功能糊了一脸。我属于第三种,加上平时做前端比较多,就想着干脆用HbuilderX搭一个通用型的生活记账App,源码自己攥着,开题报告也顺手写了,一举两得。

先把这个项目的定位说清楚:它是一个通用生活记账App,核心能力是记录日常收支、按分类统计、查看月度汇总,数据本地存储,不依赖任何后端服务。所谓"通用",指的是它不绑定某个特定人群或场景——学生记生活费、上班族记通勤开销、家庭记日常采买,都能用同一套逻辑跑起来。技术上走的是HbuilderX + uni-app这条线,一套代码可以编译到多个平台,这也是我选它的最直接原因。

适合谁来参考这篇内容?如果你有一点前端基础,熟悉Vue的基本写法,想做一个能跑起来、能交付、能写进开题报告的小型完整项目,那这套思路基本可以直接抄。如果你是完全零基础,也不用慌,我会把关键环节拆得足够细,把"为什么这么做"讲透,你照着一步步来也能落地。整篇内容围绕项目设计思路、核心技术点、实操步骤和踩坑经验展开,源码结构和开题要点也会一并交代。

我先把结论摆前面:这个项目真正的难点不在"记账"这个业务本身,收支的增删改查逻辑很朴素,难的是跨端适配的细节、本地数据结构的稳定性,以及统计口径的一致性。很多人第一次做这类项目,功能都能写出来,但用两天就发现数据对不上、切换页面状态丢失、打包后样式错乱。下面我就按我实际推进的顺序,把这些环节一个个拆开讲。

2. 项目整体设计与技术选型拆解

2.1 为什么是HbuilderX而不是别的工具链

选工具这件事,我踩过弯路。最早我想用纯原生方式分别写两套,安卓一套、小程序一套,写完发现维护成本直接翻倍,改一个字段要动两个地方,果断放弃。后来也考虑过用命令行脚手架搭Vue项目再套壳,但配置编译环境、处理各平台差异,对一个小型记账App来说属于过度工程。

HbuilderX的优势在于它把编译、调试、打包这条链路收进了一个IDE里。你新建一个uni-app项目,写Vue页面,点运行就能在模拟器或真机上看效果,点发行就能出安装包或小程序包。对于个人项目或者课程设计这种交付周期紧的场景,这种"开箱即用"的体验非常关键。它内置的编译器对uni-app的语法支持是原生的,不像外部工具那样容易出现版本对不上的问题。

当然它也有代价:HbuilderX是重量级IDE,启动慢,对机器配置有一定要求;而且它的项目结构和纯Vue CLI项目不完全一样,如果你习惯了命令行那一套,初期会有点别扭。但权衡下来,开发效率和跨端一致性这两点对这个项目更重要,所以我最终定了它。

2.2 uni-app的跨端思路与记账场景的契合度

uni-app的核心思路是"一套代码,多端编译"。你写的页面用Vue语法,调用的API是uni系列封装好的,编译时会根据目标平台转换成对应的原生或平台代码。对记账App来说,这个特性契合度很高,因为记账的交互非常标准化:列表展示、表单输入、弹窗选择、页面跳转,这些在uni-app里都有成熟组件和API覆盖,几乎不需要写平台特定的代码。

但要注意,"通用"不等于"完全无差异"。有些能力在不同平台上表现不一致,比如本地存储的容量上限、文件系统的访问方式、导航栏的样式控制。我的处理原则是:核心业务逻辑写成平台无关的纯JS,把平台差异收敛到少数几个适配层里。这样即使某个平台出问题,改动范围也可控。

2.3 数据存储方案的选择逻辑

记账App的数据有两个特点:一是结构化程度高,每条记录就是金额、类型、分类、时间、备注这几个字段;二是数据量不大,一个普通用户一年也就几千条记录。基于这两点,我没上数据库,直接用uni-app提供的本地存储能力,把记录序列化成JSON存起来。

这里有个关键决策:是每次读写整个数组,还是分条存储?我选的是整体读写。原因是记录量小,整体读写的性能开销可以忽略,而分条存储会带来键管理的复杂度,删除、修改都要维护索引,反而容易出错。当然,如果未来记录量上去了,可以平滑迁移到分页加载或者引入轻量数据库,但当前阶段没必要提前优化。

提示:本地存储有容量限制,各平台不一样。记账数据是纯文本,几千条也就几百KB,正常使用不会触顶,但如果你打算加图片凭证之类的功能,就要提前规划存储策略。

2.4 页面结构规划

整个App我拆成四个主页面加若干辅助页面。主页面是记账首页,负责快速录入;明细页展示流水列表,支持筛选;统计页做分类汇总和月度趋势;设置页管分类管理和数据导出。辅助页面包括分类选择、日期选择、记录编辑等。

这个结构的好处是职责单一,每个页面只干一件事,页面间的数据通过全局状态或者本地存储同步。我见过一些新手把所有功能堆在一个页面里,用弹窗切换,短期看着省事,后期加功能就变成一团乱麻。页面拆分虽然多写几个文件,但维护成本低得多。

3. 核心功能模块的细节解析与实操要点

3.1 记账数据模型的设计

数据模型是整个项目的地基,设计不好后面全是坑。我最终定下的单条记录结构是这样的:

{ id: '唯一标识', type: 'expense', // expense 支出 / income 收入 amount: 25.50, // 金额,单位元,保留两位小数 categoryId: 'food', // 分类标识 date: '2024-06-15', // 记账日期 remark: '午饭', // 备注,可空 createdAt: 1718438400000 // 创建时间戳 }

几个设计要点值得展开说。金额用数字而不是字符串,方便统计时直接累加,但要注意浮点精度问题,我在展示层统一做了保留两位小数的处理,计算时用整数分或者四舍五入规避误差。分类用id而不是名称,这样分类改名不影响历史记录,分类的显示名单独维护一张映射表。date和createdAt分开存,date是用户选择的记账日期,createdAt是实际录入时间,统计时按date走,排序时可以按createdAt走,两者用途不同。

分类表的结构相对简单:

{ id: 'food', name: '餐饮', type: 'expense', icon: 'icon-food' }

收入和支出各有一套分类,互不干扰。默认内置几个常用分类,用户可以在设置页增删改。

3.2 快速录入的交互设计

记账App最核心的体验就是"快"。用户记账的场景往往是碎片化的,吃完饭顺手记一笔,如果录入流程超过十秒,大概率就放弃了。所以我在录入页做了几个优化。

金额输入用自定义数字键盘,而不是系统键盘。系统键盘在小屏设备上会遮挡内容,而且数字和符号混在一起容易误触。自定义键盘只保留数字、小数点和删除键,布局固定,点击反馈明确。金额输入框做了实时格式化,输入"25"显示"25",输入"25.5"显示"25.50",避免用户对小数位产生困惑。

分类选择用横向滚动的图标列表,常用分类排前面。点击分类直接选中,不需要二次确认。日期默认今天,用户不改就直接用今天,改的话点日期区域弹出选择器。备注是可选项,不填也能保存。

保存逻辑上,我做了一个防重复提交的处理:点击保存后按钮置灰,数据写入成功再恢复。这个细节很多人忽略,用户手快连点两下就会产生两条重复记录,后期对账时非常头疼。

3.3 明细列表的渲染与性能处理

明细页要展示所有记录,按日期倒序排列。数据量小的时候直接全量渲染没问题,但我还是做了按日期分组的处理,同一天的记录归到一个分组下,组头显示日期和当日合计。这样视觉上更清晰,用户扫一眼就知道每天花了多少。

渲染时用列表组件而不是普通循环,列表组件自带虚拟滚动能力,数据量大了也不会卡。每条记录展示分类图标、分类名、备注、金额,支出用常规颜色,收入用另一种颜色区分。左滑可以删除,点击进入编辑。

这里有个容易踩的坑:删除后列表不刷新。原因是数据存在本地存储里,页面用的是内存中的副本,删除操作改了存储但没同步内存。我的做法是删除后重新从存储读取一次数据并更新页面状态,虽然多一次读写,但保证了数据一致性。另一种做法是维护一个响应式的全局数据源,所有页面共享,改动自动同步,这个方案更优雅,但对新手来说理解成本高一些,我建议先用简单方案跑通,再考虑重构。

3.4 统计模块的口径统一

统计页是记账App的价值放大器,用户记了一堆流水,最终想看的是"钱花哪了"。我做了两个统计维度:按分类汇总和按月汇总。

按分类汇总是把选定时间范围内的支出按分类聚合,算出每个分类的金额和占比,用环形图或者条形图展示。按月汇总是把每个月的历史数据聚合,看趋势。这两个统计都依赖同一个聚合函数,我把它抽成了一个独立工具方法,输入记录数组和时间范围,输出聚合结果。

口径统一是关键。比如"本月支出"到底包不包括今天?我的定义是包含,从当月1日零点到当前时刻。再比如跨月记录怎么算?按date字段归属月份,不按createdAt。这些规则必须在代码里写死并且注释清楚,否则后期加功能时很容易出现两个页面统计结果对不上的情况。

注意:统计时一定要过滤掉无效数据,比如金额为0或者分类已被删除的记录。我早期就遇到过用户删了分类,历史记录还在,统计时分类名显示为空,排查了半天才发现是没做兜底处理。

3.5 分类管理的增删改细节

分类管理看着简单,实际有不少边界情况。新增分类要校验名称非空、不重复;删除分类要处理关联记录,我的策略是不允许删除有记录的分类,提示用户先处理记录,这样避免数据悬空。修改分类名称只改映射表,历史记录不受影响,因为记录存的是id。

分类排序我用了简单的拖拽或者上下移动按钮,把顺序存进分类表的一个sort字段,展示时按sort排序。这个功能不是必须的,但用起来体验好很多,常用分类放前面,录入时少滑几下。

4. 完整实操流程与关键环节实现

4.1 项目初始化与目录结构搭建

打开HbuilderX,新建uni-app项目,模板选默认的空模板。项目建好后,我按功能划分目录:

├── pages/ // 页面 │ ├── index/ // 记账首页 │ ├── detail/ // 明细页 │ ├── stats/ // 统计页 │ └── settings/ // 设置页 ├── components/ // 公共组件 │ ├── number-keyboard/ // 数字键盘 │ └── record-item/ // 记录条目 ├── utils/ // 工具方法 │ ├── storage.js // 存储封装 │ ├── format.js // 格式化 │ └── stats.js // 统计聚合 ├── static/ // 静态资源 └── pages.json // 页面路由配置

pages.json里配置四个主页面为tabBar页面,底部导航栏放四个入口。tabBar的图标用静态图片,选中态和未选中态各一套。这里要注意图标尺寸和格式,各平台对tabBar图标的支持略有差异,建议用PNG,尺寸控制在建议范围内,避免打包后显示异常。

4.2 存储层的封装

直接调uni的存储API散落在各个页面里,后期维护很痛苦。我封了一层storage.js,把读写、序列化、异常处理都收进去:

const RECORD_KEY = 'ledger_records'; const CATEGORY_KEY = 'ledger_categories'; export function getRecords() { try { const raw = uni.getStorageSync(RECORD_KEY); return raw ? JSON.parse(raw) : []; } catch (e) { console.error('读取记录失败', e); return []; } } export function saveRecords(records) { try { uni.setStorageSync(RECORD_KEY, JSON.stringify(records)); return true; } catch (e) { console.error('保存记录失败', e); return false; } }

封装的好处是异常可控。本地存储可能因为容量、权限等原因失败,如果直接裸调API,失败时页面就崩了。包一层try-catch,失败时返回兜底值,至少保证App不白屏。分类的读写同理,单独一套key。

4.3 录入页的完整实现

录入页是整个项目的核心,我把它的实现拆成几个部分。页面结构上,顶部是收支切换,中间是金额显示区,下面是分类选择区,底部是日期、备注和保存按钮。

收支切换用两个按钮,切换时分类列表跟着换。金额显示区绑定一个响应式变量,数字键盘的点击事件往这个变量里追加字符。追加逻辑要处理几个边界:不能有多个小数点、小数点后最多两位、首位不能是多个0。这些校验写在键盘组件里,保证传出去的金额字符串是合法的。

分类选择区从存储读取分类列表,按type过滤,渲染成图标网格。选中态用边框或者背景色区分。日期选择用uni的日期选择器组件,默认值取今天。备注用普通输入框。

保存时组装记录对象,生成id(用时间戳加随机数即可),写入存储,然后清空表单、给出成功提示。这里我加了一个小优化:保存成功后保留分类选择,只清空金额和备注,因为用户可能连续记几笔同类支出,这样少点几次。

4.4 明细页的筛选与分组实现

明细页加载时读取全部记录,先按日期倒序排,再按日期分组。分组用reduce实现:

const grouped = records.reduce((acc, item) => { const key = item.date; if (!acc[key]) acc[key] = []; acc[key].push(item); return acc; }, {});

分组后转成数组渲染,组头显示日期和当日收支合计。筛选功能我做了按月份筛选和按类型筛选,月份选择器切换后重新过滤数据。筛选逻辑放在计算属性里,数据变化自动重算,不用手动触发。

删除操作加二次确认弹窗,确认后从数组里移除对应id的记录,写回存储,重新加载列表。编辑操作跳转到录入页并带上记录id,录入页根据id判断是新增还是编辑,编辑时回填表单数据。

4.5 统计页的图表呈现

统计页我没引入第三方图表库,因为记账的统计需求比较简单,用CSS画条形图足够了。按分类汇总后,每个分类渲染一行,左边分类名,中间条形,右边金额和占比。条形宽度按占比计算,用百分比控制。

月度趋势用简单的柱状图,每个月一根柱子,高度按当月支出占最大值的比例算。这种纯CSS方案的好处是包体积小、跨端一致,不依赖canvas在各平台的表现差异。如果后期需要更复杂的图表,再考虑引入图表库也不迟。

统计的时间范围默认本月,可以切换到上月或者自定义月份。切换时重新聚合数据,聚合函数接收记录数组和月份参数,返回该月的分类汇总和总额。

4.6 打包与多端验证

开发完成后,在HbuilderX里点发行,选择目标平台。我主要验证了两个方向:一个是打包成安装包在真机上跑,另一个是编译成小程序在开发者工具里跑。真机验证重点看交互流畅度和本地存储是否正常,小程序验证重点看样式兼容和API可用性。

打包前记得检查几个配置:应用名称、图标、版本号、权限声明。小程序端还要在对应平台的后台配置合法域名,虽然这个项目不请求网络,但有些平台对本地存储的使用有额外要求,提前看清楚文档能省不少事。

5. 常见问题与排查技巧实录

5.1 数据相关的高频问题

做这类本地存储的项目,数据问题占了排查量的一大半。我整理了一张速查表:

问题现象可能原因排查与解决
重启App后数据丢失存储key写错或读写不一致检查读写用的key是否完全一致,注意大小写
金额统计对不上浮点精度误差计算时转成整数分,展示时再转回元
删除分类后记录显示异常未做分类兜底分类映射找不到时显示"未分类"
列表删除后不刷新内存副本未同步操作后重新读取存储并更新状态
保存重复记录按钮未防重复点击保存期间禁用按钮,成功后再恢复

浮点精度这个问题值得单独说。JavaScript里0.1加0.2不等于0.3,记账金额累加多了误差会累积。我的处理是存储时保留原始值,计算时统一转成整数分,比如25.50存25.5,计算时乘100取整,最后结果再除100。这样能规避绝大多数精度问题。

5.2 跨端样式差异的排查

同一套样式在不同平台表现不一致,是uni-app开发的老问题。我遇到过的典型情况:某个平台下flex布局的间距和预期不符,某个平台下自定义导航栏高度算错导致内容被遮挡。

排查思路是先确认是布局问题还是单位问题。uni-app推荐用rpx做单位,它按屏幕宽度等比缩放,大部分场景下表现一致。但如果用了px,不同分辨率设备上就会有差异。我的做法是布局尺寸统一用rpx,字体大小也用rpx,个别需要固定物理尺寸的地方才用px。

导航栏这块,如果用了自定义导航栏,要动态获取状态栏高度,各平台获取方式略有不同,uni提供了统一API,但返回值需要做兜底。我一般会设一个默认高度,获取失败时用默认值,避免页面顶部塌陷。

5.3 页面状态丢失的定位方法

从明细页跳到编辑页再返回,明细页数据没更新,这是新手常遇到的问题。根因是页面栈里的旧页面不会自动重新加载。解决办法有两个:一是在页面的显示生命周期里重新拉取数据,二是用全局状态管理,数据变化自动通知。

我选的是第一种,简单直接。在明细页的onShow生命周期里重新读取存储并更新列表,这样无论从哪个页面返回,数据都是最新的。代价是每次显示都读一次存储,但数据量小,感知不到性能影响。

提示:onShow在页面首次加载和每次重新显示时都会触发,适合做数据刷新。但要注意避免在里面做耗时操作,否则页面切换会有卡顿感。

5.4 打包后的典型故障

开发环境跑得好好的,打包后就出问题,这类情况我遇到过几次。一次是静态资源路径问题,开发时用的相对路径,打包后找不到图片,改成绝对路径或者用项目内的引用方式解决。另一次是小程序端某个API不支持,开发时没注意,打包后报错,查文档发现该API在小程序端有替代方案,换掉就好。

经验是:开发阶段就定期在目标平台验证,不要等全部写完再打包,那时候问题堆在一起,定位成本高。每完成一个模块,就在真机或者目标平台上跑一遍,问题早发现早解决。

5.5 开题报告要写清楚的关键点

既然项目带"开题",我顺带说说开题报告里容易被忽略的部分。很多人只写功能列表和技术栈,但评审更关注你为什么做、解决了什么问题、技术选型的依据。我的建议是开题里明确写清楚:目标用户和使用场景、核心功能边界(哪些做哪些不做)、技术方案的对比和选择理由、预期成果和验证方式。

功能边界尤其重要。记账App可以无限扩展,预算管理、账单导入、多账本、云同步,但开题阶段要明确这一版做到哪,避免后期范围失控。我这一版就锁定在"本地记账+统计",云同步和多账本列为后续扩展,这样交付目标清晰,也显得有规划。

6. 我在这个项目里踩过的坑和几点实在建议

做完整套流程下来,有几个体会比较深。第一个是别急着写页面,先把数据模型和存储层定死。我一开始图快,直接在页面里操作存储,写到第三个页面就发现逻辑重复、改一处漏一处,回头重构花的时间比一开始就封装多得多。数据模型定好之后,页面只是数据的展示层,改起来很轻。

第二个是统计口径一定要写文档。哪怕只是给自己看的注释,也要把"本月""本日""跨月怎么算"这些规则写清楚。我中途改过一次统计逻辑,结果首页和统计页的口径不一致,用户看到两个数字对不上,这种问题排查起来特别费劲,因为代码本身没报错,是逻辑层面的分歧。

第三个是跨端验证要趁早。我习惯在开发过程中每隔一段时间就在真机上跑一遍,很多样式和交互问题在模拟器上看不出来,真机一跑就暴露。早发现早改,比最后集中打包排查轻松太多。

最后分享一个小的扩展思路:这个项目的存储层是抽象的,如果以后想加云同步,只需要在存储层加一个同步适配器,业务代码基本不用动。同理,想加多账本,在数据模型里加一个账本id字段,筛选时带上就行。架构留好口子,后续扩展就是加法而不是重写。源码和开题这套东西,核心价值不在于功能多花哨,而在于结构清晰、能讲清楚每个决策背后的理由,这才是拿得出手的交付物。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询