news 2026/10/16 11:51:41

微信小程序疫苗预约接种系统全栈源码实战拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微信小程序疫苗预约接种系统全栈源码实战拆解

简介:基于微信小程序的疫苗预约接种系统完整源码包,面向小程序开发者、Java后端学习者及高校毕设/课设人群。系统在既有疫苗预约项目基础上重构并二次开发,业务覆盖预约、支付、签到、预检、接种、留观全流程。管理员端支持查看数据分析图,对接种点、医护人员、疫苗、预约计划进行增删改查,可查询接种者的六类历史记录;接种者端支持疫苗与接种点信息查阅、提交预约申请、模拟支付并查看接种二维码状态。资源包共644个文件,以html与vue构成前端页面,java与xml担纲后端接口和配置,js负责交互逻辑,另含scss/css样式、json数据配置及sql脚本,压缩包仅3.56MB,导入IDE后修改MySQL驱动与数据库配置即可启动。已有1054人学习,适合作为毕业设计、课程设计的完整参考或二次开发练习。

1. 微信小程序疫苗预约接种系统:一套直接能改的课设级全栈源码

疫苗预约这事,难点不在「能预约」,而在「约了不重、约了能查、库存不超卖」。这套基于微信小程序的疫苗预约接种系统源码,小程序端用原生 WXML/WXSS/JS 开发,后端走 PHP + MySQL,把用户登录、疫苗列表、在线预约、接种记录、管理员后台一整条链路做齐了。拆完代码的第一感觉是,它没有堆炫技组件,但对预约系统最核心的「日期 + 时段 + 库存 + 状态」四个要素处理得相当扎实,状态机、事务扣库存、角色权限都有覆盖。适合正在做毕设、课设,或者想把小程序全栈流程完整跑通的人。拿它改业务比从零写至少省一半时间,而且代码结构足够让答辩老师问到哪都能接住话。

2. 先看骨架:页面结构、角色权限与六张核心数据表

拿到源码压缩包,先别急着导入开发者工具。我习惯先把目录结构和数据库脚本捋一遍,搞清楚这套系统是谁在用什么角色操作,数据是怎么流转的,后面改起来才不迷路。这套系统的设计思路很典型:小程序端面向普通用户,管理后台面向接种点管理员,两边共用同一套后端接口和数据库。

2.1 页面结构与角色边界:前端 pages 目录与后端 admin 两个世界

小程序端的 pages 目录一般按业务划分,常见的是 index(首页疫苗列表)、booking(预约提交)、appointment(我的预约)、vaccine(疫苗详情)、profile(个人中心)。底部 tabBar 通常是首页、预约、我的三个入口。管理端一般是独立的页面目录,或者直接做成 Web 后台,但核心业务表共用。

角色权限上,系统里至少有三类角色。普通用户只能浏览疫苗、提交预约、查看自己的预约记录和接种记录;接种点管理员负责审核预约、登记实际接种信息、维护疫苗库存;系统管理员负责维护疫苗数据、接种点信息、发布公告。这套源码里角色字段存在 user 表的 role 字段里,接口层通过 token 里的用户身份做权限判断。我拆的时候特别看了下,预约创建接口和接种登记接口不是同一个权限级别,这点做得是对的,不然普通用户就能自己把状态改成已接种。

2.2 核心数据表:预约状态从 pending 到 completed 的流转

数据库脚本文件大概率叫 vaccine_system.sql 或类似名字,导入后主要就是下面这六张表。我先用一张表把这六张表的职责说清楚。

表名核心字段职责说明
useropenid, nickname, phone, id_card, role, token用户表,openid 是微信侧唯一标识,role 区分权限
vaccinename, manufacturer, dosage, stock, description, status疫苗表,stock 是剩余库存,status 控制上下架
pointname, address, work_time接种点表,一个疫苗可以对应多个接种点
appointmentuser_id, vaccine_id, point_id, appoint_date, time_slot, status预约表,状态是这套系统的核心
recordappointment_id, user_id, vaccine_id, dose, vaccinated_time, operator_id接种记录表,接种完成后生成
noticetitle, content, create_time公告表,首页轮播或列表展示

预约表里的 status 字段是整个系统最值得看的地方。一般用字符串存四种状态:pending 待接种、completed 已接种、canceled 已取消、expired 已过期。已过期这个状态容易被忽略,但实际场景里用户约了没来,预约单不能永远挂在 pending 上,需要一个定时任务或者在查询时按日期判断自动标记过期。源码里大概率是在查询接口里动态判断的,也就是 appointment 表里没有真的去 UPDATE 过期状态,而是 SELECT 时用 appoint_date 和当前日期比对。这种做法省事,但如果你要做后台统计报表,建议还是写个定时脚本把过期状态落库。

2.3 接口对应关系:wx.request 的 URL 和后端 PHP 路由怎么对齐

小程序端所有网络请求都走 wx.request,后端是 PHP 写的,没有用框架的话就是每个接口一个 PHP 文件,路由规则是/api/文件名.php。我拆下来的接口对应关系大概是下面这样,你拿到源码后可以对照着改。

前端模块请求 URL方法说明
登录/api/login.phpPOSTcode 换 openid,生成 token
疫苗列表/api/vaccine_list.phpGET分页返回疫苗和剩余库存
疫苗详情/api/vaccine_detail.phpGET单支疫苗详情、适用人群
创建预约/api/appointment_create.phpPOST三要素校验后生成预约单
我的预约/api/appointment_list.phpGET当前用户的预约记录列表
取消预约/api/appointment_cancel.phpPOST把 pending 改成 canceled
接种记录/api/record_list.phpGET已完成的接种历史

注意看前端请求 URL 和后端文件名的对应方式,改接口时两头都要动。前端在 utils/config.js 或 app.js 里有个 baseURL 全局变量,所有 wx.request 都基于它拼 URL。后端 PHP 文件里统一 require 数据库连接和鉴权公共文件,一般是 include/db.php 和 include/auth.php。鉴权部分看的是请求头里的 X-Token 字段,后端从 user 表查出对应用户再做权限判断。这个套路不高级但很实用,课程设计答辩时被问到「你怎么做登录态保持的」,照着这个链路讲就能说清楚。

3. 三个核心流程的代码拆解:登录、疫苗列表与预约提交

搞清楚了骨架,接下来就是把最核心的三个流程一行行看明白。这三个流程分别是登录换 openid、疫苗列表加载、预约提交,它们基本决定了这套系统能不能正常跑。我按照前端调用的先后顺序来拆,每个流程都会把前端和后端的代码拿出来讲参数和逻辑。

3.1 登录不是拿 code 就完事:jscode2session 换 openid 的完整链路

微信小程序登录的第一步是 wx.login 拿到临时 code,但这个 code 不能直接当身份用。正确做法是把 code 发给后端,后端拿着 code 加上 appid 和 secret 去微信的 jscode2session 接口换 openid。openid 是用户在当前小程序下的唯一标识,后端拿到它去 user 表里查,查不到就自动注册一条,然后生成一个 token 返回前端存起来。

前端登录页面的核心逻辑大概长这样:

// pages/login/login.js const app = getApp(); Page({ onLoad() { wx.login({ success: (res) => { if (res.code) { this.exchangeOpenid(res.code); } else { wx.showToast({ title: '登录失败', icon: 'none' }); } } }); }, // 用 code 换 openid,拿到 token 后存本地 exchangeOpenid(code) { wx.request({ url: `${app.globalData.baseURL}/api/login.php`, method: 'POST', data: { code }, success: (res) => { if (res.data.code === 0) { wx.setStorageSync('token', res.data.data.token); wx.setStorageSync('userId', res.data.data.userId); wx.switchTab({ url: '/pages/index/index' }); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); } } }); } });

前端这段逻辑,坑点主要在 url 拼接上。app.globalData.baseURL 如果忘了加请求路径,或者 baseURL 配的是http://localhost:8080而不是http://localhost:8080/api,那所有请求都会 404。另外 wx.setStorageSync 存的 token 和 userId,后续所有需要登录态的接口都会从 storage 里取,这两个 key 不要改,改了你得把全工程引用一起改。

后端 login.php 的代码是整套系统的登录基石:

// api/login.php <?php require_once '../include/DB.php'; $code = $_POST['code'] ?? ''; if (!$code) { echo json_encode(['code' => -1, 'msg' => '缺少 code']); exit; } // 改成你自己的小程序 appid 和 secret $appid = 'wx1234567890abcdef'; $secret = 'your_app_secret'; $url = "https://api.weixin.qq.com/sns/jscode2session" . "?appid={$appid}&secret={$secret}&js_code={$code}&grant_type=authorization_code"; $resp = json_decode(file_get_contents($url), true); if (!isset($resp['openid'])) { echo json_encode(['code' => -2, 'msg' => 'code 无效或已过期']); exit; } $pdo = DB::getInstance(); // 先查用户是否存在,不存在则自动注册 $stmt = $pdo->prepare("SELECT id, openid FROM user WHERE openid = ?"); $stmt->execute([$resp['openid']]); $user = $stmt->fetch(); if (!$user) { $pdo->prepare("INSERT INTO user (openid, nickname, role) VALUES (?, ?, 'user')") ->execute([$resp['openid']]); $userId = $pdo->lastInsertId(); } else { $userId = $user['id']; } // 生成 token 并写回 user 表 $token = md5($resp['openid'] . time()); $pdo->prepare("UPDATE user SET token = ? WHERE id = ?")->execute([$token, $userId]); echo json_encode(['code' => 0, 'data' => ['token' => $token, 'userId' => $userId]]);

后端这段逻辑有几个参数要解释清楚。file_get_contents 直接请求微信接口是 PHP 环境最省事的做法,但生产环境建议换 curl,因为 file_get_contents 在对方接口响应慢时会阻塞较久。token 生成用 md5 加时间戳,安全性够课设用,但正式上线建议用 openssl_random_pseudo_bytes 之类的随机源。appid 和 secret 必须从后端发请求,绝不要在前端代码里出现 secret,这是底线,答辩时老师可能专门问这个。

3.2 疫苗列表与下拉刷新:剩余库存的一次性返回

疫苗列表页是用户打开小程序看到的第一个页面。前端用 onLoad 拉数据,同时支持 onPullDownRefresh 下拉刷新。注意疫苗列表接口返回的不只是疫苗基本信息,还要带上剩余库存,因为用户需要在列表页就能判断这支疫苗还能不能约。

// pages/vaccine/vaccine.js const app = getApp(); Page({ data: { list: [], loading: false }, onLoad() { this.fetchList(); }, // 下拉刷新,结束后要手动关闭刷新动画 onPullDownRefresh() { this.fetchList(() => { wx.stopPullDownRefresh(); }); }, fetchList(callback) { this.setData({ loading: true }); wx.request({ url: `${app.globalData.baseURL}/api/vaccine_list.php`, method: 'GET', data: { page: 1, pageSize: 20 }, success: (res) => { if (res.data.code === 0) { this.setData({ list: res.data.data.list }); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); } }, complete: () => { this.setData({ loading: false }); if (callback) callback(); } }); } });

这段代码注意两个细节。data 里的 loading 状态要配合 wx.showLoading 或列表页的加载中占位图,我见过很多课设直接忽略 loading,弱网环境下用户会以为页面卡死了。onPullDownRefresh 要求 app.json 里对应页面配置"enablePullDownRefresh": true,忘了配的话这个回调永远不会触发,这是新手最容易翻车的地方。

疫苗列表后端返回的库存字段,一般不是查 vaccine 表里的 stock 原值,而是 stock 减去已预约未完成的数量。如果源码里是直接返回 stock,那要注意它有没有在创建预约时同步 UPDATE stock。正确做法是预约创建成功就把 stock 减一,取消预约再加回来,这样 stock 始终表示当前实际可预约量,列表页查询不需要再做减法,性能更好。

3.3 预约提交:日期、时段、库存的三层校验与原子扣减

预约提交是整个系统技术含量最高的一段。前端用 picker 组件选日期,用 radio-group 渲染微信小程序单选框来选时段,比如上午 8:00-11:00、下午 14:00-17:00。提交时前端先做空值校验,后端再做三重校验:该用户当天该时段是否已有预约、疫苗库存是否足够、疫苗是否还在架。

前端预约页的核心提交逻辑:

// pages/booking/booking.js const app = getApp(); Page({ data: { vaccineId: '', vaccineName: '', date: '', timeSlot: '', slotList: ['上午 8:00-11:00', '下午 14:00-17:00', '晚间 18:00-20:00'] }, // 日期选择,picker 的 value 会绑定到 data.date onDateChange(e) { this.setData({ date: e.detail.value }); }, // 时段选择,radio-group 的变更事件 onTimeSlotChange(e) { this.setData({ timeSlot: e.detail.value }); }, submitBooking() { const userId = wx.getStorageSync('userId'); const token = wx.getStorageSync('token'); const { vaccineId, date, timeSlot } = this.data; if (!vaccineId) { wx.showToast({ title: '请先选择疫苗', icon: 'none' }); return; } if (!date) { wx.showToast({ title: '请选择接种日期', icon: 'none' }); return; } if (!timeSlot) { wx.showToast({ title: '请选择时间段', icon: 'none' }); return; } wx.request({ url: `${app.globalData.baseURL}/api/appointment_create.php`, method: 'POST', header: { 'X-Token': token }, data: { user_id: userId, vaccine_id: vaccineId, appoint_date: date, time_slot: timeSlot }, success: (res) => { if (res.data.code === 0) { wx.showToast({ title: '预约成功', icon: 'success' }); wx.redirectTo({ url: '/pages/appointment/list' }); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); } } }); } });

这里把 userId 和 token 都传给了后端。有一种做法是不传 userId、只靠请求头里的 token 识别身份,这样更安全,因为 userId 在请求体里可以被篡改。源码里如果两个都传了,后端鉴权逻辑应该是从 token 查出用户 id,再和 $_POST['user_id'] 比对,不一致就拒绝。我建议你自己改代码时直接删掉 user_id 参数,后端只认 token,少一个被攻击的面。

后端预约创建接口,用事务和行锁保证不超卖:

// api/appointment_create.php <?php require_once '../include/DB.php'; require_once '../include/auth.php'; // auth.php 里从 X-Token 解析出当前用户 id $userId = getCurrentUserId(); if (!$userId) { echo json_encode(['code' => 401, 'msg' => '未登录']); exit; } $vaccineId = intval($_POST['vaccine_id'] ?? 0); $date = $_POST['appoint_date'] ?? ''; $timeSlot = $_POST['time_slot'] ?? ''; if (!$vaccineId || !$date || !$timeSlot) { echo json_encode(['code' => -1, 'msg' => '参数不完整']); exit; } $pdo = DB::getInstance(); $pdo->beginTransaction(); try { // 同一天同一时段,一个用户只能有一条未完成预约 $stmt = $pdo->prepare( "SELECT id FROM appointment WHERE user_id = ? AND appoint_date = ? AND time_slot = ? AND status IN ('pending', 'completed') FOR UPDATE" ); $stmt->execute([$userId, $date, $timeSlot]); if ($stmt->fetch()) { throw new Exception('该时段已有预约,请勿重复提交'); } // 原子扣减库存,只允许 stock > 0 时减少 $stmt = $pdo->prepare( "UPDATE vaccine SET stock = stock - 1 WHERE id = ? AND stock > 0" ); $stmt->execute([$vaccineId]); if ($stmt->rowCount() === 0) { throw new Exception('疫苗库存不足'); } // 插入预约单 $pdo->prepare( "INSERT INTO appointment (user_id, vaccine_id, appoint_date, time_slot, status) VALUES (?, ?, ?, ?, 'pending')" )->execute([$userId, $vaccineId, $date, $timeSlot]); $pdo->commit(); echo json_encode(['code' => 0, 'msg' => '预约成功']); } catch (Exception $e) { $pdo->rollBack(); echo json_encode(['code' => -1, 'msg' => $e->getMessage()]); }

这段代码是整套系统的核心,几个点必须讲透。FOR UPDATE 是行级锁,锁住的是 appointment 表里满足条件的那一行,防止两个请求同时查到「没有预约」然后都插入成功。UPDATE vaccine SET stock = stock - 1 WHERE stock > 0 是原子扣减,数据库层面保证库存不会减成负数。这两步放在一个事务里,任何一个失败就回滚,预约单不会出现「库存扣了但预约没建成功」的脏数据。

提示:事务里的 FOR UPDATE 一定要配合索引,否则会升级成表锁。appointment 表的 user_id、appoint_date、time_slot 这三个字段应该建联合索引,脚本里如果没有,自己加上,不然并发一高整张表卡死。

4. 避坑与常见问题:最容易让课设翻车的五个现场

代码能跑通是一回事,部署到真机能稳定跑是另一回事。我拆这套源码的过程中,把最容易让初学者卡住的问题按「现象 → 原因 → 解决」整理成了五条踩坑记录。前四条几乎每个做小程序课设的人都会遇到,第五条属于不容易想到但一旦踩了很头疼的类型。

4.1 微信小程序开发工具接口访问正常、真机接口访问失败

现象:开发者工具里所有接口都通,预约流程走得很顺;一换到真机预览,页面空白,Network 面板里请求全部报错。

原因:开发者工具默认开启了「不校验合法域名」选项,所以 http 明文请求也能过。真机上这个选项不生效,微信强制要求所有请求域名必须是 HTTPS,并且要在小程序后台配置到 request 合法域名里。如果你是用局域网 IP 访问本地 PHP 服务,真机连的根本不是同一网络,请求直接失败。

解决:本地调试时,在真机预览面板打开「不校验合法域名」调试开关,或者用wx.setEnableDebug({ enable: true })临时开启。但这只能用于开发,正式上线必须走 HTTPS 域名。我一般建议课设阶段直接用开发者工具演示,要上真机就临时勾选调试选项,别在这上面死磕域名配置。

4.2 picker 选出来的「今天」到后端变成昨天

现象:用户在日期选择器里选了当天日期,提交预约后,后台管理端看到预约日期是前一天。

原因:picker 组件返回的 value 是本地时区的YYYY-MM-DD字符串,但如果你在提交前用new Date(dateString)转成 Date 对象,再用toISOString()或直接 JSON.stringify 传输,时区会被转成 UTC。国内是 UTC+8,转 UTC 后日期自然就前一天晚 16 点。

解决:日期字符串不要转 Date 对象,直接原样传给后端。后端 PHP 也不要再用strtotime($date)做转换,直接按字符串存 date 类型字段。如果非要转,用new Date(${date}T00:00:00)固定本地时区,或者后端接收后用date('Y-m-d', strtotime($date . ' +1 day'))补偿。最省心的方案就是全程字符串,别碰时间对象。

4.3 微信小程序登录获取手机号:个人主体接不进去的兜底方案

现象:按网上的教程写了获取手机号的 button,但真机上点击没有任何反应,或者提示「未配置该接口的权限」。

原因:微信小程序登录获取手机号这个能力,要求小程序必须通过企业认证,个人主体的小程序没有这个接口权限。很多课设用的是个人注册的账号,自然接不通。这是平台规则问题,不是你代码的问题。

解决:源码里如果依赖手机号,大概率有兜底方案。常见做法是在个人中心提供一个表单,让用户手动填写手机号,配合短信验证码(可以用阿里云短信或腾讯云短信免费额度)。openid 做登录凭证,手机号只做业务联系和接种通知用。这套方案在任何主体下都能跑,而且答辩时能讲出一套完整的「实名信息绑定」逻辑,反而加分。

4.4 库存先查再减:并发预约时超卖怎么发生

现象:疫苗库存剩最后 1 支,两个用户同时提交预约,两个人都预约成功,库存变成 -1。

原因:如果源码里的预约逻辑是「先 SELECT stock,判断大于 0,再 UPDATE stock = stock - 1」,这两步之间存在时间窗口。两个请求同时读到 stock 为 1,都通过判断,然后都执行 UPDATE,库存被减两次。这就是经典的先查后减并发问题。

解决:用第 3 章那段代码里的原子扣减写法,UPDATE vaccine SET stock = stock - 1 WHERE stock > 0,让数据库判断库存是否足够,配合 rowCount() 判断是否真的减成功了。再往上就是事务里对疫苗行加 FOR UPDATE 锁,双保险。这条经验做任何带库存的系统都适用,不只是疫苗预约。

4.5 疫苗名称带中文,GET 请求查出来乱码

现象:列表页搜索框输入「乙肝疫苗」,提交后后端查不到结果;换成英文关键词就能查到。

原因:wx.request 的 GET 请求,data 里的中文参数默认不会做 URL 编码,或者后端 PHP 没有正确设置字符集。MySQL 连接串少了charset=utf8mb4时,中文条件查询会直接失败。

解决:前端手动编码encodeURIComponent(keyword),后端在 DB.php 建立连接后执行SET NAMES utf8mb4。另外 PHP 文件头部的header('Content-Type: application/json; charset=utf-8')别漏,不然返回的中文在开发者工具里显示成乱码,排查起来很迷惑。这条属于基础配置问题,但每年都有不少人被卡住。

5. 本地部署与联调:把源码从压缩包跑到真机预览的完整动作

源码拿到手,第一步是让它先跑起来。这套系统的前后端是分离的,后端 PHP + MySQL 负责接口和数据,小程序端负责界面交互。部署顺序是先起后端,再导前端,最后联调。每一步都有固定的检查点,按顺序来能省很多排查时间。

5.1 后端环境:SQL 导入与 PHP 内置服务器

后端依赖 PHP 和 MySQL。Windows 上建议用 phpStudy 或 XAMPP,Mac 上可以用内置 PHP。先把 SQL 脚本导入数据库:

mysql -u root -p < vaccine_system.sql

导入后确认一下表是否建全。用 phpStudy 的话把项目放到WWW目录下,保证api目录能通过http://localhost/api/xxx.php访问。不装 Apache 也可以用 PHP 内置服务器,项目目录下执行:

php -S localhost:8080 -t .

-t 后面的点表示当前目录作为 Web 根目录,这样api/login.php就对应http://localhost:8080/api/login.php。启动后先在浏览器验证一个接口,比如直接访问http://localhost:8080/api/vaccine_list.php,能返回 JSON 就说明后端通了。这个步骤很多人跳过,结果前端折腾半天发现是后端没起来。

改数据库连接配置。DB.php 里的主机、用户名、密码、库名四项必须和你本地环境一致,常见错误是默认密码是 root 但本地 MySQL 设了别的密码,或者库名和 SQL 脚本里建的不一致。连接后执行一句SET NAMES utf8mb4这一步别删,第 4 章刚说过中文乱码的坑。

5.2 小程序端:AppID 与 baseURL 两个必改项

小程序端用微信开发者工具导入项目目录。导入时 AppID 可以先选「测试号」,测试号不影响本地预览和真机调试,只是不能发布上线。如果源码里有project.config.json,直接用它打开能保留原有的编译配置。

打开utils/config.js或者app.js,找到 baseURL 改成你的后端地址:

// utils/config.js module.exports = { // 后端接口地址,本地调试用局域网 IP // 真机预览时把这个 IP 换成电脑的局域网 IP baseURL: 'http://localhost:8080', version: '1.0.0' };

这里有两个细节。第一,开发者工具里用localhost没问题,但真机预览时手机访问不到电脑的 localhost,必须改成电脑的局域网 IP,比如http://192.168.1.101:8080。第二,微信开发者工具的项目详情里,本地设置必须勾选「不校验合法域名」,否则所有 http 请求在模拟器里都会被拦截。

改完之后点编译,首页疫苗列表能刷出来就说明前后端连上了。如果列表空白,打开开发者工具的调试器看 Network 面板,请求有没有发出、返回什么状态码,这一步能定位 80% 的问题。

5.3 全链路验证:从注册到接种记录的闭环检查

前后端连通后,别急着收工。我习惯按用户真实动线走一遍全流程,每个节点都检查数据库里的数据落没落对。

第一步,登录。打开小程序,确认 user 表里自动插入了一条记录,role 是 user,token 有值。第二步,预约。选一支疫苗、挑日期和时段,提交后去 appointment 表看状态是不是 pending,vaccine 表的 stock 是否减一。第三步,取消预约。在预约记录页取消,确认 stock 加回来没。如果取消后库存没恢复,说明 cancel 接口里漏了 UPDATE,这是常见 bug。第四步,模拟接种。管理端把预约单状态改成 completed,确认 record 表里多了一条接种记录,接种时间是当前时间。这条链路全通,说明这套源码的核心功能是完整的,后面改业务逻辑就在这个框架上加代码。

提示:走全链路的时候,每操作一步就去看一眼 MySQL 里的数据,比只看前端界面靠谱得多。很多接口返回成功但数据落库不对,只有查表才能发现。

6. 进阶优化:导航栏适配、请求封装与 token 过期的三个顺手改进

源码跑通之后,如果想让它更接近生产环境,我推荐做三个小改进。这三个改动都不大,但能明显提升代码质量和演示效果。

第一个是微信小程序顶部导航栏高度适配。默认导航栏在 iPhone 和安卓刘海屏机型上的高度不一样,如果页面用了自定义导航栏,胶囊按钮的位置会顶到状态栏。通用做法是用wx.getMenuButtonBoundingClientRect拿到胶囊的位置信息:

// utils/navbar.js function getNavBarInfo() { const windowInfo = wx.getWindowInfo(); const menuInfo = wx.getMenuButtonBoundingClientRect(); return { statusBarHeight: windowInfo.statusBarHeight, navBarHeight: (menuInfo.top - windowInfo.statusBarHeight) * 2 + menuInfo.height, menuTop: menuInfo.top, menuHeight: menuInfo.height }; } module.exports = { getNavBarInfo };

navBarHeight 的算法逻辑是:胶囊顶部到状态栏底部的距离乘以 2,再加胶囊本身高度,得到整个导航栏高度。按这个值去做占位视图的高度,自定义导航栏在任何机型上都不会错位。我见过太多人写死 44px,结果在带刘海的机型上按钮直接被截掉。

第二个是请求统一封装。现在前端到处是 wx.request,每个页面重复写 header 和错误处理。抽一个 Promise 封装的请求方法,所有接口调用都走它:

// utils/request.js const app = getApp(); function request(options) { return new Promise((resolve, reject) => { wx.request({ url: `${app.globalData.baseURL}${options.url}`, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', 'X-Token': wx.getStorageSync('token') || '' }, success: (res) => { if (res.data.code === 401) { // token 失效,清掉本地缓存并跳回登录页 wx.removeStorageSync('token'); wx.removeStorageSync('userId'); wx.navigateTo({ url: '/pages/login/login' }); reject(res.data); return; } resolve(res.data); }, fail: reject }); }); } module.exports = request;

第三个是 token 过期处理。很多课设源码的登录态是永久的,后端不校验 token 有效期。改进方式是 user 表里加一个 token_expire 字段,登录时设置比如 7 天过期,接口鉴权时判断当前时间是否超过过期时间,超了就返回 401。上面的 request 封装里看到 401 就自动清缓存跳登录页,用户无感知地重新登录,体验比报错弹窗好得多。

这三个改进做完,这套疫苗预约接种系统从功能到代码质量就都拿得出手了。我每次拆完一套源码,都会强制自己走一遍「跑通全流程 → 补并发和边界 → 抽公共封装」这三步,这套习惯帮我避掉了很多上线后才暴露的坑。希望你拿到这份源码后也能按这个路子把它吃透,而不是只跑通演示就完事。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 11:03:56

真空量子涨落:从极限小量到宇宙结构的物理实现

1. 项目概述&#xff1a;这不是数学游戏&#xff0c;而是一次对“无中生有”的物理重读你有没有盯着真空发过呆&#xff1f;不是实验室里抽成低压的玻璃罐&#xff0c;而是真正意义上的“空”——没有光、没有粒子、没有场扰动&#xff0c;连时间都尚未被定义的“前宇宙”状态。…

作者头像 李华
网站建设 2026/10/16 18:26:59

大模型记忆增强组件全链路设计:从存储到检索与注入落地

接到这个标题的时候&#xff0c;我第一反应是&#xff1a;“claude-mem”这肯定又是一个给大模型加记忆的小工具。这类工具这几年层出不穷&#xff0c;但真正能落地的没几个。我花了几天时间&#xff0c;把市面上类似项目&#xff08;包括这个标题指向的 claude-mem&#xff09…

作者头像 李华
网站建设 2026/10/16 14:39:48

前端工具函数进阶:树形处理、格式化、校验与浏览器辅助

1. 这个系列第四篇&#xff0c;我为什么选这四个方向从第一期写到现在&#xff0c;这个系列一直在做一件事&#xff1a;把前端日常开发里那些高频、能复用、又容易被写坏的函数单独拎出来讲透。前三期聊过字符串与类型判断、数组与对象处理、日期与本地存储&#xff0c;到了第四…

作者头像 李华
网站建设 2026/10/13 15:54:47

UltraISO制作U盘启动盘完整教程:从写入模式到启动失败排查

1. 为什么一个“过时”的工具还值得写一篇完整教程如果你现在去搜“U盘启动盘制作”&#xff0c;跳出来的结果大概率是各种一键装机工具&#xff0c;界面花哨、按钮巨大、点一下就完事。但我自己经手的机器没有一千也有八百台了&#xff0c;从老旧的BIOS主板到最新的UEFI平台&a…

作者头像 李华
网站建设 2026/10/16 22:48:03

机器人租赁亿元融资背后:商业模式、成本结构与运营实战

上周末跟几个做设备租赁的老朋友吃饭&#xff0c;话题从传统的叉车、高空作业平台&#xff0c;一路聊到刚官宣没多久的“机器人租赁亿元级融资”。桌上有人半开玩笑半认真地问&#xff1a;“机器人也能租了&#xff1f;租金比请人还便宜吗&#xff1f;”我说你先别急&#xff0…

作者头像 李华
网站建设 2026/10/13 11:06:46

计算机网络课程设计:MFC Socket编程与局域网通信完整解析

简介&#xff1a;面向计算机网络课程设计的完整资料包&#xff0c;覆盖网络聊天程序、Tracert与Ping、基于IP多播的网络会议程序、TELNET终端等经典题目&#xff0c;基于MFC/C在Visual Studio 2019环境下编写&#xff0c;适合高校学生完成类似实验或课程设计时参考。压缩包共19…

作者头像 李华