首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >餐饮数字化转型必备:微信扫码点餐&外卖小程序源码(支持多店铺数据统计)

餐饮数字化转型必备:微信扫码点餐&外卖小程序源码(支持多店铺数据统计)

原创
作者头像
用户11831480
发布2025-09-11 11:28:44
发布2025-09-11 11:28:44
32500
代码可运行
举报
运行总次数:0
代码可运行

在数字化浪潮席卷各行各业的当下,餐饮行业的数字化转型已不再是 “选择题”,而是关乎生存与发展的 “必答题”。其中,微信扫码点餐与外卖小程序凭借其低门槛、高触达、强粘性的优势,成为餐饮商家数字化转型的核心工具。本文将深入解析这一系统的核心价值,并附上部分关键源代码,助力餐饮从业者快速搭建适配多店铺运营的数字化解决方案。

微信扫码点餐 & 外卖小程序核心功能设计

1. 前端用户端(微信小程序)

  • 源码及演示:s.ymzan.top
  • 扫码点餐:用户扫描桌码后自动进入对应店铺页面,支持菜品分类浏览、规格选择(如微辣 / 中辣)、购物车编辑、订单提交;
  • 外卖功能:支持定位附近店铺、查看店铺评分与评价、在线下单、选择配送 / 自提、多种支付方式(微信支付集成);
  • 个人中心:订单查询(待支付 / 待发货 / 待收货 / 已完成)、优惠券领取与使用、地址管理、历史评价。

2. 商家管理端(Web / 小程序)

  • 店铺管理:支持单商家多门店创建,设置门店地址、营业时间、配送范围与费用;
  • 菜品管理:菜品上下架、库存修改、价格调整、分类编辑(如热菜 / 凉菜 / 饮品);
  • 订单管理:实时接收堂食 / 外卖订单,支持订单状态修改(已接单 / 制作中 / 已完成 / 已取消)、订单详情导出;
  • 营销工具:优惠券生成(满减券 / 折扣券)、会员积分规则设置、节日活动推送。

3. 多店铺数据统计端(Web)

  • 核心数据看板:展示所有店铺的总订单量、总营业额、平均客单价、用户新增数,支持按日 / 周 / 月筛选;
  • 店铺对比分析:按营业额、订单量、复购率对各店铺排序,直观呈现门店经营差距;
  • 用户画像分析:统计用户性别、年龄、消费频次、偏好菜品,为营销活动提供数据支撑;
  • 库存预警提醒:当某店铺菜品库存低于阈值时,自动发送提醒至商家,避免缺货问题。

##系统技术架构与优势

1. 技术栈选择

  • 前端:微信小程序原生框架(WXML/WXSS/JS)+ Vant Weapp 组件库(提升 UI 美观度与开发效率);
  • 后端:Node.js(Express 框架)/ Java(Spring Boot 框架),支持高并发订单处理;
  • 数据库:MySQL(存储订单、用户、菜品等结构化数据)+ Redis(缓存高频访问数据,如菜品库存、用户会话);
  • 第三方集成:微信支付 SDK(实现支付功能)、腾讯地图 API(实现定位与配送范围计算)。

2. 核心优势

  • 多店铺适配:一套系统支持多个独立店铺运营,总部可统一管理与数据监控;
  • 轻量化部署:基于微信生态,用户无需下载 APP,扫码即可使用,降低用户使用门槛;
  • 数据安全可靠:用户支付数据通过微信支付加密传输,订单与用户数据定期备份,避免数据丢失;
  • 低成本迭代:前后端分离架构,支持功能模块单独升级,无需整体重构系统。

关键功能源代码示例

以下为系统核心模块的部分源代码(以微信小程序前端 + Node.js 后端为例),助力开发者快速理解实现逻辑。

1. 微信小程序端:扫码点餐页面(菜品列表渲染)

代码语言:html
复制
\<!-- pages/scanOrder/scanOrder.wxml -->

\<view class="container">

&#x20; \<!-- 菜品分类导航 -->

&#x20; \<scroll-view class="category-scroll" scroll-x>

&#x20;   \<view&#x20;

&#x20;     wx:for="{{categoryList}}"&#x20;

&#x20;     wx:key="categoryId"

&#x20;     class="category-item {{currentCategory === item.categoryId ? 'active' : ''}}"

&#x20;     bindtap="switchCategory"

&#x20;     data-categoryid="{{item.categoryId}}"

&#x20;   \>

&#x20;     {{item.categoryName}}

&#x20;   \</view>

&#x20; \</scroll-view>

&#x20; \<!-- 菜品列表 -->

&#x20; \<view class="dish-list">

&#x20;   \<view wx:for="{{dishList}}" wx:key="dishId" class="dish-item">

&#x20;     \<image src="{{dish.imageUrl}}" mode="widthFix" class="dish-img">\</image>

&#x20;     \<view class="dish-info">

&#x20;       \<view class="dish-name">{{dish.dishName}}\</view>

&#x20;       \<view class="dish-price">¥{{dish.price.toFixed(2)}}\</view>

&#x20;       \<view class="dish-desc">{{dish.description}}\</view>

&#x20;     \</view>

&#x20;     \<view class="dish-action">

&#x20;       \<button&#x20;

&#x20;         class="add-btn"&#x20;

&#x20;         bindtap="addCart"&#x20;

&#x20;         data-dishid="{{dish.dishId}}"

&#x20;         disabled="{{dish.stock <= 0}}"

&#x20;       \>

&#x20;         {{dish.stock <= 0 ? '已售罄' : '加入购物车'}}

&#x20;       \</button>

&#x20;     \</view>

&#x20;   \</view>

&#x20; \</view>

&#x20; \<!-- 购物车悬浮按钮 -->

&#x20; \<view class="cart-btn" bindtap="goToCart">

&#x20;   \<image src="/images/cart.png" mode="widthFix" class="cart-icon">\</image>

&#x20;   \<view class="cart-count" wx:if="{{cartCount > 0}}">{{cartCount}}\</view>

&#x20; \</view>

\</view>
代码语言:javascript
代码运行次数:0
运行
复制
// pages/scanOrder/scanOrder.js

Page({

&#x20; data: {

&#x20;   categoryList: \[], // 菜品分类列表

&#x20;   dishList: \[], // 当前分类下的菜品列表

&#x20;   currentCategory: 1, // 默认选中第一个分类

&#x20;   cartCount: 0, // 购物车商品数量

&#x20;   shopId: '' // 当前店铺ID(通过扫码参数获取)

&#x20; },

&#x20; onLoad(options) {

&#x20;   // 从扫码参数中获取店铺ID

&#x20;   this.setData({ shopId: options.shopId });

&#x20;   // 加载菜品分类与默认分类菜品

&#x20;   this.getCategoryList();

&#x20;   this.getDishListByCategory(this.data.currentCategory);

&#x20;   // 加载购物车数量

&#x20;   this.getCartCount();

&#x20; },

&#x20; // 获取菜品分类列表

&#x20; getCategoryList() {

&#x20;   const { shopId } = this.data;

&#x20;   wx.request({

&#x20;     url: 'https://your-domain.com/api/category/list',

&#x20;     data: { shopId },

&#x20;     success: (res) => {

&#x20;       if (res.data.code === 200) {

&#x20;         this.setData({ categoryList: res.data.data });

&#x20;       }

&#x20;     }

&#x20;   });

&#x20; },

&#x20; // 切换菜品分类

&#x20; switchCategory(e) {

&#x20;   const categoryId = e.currentTarget.dataset.categoryid;

&#x20;   this.setData({ currentCategory: categoryId });

&#x20;   this.getDishListByCategory(categoryId);

&#x20; },

&#x20; // 根据分类ID获取菜品列表

&#x20; getDishListByCategory(categoryId) {

&#x20;   const { shopId } = this.data;

&#x20;   wx.request({

&#x20;     url: 'https://your-domain.com/api/dish/list',

&#x20;     data: { shopId, categoryId },

&#x20;     success: (res) => {

&#x20;       if (res.data.code === 200) {

&#x20;         this.setData({ dishList: res.data.data });

&#x20;       }

&#x20;     }

&#x20;   });

&#x20; },

&#x20; // 加入购物车

&#x20; addCart(e) {

&#x20;   const dishId = e.currentTarget.dataset.dishid;

&#x20;   const userId = wx.getStorageSync('userId'); // 从本地缓存获取用户ID

&#x20;   wx.request({

&#x20;     url: 'https://your-domain.com/api/cart/add',

&#x20;     method: 'POST',

&#x20;     data: { userId, dishId, shopId: this.data.shopId, quantity: 1 },

&#x20;     success: (res) => {

&#x20;       if (res.data.code === 200) {

&#x20;         wx.showToast({ title: '加入购物车成功' });

&#x20;         // 更新购物车数量

&#x20;         this.getCartCount();

&#x20;       }

&#x20;     }

&#x20;   });

&#x20; },

&#x20; // 获取购物车数量

&#x20; getCartCount() {

&#x20;   const userId = wx.getStorageSync('userId');

&#x20;   wx.request({

&#x20;     url: 'https://your-domain.com/api/cart/count',

&#x20;     data: { userId, shopId: this.data.shopId },

&#x20;     success: (res) => {

&#x20;       if (res.data.code === 200) {

&#x20;         this.setData({ cartCount: res.data.data });

&#x20;       }

&#x20;     }

&#x20;   });

&#x20; },

&#x20; // 前往购物车页面

&#x20; goToCart() {

&#x20;   wx.navigateTo({ url: \`/pages/cart/cart?shopId=\${this.data.shopId}\` });

&#x20; }

});

2. Node.js 后端:多店铺数据统计接口(总营业额查询)

代码语言:javascript
代码运行次数:0
运行
复制
// router/statistic.js

const express = require('express');

const router = express.Router();

const statisticService = require('../service/statisticService');

const { checkAdminToken } = require('../middleware/auth'); // 管理员权限校验

// 多店铺总营业额查询(需管理员权限)

router.get('/totalRevenue', checkAdminToken, async (req, res) => {

&#x20; try {

&#x20;   const { startTime, endTime } = req.query;

&#x20;   // 校验时间参数

&#x20;   if (!startTime || !endTime) {

&#x20;     return res.json({ code: 400, message: '请传入开始时间与结束时间' });

&#x20;   }

&#x20;   // 调用服务层获取数据

&#x20;   const result = await statisticService.getTotalRevenue(startTime, endTime);

&#x20;   res.json({ code: 200, message: '查询成功', data: result });

&#x20; } catch (error) {

&#x20;   console.error('统计接口异常:', error);

&#x20;   res.json({ code: 500, message: '服务器内部错误' });

&#x20; }

});

// 多店铺订单量对比查询

router.get('/shopOrderCompare', checkAdminToken, async (req, res) => {

&#x20; try {

&#x20;   const { dateType } = req.query; // dateType: day/week/month

&#x20;   const result = await statisticService.getShopOrderCompare(dateType);

&#x20;   res.json({ code: 200, message: '查询成功', data: result });

&#x20; } catch (error) {

&#x20;   console.error('店铺订单对比接口异常:', error);

&#x20;   res.json({ code: 500, message: '服务器内部错误' });

&#x20; }

});

module.exports = router;
代码语言:javascript
代码运行次数:0
运行
复制
// service/statisticService.js

const db = require('../config/db'); // 数据库连接配置

/\*\*

&#x20;\* 获取多店铺总营业额

&#x20;\* @param {string} startTime - 开始时间(YYYY-MM-DD)

&#x20;\* @param {string} endTime - 结束时间(YYYY-MM-DD)

&#x20;\* @returns {Promise\<Array>} 各店铺营业额数据

&#x20;\*/

exports.getTotalRevenue = async (startTime, endTime) => {

&#x20; const sql = \`

&#x20;   SELECT&#x20;

&#x20;     s.shop\_id,&#x20;

&#x20;     s.shop\_name,&#x20;

&#x20;     IFNULL(SUM(o.order\_amount), 0) AS total\_revenue,

&#x20;     COUNT(o.order\_id) AS total\_order

&#x20;   FROM&#x20;

&#x20;     shop s

&#x20;   LEFT JOIN&#x20;

&#x20;     \\\`order\\\` o ON s.shop\_id = o.shop\_id&#x20;

&#x20;     AND o.order\_time BETWEEN ? AND ?&#x20;

&#x20;     AND o.order\_status = 4 -- 4表示已完成订单

&#x20;   GROUP BY&#x20;

&#x20;     s.shop\_id, s.shop\_name

&#x20;   ORDER BY&#x20;

&#x20;     total\_revenue DESC

&#x20; \`;

&#x20; const \[rows] = await db.query(sql, \[startTime, endTime]);

&#x20; return rows;

};

/\*\*

&#x20;\* 获取多店铺订单量对比

&#x20;\* @param {string} dateType - 时间类型(day/week/month)

&#x20;\* @returns {Promise\<Array>} 各店铺订单量数据

&#x20;\*/

exports.getShopOrderCompare = async (dateType) => {

&#x20; let dateSql = '';

&#x20; // 根据时间类型拼接SQL

&#x20; if (dateType === 'day') {

&#x20;   dateSql = 'DATE(o.order\_time) = CURDATE()';

&#x20; } else if (dateType === 'week') {

&#x20;   dateSql = 'YEARWEEK(DATE(o.order\_time), 1) = YEARWEEK(CURDATE(), 1)';

&#x20; } else if (dateType === 'month') {

&#x20;   dateSql = 'DATE\_FORMAT(o.order\_time, "%Y-%m") = DATE\_FORMAT(CURDATE(), "%Y-%m")';

&#x20; }

&#x20; const sql = \`

&#x20;   SELECT&#x20;

&#x20;     s.shop\_id,&#x20;

&#x20;     s.shop\_name,&#x20;

&#x20;     COUNT(o.order\_id) AS order\_count

&#x20;   FROM&#x20;

&#x20;     shop s

&#x20;   LEFT JOIN&#x20;

&#x20;     \\\`order\\\` o ON s.shop\_id = o.shop\_id&#x20;

&#x20;     AND \${dateSql}&#x20;

&#x20;     AND o.order\_status = 4

&#x20;   GROUP BY&#x20;

&#x20;     s.shop\_id, s.shop\_name

&#x20;   ORDER BY&#x20;

&#x20;     order\_count DESC

&#x20; \`;

&#x20; const \[rows] = await db.query(sql);

&#x20; return rows;

};

总结

微信扫码点餐 & 外卖小程序是餐饮行业数字化转型的 “基础设施”,它不仅能解决传统餐饮的效率痛点,还能通过数据驱动帮助商家实现精细化运营。本文提供的功能设计与源代码示例,可作为商家自主开发或选择第三方系统的参考依据。在数字化转型的道路上,餐饮商家需结合自身规模与需求,选择合适的系统方案,并持续迭代优化,才能在激烈的市场竞争中占据优势。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 微信扫码点餐 & 外卖小程序核心功能设计
    • 1. 前端用户端(微信小程序)
    • 2. 商家管理端(Web / 小程序)
    • 3. 多店铺数据统计端(Web)
    • 1. 技术栈选择
    • 2. 核心优势
  • 关键功能源代码示例
    • 1. 微信小程序端:扫码点餐页面(菜品列表渲染)
    • 2. Node.js 后端:多店铺数据统计接口(总营业额查询)
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档