简介:这是一套面向高校计算机及相关专业学生的智慧养老微信小程序毕业设计源码,采用JavaScript开发,已通过指导教师审核并获优秀评价,适合用作毕业设计课题、课程实践或学期综合作业。项目聚焦养老服务数字化场景,涵盖健康监测、服务预约、紧急求助、家属绑定与后台管理等核心模块,前端界面交互、后端数据逻辑与微信API集成均有完整实现,有助于理解小程序开发生态与养老业务的结合方式。资源包共59个文件,约152KB,以json配置、wxss样式、js逻辑、wxml结构及zbak备份文件为主,另含少量png、jpg图片素材与md说明文档,目录按页面模块划分,结构清晰便于检索。目前已有33人学习下载,可作为移动端开发入门到进阶的实战参考,帮助读者快速掌握小程序项目组织方式与功能落地思路。
1. 智慧养老小程序:从一份 JavaScript 源码到能跑起来的毕业设计
很多计算机毕业设计的选题,卡在“想做个有用的东西,又怕工作量撑不起来”这个夹缝里。智慧养老微信小程序恰好落在这个夹缝的正中间:它面向的是真实存在的居家养老场景,功能边界清晰,前端用 JavaScript 就能撑起主要交互,后端可以轻量化处理,答辩时也讲得出社会价值。但真正动手的人会发现,网上搜到的“基于 JavaScript 的智慧养老微信小程序毕业设计源码”大多是残缺的——页面能点,数据是假的;或者数据是真的,逻辑是散的。这篇笔记不讲空泛的选题意义,只讲一件事:拿到或自己写一套智慧养老小程序源码,怎么把它从“能演示”推到“能答辩、能复现、能讲清每一行为什么这么写”。适合正在做计算机毕业设计、手里有 JavaScript 基础、需要一套完整小程序项目实例的读者。
2. 智慧养老小程序的功能拆解与技术选型:为什么是 JavaScript 而不是别的
2.1 养老场景到底需要哪几个页面
先把“智慧养老”这个词拆开。它不是一个功能,而是一组围绕老年人日常的轻量服务集合。落到小程序上,最常见的功能模块有五个:健康数据记录(血压、血糖、心率的手动或设备录入)、用药提醒(定时推送和打卡)、紧急呼叫(一键联系家属或社区)、活动报名(社区养老活动的发布与接龙)、家属端查看(子女远程看老人的状态)。这五个模块里,前四个是老人端,第五个是家属端,通常用角色切换或两个入口来区分。
为什么是这五个而不是更多?因为毕业设计的周期通常只有几个月,功能堆到十个以上,每个都做不深,答辩时老师一问细节就露馅。把这五个做透——每个都有真实的数据流、有边界处理、有异常状态——比做十五个半成品强得多。我一般会建议把“健康数据记录”和“用药提醒”作为核心模块,因为这两个的数据结构最清晰,最容易做出可验证的逻辑。
2.2 JavaScript 在小程序里的真实角色
微信小程序的开发语言本质上是 JavaScript 加上一套自有的视图层标记和样式。很多人以为“基于 JavaScript”就是写几个函数,实际上在小程序里,JavaScript 承担的是全部业务逻辑:页面生命周期、数据绑定、事件处理、缓存读写、网络请求、定时器管理。视图层只负责展示,逻辑层全在 JavaScript 里。
这意味着选型时不需要引入额外的框架。原生小程序开发加上 JavaScript 就够,顶多用一个轻量的状态管理方案(比如自己写一个全局数据对象挂在 app 实例上)。不推荐在毕业设计里上 uni-app 或 Taro,除非你的选题明确要求跨端。原因很简单:多一层编译,就多一层调试成本,答辩时老师问“这个页面怎么渲染的”,你答“框架帮我编译的”,说服力会打折扣。原生 JavaScript 写出来的东西,每一行你都能解释。
2.3 数据存储:云开发还是自建后端
这是做小程序毕业设计时第一个要做的技术决策。两条路:
| 方案 | 适合场景 | 优点 | 代价 |
|---|---|---|---|
| 微信云开发 | 不想碰服务器、数据量小 | 免部署、自带数据库和云函数 | 绑定微信生态、调试黑匣子感强 |
| 自建后端(Node.js + 数据库) | 想讲清完整架构、需要答辩加分 | 数据流完全可控、可画架构图 | 要处理部署、接口、跨域 |
如果你的毕设导师看重“系统完整性”,自建后端更稳。用 Node.js 写一组 REST 接口,数据库用 MySQL 或 MongoDB,小程序端用wx.request调用。如果时间紧、只想把前端逻辑做扎实,云开发是更务实的选择。我一般会建议:核心数据(用户、健康记录)走自建后端,非核心的配置类数据走云开发或本地缓存,这样既有架构可讲,又不至于被部署问题拖死。
2.4 最小可运行的项目结构
不管选哪条路,项目目录结构要一开始就定好,否则写到后面文件乱成一团。一个能跑通的智慧养老小程序,目录大致是这样:
miniprogram/ ├── app.js // 全局逻辑:登录态、全局数据 ├── app.json // 页面注册、窗口配置、tabBar ├── app.wxss // 全局样式 ├── pages/ │ ├── index/ // 首页:功能入口 │ ├── health/ // 健康数据录入与列表 │ ├── medicine/ // 用药提醒设置与打卡 │ ├── emergency/ // 紧急呼叫 │ └── family/ // 家属端查看 ├── utils/ │ ├── request.js // 封装 wx.request │ ├── storage.js // 缓存读写封装 │ └── format.js // 时间、数值格式化 └── components/ └── record-card/ // 健康记录卡片组件这个结构的关键在于utils层。很多毕设源码把请求和缓存逻辑直接写在页面里,导致同一个接口在三个页面各写一遍,改一个字段要改三处。把request.js和storage.js抽出来,后面加功能会轻松很多。
3. 从零跑通健康数据模块:页面、逻辑与缓存的三层实现
3.1 健康数据的数据结构设计
在写任何页面之前,先把一条健康记录的数据结构定下来。这决定了后面所有代码的写法:
// utils/model.js // 健康记录的标准结构,所有模块统一使用这个格式 const HealthRecord = { id: '', // 唯一标识,用时间戳加随机数生成 type: 'blood_pressure', // 类型:blood_pressure / blood_sugar / heart_rate value: '', // 主数值,血压存 "120/80" 这种格式 unit: 'mmHg', // 单位,随类型变化 measureTime: '', // 测量时间,ISO 字符串 note: '', // 备注,老人可手写补充 createBy: '' // 录入人:老人自己或家属 }; // 生成唯一 id 的简单方法,毕业设计够用 function genId() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 8); } module.exports = { HealthRecord, genId };这里有几个参数需要说明。type用英文枚举而不是中文,是为了后面做筛选和统计时不用做字符匹配。value对血压用字符串存"120/80",因为血压天然是两个数,拆成两个字段反而增加复杂度。measureTime用 ISO 字符串而不是时间戳,是为了在小程序里直接new Date()解析,省去转换。createBy字段看起来多余,但在家属端查看时,需要区分这条记录是老人自己录的还是子女代录的,答辩时这是一个能讲的设计点。
3.2 录入页面的 JavaScript 逻辑
健康数据录入页面的核心逻辑是:表单校验、数据组装、写入缓存或发送请求。下面是一个精简但完整的录入逻辑:
// pages/health/health.js const { genId } = require('../../utils/model'); const storage = require('../../utils/storage'); Page({ data: { typeIndex: 0, typeList: ['血压', '血糖', '心率'], value: '', note: '', unitMap: { '血压': 'mmHg', '血糖': 'mmol/L', '心率': '次/分' } }, // 输入框绑定,实时更新 data onValueInput(e) { this.setData({ value: e.detail.value }); }, onNoteInput(e) { this.setData({ note: e.detail.value }); }, onTypeChange(e) { this.setData({ typeIndex: Number(e.detail.value) }); }, // 提交前的校验逻辑 validate() { const { value, typeList, typeIndex } = this.data; if (!value || value.trim() === '') { wx.showToast({ title: '请填写测量值', icon: 'none' }); return false; } // 血压格式校验:必须是 "数字/数字" if (typeList[typeIndex] === '血压' && !/^\d{2,3}\/\d{2,3}$/.test(value)) { wx.showToast({ title: '血压格式应为 120/80', icon: 'none' }); return false; } return true; }, onSubmit() { if (!this.validate()) return; const { typeList, typeIndex, value, note, unitMap } = this.data; const typeName = typeList[typeIndex]; const record = { id: genId(), type: typeName, value: value.trim(), unit: unitMap[typeName], measureTime: new Date().toISOString(), note: note.trim(), createBy: 'self' }; // 先写本地缓存,保证离线也能看到 storage.appendRecord(record); wx.showToast({ title: '已保存', icon: 'success' }); // 延迟返回,让 toast 显示完整 setTimeout(() => wx.navigateBack(), 800); } });这段代码的逻辑说明:validate方法做了两层校验,非空和格式。血压的正则/^\d{2,3}\/\d{2,3}$/限制在两位数到三位数之间,能拦住大部分误输入。onSubmit里先写缓存再提示,是为了保证即使网络请求失败,数据也不会丢——这是养老场景里很重要的一个设计,老人不会因为“保存失败”反复操作。setTimeout延迟返回是为了让 toast 完整显示,否则页面跳走后提示会闪一下消失,体验上很别扭。
参数方面,typeIndex用数字索引而不是直接存中文,是因为 picker 组件的返回值是索引,存索引省一次转换。unitMap放在 data 里而不是单独定义,是为了在 wxml 里也能直接引用。
3.3 缓存封装与列表渲染
storage.js是整个项目里最容易被忽视但最重要的工具文件。它决定了数据能不能在页面之间稳定传递:
// utils/storage.js const RECORD_KEY = 'health_records'; // 读取全部记录,按时间倒序 function getRecords() { const list = wx.getStorageSync(RECORD_KEY) || []; return list.sort((a, b) => new Date(b.measureTime) - new Date(a.measureTime)); } // 追加一条记录,限制最多存 200 条,防止缓存溢出 function appendRecord(record) { const list = getRecords(); list.unshift(record); if (list.length > 200) list.length = 200; wx.setStorageSync(RECORD_KEY, list); return list; } // 按类型筛选 function getRecordsByType(type) { return getRecords().filter(r => r.type === type); } module.exports = { getRecords, appendRecord, getRecordsByType };这里的关键参数是200这个上限。微信小程序的单个 key 缓存上限是 1MB,一条健康记录大约 200 字节,200 条不到 50KB,留足了余量。如果不设上限,长期使用后缓存写入会变慢甚至失败,这是很多毕设源码跑一段时间后“莫名其妙卡住”的原因。sort放在读取时做而不是写入时做,是为了保证无论什么时候读,顺序都是对的。
列表页面只需要在onShow里调用getRecords并setData,配合 wxml 的wx:for渲染即可。注意用onShow而不是onLoad,因为从录入页返回时,onLoad不会再次触发,列表不会刷新——这是新手最常踩的坑之一。
4. 用药提醒与紧急呼叫:定时逻辑和状态管理的避坑
4.1 用药提醒的定时方案选择
用药提醒的核心是“到点提醒”。小程序里能做定时的方式有三种:setTimeout、setInterval、以及后端推送。前两种在小程序切到后台后会被挂起或回收,不能作为可靠的提醒方案。真正能用的只有两条路:一是用微信的订阅消息,由后端在指定时间推送;二是用本地通知加日历,但小程序没有直接写系统日历的权限。
所以毕业设计里合理的做法是:前端负责设置提醒规则(时间、药品名、频次),把规则存到后端或云数据库;后端用一个定时任务扫描当前时间需要提醒的记录,调用订阅消息接口推送。前端在用户授权订阅消息后,把templateId和用户的openid关联起来。
如果不想做后端定时任务,退而求其次的方案是:在小程序前台运行时用setInterval每分钟检查一次,触发时用wx.showModal弹窗提醒。这个方案只能在前台生效,但作为毕业设计的演示足够,答辩时说明“生产环境应改为订阅消息”即可。
4.2 提醒规则的数据结构与打卡逻辑
// 用药提醒规则结构 const MedicineRule = { id: '', name: '降压药', // 药品名 dosage: '1片', // 剂量 times: ['08:00', '20:00'], // 每天提醒时间点 startDate: '2025-01-01', endDate: '2025-01-31', active: true, // 是否启用 records: [] // 打卡记录,存日期字符串 }; // 判断今天某个时间点是否已打卡 function isChecked(rule, timeStr) { const today = new Date().toISOString().slice(0, 10); return rule.records.includes(`${today} ${timeStr}`); } // 打卡 function checkIn(ruleId, timeStr) { const rules = wx.getStorageSync('medicine_rules') || []; const rule = rules.find(r => r.id === ruleId); if (!rule) return false; const today = new Date().toISOString().slice(0, 10); const key = `${today} ${timeStr}`; if (rule.records.includes(key)) return false; // 已打卡,不重复 rule.records.push(key); wx.setStorageSync('medicine_rules', rules); return true; }records用"日期 时间"的字符串数组而不是对象数组,是为了让includes判断足够简单。isChecked和checkIn都基于同一个 key 格式,保证一致性。checkIn里先判断再写入,防止重复打卡——这个判断在演示时很容易被忽略,但老师如果点两次打卡按钮,没有这个判断就会出问题。
4.3 紧急呼叫的状态机设计
紧急呼叫看起来简单,其实是最容易出 bug 的模块。因为它涉及“按下 → 倒计时 → 取消或确认 → 通知”多个状态。如果不做状态管理,用户连按几次就会触发多次通知。
// pages/emergency/emergency.js Page({ data: { status: 'idle', // idle / counting / sent / cancelled countdown: 5 }, onCall() { if (this.data.status !== 'idle') return; // 防止重复触发 this.setData({ status: 'counting', countdown: 5 }); this.timer = setInterval(() => { const next = this.data.countdown - 1; if (next <= 0) { this.sendAlert(); } else { this.setData({ countdown: next }); } }, 1000); }, onCancel() { if (this.data.status !== 'counting') return; clearInterval(this.timer); this.setData({ status: 'cancelled', countdown: 5 }); // 2 秒后回到 idle,允许再次呼叫 setTimeout(() => this.setData({ status: 'idle' }), 2000); }, sendAlert() { clearInterval(this.timer); this.setData({ status: 'sent' }); // 这里调用后端接口或云函数,发送通知给家属 wx.showModal({ title: '已发送求助', content: '已通知您的紧急联系人', showCancel: false }); }, onUnload() { // 页面卸载时清理定时器,防止内存泄漏 if (this.timer) clearInterval(this.timer); } });状态机的关键在于status字段。onCall里第一行判断status !== 'idle'就直接返回,这拦住了连按。onCancel里判断status !== 'counting'也直接返回,防止在非倒计时状态取消。onUnload里清理定时器是必须的,否则页面退出后定时器还在跑,会报错。这个模式在答辩时是一个很好的讲解点:为什么用状态机而不是简单的布尔值。
5. 智慧养老小程序开发中容易翻车的五个地方
5.1 页面返回后数据不刷新
现象:从录入页保存后返回列表页,新数据不显示,手动下拉才出现。原因:列表页的数据加载写在onLoad里,而onLoad只在页面首次加载时触发,返回时不会重新执行。解决:把数据加载逻辑抽成一个loadData方法,在onLoad和onShow里都调用。注意onShow在页面首次加载时也会触发,所以可以只在onShow里调用,但首次加载时onLoad可能还没完成初始化,稳妥做法是两处都调,用标志位防止重复请求。
5.2 缓存写入失败但没有提示
现象:保存操作提示成功,但退出重进后数据没了。原因:wx.setStorageSync在缓存超限时会抛异常,但很多代码没有 try-catch,异常被吞掉,后续的 toast 照常执行。解决:所有缓存写入都包一层 try-catch,失败时用wx.showToast明确提示“存储空间不足,请清理旧记录”。同时给缓存设上限,定期清理最旧的记录。
5.3 定时器在页面隐藏后继续运行
现象:紧急呼叫倒计时中切到其他页面,倒计时还在后台跑,回来时状态乱了。原因:setInterval不会因为页面隐藏而暂停。解决:在onHide里记录当前状态并清理定时器,在onShow里根据记录的状态决定是否恢复。或者更简单:倒计时期间禁止页面跳转,用wx.showLoading遮罩住。
5.4 血压数值用数字类型存储导致格式丢失
现象:录入120/80后,列表显示变成120或NaN。原因:数据结构里value定义成了 Number 类型,或者在校验时用了parseFloat。解决:血压的value必须用字符串存储,校验用正则而不是数值转换。血糖和心率可以用数字,但为了统一处理,建议全部用字符串,展示时再按类型决定是否格式化。
5.5 家属端和老人端共用页面导致权限混乱
现象:家属登录后能看到老人的所有功能入口,包括紧急呼叫,点下去不知道通知谁。原因:没有做角色区分,所有页面共用一套逻辑。解决:在app.js的globalData里存role字段,登录时根据账号类型赋值。每个页面的onLoad里检查role,不符合的用wx.redirectTo跳转到对应首页。家属端的紧急呼叫应该改成“查看老人最近一次呼叫记录”,而不是发起呼叫。
6. 让答辩加分的一个技巧:把健康数据做成可导出的周报
大部分智慧养老小程序的健康模块止步于“录入和列表展示”,答辩时老师问“这些数据有什么用”,很难答出深度。一个成本不高但效果很好的进阶做法是:把健康数据按周聚合,生成一份可导出的文本周报,家属端可以一键复制发给医生。
实现思路分三步。第一步,在utils里加一个report.js,负责按时间范围筛选记录并做简单统计:
// utils/report.js const storage = require('./storage'); // 生成最近 7 天的健康周报文本 function generateWeeklyReport() { const all = storage.getRecords(); const now = Date.now(); const weekAgo = now - 7 * 24 * 60 * 60 * 1000; const weekRecords = all.filter(r => new Date(r.measureTime).getTime() >= weekAgo); if (weekRecords.length === 0) { return '最近 7 天没有健康记录。'; } // 按类型分组统计 const groups = {}; weekRecords.forEach(r => { if (!groups[r.type]) groups[r.type] = []; groups[r.type].push(r); }); let text = `健康周报(${new Date(weekAgo).toLocaleDateString()} 至 ${new Date().toLocaleDateString()})\n\n`; Object.keys(groups).forEach(type => { const list = groups[type]; text += `【${type}】共 ${list.length} 次记录\n`; list.forEach(r => { const time = new Date(r.measureTime).toLocaleString(); text += ` ${time} ${r.value} ${r.unit}${r.note ? ' 备注:' + r.note : ''}\n`; }); text += '\n'; }); text += '—— 由智慧养老小程序生成,仅供参考,具体请遵医嘱。'; return text; } module.exports = { generateWeeklyReport };第二步,在家属端页面加一个按钮,点击后调用generateWeeklyReport,用wx.setClipboardData把文本复制到剪贴板,提示“周报已复制,可粘贴发送给医生”。第三步,如果想做得更完整,可以把文本通过wx.request发到后端,由后端生成一个 PDF 或图片再返回下载链接,但这一步对毕业设计来说不是必须的,剪贴板方案已经足够演示。
这个技巧的价值在于:它把“数据录入”和“数据使用”连了起来,答辩时你可以说“这个模块不只是记录,还能辅助就医沟通”,比单纯说“我做了个增删改查”有说服力得多。参数上唯一需要注意的是时间范围,7 天是一个合理的默认值,如果要做成可配置的,在页面上加一个 picker 让用户选 7 天、14 天或 30 天即可。
我自己做这类项目最大的教训是:不要一开始就想着把所有功能做完再调。先把健康数据这一条链路——录入、存储、展示、导出——完整跑通,再复制这个模式到用药提醒和紧急呼叫。每加一个模块,先问自己“这个模块的数据从哪来、存到哪、怎么展示、异常怎么处理”,四个问题答完再动手。这样写出来的代码,答辩时每一行都讲得清来龙去脉。希望帮到你。
本文还有配套的精品资源,点击获取