微信小程序日记项目实战:从数据存储到页面设计全攻略 📅 发布时间:2026/9/1 22:03:54 👁 浏览次数: 简介这是一份面向K12阶段初学者与高校微信小程序入门课程学生的期末大作业实践资源聚焦日记类轻应用开发帮助学习者掌握小程序基础架构、页面跳转、数据渲染与本地存储等核心能力。压缩包共53个文件含11个带完整注释的JS逻辑文件涵盖日记增删改查、页面路由与请求封装、4个WXML结构文件、5个WXSS样式文件、3个JSON配置文件app.json、project.config.json等以及29张TabBar与功能图标PNG资源整体仅68KB轻量易导入。已有1820人下载学习适合作为课堂实训、课设参考或自学练手项目。代码结构清晰按pages目录分模块组织entry首页、list列表页、new新建页、mine个人页配套services层封装请求与工具函数支持快速二次开发与功能拓展无需额外环境配置即可运行。1. 项目整体设计与思路拆解1.1 期末大作业怎么选方向为什么是日记小程序期末大作业这件事很多同学容易踩两个极端要么选个太简单的比如纯静态页面答辩时老师一问三不知要么选个太复杂的比如电商支付直播最后代码没写完演示直接翻车。我的建议是选一个“功能闭环完整、技术栈覆盖足够、三天内能跑通”的项目日记小程序恰好符合这三个条件。日记小程序虽然叫“简单”但它天然包含了一个完整应用该有的所有模块列表展示、表单输入、数据持久化、状态切换、图片上传甚至还能加搜索、日历、统计这些扩展点。它不像商城那样要接支付、不像社交那样要做即时通讯但又能把微信小程序的核心API覆盖掉大半。我当时拿这个项目去答辩老师主要问了三个方面数据存哪里、页面之间怎么传值、图片上传用的什么方案这三个问题正好都能在日记小程序里找到对应答案整个答辩过程非常顺。1.2 技术选型原生小程序还是uni-app先泼一盆冷水不要因为看到网上一堆“uni-app跨端开发”的教程就冲动选uni-app除非你已经明确要同时发布到支付宝小程序或者App。期末大作业的场景下原生微信小程序反而是最优选择原因有三点第一原生框架的调试链路最短。你在微信开发者工具里写完代码直接编译就能看效果报错信息也是微信自己的一套体系网上随便搜都能找到答案。如果用uni-app中间多了一层编译转换一旦出现样式错乱、API不兼容排查问题的成本是翻倍的。第二期末答辩的老师大概率只认微信小程序。他打开微信开发者工具就能直接跑你的项目能直接看到模拟器里的小程序界面不需要额外装HBuilderX、不需要配置运行环境演示成本越低印象分越高。第三原生小程序的数据绑定和生命周期模型本身就是Vue风格的语法学会了之后你后面再上手uni-app、甚至Vue框架迁移成本都很低。从学习价值上讲原生方案并不吃亏。1.3 页面结构设计4个页面串起完整流程页面结构我建议控制在4个页面左右既能展示完整的业务逻辑又不至于把自己累死。我当时设计的页面结构是这样的首页日记列表默认按时间倒序展示支持下拉刷新和上拉加载。写日记页核心表单页包含标题输入、正文输入、心情单选框、图片上传。日历页按月展示日记分布情况点击某个日期进入该天的日记列表。我的页简单统计总篇数、连续写天数加上数据导出入口。每个页面承载的职责尽量单一不要搞一个巨型页面把所有功能堆在一起。写日记页面和日历页面分开哪怕只是数据上的“伪连通”在答辩时也更容易讲清楚你的设计思路。我当时就在日历页做了“按日期标注紅点”的效果逻辑很简单就是在日期数字下方渲染一个红色小圆点有日记就显示没有就不显示但视觉效果特别好老师看了第一眼就觉得“这项目做得挺完整”。2. 核心功能模块与关键实现2.1 数据层设计日记的数据模型定好了后面不返工写代码之前先把数据模型定下来这是很多新手容易忽略的一步。日记小程序的数据模型我建议用这样一个结构{ _id: 系统自动生成或自定义的ID, title: 日记标题, content: 日记正文, mood: happy, images: [cloud://xxx/xxx.jpg], createTime: 1698888888000, updateTime: 1698889999000, dateKey: 2023-11-02 }字段含义我给新同学逐个解释一下。mood是心情状态我用英文单词作为值happy、calm、sad、angry分别对应开心、平静、难过、生气这样存储上不依赖中文也方便后续做统计。images数组存图片的云存储路径或者本地临时路径用数组是因为一篇日记可能配多张图。dateKey是个字符串日期格式是“YYYY-MM-DD”这个字段主要为了解决日历功能里的日期匹配问题——你不可能用时间戳去跟自然日做等值比较把日期单独拉出来一个字符串字段最方便。我见过很多同学的代码createTime和updateTime不分写日记时只存一个时间后面想做“编辑时间”展示就没数据可用。我当时一开始也没存updateTime后来加编辑功能时发现改过的日记没法显示“修改于xxx”只能补一个字段。这属于典型的前期设计欠账后面用代码补。现在这个模型一步到位你也少走这个坑。2.2 本地存储与云开发两种方案怎么选这是整个项目里最核心的技术决策。日记数据存在哪有两种主流方案方案A本地存储用wx.setStorageSync和wx.getStorageSync数据存在用户手机的微信缓存里。优点是零成本、代码简单、不用搭后端写完就跑缺点是换设备数据就没了卸载小程序数据也清空。方案B云开发用微信云开发的云数据库和云存储。优点是数据真正在“云端”换设备也能同步图片上传走云存储也很方便整个项目看起来更像一个真实产品。缺点是需要开通云环境写云函数的逻辑开发复杂度高一些。我的建议如果学校老师没明确要求必须用云开发优先做本地存储版。原因很现实本地存储方案的数据读写是同步的代码写起来非常直白比如保存一篇日记就是const diary { title: this.data.title, content: this.data.content, mood: this.data.mood, images: this.data.images, createTime: Date.now(), updateTime: Date.now(), dateKey: formatDate(new Date()) }; const list wx.getStorageSync(diaryList) || []; list.unshift(diary); wx.setStorageSync(diaryList, list);这段代码任何一个学过JavaScript的人都能看懂没有回调地狱没有Promise链出了问题也容易排查。而云开发的写入接口是异步的需要await需要处理成功回调、失败回调、Loading状态对于期末大作业的完成速度来说完全没必要上来就挑战这个难度。但那话又说回来如果老师要求了必须用云开发或者你想用这个项目去参加某些比赛那就老老实实上云。具体做法我在第3节单独讲。2.3 心情单选框一个面试官常问的细节热搜词里有个“微信小程序单选框”我猜很多人是在写表单类页面时卡住了。日记小程序里的“心情选择”功能正好可以用单选框组实现。先看代码wxml部分view classmood-picker view wx:for{{moodList}} wx:keyvalue classmood-item {{selectedMood item.value ? active : }} bindtaponMoodSelect >data: { moodList: [ { value: happy, label: 开心 }, { value: calm, label: 平静 }, { value: sad, label: 难过 }, { value: angry, label: 生气 } ], selectedMood: happy }, onMoodSelect(e) { this.setData({ selectedMood: e.currentTarget.dataset.value }); }你可能发现了我这里没用微信原生的radio-group和radio组件而是自己用view拼了一个。原因在于原生radio的样式非常死板想改成圆角卡片风格要写一堆样式覆盖而直接用一个view加一个选中态class样式上完全自己说了算。这就是为什么面试官喜欢问“单选框实现方案”这类问题——看起来简单但其实是在考察你对组件封装的灵活程度以及你是否理解setData和>const uploadTask wx.cloud.uploadFile({ cloudPath: diary/ Date.now() - Math.floor(Math.random() * 1000) .jpg, filePath: tempFilePath }); uploadTask.then(res { console.log(上传成功, res.fileID); this.setData({ images: [...this.data.images, res.fileID] }); });我的建议是图片功能第一版先做“选择图片-显示缩略图”的展示逻辑云存储上传如果时间来不及可以后面再加。答辩时老师更关注的是你能不能解释清楚这两种方案的区别而不是你的图片到底能不能在云端存一年。能说清“临时路径”和“cloud://永久链接”这两个概念已经算过关了。3. 实操过程从零跑通日记小程序的四个核心页面3.1 工程初始化与页面注册别漏了app.json新建项目这一步不用多说微信开发者工具里直接选“小程序”模板建一个空项目。关键是要在app.json里把页面注册好这是小程序和普通Web开发最大的区别之一——每一个页面都要在pages字段里提前声明。{ pages: [ pages/index/index, pages/edit/edit, pages/calendar/calendar, pages/profile/profile ], window: { navigationBarTitleText: 我的日记, navigationBarBackgroundColor: #ffffff, navigationBarTextStyle: black }, tabBar: { list: [ { pagePath: pages/index/index, text: 日记 }, { pagePath: pages/calendar/calendar, text: 日历 }, { pagePath: pages/profile/profile, text: 我的 } ] } }tabBar是底部导航栏这里我把写日记页排除了因为写日记是个临时动作不应该常驻在底部导航里而是从首页右上角一个“”按钮跳进去。这样更像真实应用的操作逻辑。有些同学会把“写日记”也塞进tabBar按起来是方便了但页面结构反而不合理。还有一个很多新手忽略的细节tabBar的list数组最少2项、最多5项而且页面路径必须是已注册的页面。我见过有人把tabBar页面路径写错了编译直接报错找不到页面。这种错误属于低级失误在答辩时被老师一眼看出来会很尴尬。3.2 首页日记列表从Storage读到渲染完整链路首页是整个小程序的“门面”首选效果要足够好。我用的方案是onShow生命周期里从本地缓存拉取最新数据然后setData渲染到页面上。为什么要放在onShow而不是onLoad原因很关键onLoad只在页面首次加载时执行一次如果你从首页跳到写日记页写完一篇再返回首页的onLoad不会重新触发列表就不会刷新。而onShow是每次页面显示都会触发天然适合做数据同步。核心代码onShow() { const list wx.getStorageSync(diaryList) || []; this.setData({ diaryList: list }); }你可能觉得这也太简单了对就是这么简单。本地存储方案下数据读取就是这么粗暴直接。但setData的性能问题得注意如果日记数量很大比如几百篇一次性把整个数组塞给setData会让渲染卡顿。期末大作业的体量不会有这个烦恼但如果后续你想优化可以用setData只传增量数据或者做分页加载。列表项我用的是卡片式布局每一篇日记显示标题、摘要、日期、心情图标。摘要在wxml里直接用wxs处理截取正文前30个字符。这里有个你可能遇到的坑在wxml里不能直接调用JavaScript的substring方法需要用wxs脚本或者提前在js里截好。如果不知道这个限制你会白白浪费半小时查“为什么模板里方法不执行”。3.3 写日记页面表单校验与返回刷新的关键操作写日记页面的核心逻辑是输入标题、输入正文、选心情、传图片点保存把数据写入Storage然后返回首页。这里面有几个细节值得展开。第一个是表单校验。标题可以为空吗我建议标题允许为空但要有默认值比如“无题”。因为有些同学写日记就是随手记录心情不想起标题你要是强制标题必填体验就很差。但正文不能为空至少写一个字这是底线。保存时判断一下if (!this.data.content.trim()) { wx.showToast({ title: 写点内容再保存吧, icon: none }); return; }第二个是保存按钮的防重复点击。用户连续点两次保存会插入两条一模一样的日记。解决方法很简单用一个布尔变量做锁let isSaving false; function onSave() { if (isSaving) return; isSaving true; // 保存逻辑... setTimeout(() { isSaving false; }, 1000); }第三个关键点是保存后返回首页的刷新。我用的方案是wx.navigateBack()返回首页在onShow里重新读取Storage刷新完成。这种“页面间传值靠Storage读取而不是靠参数传递”的思路在小程序里非常常见因为Storage是全局共享的任何页面都能读。3.4 编辑与删除同一个页面复用用参数区分写日记页面做好之后编辑功能其实就是“复用”同一个页面从列表页点某篇日记跳到写日记页并带上日记的ID写日记页根据有没有这个ID决定是“新建”还是“回填数据”。跳转时的参数传递长这样wx.navigateTo({ url: /pages/edit/edit?id diaryId });在编辑页的onLoad里取参数onLoad(options) { if (options.id) { this.setData({ diaryId: options.id }); // 从Storage里找到对应日记回填表单 } }删除功能就简单了在日记详情页或者列表项上加一个长按/按钮触发删除过滤掉对应ID的数据重新写回Storageconst newList list.filter(item item._id ! diaryId); wx.setStorageSync(diaryList, newList);这里我踩过一个坑本地Storage存的是对象数组对象的唯一标识如果我用Date.now()生成当同毫秒内创建了两篇日记时可能重复。后来我改成Date.now() Math.random()的组合方式基本不会冲突。如果引入云开发直接用云数据库自动生成的_id就行了。3.5 日历视图用日期标识渲染日记红点日历页我用的方案是动态计算当月每天是星期几然后以7列为一组用view渲染出来。这部分的算法核心是Date对象的操作function buildCalendar(year, month) { const firstDay new Date(year, month - 1, 1); const daysInMonth new Date(year, month, 0).getDate(); const startWeekday firstDay.getDay(); // 0-60是周日 const cells []; for (let i 0; i startWeekday; i) { cells.push({ day: , empty: true }); } for (let d 1; d daysInMonth; d) { cells.push({ day: d, empty: false }); } return cells; }这个算法本身不复杂但要多解释一句new Date(year, month, 0)拿到的是上一个月的最后一天所以它的getDate()就是这个月的天数。很多同学会直接写new Date(year, month, 1)然后加加减减绕来绕去反而容易出错直接用month 1传0这个技巧一行代码搞定。红点逻辑是在渲染时判断某个日期是否存在日记我用的方案是把所有日记的dateKey取出来存成一个对象Set渲染每个日期时直接查这个对象是否有值有就显示红点。这样性能上是最优的因为查对象属性的时间复杂度是O(1)而用indexOf查数组是O(n)。4. 微信小程序开发里的高频坑结合热搜词实录写小程序这两三年我在各种群里看到过无数人问同样的问题热搜词里也反映了不少。这里挑几个跟日记小程序相关度高的集中说下我的解法。4.1 顶部导航栏高度和胶囊按钮适配有一天我调日记页面的自定义导航发现标题文字总是离顶部太远后来才反应过来iPhone的刘海屏和Android的状态栏高度不一样如果你用了自定义导航栏navigationStyle: custom就得自己适配安全区。最稳妥的做法是用微信提供的接口获取状态栏高度和胶囊按钮位置const menuButton wx.getMenuButtonBoundingClientRect(); const systemInfo wx.getSystemInfoSync(); const statusBarHeight systemInfo.statusBarHeight; const navBarHeight (menuButton.top - statusBarHeight) * 2 menuButton.height;这套计算逻辑差不多是行业标准了胶囊按钮的顶部到状态栏底部的距离乘以2再加胶囊高度就是导航栏的总高度。照这个算出来的自定义导航栏在iPhone、安卓、各种异形屏上都不会出问题。期末大作业如果用默认导航栏不涉及这个逻辑但如果你想把项目做得更“产品化”自定义导航栏是很加分的一项。4.2 tab页面切换白屏一瞬问题原生的tabBar页面切换偶尔出现白屏闪烁这在开发工具里不容易复现但在真机上可能会遇到。原因通常是页面渲染完成之前把一些重的同步逻辑放在了onLoad或onShow前面阻塞了渲染线程。处理方法有两个一是把数据初始化逻辑放到setTimeout里延迟到渲染完成后再执行二是用wx.nextTick配合setData确保数据是在渲染之后到达的。日记小程序场景下如果首页列表数据量较大建议先在onLoad里给一个空的diaryList再在onShow里拉数据这样页面至少先渲染出一个空壳框架而不是白屏等数据。4.3 base64编码解码atob在微信小程序里不可用有人可能想在日记里做数据导出把一条日记用base64编码成一个字符串保存。到了解码环节发现atob函数在微信小程序的JavaScript环境中竟然不存在直接用会报atob is not defined。解法是不要用浏览器环境的全局函数改用微信自带的方式或者用一个纯JavaScript的base64库。最简单的方案是用decodeURIComponent(escape(str))的组合替代但现在的微信基础库其实已经支持wx.arrayBufferToBase64和wx.base64ToArrayBuffer两个API。如果你存的是普通字符串不是ArrayBuffer那就自己写个映射表或者引入js-base64这类的小工具库。期末考试不建议在base64上纠缠直接用原生JSON存就好。4.4 云开发部署流程先部署云函数还是先上传代码如果最终选了云开发方案很多人会在“先部署云函数还是先上传代码”这个问题上卡住。我的建议顺序是先开通云环境然后在开发者工具里右键云函数目录选择“上传并部署云端安装依赖”全部云函数部署完之后再在云开发控制台里创建数据库集合最后才把小程序代码整体上传体验版。为什么要这个顺序因为小程序代码里如果调用了云函数而你还没部署云函数运行时会直接报Function not found错误查半天方向都在代码上实际问题是后端没就绪。数据库集合同理小程序往一个不存在的集合里插入数据会报collection not exists。前两步做完再跑小程序问题面就小很多。4.5 抓包与调试本地开发不用“抓包”热搜词里出现很多“微信小程序抓包”相关的内容我理解大家的动机想看小程序的网络请求长什么样。但如果你只是开发本地版日记小程序数据都在Storage里根本没有任何网络请求自然也不存在抓包的需求。等你真的接了云开发请求走的是wx.cloud的封装普通的抓包工具也看不到加密后的协议内容用官方控制台的“云开发”面板看日志更直接。所以期末大作业阶段别花时间在研究抓包上没有太大意义。5. 期末答辩与验收要点这些准备能让你多拿10分5.1 功能演示脚本30秒抓住老师的注意力答辩演示时千万不要上来就咔咔点页面。老师一学期要看几十个demo注意力有限你需要用一句话讲清楚“我做了什么”。我的脚本是这样设计的第一步从首页开始指着一排卡片说“老师你看这是日记列表每篇日记按时间倒序排列左上角的心情图标是根据我选的心情字段动态渲染的。”第二步点进写日记页快速输入标题和正文选一个“平静”的心情配一张图点保存。这时候故意停顿一下然后返回首页强调“注意看首页已经刷新出了这一条说明数据已经成功写入本地存储。”第三步点进日历页指着今天的日期说“今天有日记所以日期下面出现了红点。这个红点是根据每篇日记的dateKey字段匹配的。”第四步展示编辑功能把刚才那篇日记的标题改掉返回列表让老师看到标题变了同时说明“编辑是复用写日记页通过URL参数区分”。这一套下来大概一分钟节奏紧凑功能全覆盖老师基本就没啥可问的了。5.2 答辩高频问题与参考回答根据我自己的答辩经历和帮学弟学妹模拟的经验老师在日记小程序这个项目上最爱问的问题就四个问题一数据存在哪里答我用了微信小程序的本地缓存接口wx.setStorageSync把日记列表存在用户设备的缓存中读取时用wx.getStorageSync。这样不需要搭建服务器数据读写是同步的体验非常流畅。问题二这个方案有什么缺点答本地存储最大的问题是跨设备同步。换个手机或者删除小程序数据就丢了。如果后续要上生产环境可以迁移到微信云开发用云数据库和云存储。问题三你做图片上传是怎么处理的答我调用wx.chooseMedia选择图片拿到本地临时路径后把它存入images数组。如果选用了云开发方案就调wx.cloud.uploadFile把图片传到云存储拿到cloud://永久链接。问题四页面间如何传值答日记ID通过URL参数在跳转时传递比如编辑页从列表页拿到ID后从Storage里查询到对应日记数据再回填表单。同时首页通过onShow生命周期重新读取Storage来实现写完后列表自动刷新。这四个问题每一个都对应项目里的真实代码你能答出来说明项目确实是自己做的。5.3 扩展方向多写一行代码就能加分的功能期末大作业做完核心功能之后如果还有时间我建议挑下面这几个扩展点做一个性价比最高的是“按心情统计”在“我的”页面里读取所有日记按mood字段分类用简单的canvas或者纯view画一个柱状图/环形图展示每种心情各有多少篇。这个功能只用到了数组遍历和基础样式但视觉冲击力非常强老师会觉得你连数据可视化都做了。其他扩展点还有日记搜索用input组件的bindinput配合filter、日记导出把数据拼成一段文本分享出去、深色模式适配用darkmode配置。记住一个原则扩展功能不是越多越好而是要能围绕“日记”这个核心做深。6. 常见问题排查与避坑清单我凭经验把这几天开发日记小程序最容易踩的坑整理成了一张速查表建议你有问题先查表再动手问题现象原因分析解决方法页面空白控制台无报错页面路径在app.json里未注册检查pages数组补上页面路径setData数据不生效数据层级过深或忘记拼路径用this.setData({ obj.attr: value })直接指定路径图片保存后重启消失存的是本地临时路径改用云存储fileID或至少存一份到Storage的copy单选框无法选中style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />