微信小程序记账模板全解析:从页面结构到数据持久化

微信小程序记账模板全解析:从页面结构到数据持久化 简介面向个人财务管理场景的“日常记账”微信小程序模板源码适合零基础或初中级开发者学习与二次开发。压缩包内共393个文件体积约769KB包含WXML、WXSS、JS、JSON四类代码文件各50个以及139个png、46个jpg、2个gif等界面素材代码与图片分离便于替换皮肤和页面文案。模板功能覆盖引导页、登录模块、记账中心、消息提醒、通讯录和个人中心六大板块并体现数据绑定、事件处理、状态管理、API调用与性能优化等关键实现。源码结构清晰WXML定义界面结构、WXSS控制样式、JS处理业务逻辑、JSON负责页面配置开发者可按模块逐项对照学习。已有567人学习下载适合作为课程设计、毕业设计或个人练手的起步工程可基于源码快速定制收支分类、预算统计等逻辑避免从零搭建。1. 日常记账小程序模板先看它解决了什么问题把一套记账小程序模板导入微信开发者工具第一眼扫过 pages 目录六个业务模块排在那里引导页、登录、记账中心、消息、通讯录、个人中心。对一个记账工具来说这个页面数量偏多但顺着首次启动的路径走一遍会发现它是一条完整的闭环新用户先看到引导动画登录后落到记账中心底部 tabBar 再把消息和通讯录收进来个人中心兜住统计与设置。这套模板是原生微信小程序写法不是 uniapp 或 Taro跑起来最接近微信底层的真实行为。适合两类人一类是想快速验证记账产品 MVP 的前端开发者另一类是拿微信小程序模板源码做课程设计或毕业设计的在校生。不需要从零搭框架改代码时不涉及架构层重点放在业务逻辑与交互细节上就够了。2. 页面结构与配置从 app.json 认识模板骨架微信小程序的每个页面都由四个同名文件组成WXML 负责结构WXSS 负责样式JS 负责页面逻辑JSON 负责当前页面的窗口配置。拿到模板源码后第一步不是急着看某个页面怎么实现而是先把目录结构和 app.json 摸清楚。这个总纲决定了哪些页面是入口、哪些页面在底部导航里、哪些页面只能靠跳转到达也决定了后续改模块时的动刀顺序。2.1 目录结构六个页面模块与资源文件模板解压后的目录结构如下日常记账模板/ ├── pages/ │ ├── guide/ 引导页 │ ├── login/ 登录模块 │ ├── bill/ 记账中心 │ ├── message/ 消息模块 │ ├── contacts/ 通讯录模块 │ └── user/ 个人中心 ├── utils/ │ └── util.js 日期与金额格式化工具 ├── images/ │ ├── onling.gif 引导页动图 │ ├── loading.gif 加载占位图 │ ├── user.jpg 默认用户头像 │ ├── 40.jpg / 31.jpg / 41.jpg │ ├── 24.jpg / 14.jpg / 20.jpg / 30.jpg ├── app.js 全局逻辑与 globalData ├── app.wxss 全局样式 ├── app.json 页面注册、tabBar、窗口配置 └── project.config.json 开发者工具项目配置模板打包时通常不带 node_modules也没有编译构建步骤入口就是这六个页面。images 里那组编号命名的 jpg 是分类图标和示例图从文件名看不出语义具体哪张对应哪个分类要对照 bill.js 里的 categories 配置去查。其中 onling.gif 拼写疑似少了一个字母但页面引用路径一致不影响运行不需要改。页面目录是否在 tabBar职责引导页pages/guide否首次启动展示核心功能登录模块pages/login否登录鉴权与用户资料收集记账中心pages/bill是收支录入与账目列表消息模块pages/message是预算提醒与系统通知通讯录模块pages/contacts是共享账单联系人管理个人中心pages/user是统计、设置、个人信息页面的 JSON 文件同样值得留意。每个页面目录下的 .json 只对当前页面生效比如希望记账中心隐藏导航栏就在 bill.json 里写navigationStyle: custom而不是去全局配置里动所有页面。模板里常见的页面标题错乱多半是全局 window 配置和页面级 JSON 配置互相覆盖导致的。2.2 app.json页面注册、tabBar 与自定义导航app.json 是理解模板的钥匙。pages 数组第一项就是首页想让用户打开小程序先落在哪个页面直接调整这一项顺序即可这就是“修改刚进入的加载页面”的标准做法。改完还要确认目标页面是否在 tabBar 中两个区域的页面渲染行为不一样tabBar 页面自带底部导航引导页和登录页则用全屏视觉做引导不受底栏挤压。{ pages: [ pages/guide/guide, pages/login/login, pages/bill/bill, pages/message/message, pages/contacts/contacts, pages/user/user ], window: { navigationBarBackgroundColor: #2c2c2c, navigationBarTitleText: 日常记账, navigationBarTextStyle: white }, tabBar: { color: #999999, selectedColor: #07c160, list: [ { pagePath: pages/bill/bill, text: 记账 }, { pagePath: pages/message/message, text: 消息 }, { pagePath: pages/contacts/contacts, text: 通讯录 }, { pagePath: pages/user/user, text: 我的 } ] } }几个配置点说明tabBar 的 list 数量限制在 2 到 5 项pagePath 必须是 pages 数组里已注册的页面navigationBarTitleText 改的是顶部导航栏标题模板的名称在这里统一替换不需要逐个页面去找tabBar 的 iconPath 可以省略省略后底部只显示文字视觉更简洁对个人记账工具反而合适。如果要做自定义顶部导航把 window.navigationStyle 设为 custom再用 wx.getMenuButtonBoundingClientRect() 获取右上角胶囊按钮的位置。需要注意胶囊按钮位置在不同机型上不固定iPhone 刘海屏和 Android 挖孔屏的 statusBarHeight 差异明显计算导航栏高度的常见公式是const { statusBarHeight } wx.getWindowInfo(); const menu wx.getMenuButtonBoundingClientRect(); // 导航栏高度 胶囊顶部到状态栏距离的两倍 胶囊高度 const navBarHeight (menu.top - statusBarHeight) * 2 menu.height;wx.getWindowInfo 是较新的基础库 API老模板里常见的是 wx.getSystemInfoSync功能等价但新版开发者工具会提示替换。改造自定义导航栏时不要写死某个机型的高度真机预览后按上面的公式动态计算再调整样式。2.3 引导页与登录页redirectTo 与 reLaunch 的使用时机引导页的 WXML 很简洁一张动图加一个按钮view classguide-container image src/images/onling.gif modeaspectFill / button classguide-btn bindtapgoLogin立即体验/button /view对应逻辑Page({ goLogin() { // redirectTo 会关闭当前页用户按返回不会回到引导页 wx.redirectTo({ url: /pages/login/login }); } });页面跳转有三个常用 API行为差别很大接口页面栈行为适用场景wx.navigateTo保留当前页压入新页面记账列表 → 记录详情wx.redirectTo关闭当前页再开新页面引导页 → 登录页wx.reLaunch清空整个页面栈再打开登录成功 → 记账中心登录成功后推荐 wx.reLaunch 而不是 navigateTo因为登录页和引导页都不该留在页面栈里否则用户左滑返回还能退回登录状态观感很差。模板里这三处跳转的写法基本固定改业务时注意区分场景。3. 记账中心从数据绑定到账本持久化的完整链路模板的核心业务在 pages/bill拆开看是记录、列表、汇总三件事。记账中心字段不多但数据绑定、事件处理、本地存储全都会用到是整份模板源码信息密度最高的页面。把这章读透其他页面拿到手上基本不会再卡壳。3.1 一条账单记录的数据结构先把数据模型定下来后面所有渲染和统计都围绕它展开// 一条账单记录的最小字段设计 { id: Date.now() Math.random().toString(36).slice(2, 6), // 唯一主键 type: expense, // expense 支出 / income 收入 category: 餐饮, // 分类名 amount: 32.5, // 金额统一用 Number date: 2024-01-15, // 账单日期字符串便于展示 remark: 午餐, // 备注 createdAt: 1705283234567 // 创建时间戳排序用 }这个结构有几个容易改错的地方。主键只用 Date.now() 不够同一毫秒内的多次调用会得到相同 id加一段随机字符串是常见做法账目删除和编辑依赖 id 定位主键冲突会带来“删一条两条同时消失”的诡异问题。金额字段统一用 Number 而不是字符串否则统计时要反复 parseFloat还可能出现 0.1 0.2 的浮点精度问题展示时用 toFixed(2) 补救。category 要和 images 目录下的图标对应模板里 31.jpg、41.jpg、24.jpg 这些编号图实际对应的分类在 bill.js 顶部的 categories 数组里逐项映射改分类名时要同步改图标路径只动一边就会显示空白占位。如果要把模板改成“只记支出不记收入”的极简版可以把 type 字段固定为 expense但保留字段结构后续加回来时不用动存储数据。3.2 列表渲染与数据绑定wx:for 和 wx:key 的注意点账目列表的 WXML 是最值得抄的一段view classbill-item wx:for{{billList}} wx:keyid image src{{item.icon}} classbill-icon / view classbill-info text classbill-category{{item.category}}/text text classbill-remark{{item.remark}}/text /view text class{{item.type expense ? amount-expense : amount-income}} {{item.type expense ? - : }}{{item.amount}} /text /view双括号语法把 JS 数据绑定到视图层item.icon、item.category 来自 wx:for 遍历的每个元素。wx:key 必须绑定稳定字段模板里如果写成 wx:keyindex在增删记录时视图复用容易错位改成账单 id 后列表的删除、更新都能精确到具体项。金额正负号用三元表达式内联处理比在 JS 里先拼好字符串再渲染清晰得多也方便后续加筛选条件。分类选择在模板里通常有两种实现分类少用 picker分类多超过 8 个用 radio 单选框组。picker 的代码量更小也贴合微信原生交互picker modeselector range{{categories}} bindchangeonCategoryChange view classcategory-picker{{currentCategory || 选择分类}}/view /pickeronCategoryChange(e) { // selector 模式返回的是选中项的下标不是分类名称本身 const index e.detail.value; this.setData({ currentCategory: this.data.categories[index] }); }picker 的 selector 模式返回数组下标很多刚接触的人直接拿 e.detail.value 去存库存进去就变成数字列表渲染出来全是序号。正确做法是先取下标再到 categories 数组里回查名称。radio 单选框组则要注意 value 字段的配对提交表单时拿到的是选中项的 value而不是显示文字。3.3 表单提交与 setData 的数据流新增记录用 form 表单收集输入form bindsubmitonSubmit input nameamount typedigit placeholder0.00 / input nameremark placeholder备注选填 / button form-typesubmit保存/button /form提交逻辑:onSubmit(e) { const { amount, remark } e.detail.value; if (!amount || Number(amount) 0) { wx.showToast({ title: 金额不正确, icon: none }); return; } const record this.buildRecord({ amount: Number(amount), remark: remark || }); // 新记录插入数组头部列表保持倒序展示 this.setData({ billList: [record, ...this.data.billList] }); this.saveToStorage(); }input 的 typedigit 会拉起带小数点的数字键盘适合金额输入。表单用 bindsubmit 之前要先在按钮上声明 form-typesubmit否则提交事件不会触发。金额校验只写 !amount 会漏掉 0 这个边界值所以加了 Number(amount) 0 的二次判断注意 0 值在记账场景里也应视为无效输入。setData 的机制在这里体现得很完整this.data 是同步更新的视图渲染是异步的短时间多次 setData 会被合并成一次渲染。这里不用数组 unshift 是因为 unshift 修改的是原数组引用而 setData 传全新数组更符合小程序单向数据流的更新模型。数据处理完再统一 setData比在循环里多次 setData 性能好得多。3.4 本地存储与云开发模板的默认选择模板默认用本地存储做持久化读写都是同步接口单条记录几十字节没有性能压力saveToStorage() { wx.setStorageSync(billList, this.data.billList); }, onLoad() { const saved wx.getStorageSync(billList) || []; if (saved.length) { this.setData({ billList: saved }); } }本地存储和云开发数据库的取舍是记账类小程序绕不开的选择维度wx.setStorageSync云开发数据库网络依赖无离线可用需要云环境与网络换机同步不支持支持单 key 上限1MB单条记录 512KB成本零成本有免费额度超量计费共享账单无法实现天然支持模板先用本地存储把产品逻辑跑通需要多端同步或共享账单时再把 setStorageSync 换成云开发集合读写数据模型可以沿用改动成本可控。云开发还提供聚合统计能力把支出总额、分类占比这类计算放到后端执行前端只拿结果渲染。4. 消息、通讯录与个人中心微信小程序 API 授权的三个坑这三个模块不在核心记账链路上但每个都对应一个微信平台的能力边界很容易在模板改造时踩坑。4.1 登录模块code 换 token 的正确姿势登录模块的标准链路是wx.login 拿 codecode 交给后端后端用 appid 和 appsecret 调 auth.code2Session 接口换回 openid 和 session_key业务系统再签发自己的 token 返回给小程序端。代码层面wx.login({ success: (res) { // res.code 有效期 5 分钟且只能使用一次 if (res.code) { wx.request({ url: https://api.example.com/login, data: { code: res.code }, success: (resp) { // 后端签发的业务 token存入本地供后续请求鉴权 wx.setStorageSync(token, resp.data.token); } }); } } });这里的基本原则要守住接口用途当前状态wx.login获取临时 code正常wx.getUserProfile获取头像昵称已废弃auth.code2Sessioncode 换 openid服务端调用appsecret 绝不进小程序代码。小程序包可以被反编译拿到全部前端代码secret 一旦出现在前端账号体系基本等于被攻破。早期模板常见的 wx.getUserProfile 已经废弃新版微信小程序获取头像和昵称要用 button 的 open-typechooseAvatar 和 input typenickname 组合模板里如果还引用旧接口真机运行时会直接报错这是登录模块改造时出现频率最高的坑。如果模板里登录模块还带着“密码注册、找回密码”功能而项目没有后端那这些功能实际是把密码存在本地 storage 里的演示实现上线前必须替换成真实后端逻辑否则用户换手机登录就找不回账本。4.2 消息模块订阅消息的一次性原则消息模块在记账场景里最合理的功能是预算提醒。微信订阅消息的规则是“一次订阅、一条推送”wx.requestSubscribeMessage({ tmplIds: [template-id-xxx], success(res) { if (res[template-id-xxx] accept) { // 用户同意后服务端才能合法推送一条订阅消息 } } });用户每点一次允许只能换取一次推送想要持续提醒就必须在每次推送前重新请求订阅。模板里如果写的是更早的 wx.subscribeMessage 或模板消息逻辑需要全部迁到 requestSubscribeMessage否则后端推送会一直报模板 ID 无效。tmplIds 必须填公众平台申请的真实模板 ID开发者工具里随手写的字符串会直接走 fail 回调。调试这类推送时要先在微信公众平台后台看“订阅消息-公共模板库”里有没有对应模板模板标题和字段不完全匹配时授权也会失败。4.3 通讯录模块为什么模板里很难真正实现模板里的通讯录页面通常想实现“朋友之间互相借款、共享账单”。但微信小程序没有开放读取用户微信好友列表的接口wx.getUserInfo 只能拿到当前授权用户自己的信息开放数据域也只对特定类目可用。模板源码里的通讯录模块务实的改造方向是“共享账单成员”由用户手动添加成员昵称和备注后端把这些成员与当前用户 openid 建立关联记账时可以选择分摊对象。这个方案不依赖底层好友关系数据模型可以直接复用账单表加一个成员字段。设计上不要做成“读取微信通讯录”审核阶段几乎不可能通过。4.4 个人中心统计计算与页面生命周期个人中心统计页常把账单列表全量取出用 reduce 聚合computeStats(list []) { const expense list .filter((item) item.type expense) .reduce((sum, item) sum Number(item.amount), 0); const income list .filter((item) item.type income) .reduce((sum, item) sum Number(item.amount), 0); return { expense, income, balance: income - expense }; }本地数据几百条时没问题数据切到云开发后要改成服务端聚合。另一个容易被忽略的点tabBar 页面初次加载后不会自动重新执行 onLoad从记账中心新增一笔返回个人中心时统计数字可能还是旧的这时候要在个人中心页的 onShow 里重新拉取数据而不是依赖 onLoad。模板里如果把统计逻辑放在 onLoad改到 onShow 就能解决“账目变了统计不变”的问题。5. 模板改造替换资源、导出 CSV 与分包的三件小事模板跑通之后动手改造优先级最高的三件事是资源替换、账目导出和包体积控制。5.1 图片资源替换路径与体积两个检查点images 目录下的 31.jpg、24.jpg 这些编号图是分类占位图。替换时先到 bill.js 确认引用路径再决定覆盖原文件还是新增文件只换文件名不查引用页面会一直显示空白。路径统一用/images/xxx.jpg绝对写法避免相对路径在不同层级页面上出现../../错位。每张图片建议压缩到 100KB 以内整个主包必须控制在 2MB 以下超出后优先压缩图片而不是急着加分包。5.2 账目导出 CSV用 FileSystemManager 写文件导出 Excel 的诉求在小程序端用 CSV 落地最省事文件写入本地再调起打开exportCsv() { const list this.data.billList; const header 日期,分类,类型,金额,备注\n; const rows list.map((item) ${item.date},${item.category},${item.type},${item.amount.toFixed(2)},${item.remark} ).join(\n); const filePath ${wx.env.USER_DATA_PATH}/bill_export.csv; const fs wx.getFileSystemManager(); fs.writeFile({ filePath, data: header rows, encoding: utf8, success() { wx.openDocument({ filePath, fileType: csv, showMenu: true }); } }); }wx.env.USER_DATA_PATH 是用户私有的应用数据目录无需额外权限writeFile 成功后用 openDocument 打开。showMenu 设为 true打开的文件右上角才会出现转发、保存菜单。金额字段在导出时先 toFixed(2)避免 CSV 单元格出现 32.5 变成 32.49999999 这样的精度问题。如果账目里有换行或英文逗号的备注导出前要做一次字段转义否则 CSV 的行列会错位最简单的处理是把逗号替换成中文逗号、换行替换成空格。5.3 分包与懒加载包体超限前的两步准备页面数量少时不用考虑分包但引入图表库、日期组件后主包很容易逼近 2MB。先在 app.json 打开按需注入{ lazyCodeLoading: requiredComponents }这会让只有用到的自定义组件参与编译启动时注入的代码量会明显减少。再把历史账单这类低频页面拆进 subpackages{ subpackages: [ { root: pages/history, pages: [history/history] } ] }拆包后跳转历史页的路径变为/pages/history/history/history原页面文件移动到对应 root 目录即可业务逻辑不用动。模板自带的 loading.gif 只是占位资源真实上线时可以用骨架屏替代先把 loading 层的 transition 逻辑改成页面加载完成后淡出比直接删掉动画在观感上更顺滑。把上面三件事做完再打开开发者工具的性能面板跑一次评分等主包体积和启动耗时两项不再报警这套模板就可以当 MVP 去接真实用户了。本文还有配套的精品资源点击获取