简介:面向计算机相关专业毕业设计的共享充电桩微信小程序项目,是一套98分的高分毕设系统,适合正在准备论文与答辩的学生、需要项目实战练习的开发者,也可直接用于课程设计或期末大作业。项目覆盖充电桩查找、使用、计费及后台管理等核心业务模块,前后端代码完整。资源包共430个文件,以Java后端源码为主线,搭配小程序前端JS、HTML页面、CSS样式及XML配置,并包含SQL数据库脚本、项目配置说明、开发历史(功能演变)说明与需求文档,压缩包约11.93MB。代码经过严格调试,保证可直接运行,方便读者快速部署和二次开发。已有140人学习下载,借助项目说明、数据库脚本与功能演变记录,可深入理解共享充电桩的业务逻辑和小程序开发全流程,获得毕业设计、课程设计所需的完整方案与排错思路。
1. 共享充电桩小程序的关键不在扫码,而在订单状态怎么管
扫码、选择桩位、开始充电,这三个动作看起来是页面的跳转逻辑,真正决定项目能不能拿高分的是背后的订单状态机。充电桩这类硬件交互场景和普通商品下单不一样:用户扫码后桩可能被占用,充电过程中用户可能拔枪,网络中断后计费还在继续,这些异常在毕设答辩时最容易被老师追问。所以拿到这份基于微信小程序开发的共享充电桩项目源码后,我建议你先别急着看页面长什么样,而是把订单表、桩位状态字段、计费触发时机这三条线梳理清楚。
这套项目由微信小程序客户端加 Layui 管理后台组成,客户端负责扫码、选择时长、支付和查看订单,后台负责桩位管理、价格设置和订单查询。适合正在做毕业设计、期末大作业,或者想练手完整前后端联调的计算机专业学生。源码里包含了小程序前端、后台静态页面、数据库说明和需求文档,最直接的用法是把小程序端和后端接口跑通,改掉默认的演示数据,就能作为一份结构完整的毕设系统提交。下面从目录结构开始拆。
2. 源码资产清单:小程序前端、Layui 后台和文档各自负责什么
2.1 小程序端的目录结构与入口文件定位
解压项目后,第一件事是区分哪些文件属于微信小程序运行时的必须项,哪些是后台管理页面的静态资源。小程序端的核心目录是pages、utils、components,其中pages下每个子目录对应一个页面,符合微信小程序项目实例的标准组织方式。app.js负责全局数据和方法,app.json注册页面路由并配置窗口样式,project.config.json记录项目在微信开发者工具里的编译配置。
project-root/ ├── pages/ │ ├── index/ # 首页:桩位地图/列表 │ ├── scan/ # 扫码页:调起摄像头识别二维码 │ ├── charging/ # 充电中:倒计时、功率、费用 │ ├── order/ # 订单列表与详情 │ └── mine/ # 个人中心:登录、余额、优惠券 ├── utils/ │ ├── request.js # wx.request 的统一封装 │ └── util.js # 时间格式化、金额计算 ├── app.js ├── app.json └── project.config.json这段目录树来自项目源码里的小程序端根结构。utils/request.js是所有接口请求的入口,统一处理 baseUrl、token 注入和错误码提示;utils/util.js里通常放着计费计算函数,比如根据开始时间和结束时间计算金额、把秒数格式化成 HH:mm:ss。建议你先打开这两个工具文件,因为它们决定了后续联调时改接口地址、调计费规则的效率。
2.2 后台静态资源:从 Layui 到 iconfont 的定位方式
项目正文中出现的一批文件,像layui.css、layer.css、layui.mobile.css、style.css、laydate.css、code.css,这些都是管理后台的样式表,不是小程序端的文件。layer.css是弹层组件的基础样式,laydate.css是日期选择器专用,layui.mobile.css用于移动端适配,后台里订单筛选和利润统计经常同时用到它们。iconfont.eot是字体图标文件,Layui 的按钮和菜单图标依赖它,如果缺失会导致后台页面出现方块占位符。
59.gif这类文件通常是加载动画或缺省占位图,常见位置是后台登录页和列表页的 loading 状态。整套后台是典型的前后端分离静态资源模式:HTML 页面通过<link>引入 Layui 的 css 和 js,接口数据单独请求后端服务。调试时如果后台页面样式错乱,先看控制台有没有 404,再确认这些静态资源的相对路径是否因为目录移动而失效。
2.3 需求文档和源码的对应关系
共享电桩小程序需求.docx是整个项目的功能基线。文档里一般包含读者角色分析、功能模块划分、用例图和接口字段说明。拿到源码后,把文档里的模块列表和pages下的页面一一对应,能快速确认项目是否有缺页或多余页面。比如文档写了“用户可查看充电历史记录”,那pages/order目录下必然有列表页和详情页两个文件,如果只有一个,说明历史记录是通过同一个页面用参数区分的。
对照文档时我会画一张简单的功能覆盖矩阵,左边是文档功能点,右边是源码页面和关键函数,比如“扫码充电 -> pages/scan/index 的 onScan 方法 -> 请求 /api/pile/start”。这张表在写开题报告或需求分析章节时可以直接用,也是答辩时老师问“这个功能是怎么实现的”时的回答线索。
3. 联调运行:导入项目、修改 appid 和接口地址的通关步骤
3.1 微信开发者工具导入与基础配置
用微信开发者工具导入项目时,选择小程序端所在的目录,而不是整个项目根目录。如果根目录同时包含后台页面,导入整个文件夹会导致小程序编译报错,因为后台的 HTML 文件不在小程序允许的文件类型范围内。导入成功后,先确认project.config.json里的appid字段,没有注册正式小程序的话填测试号,开发者工具会在编译时自动生成一个临时 appid。
{ "appid": "touristappid", "projectname": "shared-charging-pile", "setting": { "urlCheck": false, "es6": true, "minified": true }, "compileType": "miniprogram" }setting.urlCheck是联调阶段最关键的开关。它控制是否校验合法域名,开发阶段填false可以允许请求任意 HTTP 地址,否则只能请求已配置到小程序后台的 HTTPS 域名。es6开启后可以使用 Promise、async/await 这类语法,项目源码里如果用了async/await处理扫码回调,这个开关必须保持打开。改完保存,工具会重新编译。
3.2 接口地址与真机调试配置
小程序端的接口地址集中在utils/request.js里,不要在每个页面单独写wx.request。常见的封装方式是把 baseUrl 提取成全局变量,用环境判断来区分开发环境和生产环境。后端接口跑在本地时,开发者工具里填http://localhost:8080可以直接访问,但真机预览时 localhost 指向手机本身,必须改成电脑在局域网内的 IP。
// utils/request.js const BASE_URL = 'http://192.168.1.100:8080/api' function request(path, method, data) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method: method || 'GET', data: data || {}, header: { 'Content-Type': 'application/json' }, success: (res) => { if (res.statusCode === 200 && res.data.code === 0) { resolve(res.data.data) } else { wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' }) reject(res.data) } }, fail: (err) => reject(err) }) }) } module.exports = { request, BASE_URL }这段封装做了三件事。第一,统一拼接 baseUrl,避免每个页面重复维护接口前缀;第二,统一处理 HTTP 状态码和业务状态码,后端返回{ code: 0, data: ... }表示成功,非零状态直接弹出错误信息;第三,把wx.request的回调风格转成 Promise,页面里可以用.then().catch()串起扫码后创建订单、跳转充电页等操作。真机联调时,把手机和电脑连同一个 Wi-Fi,查电脑局域网 IP 填进去,注意 Windows 防火墙要放行后端服务的端口。
3.3 常见报错与排查顺序
开发阶段最容易碰到三类问题。编译报错提示app.json: 未找到,是因为导入时选错了目录层级;请求报错url not in domain list,是urlCheck没关或域名未配置;真机上提示request:fail,优先检查 IP 是否可达、后端服务是否监听在0.0.0.0而不是只看localhost。如果把后端启动在云服务器或其他电脑上,还需要确认数据库连接配置是否跟随环境切换。
| 报错现象 | 可能原因 | 排查动作 |
|---|---|---|
| 编译时找不到 app.json | 导入目录不是小程序根目录 | 确认导入的是含pages和app.js的目录 |
| 请求报 url not in domain list | 合法域名校验未关闭 | project.config.json里urlCheck设为 false |
| 真机请求失败 request:fail | 电脑 IP 不对或端口未放行 | 手机和电脑同 Wi-Fi,测试接口地址用 IP 访问 |
| 后台页面样式错乱 | 静态资源路径失效 | 检查 css/js 的引用路径和文件名大小写 |
接口地址在真机上访问不到还有一层原因,微信开发者工具的本地调试默认走本机回环地址,后端服务如果只绑定了 127.0.0.1,局域网其他设备自然连不上。启动后端时用--host 0.0.0.0或改监听配置,再用手机浏览器访问http://电脑IP:端口/api/health验证连通性,通了再回小程序里测。
4. 核心业务模块拆解:扫码充电、订单流转和计费联动
4.1 桩位状态与订单表的数据结构设计
共享充电桩的业务核心是三张表:桩位表、订单表、用户表。桩位表的status字段是状态机的中枢,订单表的order_status记录整个充电生命周期的阶段。表结构的好坏直接决定后续计费、结算、异常恢复的代码复杂度,学校里的数据库课程设计一般只强调建表和范式,但真实项目里还会考虑查询效率和状态一致性。
CREATE TABLE pile ( id INT PRIMARY KEY AUTO_INCREMENT, pile_no VARCHAR(16) NOT NULL COMMENT '桩位编号,如 P001', address VARCHAR(64) COMMENT '桩位位置描述', category VARCHAR(8) DEFAULT 'AC' COMMENT '直流DC/交流AC', power INT DEFAULT 7 COMMENT '额定功率 kW', rate DECIMAL(5,2) DEFAULT 1.50 COMMENT '每度电单价 元/kWh', status TINYINT DEFAULT 0 COMMENT '0空闲 1充电中 2离线 3故障', create_time DATETIME ); CREATE TABLE charging_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, user_id INT NOT NULL, pile_id INT NOT NULL, start_time DATETIME, end_time DATETIME, duration_min INT DEFAULT 0, energy DECIMAL(6,2) DEFAULT 0 COMMENT '本次充电度数', amount DECIMAL(6,2) DEFAULT 0 COMMENT '应付金额', order_status TINYINT DEFAULT 0 COMMENT '0进行中 1已完成 2已取消', create_time DATETIME );建表时要注意关键字段的注释和默认值,答辩老师会看数据字典。pile.status用 TINYINT 而不是 VARCHAR,是为了后端代码里直接和整型常量比较,性能更好也不容易写错。charging_order把amount冗余存储而不是实时计算,是因为订单结束后价格规则可能调整,历史订单必须保留当时的快照值。
4.2 计费规则的实现位置
计费逻辑放在后端而不放在小程序端,原因很简单:小程序端的代码可以被反编译或篡改,金额计算这种敏感逻辑必须由后端控制。后端在结束充电时读取该桩位的单价、充电时长和能耗,计算出金额并写入订单表。源码里如果没有单独的计费服务,常见做法是在订单服务里写一个calculateAmount函数。
function calculateAmount(order, pile) { const rate = parseFloat(pile.rate) const energy = parseFloat(order.energy) const durationMin = order.durationMin let amount = 0 // 按电量计费为主,不足一小时按一小时计 if (energy > 0) { amount = energy * rate } else { const hours = Math.ceil(durationMin / 60) amount = hours * pile.hourlyPrice } // 保留两位小数,避免浮点误差 return Math.round(amount * 100) / 100 }这个函数处理了两种计费场景。按电量计费时,用总度数乘以每度单价;如果充电时间太短,电表读数变化不明显或为 0,就按最小计费单位兜底。Math.round(amount * 100) / 100是前端常见的金额取整写法,直接用toFixed(2)在部分计算场景下会因为浮点数精度问题返回不正确的进位,两者选一个保持一致即可。改动计费规则时只需要替换这个函数,不需要动订单状态流转的代码。
4.3 订单状态机的流转路径与异常恢复
订单状态是整个系统的核心。用户扫码后先创建一条order_status = 0的进行中订单,同时把桩位状态置为 1,充电中;用户主动结束或系统检测到断电后,更新结束时间和金额,桩位回到空闲。这个状态的流转必须是一个完整的事务,否则会出现订单在充电、桩位却空闲的可重复占用的脏数据。
空闲(0) --扫码创建订单--> 充电中(1) --用户结束/断电--> 已完成(2) | | | +--超时未开始--> 已取消(2) +--故障(3)------------------+注意这里两个状态用了同样的数字 2,不同表里的状态值可以独立定义,pile.status的 2 代表离线,order_status的 2 代表已取消。业务代码里不要用魔法数字散落各处,建议在常量文件里定义枚举,比如ORDER_STATUS_CANCELED = 2,这样在写后端接口时,状态流转的判断条件可读性更高,也不容易把桩位状态和订单状态搞混。
// 结束充电的伪代码,真实项目中对应 /api/order/finish async function finishOrder(userId, orderId) { const order = await getOrderById(orderId) if (order.order_status !== 0) { throw new Error('订单已结束,请刷新页面') } const now = new Date() const durationMin = Math.round((now - order.start_time) / 60000) const pile = await getPileById(order.pile_id) const finishData = { end_time: now, duration_min: durationMin, energy: await getMeterReading(order.pile_id), amount: calculateAmount({ ...order, durationMin }, pile) } // 事务里同时更新订单状态和桩位状态 await transaction(async (conn) => { await updateOrder(orderId, { ...finishData, order_status: 1 }) await updatePile(order.pile_id, { status: 0 }) }) }这个伪代码演示了结束充电的完整链路。先校验订单状态防止重复提交,然后读取电能表度数,计算金额,最后在数据库事务里同时更新订单完成状态和桩位空闲状态。如果在事务中途断电或请求超时,数据库会回滚,不会出现订单金额算了但桩位状态没改的问题。类似的逻辑也适用于扫码充电接口,只是把更新订单状态改为创建订单并占用桩位。
5. 演示和答辩前值得改的三个收尾优化点
5.1 修改刚进入的加载页面
项目默认的加载页通常是项目名的纯文本或一张默认图,答辩演示时第一屏的印象分很重要。微信小程序的启动加载页分为两种,一种是系统级的launch页面,在app.json的window配置里通过navigationBarTitleText控制标题文字;另一种是项目自定义的欢迎页,通常放在pages/index之前的pages/splash或pages/loading。如果你拿到的源码里有单独的启动屏页面,直接修改该页面的 WXML 和 WXSS 即可。
{ "pages": [ "pages/splash/index", "pages/index/index", "pages/scan/index" ], "window": { "navigationBarBackgroundColor": "#07c160", "navigationBarTitleText": "共享充电桩", "navigationBarTextStyle": "white" } }这段app.json配置把启动页放到pages数组的第一位,保证了小程序冷启动时优先加载该页面。navigationBarBackgroundColor改成和项目主题一致的绿色系,navigationBarTitleText换成具体名称而不是默认的“微信小程序”。注意启动页的跳转逻辑,一般是在onLoad里用setTimeout延迟 2 秒再调用wx.switchTab或wx.redirectTo跳转到首页,如果跳转目标配置在 tabBar 里,必须用wx.switchTab。
5.2 顶部导航栏高度适配 iPhone 的刘海屏
微信小程序的顶部导航栏分为系统导航和自定义导航两种。系统导航高度由微信自动适配,不需要开发者处理;如果是自定义导航,也就是在单个页面的 json 里设置了"navigationStyle": "custom",那么页面内容会延伸到状态栏下方,这时候需要手动获取状态栏高度和胶囊按钮的位置来偏移内容。这个细节在很多微信小程序项目实例里都会被忽略,但真机演示时一对比就看出专业性。
const { statusBarHeight } = wx.getSystemInfoSync() const { top = 44 } = wx.getMenuButtonBoundingClientRect ? wx.getMenuButtonBoundingClientRect() : {} Page({ data: { statusBarHeight, navBarHeight: (top - statusBarHeight) * 2 + 32 } })代码里wx.getSystemInfoSync().statusBarHeight返回手机状态栏的高度,单位是像素,不同机型数值不同。wx.getMenuButtonBoundingClientRect()返回胶囊按钮的坐标信息,用它的top减去状态栏高度得出导航栏的上下间距,再乘以 2 加 32 算出导航栏的整体高度,这段逻辑在社区里被称为“胶囊适配法”。把这个高度值绑定到页面根节点的padding-top上,自定义导航就不会在 iPhone 上顶到额头或者被状态栏遮挡。
5.3 用演示数据兜底,避免现场网络抖动
答辩现场最怕的一种情况是演示到扫码充电时后端接口突然超时,整个页面卡在加载状态。稳妥做法是在小程序端加一层 mock 数据开关,调试或演示环境可以走本地模拟数据,生产环境走真实接口。在utils/request.js里加一个全局变量USE_MOCK,当它为true时直接从本地 JSON 文件返回数据,不发网络请求。
定义 mock 数据时要注意字段结构和真实接口保持一致,尤其是data字段里的订单号和金额格式,这样页面代码不需要任何改动。演示时先走一遍 mock 数据把完整业务流程讲完,再切回真实接口展示前后端联调成果,这段“异常兜底”的思路本身也能成答辩里项目容错的加分项。最后建议提前录一段小程序完整操作过程的视频作为备份,万一现场设备出问题,视频演示不会让汇报中断,这是准备教学工作级演示时比较稳妥的一手准备。
本文还有配套的精品资源,点击获取