微信小程序期末作业实战:从零开发一个简单日记小程序
2026/8/27 22:56:27 网站建设 项目流程

简介:微信小程序开发是当前前端工程领域的热门技能,而数据持久化与页面生命周期管理则是绕不开的核心基础。在本地存储(Storage)机制下,开发者可以通过增删改查操作构建完整的业务闭环,并深刻理解页面渲染与数据同步的底层逻辑。一个功能简洁但结构完整的日记小程序,恰好能承载这些关键技术点:利用 wx.setStorageSync 实现本地数据读写,通过 onShow、onLoad 等生命周期函数处理页面刷新,借助 setData 完成视图更新。这种轻量级项目不仅适合大学生期末大作业,也是入门小程序开发的高效实践路径。本文以“简单日记小程序”为例,完整拆解从需求梳理、原生开发到答辩准备的每一个环节,并针对数据不刷新、长列表性能、真机白屏等高频问题给出排查方案,帮助开发者快速构建一个可演示、可扩展的工程项目。

1. 项目概述与需求拆解

1.1 为什么选“简单日记小程序”作为期末大作业

每年期末,微信小程序开发课的收官任务都让不少同学头疼。选电商项目,后端接口和支付流程能把人绕晕;选工具类项目,又容易做得像交差。我自己当年带过不少毕业生和实习生,也帮人看过几十份期末作业,最推荐的选题恰恰就是这类“功能看似简单、但五脏俱全”的日记小程序。

原因很简单:期末大作业的评审逻辑,通常不是看你的功能多炫,而是看你能不能把“增删改查”这一整套业务闭环跑通,并且代码结构清晰、界面可用。日记小程序的本质,就是围绕“数据”做一套完整的持久化操作——新增、编辑、删除、列表展示、日期筛选,这正好覆盖了小程序开发中最核心的知识点:页面生命周期、数据绑定、条件渲染、列表渲染、本地存储读写、事件处理。把这些吃透,比堆砌十几个华而不实的页面要实在得多。

再说直白一点,这类项目的数据结构足够简单。日记的核心字段无非就是标题、正文内容、创建时间、更新时间这几个,不需要设计多张关联表,也不依赖后端接口。这就意味着你可以把全部精力放在“小程序本身”上,而不是在服务端环境配置里耗掉大半时间。对于一周到两周的期末冲刺周期来说,这个投入产出比非常划算。

1.2 期末评分视角下的“产品定位”

很多同学做作业容易陷入一个误区:把“能跑”当成“完成了”。但老师评分的视角和你不一样,他更看重的是:你有没有做出“健壮性”。比如空数据时页面长什么样、用户删除日记时有没有二次确认、编辑之后列表有没有及时刷新、日期格式是不是按中国习惯显示。这些都是拉开分数差距的细节。

我把这套日记小程序定位成“单机版、本地存储、快捷记录”的轻量工具。不依赖服务器,不引入云开发,所有数据存进微信小程序的 Storage。好处有三点:一是演示时不怕断网掉链子;二是代码全在前端,逻辑一目了然,答辩时好解释;三是本地存储的读写速度快,界面响应非常跟手。

目标用户也很清晰:课程老师 + 答辩评委。所以UI要做到干净、整齐,操作路径要短。我见过不少同学把页面堆得很满,按钮放得到处都是,反而显得业余。日记类工具的界面,克制是最重要的,一个输入页、一个列表页、一个详情页,足够了。

2. 整体设计思路与技术选型

2.1 页面结构与交互流程设计

整个小程序我规划了三个核心页面:

  • 首页(日记列表页):负责展示所有日记的摘要,按时间倒序排列,支持点击进入详情,也支持长按删除。
  • 编辑页:承担“新建”和“修改”两种职责。通过接收不同的参数区分是新增还是编辑,保存时统一走同一个入库函数。
  • 详情页:展示完整日记内容,提供“编辑”“删除”两个入口,底部显示创建时间和最后修改时间。

页面之间的跳转关系是这样的:首页 → 编辑页(新建);首页 → 详情页 → 编辑页(修改);编辑页保存后返回上一页,同时触发上一页的列表刷新。这里需要用到一个关键API:wx.navigateBack配合页面onShow生命周期里的数据重新拉取,保证每次从编辑页返回列表时,数据都是最新的。

底部导航我没有做 TabBar,因为日记工具的页面层级本来就是“首页 → 二级页”的树形结构,不需要并列的 tab。真要做 TabBar,反而要考虑首页列表和搜索页之间的数据同步问题,复杂度会上去一截。对期末作业来说,页面层级越简单,演示越流畅,答辩越不容易被问倒。

2.2 为什么坚持用“原生小程序”而非框架

这个决策我是很坚持的。现在网上铺天盖地的模板都是 uniapp 或者 Taro 写的,语法确实贴近 Vue,上手觉得爽。但期末答辩时,老师大概率会翻你的源代码,问你这个指令对应底层做了什么。如果你用 uniapp,他可能问“v-model 在原生里是怎么实现的”“uniapp 的编译链路是什么样的”,这些问题如果你没深入过,很容易卡壳。

原生小程序的语法体系虽然啰嗦一点,但胜在透明。WXML 就是标签,WXSS 就是样式,JS 就是逻辑,JSON 就是配置。每一行代码都能找到对应的官方文档,每一个 API 调用都能直接对应到运行时的行为。我用原生语法写这套日记小程序,核心代码量控制在 500 行以内,逻辑直观,注释清晰,老师翻代码的时候会感觉非常舒服。

另外还有一个非常现实的原因:微信开发者工具对原生项目的调试支持最完整。从 Storage 的可视化查看到页面栈的追踪,都能在工具里直接完成。这对期末阶段的反复调试来说,效率比任何框架都高。

2.3 数据存储方案:从 Storage 到预留云开发

数据全部存本地 Storage,用wx.setStorageSyncwx.getStorageSync完成同步读写。Storage 的本质是给每个小程序分配一个独立的本地存储空间,最大容量有 10MB 的限制。对日记这种纯文本数据来说,假设一篇日记平均 500 字(大概 1KB),10MB 差不多可以存一万篇,期末演示绰绰有余。

但为了答辩时能“拔高”项目层次,我在代码里预留了一个数据访问层。简单说,就是封装了getDiaryListsaveDiarydeleteDiary三个函数,页面里所有操作都走这三个函数,而不是直接去碰 Storage 的 API。这样如果老师问“如果以后要上云端怎么办”,你可以很自然地回答:“我把数据层做了隔离,后续接入云开发数据库时,只需要重写这三个函数的内部实现,页面代码完全不用动。”

这个“数据访问层”设计虽然只有十几行代码,但体现的是架构意识。在小程序这种小体量项目里,这种意识比会调 API 更值钱,也是答辩的加分点。

3. 核心功能实现与技术要点

3.1 日记数据结构的定义与初始化

在项目的utils/diary.js里,我定义了一套统一的数据结构,所有的日记对象遵循这个格式:

// 日记对象结构 { id: number, // 唯一标识,使用时间戳生成 title: string, // 日记标题,可为空,为空时自动取正文前10个字 content: string, // 正文内容 createTime: string, // 创建时间,格式 yyyy-MM-dd HH:mm updateTime: string // 最后修改时间,格式 yyyy-MM-dd HH:mm }

这里有一个比较容易忽略的坑:id不能用自增数字,因为删除日记后自增序列会乱,而且本地存储里自增要额外维护一个计数变量,太绕。最稳妥的方式是用Date.now()生成 13 位毫秒时间戳作为 id。同一毫秒内创建两条记录的概率在日记场景下几乎为零,即便真撞了,也可以补个随机数后缀。

初始化数据的代码我放在app.jsonLaunch里,首次启动时往 Storage 里塞一两条示例日记。这有一个很实际的好处:演示时打开小程序就能看到内容,不用现场敲字。不然老师盯着你输入半天,体验很差。

// app.js 片段 onLaunch() { const list = wx.getStorageSync('diaryList'); if (!list || list.length === 0) { const seedDiary = { id: Date.now(), title: '欢迎使用日记小程序', content: '这是一篇示例日记,点击右下角按钮可以新建日记,长按列表项可以删除日记。', createTime: this.formatTime(new Date()), updateTime: this.formatTime(new Date()) }; wx.setStorageSync('diaryList', [seedDiary]); } }

formatTime这个函数我单独抽到了utils/util.js里,因为在页面中多处都要用到时间格式化。它的实现并不复杂,核心逻辑是用getFullYeargetMonth+1getDate等 API 拼字符串,注意月份要加 1,这是新手最容易写错的地方。

3.2 首页列表渲染与空态处理

首页的列表是所有功能的入口。WXML 里用wx:for循环渲染日记卡片,每一张卡片展示标题、内容摘要和创建日期。摘要内容我做了截断处理,超过两行就用 CSS 的text-overflow: ellipsis省略,而不是在 JS 里去切字符串,这样更灵活,也保留了原文的完整性。

列表按创建时间倒序排列,用Array.prototype.sort比较createTime字符串即可。因为时间格式是加了前导零的yyyy-MM-dd HH:mm,按字符串排序的结果和时间顺序完全一致,不需要转成时间戳再比,这个技巧能少写好几行代码。

空态处理是我特别强调的。当 Storage 里没有任何日记时,页面不能显示一个光秃秃的白屏,要有一张居中的插图和一行提示文案:“还没有日记,点击下方按钮开始记录吧”。空态的作用不只是好看,更重要的是引导用户完成下一步操作。这种细节,老师一眼就能看出来你是有产品思维还是单纯的代码搬运工。

卡片点击跳转详情页时,通过 URL 参数传递日记 id:

// 列表页跳转 goDetail(e) { const id = e.currentTarget.dataset.id; wx.navigateTo({ url: `/pages/detail/detail?id=${id}` }); }

详情页在onLoad里接收参数,再从完整的日记列表中find出对应的对象。这里要提醒一点:从wx.navigateTo传来的参数都是字符串类型,所以id比较时要么用转义后的数字比较,要么就用String()统一类型。用==松散比较虽然能处理,但期末答辩时被问到“双等号和三等号的区别”会很尴尬,直接全用严格等于比较规范。

3.3 编辑页:新建与编辑的双重身份

编辑页是这套小程序里逻辑最密集的页面。它既要处理新建,又要处理编辑,所以需要根据onLoad里有没有收到id参数来区分:

  • 收到id:说明是编辑模式,需要从 Storage 里把原数据读出来,回填到表单里。
  • 没收到id:说明是新建模式,表单初始化为空。

WXML 里的输入控件我用<textarea>做正文、<input>做标题。这里有一个很重要的属性配置:textareamaxlength不要设成 -1(无限),建议默认 140 或 500。我设的是 500,够写一篇小日记,也不会让数据体积失控。同时配合一个实时字数统计:“剩余 xxx 字”,用bindinput事件监听输入,更新剩余字数。

保存逻辑我写在一个save方法里,处理内容包括:

save() { const title = this.data.title.trim(); const content = this.data.content.trim(); if (!content) { wx.showToast({ title: '内容不能为空', icon: 'none' }); return; } const finalTitle = title || content.slice(0, 10); // 判断编辑或新增 if (this.data.id) { // 编辑:更新对应记录 } else { // 新增:push 新记录 } wx.setStorageSync('diaryList', newList); wx.navigateBack(); }

标题为空时自动截取正文前十个字作为标题,这个小细节是我个人很喜欢的,既省了用户手动填标题的步骤,又保证列表页不会出现“无标题”这种尴尬情况。content.slice(0, 10)这里我直接用字符串方法,不涉及中英文字符差异,因为 JavaScript 的slice是按 Unicode 码点切的,中文不会有问题,但要注意如果截取到半截 emoji 可能出现显示异常,日记场景基本不影响。

编辑页还有一个体验细节:正文输入框要设置auto-height或固定高度并配合cursor-spacing属性,这样键盘弹起时不会被遮挡。cursor-spacing的作用是控制输入光标与键盘上边缘的距离,我设的是 20px,实测效果不错。这个属性很多同学不知道,答辩时能说出来是一个加分项。

3.4 详情页的时间展示与操作入口

详情页是信息展示为主,核心元素包括:大标题、创建时间、更新时间、完整正文、底部操作栏(编辑按钮 + 删除按钮)。

时间展示我用了两块并排的布局,看起来像产品详情页的规格参数,一眼就能看出信息的层次感。在 WXML 里直接用{{diary.createTime}}输出字符串,不需要再做任何格式化处理,因为数据入库时就已经是标准格式了。

删除操作做了二次确认弹窗,用wx.showModal实现:

deleteDiary() { wx.showModal({ title: '提示', content: '确定删除这篇日记吗?删除后不可恢复。', confirmColor: '#e64340', success: (res) => { if (res.confirm) { const list = getDiaryList().filter(item => item.id !== this.data.id); saveDiaryList(list); wx.navigateBack(); } } }); }

confirmColor我特意设成了红色系,通过视觉暗示这个操作是风险操作。这个细节在用户研究里叫“操作可见性与反馈一致性”,虽然听起来高大上,但本质上就是让人一眼看懂“这个按钮不能乱点”。

编辑操作直接wx.redirectTo跳转到编辑页并带上当前日记的 id。用redirectTo而不是navigateTo,是因为详情页完成任务后就没有保留的必要了,不让页面栈里堆积无用页面,也避免用户从编辑页返回时又看到详情页的重复路径。

4. 开发环境配置与踩坑记录

4.1 AppID 的选择与项目初始化

期末阶段大部分同学没有注册小程序账号,这里有一个非常实用的选择:微信开发者工具支持“测试号”,在新建项目时选择“测试号”即可免注册 AppID,所有核心 API 都能正常使用。区别仅在于测试号无法真机预览、无法上传发布,但对纯本地存储的日记小程序来说,模拟器上完全够用。

如果你已经注册了个人小程序账号(流程很简单,去微信公众平台用邮箱注册就行),建议直接用真实 AppID。好处是可以用手机真机预览,测试键盘弹起、触摸滑动等模拟器上无法完全模拟的交互。真机调试时记得在工具里打开“开发环境不校验请求域名”,虽然本项目没有网络请求,但这是通用习惯,顺手就要勾上。

项目初始化时,模板选择“不使用模板”就好,用空白目录建项目。新建完成后,我习惯先把app.jsonwindow配置调好——导航栏标题设为“简单日记”、背景色设为白色、导航栏文字颜色设为黑色。这些基础配置看着简单,但直接影响第一印象。

4.2 开发者工具中的调试技巧与日志定位

微信开发者工具的控制台是期末项目最重要的调试阵地。我在每个核心函数里都留了console.log输出,比如保存成功时打印[diary] save success, list length = xxx。表面上看多打几行日志无所谓,但调试时能大幅缩短定位时间。

Storage 面板是非常好用的可视化工具。在“调试器 → Storage”标签页里,可以直接看到diaryList这个 key 的全部数据,甚至可以手动编辑或删除某条数据来模拟异常场景。测试空态显示效果时,我就是在 Storage 面板里把diaryList直接置空,一步到位,不用先把所有日记删光。

还有一个测试技巧:用“编译模式”快速进入指定页面。默认编译是进入首页,如果我在调试编辑页,每次都要首页 → 新建 → 输入 → 保存,非常浪费时间。通过在“普通编译 → 添加编译模式”里设置启动页面为pages/edit/edit,并附上参数id=xxxx,就能一键直达编辑状态,效率提升非常明显。

4.3 模拟器与真机表现不一致的排障思路

日记小程序虽然没有网络请求,但还是会遇到一些环境差异问题。最典型的是键盘弹起时的页面表现。模拟器里点输入框,键盘区是模拟的,不会遮挡页面;但真机上键盘会实实在在地弹起来,如果没有处理好,输入框会被挡掉很大一块。解决方案就是前面提到的cursor-spacing属性,另外还可以给页面adjust-position设 false、自己手动onKeyboardHeightChange调整,但日记项目用cursor-spacing就够。

另一个真机差异是页面滚动的回弹效果。模拟器上滚动到底部很干脆,真机上会有橡皮筋效果,这是 iOS WebView 的默认行为,不用管它,属于正常的系统交互。但如果你在详情页发现内容上下弹动很突兀,检查一下page的高度设置,确保min-height: 100vh而不是固定height: 100vh,否则内容超出一屏时滚动会有问题。

时间格式化在真机上还有一个隐藏的坑:iOS 对new Date('2023-06-01 10:00:00')这种带横杠和空格的字符串解析支持不稳定,在某些版本中会返回Invalid Date。写formatTime函数时要注意,传入的 date 对象如果来自字符串,最好先手动解析成年月日时分秒,而不是直接塞给new Date。我在项目里的所有时间都是自己格式化生成的,不走new Date逆向解析,绕开了这个坑。

5. 常见问题排查与避坑经验

5.1 数据不刷新:onShow 与 onLoad 的时机陷阱

这是期末项目中频率最高的问题:从编辑页保存返回首页,列表却没有更新。原因在于onLoad只在页面首次加载时执行一次,从编辑页返回属于“从二级页回退”,首页本身已经在页面栈里活着,不会重新触发onLoad。所以要在首页的onShow里重新拉取 Storage 数据,并setData到页面上。

// 首页 onShow() { this.loadDiaryList(); }

这个问题的本质是“页面生命周期事件的选择”。onLoad适合做一次性初始化,onShow适合做每次回显时的数据同步。我在一开始写的时候也没意识到,第一次测试时发现列表不更新,愣是查了半天代码,最后才想起onShow这回事。写这篇博文的时候特意强调,大家千万不要在这个地方浪费一晚上。

5.2 长列表性能问题:setData 的批量更新策略

如果期末作业里你测试时录了上百条日记,可能会发现列表偶尔掉帧。这个问题的根源在于setData是“全量更新”机制,每次调用都会把数据从逻辑层传到渲染层,数据量越大,开销越大。优化方案有三种:

  • onShow拉取数据后,先做切片,只渲染最近 30 条。
  • wx:for的每一项加上wx:key,让渲染层可以精确复用节点,而不是全部重建。
  • 对于超过一定条数的列表,用onReachBottom触底分页加载,每次追加 20 条。

我在项目里用的是“数据切片 +wx:key”双保险。切片的逻辑很简单,getDiaryList().slice(0, 30),想偷懒的话甚至可以不做分页,直接保证单次渲染的数据量足够小。这个优化在期末演示时评委不太会注意,但它体现的是工程能力,写进答辩 PPT 里很有面子。

5.3 重复保存与快速点击的问题

快速连续点击保存按钮,会产生两条相同内容的日记。这个问题的根因是“事件触发频率高于业务逻辑的执行速度”。我的处理方式是加一个“保存锁”:进入保存函数后先检查isSaving标记,如果为 true 就直接 return,否则置为 true,保存完成后释放。

save() { if (this.data.isSaving) return; this.setData({ isSaving: true }); // ... 保存逻辑 this.setData({ isSaving: false }); wx.navigateBack(); }

同理,列表项的点击跳转也可以加“防抖”,避免手滑连续点两次进两个页面。通用做法是在跳转函数里设一个this.isNavigating标记,跳转后立即置 true,页面onHideonUnload时再复位。这些细节,跟工作里接触到的“防止重复提交”是一个思路,提前在小程序里体会一遍,以后做后台系统时会很有感触。

5.4 真机预览时白屏的排查流程

真机预览出现白屏,是每个开发者迟早会遇到的事。我按优先级整理了一份排查清单:

  1. 先看手机端调试面板的 Console,有没有报错。最常见的是渲染层报xxx is not defined,通常是 WXML 里引用了不存在的变量名。
  2. app.json里注册的页面路径是否和实际文件路径一致。路径不一致时工具会直接报编译错误,但有时不会被留意,一直点“预览”导致白屏。
  3. 清掉小程序缓存再重新预览。开发者工具改过代码后偶尔会有缓存残留,手机端也有缓存,删掉小程序再重新扫码是最粗暴但有效的方案。
  4. 检查 WXML 里是否写了无法解析的属性。比如自定义组件标签的properties里用了Object类型但没有给默认值,渲染层拿到的可能是undefined导致崩溃。

日记小程序结构简单,白屏概率不大,但万一出了别慌,按顺序查下来肯定能定位。

5.5 系统全面的常见问题速查表

现象可能原因解决方案
列表不更新onLoad里拉数据,onShow未触发把拉数据逻辑移到onShow
保存后返回首页数据丢失wx.setStorageSync传入的 key 不一致统一封装saveDiaryList,key 常量统一管理
日期显示为NaN-NaN-NaNiOS 下new Date解析字符串失败避免用字符串转Date,手动拼格式
键盘遮挡输入框未设置cursor-spacing输入框增加cursor-spacing="20"
删除无反应二次确认弹窗的success回调里没清数据success中判断res.confirm后执行删除
真机白屏页面注册路径错误或代码报错查编译日志 + 删缓存重新预览
输入内容长,页面卡顿没做数据切片列表slice截取数据 + 分页加载

6. 答辩演示准备与项目进阶建议

6.1 演示脚本与操作路径规划

期末答辩现场容易紧张,一紧张就会手忙脚乱,点错按钮、操作顺序混乱。我的建议是提前准备一份“演示脚本”,把核心操作路径固定下来:

  1. 打开小程序,停 3 秒,让老师看清示例日记的展示效果,口播:“这是本地存储的日记列表,按时间倒序排列。”
  2. 点击新建,输入标题和正文,保存。
  3. 返回列表,指出刚才新写的日记出现在最顶部,“这个交互依赖首页的 onShow 生命周期完成数据刷新。”
  4. 点击刚写的日记进入详情,再点编辑,修改标题,保存。
  5. 返回列表后,长按一条日记删除,走完二次确认弹窗。
  6. 最后展示空态:把列表数据清空,刷新页面,演示“空数据引导”。

这套流程走下来,把增、删、改、查、空态全部覆盖,全程 3 分钟搞定。每一步都能对应到一个知识点,老师怎么提问你都有话可接。

6.2 从期末作业到可上线项目的两个方向

如果你学有余力,想在期末作业基础上继续扩展,我推荐两个方向:

第一个方向是“加云开发”。把数据从本地 Storage 迁移到微信云开发的数据库,实现多设备同步。这个升级的改动量不大,因为我在项目里已经做了数据访问层,只需要重写getDiaryListsaveDiarydeleteDiary三个函数内部的具体实现,页面的调用代码一行都不用改。改用云开发后,还能顺手加一个“用户登录”,用wx.openUserProfile或手机号快捷登录,项目的完整度立刻上升一个档次。

第二个方向是“加 AI 能力”。现在小程序云开发已经支持云函数调用大模型 API,可以在编辑页放一个“一键润色”按钮,调用云函数把日记内容发送给大模型生成润色版本。这个功能看起来高端,实际实现起来也不算复杂,核心就是云函数里发一个 HTTP 请求的事。期末能做出这个,基本上是满分级的存在。

6.3 答辩高频问题与应答参考

老师可能会问建议回答思路
数据存在哪里?有什么限制?存在本地 Storage,上限 10MB,适合轻量数据;已预留数据访问层,可平滑迁移到云开发
为什么用时间戳当 id?避免自增 id 在删除后冲突,时间戳全局唯一,生成成本低
列表的性能如何优化?数据切片 +wx:key复用 + 触底分页
Storage 和全局变量的区别?Storage 持久化,App 的 globalData 是内存态,小程序杀进程后会丢失
后期如何支持多端?小程序的逻辑层已经和渲染层分离,可以借鉴 Taro/uniapp 做编译适配,但需要重构页面代码

7. 总结:我个人的一点实操体会

这套日记小程序完整做下来,我自己最大的体会是:小项目才是练基本功的地方。字段设计、生命周期管理、数据流向、异常处理,这些在大项目里会被各种框架和组件稀释掉的东西,在几百行的小程序代码里反而被放大得很清晰。你写的每一行代码都能在微信开发者工具里立刻看到反馈,这种正反馈循环对学习非常有效。

最后再分享一个小技巧:给项目写一份简短的README.md,把自己的项目结构、核心文件、启动方式、踩过的坑都记下来。期末答辩时这份文档可以直接作为项目说明书的底稿,平时自己回看也能快速回忆整个开发脉络。很多同学修完 bug 就结束了,但我建议你多花半小时整理一下,这个习惯在工作以后会受益很久。

如果你照着这篇文章的思路把项目做完,一定能交出一份能进优秀作业名单的期末作品。如果开发过程中遇到具体问题,欢迎在评论区留下报错信息,我会尽量帮你排查。

本文还有配套的精品资源,点击获取

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

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

立即咨询