简介:这份资源面向学习微信小程序开发与校园信息化管理的学生和开发者,提供一套可直接参考的宿舍管理系统完整项目源码,解决宿管与学生双向登录、日常事务线上化的问题。压缩包共约2000个文件,整体8.4MB,以769个js与393个ts脚本承载业务逻辑,258个json与236个md用于配置和说明文档,另有wxml、wxss页面文件及proto、yml等辅助配置,结构完整便于按模块研读。已有1958人学习下载,说明其在同类小程序项目中具备一定参考热度。项目围绕签到、卫生评分、留言回复、晚归申请等功能展开,并预留MySQL数据库接口用于密码校验与数据存储,读者可据此理解双角色权限划分、GPS定位签到与积分激励的实现思路,适合作为课程设计或毕业设计的对照范本。
1. 宿舍管理这件小事,为什么值得用小程序重做一遍
每年开学季,宿管阿姨桌上那本翻烂的登记册、辅导员群里刷屏的“谁还没交住宿费”、维修师傅手里对不上号的报修单,几乎是每栋宿舍楼都会重演的场景。传统做法要么靠 Excel 传来传去,要么靠纸质表格层层签字,数据一多就乱,责任一追就断。基于微信小程序的宿舍管理系统,正是冲着这个痛点来的:学生打开微信就能查宿舍、报修、申请调宿,宿管在后台一键审批、导出统计,管理员不用装任何 App,扫码即用。它适合两类人——一类是想拿一个完整业务闭环练手小程序开发的学生或初级开发者,另一类是高校后勤、公寓管理场景里想快速落地一套轻量工具的技术负责人。微信开发者工具打开就能跑,不需要服务器运维经验也能先把流程走通。
2. 技术选型与目录结构:这套系统到底由哪几块拼起来
2.1 为什么是“小程序 + 云开发”而不是传统前后端分离
这个项目的核心诉求是“轻”。学生端和管理端都在微信生态里,最省事的组合是小程序原生框架配云开发:前端用 WXML/WXSS/JS,后端逻辑直接写成云函数,数据库用云数据库,文件(比如报修照片)走云存储。好处是省掉了买服务器、配域名、搞 HTTPS 证书这一整套流程,对课程设计或小规模上线非常友好。常见做法是把学生端和宿管端做成两个角色,通过用户表里的 role 字段区分,登录时用wx.login拿 code 换 openid,再查角色决定跳哪个首页。
如果团队后续要接学校统一身份认证,也可以把云函数换成自建 Node 服务,前端页面基本不用动,只改请求层。这也是我一般会建议的结构:业务页面和数据访问分离,别把wx.cloud.callFunction散落在每个页面里。
2.2 目录结构长什么样
一个能跑起来的工程,目录大致是这样组织的:
dorm-system/ ├── miniprogram/ │ ├── app.js # 全局初始化,云开发环境 ID 在这里配 │ ├── app.json # 页面路由、tabBar、权限声明 │ ├── pages/ │ │ ├── login/ # 登录与角色分流 │ │ ├── index/ # 学生首页:我的宿舍、公告 │ │ ├── repair/ # 报修提交与进度 │ │ ├── adjust/ # 调宿申请 │ │ └── admin/ # 宿管端:审批、统计 │ ├── components/ # 通用卡片、状态标签 │ └── utils/request.js # 统一封装云函数调用 ├── cloudfunctions/ │ ├── login/ # 换取 openid、写用户表 │ ├── repair/ # 报修增删改查 │ ├── adjust/ # 调宿审批流 │ └── stats/ # 统计聚合 └── project.config.json # 开发者工具项目配置app.json里要提前把页面路径注册好,tabBar 至少放“首页 / 报修 / 我的”三个入口。云函数目录和miniprogram平级,这是云开发默认约定,别放错层级,否则开发者工具识别不到。
2.3 数据模型先定清楚,后面少返工
宿舍管理系统的表不多,但关系要理顺。核心四张表:用户表(openid、姓名、学号、角色、宿舍号)、宿舍表(楼栋、房间号、床位数、已住人数)、报修表(提交人、宿舍号、问题描述、图片、状态、处理人)、调宿表(申请人、原宿舍、目标宿舍、理由、审批状态)。状态字段建议用数字枚举而不是中文,比如报修状态 0 待处理、1 处理中、2 已完成,前端做映射显示,后面统计聚合时不用再做字符串匹配。
提示:云数据库的权限设置容易被忽略。用户表建议设为“仅创建者可读写”,报修表设为“所有用户可读、仅创建者可写”,宿管端的写操作全部走云函数,用云函数的管理员权限绕过前端限制。
3. 核心模块拆解:登录分流、报修流转和审批状态机
3.1 登录与角色分流:一次 code 换 openid 的完整链路
登录是整个系统的入口,做不好后面每个页面都要重复判断身份。标准做法是页面加载时调wx.login拿临时 code,传给 login 云函数换 openid,再查用户表决定角色。
// pages/login/login.js Page({ data: { loading: false }, onLoad() { this.doLogin(); }, async doLogin() { this.setData({ loading: true }); try { // 1. 拿临时凭证 const { code } = await wx.login(); // 2. 调云函数换取 openid 并查询角色 const res = await wx.cloud.callFunction({ name: 'login', data: { code } }); const { role, registered } = res.result; if (!registered) { // 首次进入,跳完善信息页 wx.redirectTo({ url: '/pages/profile/profile' }); return; } // 3. 按角色分流 const target = role === 'admin' ? '/pages/admin/index' : '/pages/index/index'; wx.switchTab({ url: target }); } catch (err) { wx.showToast({ title: '登录失败,请重试', icon: 'none' }); console.error('login error', err); } finally { this.setData({ loading: false }); } } });这段代码的关键点在三个地方。wx.login返回的 code 只能用一次,五分钟内有效,所以不要在多个页面重复调用,统一在登录页处理。云函数里用cloud.getWXContext()直接拿 openid,不需要前端传,安全性更高。registered字段用来区分新老用户,新用户先补学号姓名,避免用户表里出现空记录。参数上,role建议只允许student和admin两个值,宿管账号由管理员在后台手动改,不开放自助注册。
云函数侧对应逻辑:
// cloudfunctions/login/index.js const cloud = require('wx-server-sdk'); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); const db = cloud.database(); exports.main = async () => { const { OPENID } = cloud.getWXContext(); const userRes = await db.collection('users').where({ openid: OPENID }).get(); if (userRes.data.length === 0) { return { registered: false, role: 'student' }; } return { registered: true, role: userRes.data[0].role }; };DYNAMIC_CURRENT_ENV表示跟随当前部署环境,多环境切换时不用改代码。查询用where而不是doc,因为 openid 是业务字段不是文档 ID,这一点新手容易搞混。
3.2 报修模块:从提交到完成的四步流转
报修是使用频率最高的功能,流程必须顺。学生填问题描述、选宿舍号、传一张照片,提交后生成一条待处理记录;宿管端看到列表,点“接单”变处理中,修完点“完成”并填处理备注。状态机只有三个状态,但每一步都要校验当前状态,防止重复点击导致状态错乱。
// pages/repair/repair.js 提交报修 async submitRepair() { const { desc, dorm, imageUrl } = this.data; if (!desc || !dorm) { return wx.showToast({ title: '请填写完整信息', icon: 'none' }); } await wx.cloud.callFunction({ name: 'repair', data: { action: 'create', payload: { desc, dorm, imageUrl, status: 0 } } }); wx.showToast({ title: '提交成功' }); this.loadMyRepairs(); // 刷新列表 }云函数里按action分发,比一个函数只做一件事更好维护:
// cloudfunctions/repair/index.js exports.main = async (event) => { const { action, payload } = event; const { OPENID } = cloud.getWXContext(); switch (action) { case 'create': return db.collection('repairs').add({ data: { ...payload, openid: OPENID, createTime: db.serverDate() } }); case 'updateStatus': { // 只有处理人才能改状态,且必须从当前状态合法流转 const { id, from, to, remark } = payload; const cur = await db.collection('repairs').doc(id).get(); if (cur.data.status !== from) { throw new Error('状态已变化,请刷新'); } return db.collection('repairs').doc(id).update({ data: { status: to, remark, handler: OPENID, updateTime: db.serverDate() } }); } default: throw new Error('未知操作'); } };from和to两个参数是状态机的核心,前端传“我以为的当前状态”,云函数比对数据库真实状态,不一致就拒绝。这样即使两个人同时点接单,也只有一个人成功。db.serverDate()用服务端时间,避免手机时间不准导致排序错乱。
3.3 调宿审批:一个带理由和审批意见的双向流程
调宿比报修多一层审批。学生提交申请时填原宿舍、目标宿舍、理由;宿管审批时通过或驳回,驳回要填意见。这里的关键是审批动作必须幂等,且审批后要同步更新宿舍表的入住人数。常见做法是在云函数里用事务处理,云开发支持db.startTransaction(),把调宿状态更新和两个宿舍的人数增减放在一个事务里,避免出现“申请通过了但人数没变”的脏数据。
// cloudfunctions/adjust/index.js 审批通过 const transaction = await db.startTransaction(); try { await transaction.collection('adjusts').doc(id).update({ data: { status: 1, reviewer: OPENID, reviewTime: db.serverDate() } }); await transaction.collection('dorms').doc(fromDormId).update({ data: { used: _.inc(-1) } }); await transaction.collection('dorms').doc(toDormId).update({ data: { used: _.inc(1) } }); await transaction.commit(); } catch (e) { await transaction.rollback(); throw e; }_.inc是原子自增操作符,比先读再写安全。事务里任何一步失败都回滚,这是血泪经验——早期不做事务,测试时出现过人数变成负数的情况,排查了半天才发现是并发审批。
4. 跑起来:微信开发者工具从导入到真机预览
4.1 环境准备和项目导入
先装微信开发者工具稳定版,注册一个小程序 AppID(没有的话可以用测试号,但云开发需要真实 AppID)。打开工具选“导入项目”,目录指向dorm-system根目录,AppID 填自己的。导入后如果提示云开发未开通,点工具栏“云开发”按钮开通,记下环境 ID。
把环境 ID 填到app.js:
// app.js App({ onLaunch() { wx.cloud.init({ env: 'your-env-id', // 替换成自己的环境 ID traceUser: true }); } });traceUser打开后可以在云开发控制台看到用户访问记录,调试阶段有用。环境 ID 填错是最常见的启动失败原因,报错通常是“cloud init error”,看到这个先回去核对。
4.2 建库、传云函数、跑通第一条数据
云开发控制台里手动建四张集合:users、dorms、repairs、adjusts。权限按第 2 章说的设。然后在开发者工具里右键每个云函数目录选“上传并部署:云端安装依赖”,四个函数都传一遍。部署完在“云开发 - 数据库”里手动插一条 users 记录,openid 先空着,等第一次登录后从日志里拿到真实 openid 再补上,或者直接在 login 云函数里做首次注册逻辑。
编译预览,登录页应该能正常跳转。如果卡在登录,打开调试器的 Network 面板看云函数调用返回,常见是云函数没部署或环境 ID 不匹配。
4.3 真机预览和常见配置项
点“预览”生成二维码,手机微信扫码即可。真机上要注意两点:图片上传需要在小程序后台配置合法域名(云存储默认已配好,自建存储才需要);app.json里如果用了getUserProfile相关能力,要确认基础库版本。调试阶段建议在project.config.json里打开"urlCheck": false,省去域名校验的麻烦,上线前再改回来。
注意:云开发的免费额度对课程设计够用,但数据库读写次数和云函数调用次数有上限,批量导入宿舍数据时别用循环单条写,用批量接口或分批处理。
5. 避坑与排查:这几个坑我踩过,你可以绕开
5.1 现象:登录后角色一直是学生,宿管端进不去
原因多半是 users 表里 role 字段没写或者写成了中文“管理员”。云函数里判断的是role === 'admin',中文值永远不匹配。解决方法是统一用英文枚举,后台改角色时也走云函数校验,别在控制台手改。
5.2 现象:报修列表图片加载不出来
云存储返回的 fileID 不能直接当图片地址用,需要先调wx.cloud.getTempFileURL换成临时链接,或者在小程序里用<image src="{{fileID}}">让基础库自动处理。前者适合需要缓存链接的场景,后者更省事。如果用了自建存储,还要检查域名是否在小程序后台白名单里。
5.3 现象:两个人同时审批同一条调宿,人数加了两遍
这就是第 3 章说的事务问题。没加事务时,两个请求都读到“待审批”状态,都执行了通过逻辑。解决办法是把状态校验和更新放进事务,或者用where({ status: 0 }).update()的条件更新,根据返回的stats.updated判断是否真的更新成功,为 0 就说明被别人抢先了。
5.4 现象:真机上 tabBar 页面跳转报错
wx.switchTab只能跳 tabBar 里配置过的页面,且路径不能带参数。登录分流时如果目标页不是 tabBar 页,要用wx.redirectTo。另外app.json里 tabBar 的list至少两项,路径必须和 pages 里注册的完全一致,大小写都不能错。
5.5 现象:云函数本地调试正常,上传后报“collection not exists”
本地调试连的是本地模拟环境,上传后连的是云端真实数据库。集合名大小写敏感,本地建了Users云端建了users就会报这个。统一用小写加复数命名,建库时对着代码里的集合名逐个核对一遍。
6. 二次开发:把单栋楼的原型改成多校区系统的改造思路
这套系统的骨架是通用的,但真要用到多栋楼甚至多校区,得在三个地方动刀。第一是数据隔离,给 users、dorms、repairs 都加一个campusId字段,所有查询带上这个条件,宿管账号绑定校区,云函数里从用户表反查 campusId 而不是信任前端传值。第二是权限分级,现在只有学生和宿管两个角色,多校区至少要加“校区管理员”,审批流从一级变两级,调宿表加campusReviewer字段,状态机从三态扩到五态,改造时先把状态流转图画在纸上再写代码,比直接改代码快。
第三是统计模块的聚合方式。单校区时前端拉全量数据自己算就行,多校区必须用云数据库的聚合操作在服务端算:
// cloudfunctions/stats/index.js 按楼栋统计报修完成率 const res = await db.collection('repairs') .aggregate() .match({ campusId, createTime: _.gte(startOfMonth) }) .group({ _id: '$dorm', total: $.sum(1), done: $.sum($.cond({ if: $.eq(['$status', 2]), then: 1, else: 0 })) }) .end();$.sum($.cond(...))是聚合里的条件计数写法,比拉回前端过滤省流量也更准。match阶段一定要带 campusId 和时间范围,否则数据量一大就超时。
改造后的验证方法我一般分三步走:先用测试号造两个校区的数据,确认 A 校区宿管看不到 B 校区的报修;再模拟并发审批,看事务是否还稳;最后用真机在弱网下走一遍完整流程,看 loading 态和错误提示是否到位。表格对比一下改造前后的关键差异:
| 维度 | 单校区原型 | 多校区改造后 |
|---|---|---|
| 数据隔离 | 无 | 全表加 campusId,云函数强制注入 |
| 角色 | student / admin | 增加 campusAdmin,审批两级 |
| 统计 | 前端计算 | 聚合管道服务端计算 |
| 部署 | 单环境 | 按校区或按环境拆分 |
有个教训值得说:早期我图省事,把 campusId 交给前端传,结果测试时有人改了请求参数就看到了别的校区数据。从那以后我每次做多租户改造,都强制在云函数入口从用户表反查归属,前端传的一律不信。这套宿舍系统的代码量不大,正好适合拿来练这种“边界校验”的习惯,改完一轮你对小程序云开发的理解会比看十篇教程都扎实。希望帮到你。
本文还有配套的精品资源,点击获取