基于Vue和Megalo的微信小程序台球管理系统实战拆解
2026/9/15 12:54:27 网站建设 项目流程

简介:基于微信小程序的台球管理系统毕业设计项目,面向高校计算机专业学生,可作毕业设计参考,也可用于学习小程序开发。系统围绕台球厅日常运营的会员管理、预约登记、计费结算、库存盘点与报表统计等核心模块展开,并提供优惠活动、桌台状态管理等实用功能,覆盖前台预约到后台管理的主要流程。压缩包共54个文件,以25个页面组件、6个逻辑脚本、5个样式文件为主,配有若干图片与说明文档,整体约366KB,结构清晰,便于快速查看。目前已有180人学习/下载。项目提供可直接运行的微信小程序前端工程,包含页面展示、状态管理、工具函数与静态资源,能帮助读者理解工程项目结构,掌握数据交互和跨端框架开发思路,并借助说明文档快速定位核心代码,适合二次开发及答辩准备。

1. 台球管理系统毕业设计,为什么值得拆开读一遍

微信小程序的毕设项目里,"管理系统"是最容易做成摆设的方向:界面抄后台模板,逻辑只有增删改查。这套基于微信小程序的台球管理系统不是那个路子。前端跑在微信小程序里,用的是 Vue + Megalo 编译方案,会员登录、桌台预约、分钟级计费、待支付订单汇总是一条完整闭环;工程上环境变量、store、请求封装也都是正经 npm 项目的做法,不是单页堆代码。适合两类人:管理系统类毕业设计的学生,照着抄业务设计和目录组织;想给台球厅做轻量运营工具的一线开发者,从中提炼预约冲突检测这类可复用逻辑。下文按骨架、业务、数据层、发布四步拆,每个环节都给出可执行命令和参数说明。

2. 从 megalo.config.js 拆解这份 Vue 风格的小程序骨架

2.1 目录里每一样东西是干什么的

拿到解压目录,先别急着执行 yarn install,花十分钟把骨架认清楚。src 下面是 pages、App.vue、store、main.js、utils、static,标准的 Vue CLI 布局;Megalo 把这套 Vue 单文件组件编译成微信小程序能运行的 WXML 和 JS,所以开发期写的是 .vue,产物是原生小程序代码。根目录的 native 文件夹放原生小程序侧的文件,编译时会合并进输出目录,微信插件或原生自定义组件的兜底行为通常放这里。

路径/文件职责改动频率
src/pages每个子目录一个小程序页面开发期最高
src/App.vue全局生命周期与公共样式
src/main.js创建 Vue 实例并挂载 store
src/storeVuex 模块:user / table / order
src/utils请求封装、计费工具函数
src/static图片、图标等静态资源
native原生小程序文件,构建时合并
megalo.config.js页面注册、导航栏、tabBar 配置

这张表已经说明修改节奏:页面和 utils 是主力,App.vue、main.js、native 这类入口文件尽量少动。.eslintrc.js.eslintignore是代码规范配置,提交前跑一遍 lint 能拦住未使用变量和分号风格问题。有个细节要注意:项目同时带着 yarn.lock 和 package-lock.json,说明开发过程换过包管理器。我拆这类项目时习惯先看 package.json 的 scripts 确认启动命令,然后只保留对应的 lock 文件,两个混用会让 CI 和本地装出不同依赖版本,排错成本很高。

2.2 环境变量拆成两份,换来的是部署时的零改动

.env.development 和 .env.production 的作用是区分接口地址。开发时小程序连本地后端,上线时切线上域名,两个文件写好之后代码里不需要任何环境判断。

# .env.development —— 本地联调环境 VUE_APP_BASE_URL=http://127.0.0.1:3000/api VUE_APP_HALL_ID=1001 # .env.production —— 线上环境 VUE_APP_BASE_URL=https://api.yourdomain.com/api VUE_APP_HALL_ID=1001

VUE_APP_ 前缀是 Vue CLI 的约定,只有带这个前缀的变量才会被打进客户端代码,工具函数里用process.env.VUE_APP_BASE_URL读取。注意,小程序代码最终是公开的,任何放进 env 文件的内容都能被扒出来,appsecret、数据库密码这类敏感项绝不能出现在这两个文件里。VUE_APP_HALL_ID 在这里是留好的多店字段——同一个后端服务多个台球厅时,用门店 ID 隔离数据,属于常见的 SaaS 化设计,答辩被问"如何扩展多店"可以直接讲这个字段。

提示:小程序包下发到用户手机后可以被解包查看,env 文件里写密钥等于把密钥公开。

2.3 页面注册:pages 数组的先后顺序就是启动逻辑

Megalo 的小程序页面配置集中在 megalo.config.js,编译时映射到小程序 app.json 的 pages 字段。页面注册分两块:pages 数组决定有哪些页面和冷启动顺序,tabBar 决定底部导航。

module.exports = { pages: [ 'pages/index/index', // 冷启动页:首页 + 桌台总览 'pages/member/member', // 会员列表与详情 'pages/booking/booking', // 预约选台 'pages/billing/billing', // 计费与结账 'pages/report/report' // 营业报表 ], window: { navigationBarTitleText: '台球管理系统', navigationBarBackgroundColor: '#1a1a2e', navigationBarTextStyle: 'white' }, tabBar: { color: '#999999', selectedColor: '#1a1a2e', list: [ { pagePath: 'pages/index/index', text: '首页' }, { pagePath: 'pages/member/member', text: '会员' }, { pagePath: 'pages/booking/booking', text: '预约' } ] } }

pages 数组第一个元素就是用户进入小程序看到的第一个页面,所以做管理系统时把首页放第一位,登录态检查放在首页启动时静默完成,比单独搞一个登录中转页体验好。tabBar 配置有两个知识点:list 最多五页,且 tabBar 页必须出现在 pages 数组里,否则编译直接报错;selectedColor 要跟导航栏背景色形成对比,台球厅场景深色导航栏配浅色文字观感更协调。小程序页面栈深度限制是十层,预约、计费这类流程页不要靠长跳转链,流程结束用wx.redirectTowx.reLaunch清栈,这是页面设计里最容易被问到的问题。

3. 会员、预约、计费三条业务线的关键实现

3.1 会员登录:微信 code 换业务 token 的链路

小程序没有传统意义上的账号密码,会员识别的标准做法是微信登录。wx.login 返回临时 code,有效期约五分钟且只能用一次;后端拿这个 code 调微信 code2session 接口,换到 openid 和 session_key,再签发自己的业务 token 返回。业务 token 存进 Vuex 和 storage,后续请求都带它。

// src/store/modules/user.js import request from '../../utils/request' const TOKEN_KEY = 'billiards_token' export default { state: { token: wx.getStorageSync(TOKEN_KEY) || '' }, mutations: { setToken(state, token) { state.token = token wx.setStorageSync(TOKEN_KEY, token) // 存本地,冷启动直接恢复登录态 } }, actions: { async wxLogin({ commit }) { // code 只能使用一次,前端不要缓存它 const { code } = await wx.login() const { token } = await request({ url: '/member/login', method: 'POST', data: { code } }) commit('setToken', token) } } }

最容易踩的坑是把 appid、secret 写进小程序代码。code2session 必须由后端发起,token 由后端生成,小程序端只负责传 code、收 token。openid 对每个微信用户唯一,后端直接用 openid 做会员表的唯一键,避免重复建档。如果毕设带了会员等级,会员表和 openid、积分、储值余额的关联设计是答辩高频提问点,提前把"一个 openid 对应一个会员档案,订单表单独记 memberId"讲清楚,比泛泛说"实现了会员管理"有说服力。

3.2 预约台桌:时间段冲突检测用两端校验

预约模块的数据落点是一张预约单,核心字段如下:

字段类型含义
idinteger预约单号,主键
table_idinteger台球桌编号
member_idinteger预约会员
start_timedatetime开始时间
end_timedatetime预计结束时间
statustinyint0 待使用 / 1 使用中 / 2 已完成 / 3 已取消

空闲判断的本质是区间相交检测:新预约时间段与任意已有未结束预约相交,就判定冲突。两个区间相交的充要条件是"新开始小于旧结束,且新结束大于旧开始",写成函数只有几行。

// src/utils/book.js // 判断新时间段是否与已有预约冲突 export function hasConflict(bookings, newStart, newEnd) { return bookings.some(item => { const s = new Date(item.start_time).getTime() const e = new Date(item.end_time).getTime() return newStart < e && newEnd > s }) } // 页面提交预约前的本地校验 const conflict = hasConflict( table.bookings.filter(b => b.status === 0 || b.status === 1), startTime, endTime ) if (conflict) { wx.showToast({ title: '该时段已被预约', icon: 'none' }) return }

前端校验只负责减少无效请求,真正的数据一致性要后端兜底:创建预约时用事务加行锁或唯一约束,防止两个会员在同一毫秒提交同一时间片。毕设里把"前端防呆 + 后端锁"这套双保险写进测试章节,比单纯贴截图有价值。前端边界条件还要处理 startTime >= endTime 的非法输入,以及跨天预约——台球厅散台基本不跨天,但夜场到次日凌晨的单子要能把日期边界算对,凌晨两点结束的订单不应和前一天白天的时间片发生误判冲突。

3.3 计费结算:分钟折算与不要用定时器计时

计费规则按桌型单价计算,以小时为基准单价、按分钟折算:

桌型单价(元/小时)不足一小时
普通台30按分钟折算
美式台40按分钟折算
斯诺克台60按分钟折算
// src/utils/billing.js // 按分钟折算费用,向上取整到 0.5 元,避免分单位误差 export function calcFee(startTime, endTime, ratePerHour) { const elapsedMs = new Date(endTime).getTime() - new Date(startTime).getTime() if (elapsedMs <= 0) return 0 const minutes = Math.ceil(elapsedMs / 60000) // 不足一分钟按一分钟计 const fee = (minutes / 60) * ratePerHour return Math.ceil(fee * 2) / 2 }

这里有个关键设计:结束时间取客户点"结账"的当前时间,而不是依赖前端定时器自动计时。小程序切到后台后 setInterval 会被挂起,回到前台才恢复,用定时器累计时长必然少算钱。常见做法是开台时把 startTime 传给后端,结账时前端把当前时间作为 endTime 提交,后端按两个时间戳计算;需要展示"已计时 xx 分钟"时,才用 setInterval 做每秒刷新,且每次刷新只更新界面,不做费用累计。状态流转也简单:开台创建订单记使用中,结账生成待支付金额,支付成功回写状态。订单表加一个 pay_status 字段,报表统计就是一条 group by。

提示:依赖前端定时器计时是这类系统最隐蔽的 bug。小程序切后台,setInterval 停止,回到前台也不会自动补计时。

4. Vuex 状态管理与请求层:小程序项目最该抄的部分

4.1 store 按业务域拆模块

src/store 按 user、table、order 三个域拆模块,index.js 负责汇总。table 模块存桌台列表与每张桌的实时状态,order 模块存当前计费单,user 模块管会员信息,三者互不直接读写,页面里通过 mapState 取数据。

// src/store/index.js import Vue from 'vue' import Vuex from 'vuex' import user from './modules/user' import table from './modules/table' import order from './modules/order' Vue.use(Vuex) export default new Vuex.Store({ modules: { user, table, order }, // 严格模式只在开发环境开,生产环境避免直接改 state 带来的额外开销 strict: process.env.NODE_ENV === 'development' })

模块拆分的标准是"一个页面关心的数据尽量只来自一个模块"。预约页读 table 模块和 booking 模块,计费页读 order 模块,会员详情读 user 模块;跨模块联动一律走 action 分发,不要在组件里直接改另一个模块的 state。Vuex 的 mutation 在 DevTools 里可回溯,调试时打开 Vue Devtools 看每张桌子的状态变化,定位问题比原生小程序里打 setData 日志高效得多。

4.2 请求封装:token 注入、401 跳转、统一报错

全套接口走 utils/request.js 这个封装,核心逻辑是把 wx.request 包成 Promise,统一处理登录态和错误提示。

// src/utils/request.js import store from '../store' export default function request(options) { return new Promise((resolve, reject) => { wx.request({ url: process.env.VUE_APP_BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', Authorization: store.state.user.token ? `Bearer ${store.state.user.token}` : '' }, success(res) { const body = res.data if (body.code === 0) { // 业务成功:直接抛业务数据,页面不用再解一层 resolve(body.data) } else if (body.code === 401) { // token 失效:清状态并回到首页重新走静默登录 store.commit('user/setToken', '') wx.reLaunch({ url: '/pages/index/index' }) reject(body) } else { wx.showToast({ title: body.message || '请求失败', icon: 'none' }) reject(body) } }, fail(err) { wx.showToast({ title: '网络异常,请重试', icon: 'none' }) reject(err) } }) }) }

几点参数说明:url 只传相对路径,baseURL 由环境变量注入,换环境时接口代码一行不动;header 里带 Bearer token 是后端常见的鉴权写法;401 用 wx.reLaunch 而不是 navigateBack,因为 token 失效时返回栈里的旧页面全部作废,清栈最省事。业务 code 与 HTTP status 分开判断是值得保留的标准做法,后端返回 200 但业务失败的情况(比如余额不足)不会误进 catch。所有失败请求统一 toast,页面里只需要处理成功分支,代码量减一半。

4.3 Megalo 和原生小程序、uni-app 的差异要心里有数

如果你之前写过原生小程序或 uni-app,迁到 Megalo 项目前先看这张对照表:

对比项Megalo原生小程序uni-app
页面写法Vue 单文件 .vue.wxml / .js / .wxss 三件套Vue 单文件
状态管理Vuex 直接用自己写全局对象或引库Vuex
原生 APIwx.* 直接调wx.*uni.* 包装层
数据更新Vue 响应式赋值this.setData 指定路径Vue 响应式赋值
页面参数this.$root.$mp.queryonLoad(options)onLoad(options)

最直观的差异是数据更新:原生小程序里写this.setData({ 'userInfo.nickname': 'xx' })这种带路径的 key,嵌套层级一多很容易更新错位置;Megalo 里直接this.userInfo = res就完成更新,不用关心 key 路径,这是把原生页面迁到本项目时最省心的一点。页面参数读取上,编译型框架通常会把小程序 Page onLoad 的参数挂到this.$root.$mp.query,测试发现取不到就退回在 mounted 里读。右上角那颗胶囊按钮(三个点和圆圈)是微信原生 UI,不能关闭也不能改样式,只能通过导航栏背景色做视觉配合;自定义导航时顶部高度用wx.getSystemInfoSync().statusBarHeight,导航栏再补 44px,不同机型算出来不一样,不能写死 64。

5. 上真实机前必须过的三关:编译产物、体验版、报表导出

5.1 开发工具导入与真机调试

编译产物默认输出到 dist 目录,微信开发者工具导入时选 dist 而不是项目根目录,这是"未找到 app.json"报错最常见的来源。开发阶段在"详情 - 本地设置"里勾选"不校验合法域名",本地联调 http 后端不会失败;要做真机预览,点工具栏"预览"生成二维码,手机和电脑需在同一局域网,调试模式下手机端会出现 vConsole 入口,网络请求、storage、报错都能直接看。

5.2 上传版本与体验版设置

代码稳定后用开发者工具"上传"按钮提交,弹出框会要求填版本号和项目备注。上传完成后,只有小程序管理员能在 mp 后台把该版本"设为体验版",生成体验版二维码给测试同学扫码,这个操作开发者工具本身没有入口,很多人卡在这里。注意 request 合法域名必须在 mp 后台配置且要求 HTTPS;上线后接口全部失败,先查合法域名是否配全,再查证书是否为有效 HTTPS。

5.3 报表导出:用 wx.downloadFile 打通 Excel 下载

营业报表除了页面展示,答辩时现场导出一份 Excel 很出效果。标准链路是后端生成文件返回可访问地址,小程序端下载后用 openDocument 打开:

// 导出当日订单报表 wx.downloadFile({ url: process.env.VUE_APP_BASE_URL + '/report/export?type=daily', filePath: wx.env.USER_DATA_PATH + '/daily.xlsx', // 指定落盘路径 success(res) { wx.openDocument({ filePath: res.filePath, fileType: 'xlsx', showMenu: true // 用户可在右上角菜单保存或转发 }) } })

wx.env.USER_DATA_PATH 是小程序本地用户目录,指定 filePath 决定文件落盘位置,不指定则由系统生成临时路径;openDocument 唤起文件查看页,showMenu 为 true 用户才能调用右上角菜单保存或转发文件。调用顺序固定为先下载拿本地路径、再 openDocument 打开,fileType 要和文件后缀对应;这条链路在 iOS 和 Android 表现一致,省去自己写文件系统交互。

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

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

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

立即咨询