前言
周饼伦在街头摊煎饼,摊后人群熙熙攘攘。他忙得不可开交,既要记住面前小哥要加的培根,又要记住身后奶奶要加的生菜,这时又来了个小妹妹,点的煎饼既要培根又要腊肠。他把鸡蛋打进煎饼后,竟突然忘了前面光头大叔要加的料,是火腿还是鸡柳?一时记不清,好像是火腿,对。然而当把煎饼交给大叔,大叔却怒了,说要的是鸡柳。😡
这可咋办?周饼伦赶忙道歉,大叔却语重心长地说:“试试用小程序云开发吧!最近的数据模型新功能好用得很!” 周饼伦亮出祖传手艺,边摊煎饼边开发小程序,把新开发的小程序点餐页面二维码贴在摊前。从此再没出过错,终于能安心摊煎饼啦!
设计思路
客户扫摊子上面贴的二维码后,会进入点餐页面,在选好要加的配料之后,点击确定就可以点餐,随后,即可在云后台上看到食客提交的数据
实现过程
周饼伦就把当前摊位的主食、配菜,以及各自相应的价格贴在了摊位上,也要把食客的点餐内容记在脑里或者用笔写在纸上。
点餐页要实现两个功能:1.展示当前摊位有的主食、配菜、口味 2.提交订单到周饼伦的订单页面。
煎饼摊子主食(staple food)目前只有摊饼、青菜饼,主食下面有的配菜(side dish),有鸡柳、生菜、鸡蛋、火腿、腊肠。
同理,数据库里面也需要呈现相应的结构。
数据表的实现
数据模型现在提供了一种便捷的能力来,可以快速创建一套可用的数据表来记录摊煎饼的相关数据。
在云后台中新增了一个基于 MySQL 的数据模型,数据模型相当于一张纸,可以在上面记录任何想要记录的数据,比如周饼伦摊位的提供的菜品
创建了基于云开发MySQL数据库的主食表,主食表中包含主食名称,主食价格
字段的详细设置如下
加了主食、配菜两个表之后,将当前的主食和配菜一起加进数据表中
现在就实现了记录当前摊子的主食和配菜。还需要一个订单表,来记录用户的点餐数据
配菜的类型是一个数组文本,用来记录配菜的类型,结构如下
接着需要分别设置每个数据模型的权限。在使用小程序查看订单时,也是以用户的身份来读取的,所以,需要配置用户权限,通过页面访问来控制用户能够访问到哪些页面
至此,数据表就已经大功告成!现在完全可以使用三个表来记录当前摊子的菜品、营业情况。
但是,别忘了周饼伦的目的不止于此,为了周饼伦实现早日暴富,当上CEO,所以,还要利用小程序实现一个界面,来给”上帝“们点餐,并且提供各位CEO查看订单
小程序实现过程
一. 初始化 SDK
在云后台的数据管理中的右侧中,可以方便的查询到使用的文档
新建一个基于云开发的小程序,删除不必要的页面,并且按照文档的步骤进行初始化👇
1.按照指引在 miniprogram 目录下初始化 npm 环境并安装 npm 包
请注意,这里需要在 miniprogram 目录下初始化 npm ,不然需要编辑 project.config.json 手动指定 npm 包的位置
在 miniprogram 目录下打开终端
2.初始化当前 npm 并且安装 @cloudbase/wx-cloud-client-sdk npm 包
npm init -y & npm install @cloudbase/wx-cloud-client-sdk --save
3.在小程序中构建 npm
4.在小程序 app.js 中初始化环境
// app.js
App({globalData: {// 在这里提供全局变量 models 数据模型方法,方便给页面使用models: null},onLaunch: async function () {const {init} = require('@cloudbase/wx-cloud-client-sdk')// 指定云开发环境 IDwx.cloud.init({env: "ju-9g1guvph88886b02",});const client = init(wx.cloud);const models = client.models;// 可以取消注释查看效果// const { data } = await models.stapleFood.list({// filter: {// where: {}// },// pageSize: 10,// pageNumber: 1,// getCount: true,// });// console.log('当前的主食数据:');// console.log(data.records);}
});
二. 下单页面的实现
首先创建一个页面 goods-list
页面作为首页
顾客如果浏览下单页面,那么就需要看到当前可以选择的主食、配菜,还有他们分别的价格。所以首先我们需要把主食、配菜加载进来
// 加载主食
const stapleFood = (await models.stapleFood.list({filter: {where: {}},pageSize: 100, // 一次性加载完,pageNumber: 1,getCount: true,
})).data.records;// 加载配菜
const sideDish = (await models.sideDish.list({filter: {where: {}},pageSize: 100, // 一次性加载完,pageNumber: 1,getCount: true,
})).data.records;
// pages/goods-list/index.js
Page({data: {// 总价格totalPrize: 0,// 选中的主食selectedStapleFoodName: '',// 选中的配菜selectedSideDishName: [],// 所有的主食stapleFood: [],// 所有的配菜sideDish: [],
以下是全部的js代码
// pages/goods-list/index.js
Page({data: {// 总价格totalPrize: 0,// 选中的主食selectedStapleFoodName: '',// 选中的配菜selectedSideDishName: [],// 所有的主食stapleFood: [],// 所有的配菜sideDish: [],},async onLoad(options) {const models = getApp().globalData.models;console.log('models', models)// 加载主食const stapleFood = (await models.stapleFood.list({filter: {where: {}},pageSize: 100, // 一次性加载完,pageNumber: 1,getCount: true,})).data.records;// 加载配菜const sideDish = (await models.sideDish.list({filter: {where: {}},pageSize: 100, // 一次性加载完,pageNumber: 1,getCount: true,})).data.records;console.log({stapleFood,sideDish});this.setData({stapleFood: stapleFood,sideDish: sideDish})},// 选中主食onSelectStapleFood(event) {this.setData({selectedStapleFoodName: event.currentTarget.dataset.data.name});this.computeTotalPrize();},// 选中配菜onSelectedSideDish(event) {console.log(event);// 选中配菜名字const sideDishName = event.currentTarget.dataset.data.name;// 如果已经选中,则取消选中if (this.data.selectedSideDishName.includes(sideDishName)) {this.setData({selectedSideDishName: this.data.selectedSideDishName.filter((name) => (name !== sideDishName))});} else {// 未选中,则选中this.setData({selectedSideDishName: this.data.selectedSideDishName.concat(sideDishName)});}this.computeTotalPrize();},// 重新计算价格computeTotalPrize() {// 主食价格let staplePrize = 0;if (this.data.selectedStapleFoodName) {staplePrize = this.data.stapleFood.find((staple) => staple.name === this.data.selectedStapleFoodName).prize;}// 配菜价格let sideDish = 0;this.data.selectedSideDishName.forEach((sideDishName) => {sideDish += this.data.sideDish.find((sideDishItem) => (sideDishItem.name === sideDishName)).prize;});// 总价格this.setData({totalPrize: staplePrize + sideDish})},// 提交async onSubmit() {// 提示正在加载中wx.showLoading({title: '正在提交订单',});const models = getApp().globalData.models;const { data } = await models.order.create({data: {served: false, // 是否已出餐sideDish: this.data.selectedSideDishName, // 配菜stapleFoodName: this.data.selectedStapleFoodName, // 主食名称prize: this.data.totalPrize, // 订单总价格}});console.log(data);wx.hideLoading();}
});
接着来实现页面
<!--pages/goods-list/index.wxml-->
<view><view class="title"><image src='/asset/pancake.png'></image><text class="title">请选择主食</text></view><!-- 主食展示 --><view class="staple-food"><view wx:for="{{stapleFood}}" wx:key="_id"><view bindtap="onSelectStapleFood" data-data="{{item}}" class="staple-food-item {{selectedStapleFoodName === item.name ? 'selected' : ''}}"><image src="{{item.imageUrl}}"></image><view class="prize">{{item.prize}}¥</view></view></view></view><!-- 选择配菜 --><view class="title"><image src='/asset/sideDish.png'></image>请选择配菜</view><!-- 配菜展示 --><view class="side-dish"><view wx:for="{{sideDish}}" wx:key="_id"><!-- 使得class动态绑定支持 includes 语法 --><wxs module="tool">var includes = function (array, text) {return array.indexOf(text) !== -1}module.exports.includes = includes;</wxs><view class="side-dish-item {{tool.includes(selectedSideDishName, item.name) ? 'selected' : ''}}" bindtap="onSelectedSideDish" data-data="{{item}}"><image src="{{item.imageUrl}}"></image><view class="prize">{{item.prize}}¥</view></view></view></view><!-- 底部菜单 --><view class="bottom-content"><view class='bottom-info'><view wx:if="{{!!selectedStapleFoodName}}">主食:{{selectedStapleFoodName}}</view><view wx:if="{{selectedSideDishName.length !== 0}}">配菜:{{selectedSideDishName}}</view></view><view class="bottom-operate"><view class="total-prize">当前价格<text class="prize">{{totalPrize}}¥</text></view><view class="submit-button {{!selectedStapleFoodName ? 'disabled' : ''}}" bind:tap="onSubmit">下单</view></view></view>
</view>
再添加一点点的样式
/* pages/goods-list/index.wxss */
.title {display: flex;align-items: center;gap: 16rpx;padding: 0 20rpx;
}.title image {height: 46rpx;width: 46rpx;
}.staple-food {display: flex;margin-bottom: 60rpx;overflow: auto;
}.staple-food-item {margin: 20rpx 10rpx;display: flex;flex-direction: column;border: 1px solid #f3f0ee;box-shadow: 6rpx 6rpx 6rpx #dfdfdf, -6rpx -6rpx 6rpx #dfdfdf;border-radius: 6rpx;padding: 8rpx;
}.staple-food-item.selected, .side-dish-item.selected {box-shadow: 6rpx 6rpx 6rpx #58b566, -6rpx -6rpx 6rpx #58b566, 6rpx -6rpx 6rpx #58b566, -6rpx 6rpx 6rpx #58b566;
}.staple-food-item image {border-radius: 6rpx;width: 300rpx;height: 300rpx;
}.prize {padding: 6rpx 6rpx 0;text-align: right;color: orangered
}.side-dish {padding: 20rpx 12rpx;display: flex;gap: 12rpx;overflow: auto;
}.side-dish image {height: 200rpx;width: 200rpx;
}.side-dish-item {border-radius: 8px;padding: 16rpx;box-shadow: 6rpx 6rpx 6rpx #dfdfdf, -6rpx -6rpx 6rpx #dfdfdf;
}.bottom-content {position: absolute;bottom: 0;left: 0;right: 0;
}.bottom-info {padding: 30rpx;display: flex;flex-direction: column;color: grey;font-size: 0.5em;
}.bottom-content .total-prize {padding: 0 30rpx;
}.bottom-operate {border-top: 1px solid #dfdfdf;display: flex;width: 100%;justify-content: space-between;align-items: center;height: 100rpx;
}.submit-button {width: 350rpx;color: white;background: #22b85c;height: 100%;display: flex;align-items: center;justify-content: center;
}.submit-button.disabled {background: grey;/* 注意,这里设置了当按钮置灰的时候,不可点击 */pointer-events: none;
}
于是,煎饼摊的小程序就大功告成了!
接着就可以在云后台管理订单了,在将订单完成之后,即可在云后台将订单的状态修改成已完成。
我们还可以做的更多…
是否可以在订单中新增一个点餐号,这样就知道是哪个顾客点的餐?是否可以使用数据模型的关联关系将配菜、主食和订单关联起来?
是否可以在小程序中创建一个管理订单的页面?是否可以添加优惠券数据表,来给客户一些限时优惠?
期待大家的体验反馈!
代码地址:https://github.com/vancece/qiLiXiang/tree/main
点击体验:https://tcb.cloud.tencent.com/cloud-admin?_tcbProviderId=mp