十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

微信小程序记账本实战:从app.json到utils的完整开发闭环

微信小程序记账本实战:从app.json到utils的完整开发闭环 简介本资源是一套完整的微信小程序实战项目——日常生活记账本源码面向前端初学者及小程序开发入门者聚焦移动端轻应用开发场景解决个人收支管理工具从零搭建的核心问题。压缩包共44个文件含9个JS逻辑文件实现数据绑定、本地存储与页面交互、7个WXML结构文件构建主页、记账页、设置页等多页面视图、8个WXSS样式文件支持主题色、响应式布局、9个JSON配置文件含app.json、页面路由及项目配置辅以5张PNG图标与README.md说明文档整体仅83KB轻量易读。已有1040人学习下载代码结构清晰、模块职责分明完整呈现MVVM数据流、wx.setStorageSync持久化方案、收支分类与预算提醒功能实现逻辑是理解小程序生命周期、WXML/WXSS语法及真实业务功能落地的优质练手案例。1. 一个能跑通、能改、能上线的微信小程序记账本不是模板套壳而是从app.json到utils的真实闭环你打开微信开发者工具新建项目后看到的app.json里只有pages: [pages/index/index]但真正做记账本时你会发现首页要展示本月收支汇总添加页要带日期选择和分类下拉列表页得支持按周/月筛选数据还得本地持久化——这些都不是wx:for循环一遍就能解决的。这个「日常生活记账本」项目本质是微信小程序基础能力的集中验证场它不依赖云开发或第三方 SDK用原生框架把project.config.json的编译配置、app.json的页面路由与生命周期、utils目录下的格式化与计算逻辑全部串起来。适合刚学完 WXMLWXSS 基础、正卡在「怎么让数据动起来」的新手也适合需要快速交付轻量记账功能、拒绝 uni-app 抽象层干扰的前端工程师。它不追求炫酷动画但每一步操作都有明确的数据流向和错误反馈——比如点击「添加」按钮后utils/formatDate.js会把用户选的日期转成2024-06-15标准格式再由utils/calculate.js实时更新余额最后通过wx.setStorageSync写入本地。这才是小程序开发的真实节奏小步验证层层归因。2. 从app.json路由定义到project.config.json编译配置构建可调试的最小记账骨架微信小程序的启动逻辑始于app.json它不仅是页面清单更是整个应用的结构契约。记账本项目必须明确声明所有页面路径、窗口样式及 tabBar 导航否则开发者工具无法正确加载页面栈。而project.config.json则决定了你在本地开发时能否启用 ES6 转译、是否开启调试器、以及如何映射utils目录——这两份配置文件共同构成可复现的开发环境基线。2.1app.json中的页面路由与 tabBar 配置必须匹配实际目录结构记账本至少包含 4 个核心页面首页统计汇总、添加页表单录入、列表页明细查看、设置页分类管理。它们在app.json中的声明必须与物理路径严格一致且顺序影响页面栈初始状态{ pages: [ pages/index/index, pages/add/add, pages/list/list, pages/settings/settings ], tabBar: { color: #7A7E83, selectedColor: #3cc51f, borderStyle: black, list: [ { pagePath: pages/index/index, text: 首页, iconPath: assets/icons/home.png, selectedIconPath: assets/icons/home-active.png }, { pagePath: pages/list/list, text: 明细, iconPath: assets/icons/list.png, selectedIconPath: assets/icons/list-active.png } ] }, window: { navigationBarTitleText: 我的记账本, navigationBarBackgroundColor: #ffffff, navigationBarTextStyle: black } }提示tabBar中list数组长度不能超过 5且pagePath必须是pages数组中已声明的路径。图标文件需提前放入assets/icons/目录否则真机调试时 tabBar 图标显示为空白。若误将pagePath: pages/add/add写成pages/add开发者工具会报错Error: page pages/add doesnt exist且无法进入添加页。2.2project.config.json控制开发体验关键字段决定utils目录能否被正确识别project.config.json是微信开发者工具的私有配置它不参与代码包上传但直接影响本地开发效率。记账本项目中utils目录用于存放日期处理、金额计算等纯函数模块必须确保其路径被工具正确解析{ description: 日常生活记账本, packOptions: { ignore: [] }, setting: { urlCheck: true, es6: true, enhance: true, postcss: true, preloadBackgroundCode: true, minified: false, newFeature: true, coverView: true, nodeModules: false, autoAudits: false, scopeDataCheck: false, uglifyFileName: false, compileHotReLoad: false, useApiHook: true, babelSetting: { ignore: [], disablePlugins: [], outputPath: } }, compileType: miniprogram, libVersion: 2.29.4, appid: wx1234567890abcdef, projectname: daily-account-book, debugOptions: { hidedInDevtools: [] }, isGameProject: false, scriptLevel: ES2015, minPlatformVersion: 1.0.0, other: { usingComponents: true } }注意es6: true和enhance: true必须同时开启否则utils/formatDate.js中的export const formatDate (date) {...}语法会被转译失败usingComponents: true启用自定义组件支持为后续扩展分类选择器埋下伏笔。若appid未填写或格式错误如含空格真机预览时会提示当前项目未绑定 AppID请前往微信公众平台设置此时需登录微信公众平台在「开发管理」→「开发设置」中复制正确的 AppID 并粘贴至此。2.3 页面级json配置决定导航栏与下拉刷新行为首页与添加页差异化设置每个页面可拥有独立的json配置覆盖app.json的全局设置。记账本中首页需启用下拉刷新以同步最新数据而添加页则需隐藏导航栏以聚焦表单输入pages/index/index.json{ enablePullDownRefresh: true, onReachBottomDistance: 50, navigationBarTitleText: 收支总览 }pages/add/add.json{ navigationStyle: custom, navigationBarBackgroundColor: #ffffff, navigationBarTextStyle: black }提示navigationStyle: custom表示完全自定义导航栏此时wx.navigateTo跳转到该页面时系统默认返回箭头消失需手动在 WXML 中添加view classnav-back bindtapgoBack←/view并在 JS 中实现goBack() { wx.navigateBack() }。若忘记在add.js中定义goBack方法点击自定义返回按钮将无响应且控制台无报错——这是新手高频踩坑点。3.utils目录下的核心函数日期格式化、金额计算与本地存储封装记账本的数据灵魂不在 UI 层而在utils目录。这里存放的不是工具类库而是针对记账场景定制的纯函数它们不依赖 this 或组件实例输入确定输出唯一便于单元测试和跨页面复用。utils/formatDate.js处理时间维度utils/calculate.js处理金额维度utils/storage.js封装本地存储——三者构成数据流的稳定三角。3.1utils/formatDate.js统一日期标准规避new Date()在 iOS 上的解析歧义微信小程序中new Date(2024-06-15)在 Android 设备上正常但在 iOS 上可能返回Invalid Date因为 Safari 对 ISO 8601 格式支持不一致。记账本必须将用户选择的日期如2024/06/15标准化为YYYY-MM-DD格式并提供按周/月分组的辅助方法// utils/formatDate.js /** * 将任意格式日期字符串转为 YYYY-MM-DD 标准格式 * param {string|Date} date - 输入日期支持 2024/06/15、2024-06-15、new Date() * returns {string} 标准化后的日期字符串如 2024-06-15 */ export const formatDate (date) { if (!date) return ; const d new Date(date); if (isNaN(d.getTime())) { // 兜底处理尝试替换斜杠为短横线 const fixed String(date).replace(/\//g, -); return formatDate(fixed); } const year d.getFullYear(); const month String(d.getMonth() 1).padStart(2, 0); const day String(d.getDate()).padStart(2, 0); return ${year}-${month}-${day}; }; /** * 获取指定日期所在周的起始日周一和结束日周日 * param {string} dateStr - YYYY-MM-DD 格式日期 * returns {{start: string, end: string}} 周范围对象 */ export const getWeekRange (dateStr) { const date new Date(dateStr); const day date.getDay() || 7; // getDay() 返回 0周日时转为 7 const diffToMonday date.getDate() - day 1; const monday new Date(date.setDate(diffToMonday)); const sunday new Date(monday); sunday.setDate(monday.getDate() 6); return { start: formatDate(monday), end: formatDate(sunday) }; };逻辑说明formatDate函数首先尝试直接构造Date对象失败时用正则将/替换为-再试一次避免 iOS 下2024/06/15解析失败。getWeekRange中date.getDay() || 7确保周日被识别为第 7 天从而正确计算周一日期。参数dateStr必须是YYYY-MM-DD格式否则new Date(dateStr)可能返回Invalid Date。3.2utils/calculate.js收支分类聚合与余额实时计算避免浮点数精度误差记账本的核心计算逻辑是对同一日期、同一分类的多条记录求和并累加得到总余额。JavaScript 的0.1 0.2 ! 0.3问题在金额计算中不可接受必须使用整数运算// utils/calculate.js /** * 将元单位金额转为分单位整数避免浮点误差 * param {number|string} amount - 元为单位的金额如 19.99 * returns {number} 分为单位的整数如 1999 */ export const yuanToCent (amount) { return Math.round(parseFloat(amount) * 100); }; /** * 将分单位整数转为元单位字符串保留两位小数 * param {number} cents - 分为单位的整数 * returns {string} 元为单位的字符串如 19.99 */ export const centToYuan (cents) { return (cents / 100).toFixed(2); }; /** * 按分类聚合收支数据 * param {Array} records - 记账记录数组每项含 {date, type, category, amount} * returns {Object} 按 category 分组的 {income: number, expense: number} 对象 */ export const groupByCategory (records) { const result {}; records.forEach(record { const cat record.category || 其他; if (!result[cat]) { result[cat] { income: 0, expense: 0 }; } const cents yuanToCent(record.amount); if (record.type income) { result[cat].income cents; } else { result[cat].expense cents; } }); return result; };参数说明yuanToCent使用Math.round而非parseInt防止19.999被截断为1999centToYuan的toFixed(2)确保输出恒为两位小数避免19.9显示为19.90。groupByCategory的record.type必须严格为income或expense字符串否则分类统计将出错。3.3utils/storage.js封装wx.setStorageSync与wx.getStorageSync增加错误边界本地存储是记账本的数据基石但直接调用wx.setStorageSync存储大对象易触发storage limit exceeded错误。utils/storage.js提供带容量检查与 JSON 序列化封装的存取接口// utils/storage.js const STORAGE_KEY daily_account_records; /** * 安全写入记账记录到本地存储 * param {Array} records - 记账记录数组 * returns {boolean} 是否写入成功 */ export const saveRecords (records) { try { const str JSON.stringify(records); const size str.length; // 微信小程序单 key 最大存储 10MB此处预留安全余量 if (size 8 * 1024 * 1024) { console.error(记录数据过大超出存储限制); return false; } wx.setStorageSync(STORAGE_KEY, str); return true; } catch (e) { console.error(保存记录失败:, e); return false; } }; /** * 从本地存储读取记账记录 * returns {Array} 记账记录数组失败时返回空数组 */ export const loadRecords () { try { const str wx.getStorageSync(STORAGE_KEY); return str ? JSON.parse(str) : []; } catch (e) { console.error(读取记录失败:, e); return []; } };提示saveRecords中str.length计算的是 UTF-16 编码字节数与微信文档中「10MB」限制单位一致。若records包含undefined或functionJSON.stringify会忽略这些字段导致数据丢失——因此在调用saveRecords前必须确保records中每个对象的date、type、category、amount字段均为有效值。4. 页面逻辑串联首页数据渲染、添加页表单提交与列表页筛选联动单个函数写得再好若页面间数据不流通记账本仍是碎片。本章将utils的能力注入具体页面首页通过loadRecords获取数据并调用groupByCategory渲染分类统计添加页收集表单后经formatDate标准化日期、yuanToCent转换单位再saveRecords持久化列表页则利用getWeekRange实现按周筛选。三者通过App()全局实例共享数据形成闭环。4.1 首页index.js下拉刷新触发数据重载与视图更新首页是用户第一入口需在onLoad时加载数据并在onPullDownRefresh时强制刷新// pages/index/index.js import { loadRecords } from ../../utils/storage.js; import { groupByCategory } from ../../utils/calculate.js; import { formatDate } from ../../utils/formatDate.js; Page({ data: { summary: { income: 0.00, expense: 0.00, balance: 0.00 }, categories: {} }, onLoad() { this.loadAndRender(); }, onPullDownRefresh() { this.loadAndRender(); }, loadAndRender() { const records loadRecords(); const grouped groupByCategory(records); // 计算总收入、总支出、余额 let totalIncome 0; let totalExpense 0; Object.values(grouped).forEach(cat { totalIncome cat.income; totalExpense cat.expense; }); this.setData({ summary: { income: centToYuan(totalIncome), expense: centToYuan(totalExpense), balance: centToYuan(totalIncome - totalExpense) }, categories: grouped }); wx.stopPullDownRefresh(); // 必须手动停止下拉刷新动画 } });逻辑说明loadAndRender方法被onLoad和onPullDownRefresh共同调用确保首次进入和下拉刷新行为一致。wx.stopPullDownRefresh()必须显式调用否则下拉动画不会消失。centToYuan函数需从utils/calculate.js导入若忘记导入setData中将出现centToYuan is not defined报错。4.2 添加页add.js表单提交前校验与数据标准化添加页的form组件提交事件需拦截原始值进行日期标准化、金额单位转换并追加到现有记录中// pages/add/add.js import { saveRecords } from ../../utils/storage.js; import { loadRecords } from ../../utils/storage.js; import { formatDate } from ../../utils/formatDate.js; import { yuanToCent } from ../../utils/calculate.js; Page({ data: { date: formatDate(new Date()), type: expense, category: 餐饮, amount: }, handleFormSubmit(e) { const { detail } e; const { date, type, category, amount } detail.value; // 基础校验 if (!amount || isNaN(parseFloat(amount)) || parseFloat(amount) 0) { wx.showToast({ title: 请输入有效金额, icon: none }); return; } // 构建新记录 const newRecord { date: formatDate(date), // 强制标准化 type, category, amount: parseFloat(amount).toFixed(2) // 保留两位小数 }; // 读取旧记录追加新记录保存 const records loadRecords(); records.push(newRecord); const success saveRecords(records); if (success) { wx.showToast({ title: 添加成功, icon: success }); // 重置表单 this.setData({ date: formatDate(new Date()), type: expense, category: 餐饮, amount: }); // 2秒后返回首页 setTimeout(() wx.navigateBack(), 2000); } else { wx.showToast({ title: 保存失败请重试, icon: none }); } } });参数说明detail.value来自form组件的bindsubmit事件其中date字段值为2024-06-15Picker 日期选择器输出amount为字符串19.99。parseFloat(amount).toFixed(2)确保金额字符串恒为两位小数避免19.9存入后显示为19.90。4.3 列表页list.js按周/月筛选与getWeekRange的实际应用列表页需支持切换筛选维度getWeekRange在此发挥关键作用// pages/list/list.js import { loadRecords } from ../../utils/storage.js; import { formatDate } from ../../utils/formatDate.js; import { getWeekRange } from ../../utils/formatDate.js; Page({ data: { records: [], filterType: all, // all, week, month currentWeek: , currentMonth: }, onLoad() { this.setData({ currentWeek: getWeekRange(new Date()).start, currentMonth: formatDate(new Date()).slice(0, 7) // 2024-06 }); this.filterAndRender(all); }, switchFilter(e) { const type e.currentTarget.dataset.type; this.filterAndRender(type); }, filterAndRender(type) { const allRecords loadRecords(); let filtered []; if (type week) { const weekRange getWeekRange(new Date()); filtered allRecords.filter(r r.date weekRange.start r.date weekRange.end ); } else if (type month) { const month formatDate(new Date()).slice(0, 7); filtered allRecords.filter(r r.date.startsWith(month)); } else { filtered allRecords; } this.setData({ records: filtered, filterType: type }); } });提示getWeekRange(new Date())返回当前周范围r.date weekRange.start r.date weekRange.end利用字符串字典序比较日期2024-06-15 2024-06-10成立无需转换为Date对象性能更高。r.date.startsWith(month)用于月筛选比r.date.slice(0, 7) month更简洁。5. 真机调试与发布前必查app.json路径一致性、utils模块导入链与存储容量预警项目开发完成不等于可上线。微信小程序的真机表现与开发者工具存在差异尤其在路径解析、存储上限和日期 API 兼容性上。本章聚焦三个发布前必验点用console.log验证app.json页面路径是否被正确加载检查utils模块导入是否形成循环引用通过wx.getStorageInfoSync主动探测剩余存储空间。5.1 用console.log在App.onLaunch中验证app.json页面路径加载状态app.js的onLaunch是小程序初始化入口此处打印getCurrentPages()可确认首页是否被正确注册// app.js App({ onLaunch() { console.log(App launched, current pages:, getCurrentPages()); // 输出应为 [{ route: pages/index/index, ... }] // 若输出为空数组或 route 不匹配 app.json pages 中的路径则页面注册失败 } });操作步骤在开发者工具中点击「编译」观察控制台输出。若getCurrentPages()返回空数组检查app.json中pages数组是否为空或路径拼写错误如pages/index/index误写为pages/index若返回对象中route字段为pages/add/add说明首页未被设为首个页面需调整app.json中pages数组顺序。5.2 检查utils模块导入链避免A → B → A的循环依赖记账本中utils/calculate.js依赖utils/formatDate.js若formatDate.js又反向导入calculate.js将导致Cannot assign to read only property exports错误。验证方法是在utils目录下执行# 在项目根目录运行需安装 nodejs npx madge --circular --extensions js ./utils/逻辑说明madge是静态分析工具--circular参数检测循环依赖。若输出No circular dependencies found说明导入链健康若输出类似utils/formatDate.js → utils/calculate.js → utils/formatDate.js则需重构将共用逻辑抽离至utils/common.js或改用参数传递替代模块导入。5.3 主动探测存储容量wx.getStorageInfoSync返回值解读与阈值告警wx.getStorageInfoSync返回对象包含currentSize已用字节数和limitSize总上限字节数需在添加页提交前主动检查// pages/add/add.js 中 handleFormSubmit 方法内插入 const storageInfo wx.getStorageInfoSync(); const usedRatio storageInfo.currentSize / storageInfo.limitSize; if (usedRatio 0.8) { wx.showModal({ title: 存储空间紧张, content: 已使用 ${Math.round(usedRatio * 100)}%建议清理旧记录, showCancel: true, confirmText: 立即清理, success: (res) { if (res.confirm) { // 执行清理逻辑 wx.clearStorageSync(); wx.showToast({ title: 已清空, icon: success }); } } }); }参数说明storageInfo.limitSize在多数设备上为1048576010MBcurrentSize为当前占用字节数。usedRatio 0.8设定 80% 为预警阈值既留出缓冲空间又避免用户突然无法添加新记录。wx.clearStorageSync()会清空所有wx.setStorageSync数据生产环境应改为选择性删除过期记录。本文还有配套的精品资源点击获取
返回列表