十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微信小程序点餐系统全链路实战:前后端分离教学样本

微信小程序点餐系统全链路实战:前后端分离教学样本 简介这是一套完整的微信小程序点餐系统源码涵盖前端小程序与后端服务代码适用于计算机专业学生开展毕业设计、期末大作业或课程设计尤其适合具备基础JavaScript和Node.js能力的学习者快速上手实践。资源共65个文件包含9个JS逻辑文件如app.js、foods.js、router.js、9个JSON配置与数据文件如dbfood.json、package.json、5个WXSS样式文件、4个WXML页面结构文件以及图片22个JPG、6个PNG和文档README.md、sitemap.json等整体压缩包仅1.92MB轻量易部署。已有613人学习下载源码经本地编译验证可直接运行评审得分高达98分内容由助教老师审定难度适中、模块清晰——含用户点餐、菜品管理、订单处理、后台服务等完整业务链路目录结构规范便于理解前后端交互逻辑与小程序工程组织方式。1. 这不是“又一个点餐 demo”而是一套能跑通微信小程序全链路闭环的生产级教学样本你打开过几十个标着“点餐小程序源码”的压缩包解压后发现前端页面能渲染但点击下单就报404后端node服务启动了curl http://localhost:3000/api/foods却返回空数组数据库配置写死在dbfood.json里连mysql或sqlite的影子都找不到——这根本不是项目是幻灯片。而本资源里的system-master目录是一个真实可编译、可调试、可部署的微信小程序点餐系统前端基于原生小程序框架非 uni-app后端用 Express 内存 JSON 模拟数据层含router.js路由分发、foods.js业务逻辑、dbfood.json初始菜品库app.js和pages/下完整实现用户登录态管理、购物车增删改查、订单生成与状态流转。它被用于高校计算机专业毕业设计评审98 分背后是清晰的模块边界、可读的命名规范、符合微信小程序生命周期的组件设计以及关键路径上完整的错误处理比如wx.request失败时的 toast 提示和 loading 状态控制。适合需要交付可运行成果的期末大作业、课程设计也适合作为理解“前后端分离”在小程序场景下如何落地的第一手实操材料。2. 前端结构解析从app.json到pages/的页面路由与状态管理机制微信小程序的前端并非传统 Web SPA其路由由app.json静态声明页面状态依赖Page()构造器与data响应式绑定。本项目app.json中定义了pages数组包含index首页、cart购物车、order订单列表、detail菜品详情等 7 个页面路径同时通过tabBar配置底部导航栏图标与文字style: v2表明使用新版 tabBar 样式。这种静态路由方式决定了页面跳转必须严格匹配pages/目录下的文件结构例如pages/index/index.wxml对应首页模板pages/index/index.js中的Page({})对象则承载该页全部逻辑。2.1 页面数据流setData如何驱动视图更新与购物车同步购物车功能是本项目前端核心交互点其实现不依赖第三方状态库而是通过app.js全局globalData与页面data双向同步完成。app.js中定义App({ globalData: { cartList: [], // 全局购物车数组 userInfo: null } })当用户在pages/index/index.js点击“加入购物车”按钮时触发事件处理器// pages/index/index.js addToCart(e) { const food e.currentTarget.dataset.food; const cartList getApp().globalData.cartList; const existing cartList.find(item item.id food.id); if (existing) { existing.count 1; } else { cartList.push({ ...food, count: 1 }); } // 同步更新当前页 data 中的购物车数量显示 this.setData({ cartCount: cartList.reduce((sum, item) sum item.count, 0) }); // 触发全局事件通知其他页面如 tabBar刷新角标 wx.$emit(cartChange, cartList.length); }注意wx.$emit并非微信原生 API而是项目中自行封装的事件总线位于utils/event.js用于解耦页面间通信。若直接修改getApp().globalData.cartList而不调用setData当前页面视图不会自动更新这是小程序数据响应式机制的关键约束。2.2 组件化实践icons/目录与自定义icon组件的复用逻辑项目未使用iconfont或weui而是在utils/icons/目录下存放 SVG 资源并通过components/icon/icon.js封装为可复用组件。icon组件接受type属性如cart、home动态加载对应 SVG 路径// components/icon/icon.js Component({ properties: { type: { type: String, value: default } }, data: { svgPath: }, lifetimes: { attached() { // 根据 type 映射到实际 SVG 文件路径 const iconMap { cart: /utils/icons/cart.svg, home: /utils/icons/home.svg, user: /utils/icons/user.svg }; this.setData({ svgPath: iconMap[this.data.type] || iconMap[default] }); } } });在pages/index/index.wxml中调用icon typecart classnav-icon/icon这种做法避免了在每个页面重复引入 SVG且lifetimes.attached确保组件挂载时才解析路径减少首屏渲染阻塞。对比直接内联 SVG 或使用字体图标此方案更利于按需加载与主题切换后续可扩展color属性控制 SVG fill。2.3 网络请求封装utils/request.js中的统一拦截与错误处理所有 API 请求均通过utils/request.js发起而非直接调用wx.request。该文件实现了请求拦截、响应解析与错误统一处理// utils/request.js function request(options) { return new Promise((resolve, reject) { wx.showLoading({ title: 加载中... }); wx.request({ url: http://localhost:3000${options.url}, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { wx.hideLoading(); if (res.statusCode 200) { resolve(res.data); } else if (res.statusCode 401) { wx.showToast({ title: 登录已过期, icon: none }); wx.navigateTo({ url: /pages/login/login }); } else { reject(new Error(HTTP ${res.statusCode}: ${res.data.message || 请求失败})); } }, fail: (err) { wx.hideLoading(); reject(new Error(网络错误: ${err.errMsg})); } }); }); } module.exports { request };关键参数说明url字段拼接本地后端地址http://localhost:3000实际部署时需替换为线上域名header.Authorization从wx.getStorageSync(token)读取支持登录态透传statusCode401 时自动跳转登录页避免业务代码重复判断fail回调捕获超时、DNS 错误等底层异常统一抛出Error对象供catch处理。在pages/cart/cart.js中调用示例// 获取购物车列表 loadCart() { request({ url: /api/cart, method: GET }) .then(data { this.setData({ cartItems: data.items }); }) .catch(err { console.error(加载购物车失败:, err); wx.showToast({ title: 加载失败, icon: none }); }); }3. 后端服务剖析Express 路由设计与 JSON 数据持久化的轻量级实现本项目后端采用 Node.js Express 框架摒弃 ORM 与关系型数据库以dbfood.json作为数据源通过fs.readFileSync同步读取并内存缓存兼顾开发效率与教学清晰度。server.js是入口文件router.js定义所有 API 路由foods.js封装菜品相关业务逻辑形成典型的三层结构路由层 → 业务层 → 数据层。3.1 路由分发机制router.js中的 RESTful 接口设计router.js使用 Express Router 实例按资源划分路由前缀符合 RESTful 规范// router.js const express require(express); const router express.Router(); const foodsController require(./foods); // 菜品资源 router.get(/api/foods, foodsController.getAllFoods); router.get(/api/foods/:id, foodsController.getFoodById); router.post(/api/foods, foodsController.createFood); // 仅用于管理后台小程序端不调用 // 订单资源 router.post(/api/orders, foodsController.createOrder); router.get(/api/orders, foodsController.getOrdersByUser); // 购物车资源模拟 router.get(/api/cart, foodsController.getCart); router.post(/api/cart, foodsController.addToCart); router.put(/api/cart/:id, foodsController.updateCartItem); router.delete(/api/cart/:id, foodsController.removeCartItem); module.exports router;提示/api/cart等接口虽名为“购物车”但实际数据仍存储于内存中的cartList数组foods.js内部维护未写入文件。这是教学项目权衡——避免引入 Redis 或文件锁复杂度同时明确展示“状态暂存”与“持久化”的区别。3.2 业务逻辑封装foods.js中的订单生成与库存校验逻辑foods.js是核心业务文件createOrder方法体现典型电商流程校验库存、扣减库存、生成订单号、写入订单列表// foods.js const fs require(fs); const db require(./dbfood.json); // 内存加载的菜品数据库 let cartList []; // 模拟用户购物车实际应关联用户ID let orderList []; // 模拟订单列表 function createOrder(req, res) { const { items } req.body; // 前端传入的购物车商品数组 if (!items || items.length 0) { return res.status(400).json({ error: 购物车为空 }); } // 1. 库存校验遍历每项检查库存是否充足 const insufficient items.filter(item { const food db.find(f f.id item.id); return food food.stock item.count; }); if (insufficient.length 0) { return res.status(400).json({ error: 部分商品库存不足, insufficient: insufficient.map(i i.id) }); } // 2. 扣减库存更新 dbfood.json 内存副本 items.forEach(item { const food db.find(f f.id item.id); if (food) food.stock - item.count; }); // 3. 生成订单简化版时间戳随机数 const order { id: Date.now().toString(36) Math.random().toString(36).substr(2, 5), userId: req.headers[x-user-id] || demo-user, items: items.map(item ({ ...item, name: db.find(f f.id item.id)?.name || 未知商品 })), totalAmount: items.reduce((sum, item) sum (item.price * item.count), 0), status: created, createdAt: new Date().toISOString() }; orderList.push(order); // 4. 清空当前用户购物车此处简化为清空全局 cartList cartList []; res.status(201).json({ order }); } module.exports { getAllFoods: (req, res) res.json(db), getFoodById: (req, res) { const food db.find(f f.id parseInt(req.params.id)); food ? res.json(food) : res.status(404).json({ error: 菜品不存在 }); }, createOrder, // 其他方法... };关键参数说明req.body.items是前端提交的购物车商品数组含id、count、price字段dbfood.json在require时已加载为内存对象food.stock - item.count直接修改其属性订单号生成使用Date.now().toString(36)36 进制时间戳拼接随机字符串避免碰撞res.status(201)表明资源创建成功符合 HTTP 语义。3.3 数据持久化策略dbfood.json结构与初始化脚本dbfood.json是项目数据基石其结构直接影响前端渲染与后端查询效率[ { id: 1, name: 宫保鸡丁, price: 32, stock: 50, category: 热菜, image: /images/food1.jpg, description: 经典川菜酸甜微辣 }, { id: 2, name: 凉拌黄瓜, price: 12, stock: 100, category: 凉菜, image: /images/food2.jpg, description: 清爽开胃 } ]注意image字段值为相对路径/images/food1.jpg对应小程序project.config.json中packOptions.ignore未排除的图片资源。若部署到服务器需确保public/images/目录存在且可访问。项目未提供数据库迁移脚本但可通过以下命令快速重置数据# 备份原数据 cp dbfood.json dbfood.json.bak # 用空数组覆盖模拟清空 echo [] dbfood.json # 重启服务后前端首次请求将触发空数据展示4. 前后端联调实战启动服务、配置代理、验证接口连通性拿到源码后90% 的失败源于环境配置不一致。本节提供可复现的联调步骤覆盖端口冲突、HTTPS 限制、跨域问题三大高频障碍。4.1 启动后端服务npm start与端口占用排查项目根目录下执行npm install npm start默认启动server.js监听http://localhost:3000。若报错Error: listen EADDRINUSE :::3000说明端口被占用。排查并释放端口# macOS/Linux 查看占用 3000 端口的进程 lsof -i :3000 # 杀死进程PID 替换为实际数值 kill -9 PID # Windows 查看并结束 netstat -ano | findstr :3000 taskkill /PID PID /F提示package.json中scripts.start为node server.js若需修改端口在server.js中调整app.listen(3000)的端口号并同步更新前端utils/request.js中的url基础地址。4.2 微信开发者工具配置project.config.json与合法域名设置微信小程序要求所有wx.request必须指向已配置的“合法域名”。本地开发时需在微信公众平台后台mp.weixin.qq.com进入「开发管理」→「开发设置」→「服务器域名」将http://localhost:3000添加到request合法域名列表注意必须是 HTTPS 域名localhost 不被允许。因此实际联调需启用开发者工具的“不校验合法域名”选项打开微信开发者工具 → 顶部菜单「详情」→「本地设置」勾选「不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书」重启工具使设置生效。同时确认project.config.json中setting字段包含{ setting: { urlCheck: false, // 关键禁用 URL 校验 es6: true, enhance: true, postcss: true, minified: false, newFeature: true } }4.3 接口连通性验证curl与wx.request返回结果比对启动后端后在终端执行curl验证 API 可达性# 获取所有菜品 curl http://localhost:3000/api/foods | jq .[0].name # 输出宫保鸡丁 # 创建测试订单模拟前端请求体 curl -X POST http://localhost:3000/api/orders \ -H Content-Type: application/json \ -d {items:[{id:1,count:2,price:32}]}若curl返回正常但在小程序中wx.request报fail检查开发者工具是否启用「不校验合法域名」utils/request.js中url是否拼写错误如多写/api后端router.js是否正确挂载app.use(/api, router)控制台 Network 面板查看请求 URL、Headers、Response确认statusCode为200。5. 毕业设计优化技巧添加支付模拟、订单状态机与性能监控埋点评审高分98 分不仅来自功能完整更在于细节深度。本节提供三个可立即落地的优化点无需重构仅需少量代码注入即可显著提升项目专业度。5.1 支付流程模拟在pages/order/order.js中注入微信支付 SDK 伪调用微信小程序支付需企业资质教学项目可用wx.requestPayment的模拟回调替代真实支付。在订单确认页添加按钮!-- pages/order/order.wxml -- button bindtapmockPay disabled{{paying}} stylebackground:#07c160;{{paying ? 支付中... : 立即支付}}/button// pages/order/order.js mockPay() { this.setData({ paying: true }); // 模拟支付耗时2秒 setTimeout(() { // 伪支付成功调用后端更新订单状态 wx.request({ url: http://localhost:3000/api/orders/confirm, method: POST, data: { orderId: this.data.order.id }, success: (res) { if (res.data.success) { wx.showToast({ title: 支付成功, icon: success }); // 跳转至订单完成页 wx.navigateTo({ url: /pages/success/success?orderId this.data.order.id }); } }, fail: () { wx.showToast({ title: 支付失败, icon: none }); }, complete: () { this.setData({ paying: false }); } }); }, 2000); }注意/api/orders/confirm需在router.js中新增路由foods.js中实现将订单状态从created更新为paid并记录paidAt时间戳。5.2 订单状态机用status字段驱动 UI 样式与操作按钮dbfood.json中订单对象增加status字段前端根据值渲染不同状态status中文描述可操作按钮created待支付立即支付paid已支付查看物流模拟delivered已送达评价跳转评价页在pages/order/order.js的onLoad中onLoad(options) { const orderId options.orderId; // 根据 status 动态设置按钮文本与禁用状态 const statusMap { created: { btnText: 立即支付, disabled: false }, paid: { btnText: 查看物流, disabled: false }, delivered: { btnText: 去评价, disabled: false } }; const order getOrderById(orderId); // 从全局或 API 获取 this.setData({ order, actionBtn: statusMap[order.status] || statusMap[created] }); }5.3 性能监控埋点在app.jsonLaunch中记录首屏加载时间小程序启动性能是评审隐性指标。在app.js中添加时间戳记录// app.js App({ onLaunch() { // 记录小程序启动时间 this.globalData.launchTime Date.now(); // 监听页面显示事件计算首屏时间 wx.onAppShow(() { const firstScreenTime Date.now() - this.globalData.launchTime; console.log(【性能监控】首屏加载耗时: ${firstScreenTime}ms); // 可上报至自建日志服务或打印到控制台 if (firstScreenTime 3000) { wx.showToast({ title: 加载稍慢, icon: none, duration: 2000 }); } }); } });此埋点无需额外依赖直接利用微信原生onAppShow生命周期精准反映用户感知的启动速度。评审老师常会关注控制台输出此类细节恰是区分“能跑”与“专业”的分水岭。本文还有配套的精品资源点击获取
返回列表