ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微信小程序记账统计项目源码拆解:从数据链路到支付接入

微信小程序记账统计项目源码拆解:从数据链路到支付接入 简介这是一份基于微信原生开发框架的记账统计小程序源码包适合初中级开发者通过完整项目学习小程序页面搭建、事件绑定与数据管理也适合课程设计或日常工具类应用参考。项目围绕记账与统计需求设计目录将页面、配置、工具函数与效果图分离便于按模块阅读及二次开发。资源共49个文件包含11个js逻辑文件、9个wxml页面结构、9个wxss样式、9个json配置以及12张png效果截图整体仅64KB轻量但不缺关键环节从页面布局到数据统计均可对照查看。目前已有286人学习/下载。借助源码可对照效果截图快速还原界面理解原生框架下从页面渲染、数据绑定到统计输出的实现路径同时预置的配置与工具模块也能直接复用在其他小程序项目中是一份实用且开放的学习模板。1. 记账统计小程序源码拆解原生框架里的那条完整数据链路很多人练手微信小程序第一个念头是去跑 uni-app 或 Taro 的模板但真把项目部署上线时才发现编译链多出来的那一层反而成了排查问题的障碍。这个记账统计项目走的是微信原生开发框架wxml、wxss、js、json 四件套直接面对开发者没有虚拟 DOM 和编译中转页面加载路径、组件渲染时机、storage 读写逻辑都摊在明面上非常适合用来理解小程序的数据流和生命周期。项目本身功能也不薄账单录入、分类统计、图表展示、支付对接全都有还带了可运行的效果截图能当毕设底子也能当二次开发基座。这篇就按源码目录、数据录入链路、统计聚合、支付与发布这条线把项目拆开讲。2. app.json 全局注册与 utils 账务模块先理清项目骨架拿到压缩包解压后第一件该做的事不是急着预览而是把目录结构结合 app.json 读一遍。原生框架的页面路由不是靠文件夹自动扫描是靠 app.json 里的 pages 数组逐项注册漏注册的页面即使文件存在也无法编译访问。项目里 WechatMiniApp-master 的根目录结构大致如下├── app.js # 全局逻辑生命周期与 globalData ├── app.json # 全局配置页面路由与窗口表现 ├── app.wxss # 全局样式变量 ├── config.js # 业务配置接口地址与支付参数占位 ├── utils/ │ ├── util.js # 日期格式化与金额处理 │ └── storage.js # 本地缓存封装 ├── pages/ │ ├── index/ # 首页账单流 │ ├── add/ # 记账录入页 │ ├── statistics/ # 统计图表页 │ ├── mine/ # 个人中心 │ └── webview/ # 内嵌 H5 容器 └── images/ └── tabbar/ # 底部导航图标关注 app.json 里几个直接影响运行表现的字段pages 首项是启动页项目默认把 pages/index/index 放在数组第一位意味着冷启动直达账单流水页window 节点里的 navigationBarBackgroundColor 配的是首页品牌色下拉刷新与上拉触底逻辑分布在各个页面的 json 配置里。代码里对应片段如下{ pages: [ pages/index/index, pages/add/add, pages/statistics/statistics, pages/mine/mine, pages/webview/webview ], window: { navigationBarBackgroundColor: #2c3e50, navigationBarTitleText: 随手记, navigationBarTextStyle: white, backgroundColor: #f5f6f8 }, tabBar: { list: [ { pagePath: pages/index/index, text: 账单 }, { pagePath: pages/statistics/statistics, text: 统计 }, { pagePath: pages/mine/mine, text: 我的 } ] } }pages 数组顺序决定了底部 tabBar 里页面的切换顺序但如果把 webview 这类非 tab 页也写进 pages 并不会显示在 tabBar 里它只是获得了一个合法的路由地址供 wx.navigateTo 跳转使用。tabBar 限制最少 2 个最多 5 个图标文件路径必须是本地绝对路径不支持网络图片这也是 images/tabbar 目录存在的意义。utils 层是容易被忽略但很关键的部分。生产环境里的记账应用后端接口返回的往往是时间戳或 ISO 字符串页面里直接显示会变成一长串数字所以 util.js 里的 formatDateTime 做了统一转换。storage.js 则是在 wx.setStorageSync 与 getStorageSync 外面包了一层 key 管理所有缓存键名集中定义避免后续维护时散落在业务页面里。这种组织方式在首页和统计页都会用到比如切换 tab 时数据同步就依赖 storage 里同一把 key。从骨架层面能得到一个结论原生项目里凡是跟“全局配置”沾边的优先查 app.json凡是跟“数据加工”沾边的应该往 utils 里归位而不是堆在 Page() 里。这样调试时只需要关注业务页面自身的渲染与交互不纠结数据来源。3. 记账输入的完整链路表单校验、数据存储与选择器交互记账页是整个项目交互密度最高的一页打开 pages/add/add 的 wxml 可以看到典型的原生表单结构金额输入框用 typedigit 调起自带小数点的数字键盘类型切换使用 radio-group 区分支出与收入分类图标区是一组带>function normalizeAmount(raw) { // 去掉首尾空格与千分位逗号再保留下两位小数 return raw.replace(/[^\d.]/g, ).replace(/^\./, ).replace(/\.{2,}/, .); } Page({ data: { amount: , category: 餐饮, type: expense, note: , categories: [ { id: cate_food, name: 餐饮, icon: /images/cate_food.png }, { id: cate_transport, name: 交通, icon: /images/cate_bus.png }, { id: cate_shopping, name: 购物, icon: /images/cate_bag.png } ] }, onAmountInput(e) { this.setData({ amount: normalizeAmount(e.detail.value) }); }, onCategoryTap(e) { const index e.currentTarget.dataset.index; const category this.data.categories[index].name; this.setData({ category }); }, submitBill() { const amount parseFloat(this.data.amount); if (!amount || amount 0) { wx.showToast({ title: 请输入有效金额, icon: none }); return; } const bill { id: ${Date.now()}_${Math.floor(Math.random() * 1000)}, type: this.data.type, category: this.data.category, amount, note: this.data.note.trim(), createdAt: new Date().toISOString() }; const list wx.getStorageSync(bill_list) || []; list.unshift(bill); wx.setStorageSync(bill_list, list); wx.navigateBack(); } });normalizeAmount 这层清洗不能省digit 键盘允许输入连续小数点与负号直接入库会导致统计页 toFixed 阶段抛错。parseFloat 后重新赋值的思路是宁可清洗掉非法字符也不在提交时才弹窗提示因为实时修正的体感比提交后返工要好得多。分类选择的 dataset.index 是数字索引取出来定位 categories 数组对应项这套写法的好处是分类数据与视图解耦后续从服务端拉取动态分类也能复用同一套渲染逻辑不需要改 wxml。存储方面用 bill_list 作为 storage keyunshift 保证新账单在数组头部首页读取后直接用 wx:for 渲染省掉一次排序操作。但这里有个容易被忽略的边界微信小程序本地缓存有 10MB 上限而且 wx.getStorageSync 是同步阻塞 API账单量到几千条时在低端 Android 上会有可感知卡顿。如果是正式产品我会在 submitBill 里加一条阈值判断超过 2000 条时改为增量追加到后端接口本地只保留最近 90 天数据。临时 demo 项目维持现状没问题但你要清楚这个上限的存在。再从交互层看一个细节类别选择在原生框架下不建议用 picker 的 modeselector 替代自绘宫格因为 picker 在自定义 tabBar 页面里偶尔会出现遮罩层高度错位而自绘宫格用 flex 布局天然适配不同机型。项目里这组分类卡片是可点击 view 加边框高亮配合 hover-class 提供点击反馈比 picker 的滚动体验更贴近工具类 App 的操作习惯。4. 统计页面的多维聚合日期分组、canvas 图表与渲染性能统计页是记账类小程序的价值核心pages/statistics/statistics 里干的活可以拆成三块读存储、做聚合、画图表。聚合逻辑放在 onLoad 里同步执行数据源还是 bill_list 这个 key但要按日期归组、按分类求和。原生框架没有内置数组 groupBy手写 reduce 是最直接的方案代码片段如下function groupByCategory(bills, type) { return bills .filter((b) b.type type) .reduce((acc, bill) { const key bill.category; acc[key] (acc[key] || 0) bill.amount; return acc; }, {}); } function groupByMonth(bills) { const map {}; bills.forEach((bill) { const month bill.createdAt.slice(0, 7); // 2025-06 if (!map[month]) map[month] 0; map[month] bill.amount; }); return Object.keys(map) .sort() .map((month) ({ month, total: Math.round(map[month] * 100) / 100 })); }filter 先按收支类型分流再按分类聚合返回的是以分类名为 key 的对象便于 canvas 绘图时直接取长度做扇形角度。这里注意 groupByMonth 里 createdAt 用的是 toISOString 生成的 UTC 字符串slice(0, 7) 拿的是 UTC 月份如果项目面向国内用户且存在跨月记账的时区误差建议在存入 bill 时就改成本地格式化字符串否则 1 号凌晨 0 点到 8 点的账单可能被归到上月。canvas 绘图是统计页代码量最大的一块。项目用 Canvas 2D 接口绘制饼图每次 setData 触发数据变更后调用 drawPieChart 重绘核心是计算每个分类的起始弧度与结束弧度再用 context.arc 和 context.fill 填充。原生 canvas 组件的 type2d 写法比旧版 wx.createCanvasContext 更接近 Web Canvas API节点获取方式变了需要先通过 wx.createSelectorQuery 拿到 canvas 节点const query wx.createSelectorQuery(); query.select(#pieChart).fields({ node: true, size: true }).exec((res) { if (!res || !res[0] || !res[0].node) return; const canvas res[0].node; const ctx canvas.getContext(2d); const dpr wx.getWindowInfo().pixelRatio; canvas.width res[0].width * dpr; canvas.height res[0].height * dpr; ctx.scale(dpr, dpr); const data groupByCategory(this.data.bills, expense); const total Object.values(data).reduce((a, b) a b, 0); let startAngle -Math.PI / 2; Object.keys(data).forEach((key, i) { const angle (data[key] / total) * Math.PI * 2; ctx.beginPath(); ctx.moveTo(res[0].width / 2, res[0].height / 2); ctx.arc( res[0].width / 2, res[0].height / 2, Math.min(res[0].width, res[0].height) / 2 - 10, startAngle, startAngle angle ); ctx.fillStyle [#5470c6, #91cc75, #fac858, #ee6666][i % 4]; ctx.fill(); startAngle angle; }); });dpr 缩放这行不能漏不做这步高分屏下 canvas 会模糊这跟 Web 端 canvas 绘制的原理一致只是小程序里需要通过 wx.getWindowInfo().pixelRatio 拿到设备像素比。绘图数据变更的时机绑定在 onShow 而不是 onLoad因为从记账页 navigateBack 返回后 onLoad 不会再次触发只有 onShow 能感知缓存更新这是统计页最容易出现的“记账后图表不刷新”问题。性能方面渲染层与逻辑层是双线程通信setData 传大数据集会显著拖慢滚动帧率。如果统计页的 bills 数组上千条不建议直接 setData 到视图层先聚合为分类汇总对象再 setData让视图层只做展示不参与计算。另外canvas 绘制和 setData 不要放在同一个同步循环里必要时用 setTimeout 把绘制滞后到下一帧避免同一 tick 内多次布局计算导致 Android 端白屏闪烁。这类账目数据量不高的模板项目不容易踩到但真实记账场景三个月就会积累到千级条数值得提前留好优化位置。5. 微信支付接入与发布审核v3 接口、平台证书与常见驳回避坑支付功能是源码里标注“集成微信支付”后最容易被卡住的部分。先从流程捋一遍小程序端 wx.requestPayment 拉起收银台只是最后一步在这之前需要后端调用微信支付 v3 的下单接口获取 prepay_id再通过签名生成 payment 参数返回到前端。源码 config.js 里通常只留了参数占位真正对接时要注意 v3 和 v2 的签名差异v3 用 SHA256-RSA2048 签名要求商户 API 私钥与证书序列号微信支付平台证书用于验签响应报文v2 的老签名方式虽然兼容但新商户号默认走 v3旧代码直接搬过去大概率报“无可用的平台证书”需要在商户平台 API 安全里下载最新平台证书并上传到服务端。一个最小可用的请求头组成如下POST /v3/pay/transactions/jsapi HTTP/1.1 Authorization: WECHATPAY2-SHA256-RSA2048 mchid商户号,nonce_str随机串,signature签名,timestamp时间戳,serial_no证书序列号 Content-Type: application/json前端拿到 prepay_id 后的调起代码是固定套路需要注意 timeStamp 是字符串nonceStr 随机串每次调用都要重新生成paySign 的签名串由 appId、timeStamp、nonceStr、package 拼接后 RSA 签名顺序不能乱wx.requestPayment({ timeStamp: res.data.timeStamp, nonceStr: res.data.nonceStr, package: res.data.package, signType: RSA, paySign: res.data.paySign, success: () { wx.showToast({ title: 支付成功, icon: success }); }, fail: (err) { if (err.errMsg.includes(cancel)) return; wx.showToast({ title: 支付失败, icon: none }); } });signType 要跟后端签名算法匹配v3 接口必须写 RSA写 MD5 会直接报签名错误。这边有个屡见不鲜的坑成功回调里如果直接刷新余额或列表容易把“支付成功”和“回调通知成功”混为一谈。前端收到 success 只代表微信收银台关闭且支付成功业务侧订单状态应以服务端收到支付回调为准否则网络异常时客户端没收到的订单就漏了。严谨的做法是前端支付成功后调一次查询接口确认订单状态。发布审核阶段最容易驳回的理由集中在隐私协议与导航栏适配上。源码里 webview 页面如果用于加载第三方 H5审核时会被要求补充“网页内用户信息收集说明”而且 webview 的域名必须配置在业务域名白名单里。另外自定义导航栏高度问题值得单独说项目如果改用了自定义导航栏状态栏高度用 wx.getWindowInfo().statusBarHeight 获取再算上胶囊按钮的 bottom 与 top 差值才能确定整个导航栏高度直接写死 44px 在全面屏和折叠屏上都会错位。审核人员如果操作的是非主流机型错位的按钮就会被判定为“功能使用异常”。把这两点处理掉审核通过率会明显提高。本文还有配套的精品资源点击获取
返回列表