微信小程序06-综合项目点餐系统

server/2024/9/25 2:51:43/

零、文章目录

小程序>微信小程序06-综合项目点餐系统

1、项目开发准备

(1)开发背景
  • 现如今,相比以服务员为中介完成点餐、送餐、买单的传统点餐方式,越来越多的餐厅开始使用小程序>微信小程序进行点餐。商家可以在小程序>微信小程序中添加点餐和收款功能,顾客可以实现点餐、付款等功能,顾客可以提前进行点餐,商家提前进行备餐,减少等候时间,提升用户体验。
  • 小程序>微信小程序中有“转发给朋友”“分享到朋友圈”等功能,商家可以通过提供优惠券的方式刺激顾客进行转发、分享。而顾客由于可以享受到优惠,所以很愿意进行转发,当顾客进行转发、分享后,往往会为商家带来大量流量。
(2)项目模块划分
  • **用户登录:**当“点餐”小程序>微信小程序启动后,会自动进行用户登录。
  • **商家首页:**包括轮播图区域、中间区域和底部区域,为用户提供了直观的界面需求。在商家首页点击“开始点餐之旅”,跳转到菜单列表页进行点餐。
  • **菜单列表页:**左侧为菜单栏区域,右侧为商品列表区域,点击左侧菜单栏可以定位到右侧相应位置。每个商品列表项包括图片、价格、标题、“+”等信息。用户可以在菜单列表页中根据自己的需求选择商品,点击“+”将所选商品加入购物车。
  • **购物车:**当购物车中商品数量为0时,点击购物车图标不会展开购物车;当购物车中商品数量不为0时,点击底部购物车图标,在弹出层中显示已选购的商品,包括商品的图片、价格、名称、数量等信息,此时可以对购物车中已选购的商品进行操作,包括动态添加商品数量、实时计算出商品总价格、清空购物车。选购完商品之后,点击“选好了”按钮,跳转到订单确认页。
  • **订单确认页:**在订单确认页中,可核对选择的商品是否正确,并可以根据自己的需求填写备注信息,若信息无误,点击“去支付”,会跳转到订单详情页。
  • **订单详情页:**包括取餐号、订单信息等。·订单列表页:在订单列表页可以查看订单状态,是否取餐,若已经取餐会标识“已取餐”,若未取餐会标识“未取餐”。点击“查看详情”可以跳转到订单详情页。
  • **消费记录页:**消费记录页显示了历史消费记录信息。
(3)项目初始化
  • ①创建项目。在微信开发者工具中创建一个新的小程序>微信小程序项目,项目名称为“点餐”,模板选择“不使用模板”。
  • ②配置页面。项目创建完成后,在app.json文件中配置页面。
"pages": ["pages/index/index","pages/list/list","pages/order/checkout/checkout","pages/order/detail/detail","pages/order/list/list","pages/record/record" 
  • ③配置导航栏。在app.json文件中配置导航栏样式。
 1  "window": {2    "backgroundTextStyle": "light",3    "navigationBarBackgroundColor": "#FF9C35",4    "navigationBarTitleText": "美食屋",5    "navigationBarTextStyle": "white"6  }, 
  • ④创建其他文件。
    • 创建app.wxss文件,该文件中保存了本项目所用到的公共样式。
    • 创建images文件夹,该文件夹保存了该项目所用的素材。
    • 创建utils/shopcartAnimate.js文件,该文件保存了实现购物车中动画效果的代码。
    • 创建utils/decodeCookie.js文件,该文件保存了用于解析服务器返回的Cookie,将Cookie字符串转换成对象的代码。
  • ⑤配置标签栏。在app.json文件中添加tabBar配置项的属性配置标签栏
"tabBar": {"color": "#8a8a8a","selectedColor": "#FF9C35","borderStyle": "black","list": [{"selectedIconPath": "images/home_s.png","iconPath": "images/home.png","pagePath": "pages/index/index","text": "首页"}, {"selectedIconPath": "images/order_s.png","iconPath": "images/order.png","pagePath": "pages/order/list/list","text": "订单"}, {"selectedIconPath": "images/user_s.png","iconPath": "images/user.png","pagePath": "pages/record/record","text": "我的"}]
}, 
  • 目录结构如下

image-20240916161740080

2、封装网络请求

(1)保存接口地址
  • 在实际项目开发中,很多页面的请求地址URL的前半部分都是相同的,重复书写会导致代码冗余,而且如果请求地址更换了域名,修改也比较麻烦。在本项目中,会将URL的公共部分提取出来,单独放置到配置文件中,从而方便后期修改。
  • 在utils文件夹下新建config.js文件,在utils/config.js文件中编写URL的公共部分,具体代码如下。
module.exports = {baseUrl: 'http://localhost/api'}
(2)封装网络请求函数
  • 由于wx.request()方法是一个异步方法,利用Promise可以简化异步操作。在编写服务器接口地址时,可以自动拼接URL的公共部分,使用时只需要传入请求参数即可。
  • 在utils文件夹下新建fetch.js文件,在fetch.js文件中编写封装网络请求的代码,具体如下。
const config = require('./config.js')
const decodeCookie = require('./decodeCookie.js')
var sess = wx.getStorageSync('PHPSESSID')module.exports = function (path, data, method) {return new Promise((resolve, reject) => {wx.request({url: config.baseUrl + path,method: method,data: data,header: {'Cookie': sess ? 'PHPSESSID=' + sess : ''},      success: res => {if (res.header['Set-Cookie'] !== undefined) {sess = decodeCookie(res.header['Set-Cookie'])['PHPSESSID']wx.setStorageSync('PHPSESSID', sess)}      // 请求成功if (res.statusCode !== 200) {fail('服务器异常', reject)return}if (res.data.code === 0) {fail(res.data.msg, reject)return}      resolve(res.data)},fail: function () {// 请求失败fail('加载数据失败', reject)}})})
}
function fail(title, callback) {wx.hideLoading()wx.showModal({title: title,confirmText: '重试',success: res => {if (res.confirm) {callback()}}})
}
  • 在app.js文件中引入fetch.js文件,方便在整个项目使用,具体代码如下。
App({fetch: require('./utils/fetch.js'),
}) 
  • 在pages/index/index.js文件中发送网络请求,具体代码如下。
// index.js
const app = getApp()
const fetch = app.fetch
Page({data: {swiper: [],ad: '',category: []},onLoad: function () {var callback = () => {wx.showLoading({title: '努力加载中',mask: true})fetch('/food/index').then(data => {wx.hideLoading()this.setData({swiper: data.img_swiper,ad: data.img_ad,category: data.img_category})}, () => {callback()})}if (app.userLoginReady) {callback()} else {app.userLoginReadyCallback = callback}  },start: function () {wx.navigateTo({url: '/pages/list/list',})}  
})

3、用户登录

(1)判断登录状态
  • 小程序>微信小程序启动时,需要通过/user/checkLogin接口判断是否处于登录状态。
  • 在app.js文件中编写onLaunch()函数,实现判断登录状态。
(2)执行登录操作
  • 当用户未登录时,需要调用wx.login()方法执行登录操作。
  • wx.login()方法执行成功后会通过success回调函数的参数返回code,即用户登录凭证。
  • 然后发起网络请求将code发送给服务器接口’/user/login’进行校验,从而让服务器识别用户身份。
(3)记住登录状态
  • 在用户登录成功后,服务器为了记住用户的状态,会返回一个自定义登录态。在本项目中,自定义登录态是通过会话技术实现的,服务器会响应一个名称为PHPSESSID的Cookie给客户端,客户端需要记住服务器返回的Cookie,并在下次请求中发送Cookie,这样可以让服务器能够辨别用户身份。在后续发送请求的时候需要携带Cookie。在Cookie有效期内,如果小程序>微信小程序重新启动了,仍然维持已登录的状态。

  • 在本项目中,小程序>微信小程序应先读取本地缓存中的Cookie,如果读取结果为空字符串说明用户未登录。当用户登录成功后,需要从服务器返回的Set-Cookie响应头中取出名称为PHPSESSID的Cookie,将它保存到本地缓存中,然后在wx.request()方法发起请求时,将Cookie附加到请求头中传递

(4)app.js完整代码
// app.js
App({fetch: require('./utils/fetch.js'),onLaunch: function () {wx.showLoading({title: '登录中',mask: true})this.fetch('/user/checkLogin').then(data => {if (data.isLogin) {// 已登录this.onUserLoginReady()console.log('通过保存的Cookie登录成功') // 新增代码} else {// 未登录this.login({success: () => {			// 登录成功this.onUserLoginReady()},fail: () => {				// 登录失败,重新登录this.onLaunch()}})        }}, () => {this.onLaunch()})},login: function (options) {wx.login({success: res => {this.fetch('/user/login', {js_code: res.code}).then(data => {if (data && data.isLogin) {options.success()} else {wx.hideLoading()wx.showModal({title: '登录失败(请使用真实的AppID,并检查服务器端配置)',confirmText: '重试',success: res => {if (res.confirm) {options.fail()}}})}}, () => {options.fail()})}})  },userLoginReady: false,userLoginReadyCallback: null,onUserLoginReady: function() {wx.hideLoading()if (this.userLoginReadyCallback) {this.userLoginReadyCallback()}this.userLoginReady = true}
})

4、商家首页

(1)实现页面结构
  • 在pages/index/index.wxml文件中实现商家首页页面结构。
<!--index.wxml-->
<swiper class="swiper" indicator-dots="true" autoplay="true" interval="5000" duration="1000"><block wx:for="{{ swiper }}" wx:key="*this"><swiper-item><image src="{{ item }}" /></swiper-item></block>
</swiper>
<!-- 开启点餐之旅 -->
<view class="menu-bar"><view class="menu-block" bindtap="start"><view class="menu-start">开启点餐之旅→</view></view>
</view>
<!-- 最新消息展示 -->
<view class="ad-box"><image src="{{ ad }}" class="ad-image" />
</view>
<view class="bottom-box"><view class="bottom-pic" wx:for="{{ category }}" wx:key="index"><image src="{{ item }}" class="bottom-image" /></view>
</view>
(2)实现页面样式
  • 在pages/index/index.wxss文件中编写页面样式。
/**index.wxss**/
.swiper {height: 350rpx;
}
.swiper image {width: 100%;height: 100%;
}
.menu-bar {display: flex;margin-top: 20rpx;
}
.menu-block {display: flex;justify-content: center;margin: 0 auto;
}
.menu-start {text-align: center;font-size: 38rpx;color: #fff;padding: 16rpx 80rpx;background: #ff9c35;border-radius: 80rpx;
}
.ad-box {margin-top: 20rpx;width: 100%;text-align: center;
}
.ad-image {width: 710rpx;height: 336rpx;
}
.bottom-box {margin: 20rpx 0;width: 100%;box-sizing: border-box;padding: 0 20rpx; display: flex;flex-direction: row;flex-wrap: wrap;justify-content: space-between;
}
.bottom-pic {width: 49%;display: inline-block;
}
.bottom-image {width: 100%;height: 170rpx;
}
(3)实现页面逻辑
  • 在pages/index/index.js文件中实现页面逻辑。
// index.js
const app = getApp()
const fetch = app.fetch
Page({data: {swiper: [],ad: '',category: []},onLoad: function () {var callback = () => {wx.showLoading({title: '努力加载中',mask: true})fetch('/food/index').then(data => {wx.hideLoading()this.setData({swiper: data.img_swiper,ad: data.img_ad,category: data.img_category})}, () => {callback()})}if (app.userLoginReady) {callback()} else {app.userLoginReadyCallback = callback}  },start: function () {wx.navigateTo({url: '/pages/list/list',})}  
})
(4)实现页面效果

image-20240916214516902

5、菜单列表页

(1)实现页面结构
  • 在pages/list/list.wxml文件中实现页面结构
<!--pages/list/list.wxml-->
<view class="discount"><text class="discount-txt"></text>满{{ promotion.k }}元减{{ promotion.v }}元(在线支付专享)
</view>
<view class="content"><!-- 左侧菜单栏区域 --><scroll-view class="category" scroll-y><view wx:for="{{ foodList }}" wx:key="id" class="category-item category-{{ activeIndex == index ? 'selected' : 'unselect' }}" data-index="{{ index }}" bindtap="tapCategory"><view class="category-name">{{ item.name }}</view></view></scroll-view><!-- 右侧商品列表区域 --><scroll-view class="food" scroll-y scroll-into-view="category_{{ tapIndex }}" scroll-with-animation bindscroll="onFoodScroll"><block wx:for="{{ foodList }}" wx:for-item="category" wx:key="id" wx:for-index="category_index"><view class="food-category" id="category_{{ category_index }}">{{ category.name }}</view><view class="food-item" wx:for="{{ category.food }}" wx:for-item="food" wx:key="id"><view class="food-item-pic"><image mode="widthFix" src="{{ food.image_url }}" /></view><view class="food-item-info"><view>{{ food.name }}</view><view class="food-item-price">{{ priceFormat(food.price) }}</view></view><view class="food-item-opt"><i class="iconfont" data-category_index="{{ category_index }}" data-index="{{ index }}" bindtap="addToCart"></i></view></view></block></scroll-view>
</view>
<!-- 购物车界面 -->
<view class="shopcart" wx:if="{{ showCart }}"><view class="shopcart-mask" bindtap="showCartList" wx:if="{{ showCart }}"></view><view class="shopcart-wrap"><view class="shopcart-head"><view class="shopcart-head-title">已选商品</view><view class="shopcart-head-clean" bindtap="cartClear"><i class="iconfont"></i>清空购物车</view></view><view class="shopcart-list"><view class="shopcart-item" wx:for="{{ cartList }}" wx:key="id"><view class="shopcart-item-name">{{ item.name }}</view><view class="shopcart-item-price"><view>{{ priceFormat(item.price * item.number) }}</view></view><view class="shopcart-item-number"><i class="iconfont shopcart-icon-dec" data-id="{{ index }}" bindtap="cartNumberDec"></i><view>{{ item.number }}</view><i class="iconfont shopcart-icon-add" data-id="{{ index }}" bindtap="cartNumberAdd"></i></view></view></view></view>
</view>
<!-- 满减优惠信息 -->
<view class="promotion"><label wx:if="{{ promotion.k - cartPrice > 0 }}">满{{ promotion.k }}立减{{ promotion.v }}元,还差{{ promotion.k - cartPrice }}元</label><label wx:else>已满{{ promotion.k }}元可减{{ promotion.v }}元</label>
</view>
<!-- 小球动画 -->
<view class="operate"><view class="operate-shopcart-ball" hidden="{{ !cartBall.show }}" style="left: {{ cartBall.x }}px; top: {{ cartBall.y }}px;"></view><view class="operate-shopcart" bindtap="showCartList"><i class="iconfont operate-shopcart-icon {{ cartNumber > 0 ? 'operate-shopcart-icon-activity' : '' }}"><span wx:if="{{ cartNumber > 0 }}">{{ cartNumber }}</span></i><view class="operate-shopcart-empty" wx:if="{{ cartNumber === 0 }}">购物车是空的</view><view class="operate-shopcart-price" wx:else><block wx:if="{{ cartPrice >= promotion.k }}"><view>{{ priceFormat(cartPrice - promotion.v )}}</view><text>{{ priceFormat(cartPrice) }}</text></block><view wx:else>{{ priceFormat(cartPrice) }}</view></view></view><view class="operate-submit {{ cartNumber !== 0 ? 'operate-submit-activity' : '' }}" bindtap="order">选好了</view>
</view>
<wxs module="priceFormat">module.exports = function (price) {return '¥ ' + parseFloat(price)}
</wxs>
(2)实现页面样式
  • 在pages/list/list.wxss文件中实现页面样式
/* pages/list/list.wxss */
page {display: flex;flex-direction: column;height: 100%;
}
/* 折扣信息区 */
.discount {width: 100%;height: 70rpx;line-height: 70rpx;background: #fef9e6;font-size: 28rpx;text-align: center;color: #999;
}
.discount-txt {color: #fff;padding: 5rpx 10rpx;background: red;margin-right: 15rpx;
}
.content {flex: 1;display: flex;overflow: hidden;
}
.category {width: 202rpx;height: 100%;background: #fcfcfc;font-size: 28rpx;
}
/* 隐藏滚动条 */
::-webkit-scrollbar {width: 0;height: 0;color: transparent;
}
.category-item {height: 100rpx;line-height: 100rpx;text-align: center;
}
.food-category {font-size: 24rpx;background: #f3f4f6;padding: 10rpx;color: #ff9c35;
}
.food-item {display: flex;margin: 40rpx 20rpx;
}
.food-item-pic {margin-right: 20rpx;width: 94rpx;height: 94rpx;
}
.food-item-pic > image {width: 100%;height: 100%;
}
.food-item-info {flex: 1;font-size: 30rpx;margin-top: 4rpx;
}
.food-item-price {margin-top: 14rpx;color: #f05a86;
}
.food-item-opt {margin-top: 40rpx;
}
.food-item-opt > i:before {font-size: 44rpx;color: #ff9c35;content: "\e728";
}
.category-unselect {color: #6c6c6c;background: #f9f9f9;border-bottom: 1rpx solid #e3e3e3;
}
.category-selected {color: #ff9c35;background: white;border-left: 6rpx solid #ff9c35;
}
.category-selected:last-child {border-bottom: 1rpx solid #e3e3e3;
}
/* 购物车区域 */
.operate {height: 110rpx;display: flex;
}
.operate-shopcart {display: flex;width: 74%;padding: 10rpx;background: #353535;
}
/* “选好了”按钮 */
.operate-submit {width: 26%;font-size: 30rpx;background: #eee;color: #aaa;text-align: center;line-height: 110rpx;
}
.operate-submit-activity {background: #ff9c35;color: #fff;
}
/* 购物车图标 */
.operate-shopcart-icon {font-size: 80rpx;color: #87888e;margin-left: 20rpx;position: relative;
}
.operate-shopcart-icon:before {content: "\e73c";
}
.operate-shopcart-icon-activity {color: #ff9c35;
}
/* 购物车为空 */
.operate-shopcart-empty {color: #a9a9a9;line-height: 88rpx;font-size: 30rpx;margin-left: 20rpx;
}
/* 购物车中的商品购买数量 */
.operate-shopcart-icon > span {padding: 2rpx 14rpx;border-radius: 50%;background: red;color: white;font-size: 28rpx;position: absolute;top: 0px;right: -10rpx; text-align: center;
}
/* 购物车中的商品价格 */
.operate-shopcart-price {display: flex;
}
.operate-shopcart-price > view {font-size: 40rpx;line-height: 88rpx;margin-left: 25rpx;color: #fff;
}
.operate-shopcart-price > text {font-size: 24rpx;line-height: 92rpx;margin-left: 15rpx;color: #aaa;text-decoration: line-through;
}
/* 小球的样式 */
.operate-shopcart-ball {width: 36rpx;height: 36rpx;position: fixed;border-radius: 50%;left: 50%;top: 50%;background: #ff9c35;
}
/* 满减优惠区域 */
.promotion {padding: 7rpx 0 9rpx;background: #ffcd9b;color: #fff7ec;font-size: 28rpx;text-align: center;
}
.shopcart {position: fixed;top: 0;left: 0;bottom: 149rpx;right: 0;font-size: 28rpx;
}
.shopcart-wrap {position: absolute;width: 100%;max-height: 90%;bottom: 0;background: #fff;overflow: scroll;
}
.shopcart-mask {position: absolute;top: 0;right: 0;bottom: 0;left: 0;background: #000;opacity: 0.5;
}
.shopcart-head {position: fixed;width: 100%;background: #f0f0f0;color: #878787;line-height: 100rpx;font-size: 26rpx;overflow: hidden;
}
.shopcart-head-title {float: left;margin-left: 40rpx;
}
.shopcart-head-title:before {background: #ff9c35;width: 8rpx;height: 32rpx;content: "";display: inline-block;margin-right: 10rpx;position: relative;top: 6rpx;
}
.shopcart-head-clean {float: right;margin-right: 20rpx;
}
.shopcart-head-clean > i:before {content: "\e61b";position: relative;top: 2rpx;
}
.shopcart-list {margin-top: 101rpx;
}
.shopcart-item {display: flex;padding: 30rpx 20rpx;line-height: 40rpx;
}
.shopcart-item > view {margin-left: 30rpx;
}
.shopcart-item:not(:last-child) {border-bottom: 1rpx solid #e3e3e3;
}
.shopcart-item-name {flex: 1;
}
.shopcart-item-price {color: #ff9c35;
}
.shopcart-item-number {display: flex;
}
.shopcart-item-number > view {margin: 0 15rpx;
}
.shopcart-icon-dec:before {content: "\e61a";font-size: 44rpx;color: #888;
}
.shopcart-icon-add:before {content: "\e728";font-size: 44rpx;color: #ff9c35;
}
(3)实现页面逻辑
  • 在utils/shopcartAnimate.js中实现购物车动画
module.exports = function (iconClass, page) {var busPos = {}wx.createSelectorQuery().select(iconClass).boundingClientRect(rect => {busPos.x = rect.left + 15busPos.y = rect.top}).exec()return {start: function (e) {var finger = {x: e.touches[0].clientX - 10,y: e.touches[0].clientY - 10}var topPoint = {}if (finger.y < busPos.y) {topPoint.y = finger.y - 150} else {topPoint.y = busPos.y - 150}topPoint.x = Math.abs(finger.x - busPos.x) / 2if (finger.x > busPos.x) {topPoint.x = (finger.x - busPos.x) / 2 + busPos.x} else {topPoint.x = (busPos.x - finger.x) / 2 + finger.x}var linePos = bezier([busPos, topPoint, finger], 30)var bezier_points = linePos.bezier_pointspage.setData({'cartBall.show': true,'cartBall.x': finger.x,'cartBall.y': finger.y})var len = bezier_points.lengthvar index = lenlet i = index - 1var timer = setInterval(function () {i = i - 5if (i < 1) {clearInterval(timer)page.setData({'cartBall.show': false})return}page.setData({'cartBall.show': true,'cartBall.x': bezier_points[i].x,'cartBall.y': bezier_points[i].y})}, 50)}}function bezier(pots, amount) {var potvar linesvar ret = []var pointsfor (var i = 0; i <= amount; ++i) {points = pots.slice(0)lines = []while (pot = points.shift()) {if (points.length) {lines.push(pointLine([pot, points[0]], i / amount))} else if (lines.length > 1) {points = lineslines = []} else {break}}ret.push(lines[0])}function pointLine(points, rate) {var pointA, pointB, pointDistance, xDistance, yDistance, tan, radian, tmpPointDistancevar ret = []pointA = points[0]pointB = points[1]xDistance = pointB.x - pointA.xyDistance = pointB.y - pointA.ypointDistance = Math.pow(Math.pow(xDistance, 2) + Math.pow(yDistance, 2), 1 / 2)tan = yDistance / xDistanceradian = Math.atan(tan)tmpPointDistance = pointDistance * rateret = {x: pointA.x + tmpPointDistance * Math.cos(radian),y: pointA.y + tmpPointDistance * Math.sin(radian)}return ret}return {bezier_points: ret}}
}
  • 在pages/list/list.js文件中实现页面逻辑
// pages/list/list.js
// 引入购物车动画模块
const shopcartAnimate = require('../../utils/shopcartAnimate.js')
const app = getApp()
const fetch = app.fetch
const categoryPosition = [] // 右列表各分类高度数组
Page({data: {foodList: [],promotion: {},activeIndex: 0,tapIndex: 0,cartPrice: 0, // 购物车中商品的总价格cartNumber: 0, // 购物车中商品的总数量cartList: {},  // 保存购物车数据showCart: false,},disableNextScroll: false,shopcartAnimate: null,onLoad: function () {wx.showLoading({title: '努力加载中'})fetch('/food/list').then(data => {wx.hideLoading()this.setData({foodList: data.list,promotion: data.promotion[0]}, () => {var query = wx.createSelectorQuery()var top = 0var height = 0query.select('.food').boundingClientRect(rect => {top = rect.topheight = rect.height})query.selectAll('.food-category').boundingClientRect(res => {res.forEach(rect => {categoryPosition.push(rect.top - top - height / 3)})})query.exec()})}, () => {this.onLoad()})this.shopcartAnimate = shopcartAnimate('.operate-shopcart-icon', this)},tapCategory: function (e) {this.disableNextScroll = truevar index = e.currentTarget.dataset.indexthis.setData({activeIndex: index,tapIndex: index})},onFoodScroll: function (e) {if (this.disableNextScroll) {this.disableNextScroll = falsereturn}var scrollTop = e.detail.scrollTopvar activeIndex = 0categoryPosition.forEach((item, i) => {if (scrollTop >= item) {activeIndex = i}})if (activeIndex !== this.data.activeIndex) {this.setData({ activeIndex })}},// 加入购物车addToCart: function (e) {const index = e.currentTarget.dataset.indexconst category_index = e.currentTarget.dataset.category_indexconst food = this.data.foodList[category_index].food[index]const cartList = this.data.cartListif (cartList[index]) {++cartList[index].number} else {cartList[index] = {id: food.id,name: food.name,price: parseFloat(food.price),number: 1}}this.setData({cartList,cartPrice: this.data.cartPrice + cartList[index].price,cartNumber: this.data.cartNumber + 1})this.shopcartAnimate.start(e)},showCartList: function () {if (this.data.cartNumber > 0) {this.setData({showCart: !this.data.showCart})}},cartNumberAdd: function(e) {var id = e.currentTarget.dataset.idvar cartList = this.data.cartList++cartList[id].numberthis.setData({cartList: cartList,cartNumber: ++this.data.cartNumber,cartPrice: this.data.cartPrice + cartList[id].price})},cartNumberDec: function(e) {var id = e.currentTarget.dataset.idvar cartList = this.data.cartListif (cartList[id]) {var price = cartList[id].priceif (cartList[id].number > 1) {--cartList[id].number} else {delete cartList[id]}this.setData({cartList: cartList,cartNumber: --this.data.cartNumber,cartPrice: this.data.cartPrice - price})if (this.data.cartNumber <= 0) {this.setData({showCart: false})}}},// 清空购物车cartClear: function() {this.setData({cartList: {},cartNumber: 0,cartPrice: 0,showCart: false})},// 实现跳转到订单确认页order: function() {if (this.data.cartNumber === 0) {return}wx.showLoading({title: '正在生成订单'})fetch('/food/order', {order: this.data.cartList}, 'POST').then(data => {wx.navigateTo({url: '/pages/order/checkout/checkout?order_id=' + data.order_id})}, () => {this.order()})}})
(4)实现页面效果

image-20240916214950744

6、订单确认页

(1)实现页面结构
  • 在pages/order/checkout/checkout.wxml文件中实现页面结构
<!--pages/order/checkout/checkout.wxml-->
<view class="content"><!-- 标题 --><view class="content-title">请确认您的订单</view><!-- 订单信息--><view class="order"><view class="order-title">订单详情</view><view class="order-list"><!-- 订单商品列表项 --><view class="order-item" wx:for="{{ order_food }}" wx:key="id"><view class="order-item-left"><image class="order-item-image" mode="widthFix" src="{{ item.image_url }}" /><view><view class="order-item-name">{{ item.name }}</view><view class="order-item-number">x {{ item.number }}</view></view></view><view class="order-item-price">{{ priceFormat(item.price * item.number) }}</view></view><!-- 满减信息 --><view class="order-item" wx:if="{{ checkPromotion(promotion) }}"><view class="order-item-left"><i class="order-promotion-icon"></i>满减优惠</view><view class="order-promotion-price">- {{ priceFormat(promotion) }}</view></view><!-- 小计 --><view class="order-item"><view class="order-item-left">小计</view><view class="order-total-price">{{ priceFormat(price) }}</view></view></view></view><!-- 备注功能 --><view class="content-comment"><label>备注</label><textarea placeholder="如有其他要求,请输入备注" bindinput="inputComment"></textarea></view>
</view>
<!-- 支付功能 -->
<view class="operate"><view class="operate-info">合计:{{ priceFormat(price) }}</view><view class="operate-submit" bindtap="pay">去支付</view>
</view>
<!-- 处理商品价格格式 -->
<wxs module="priceFormat">module.exports = function (price) {return price ? '¥ ' + parseFloat(price) : ''}
</wxs>
<wxs module="checkPromotion">module.exports = function (promotion) {return parseFloat(promotion) > 0}
</wxs>
(2)实现页面样式
  • 在pages/order/checkout/checkout.wxss文件中实现页面样式
page {display: flex;flex-direction: column;height: 100%;background: #f8f8f8;
}
.content {flex: 1;overflow: scroll;margin-bottom: 40rpx;
}
::-webkit-scrollbar {display: none;
}
.content-title {height: 80rpx;line-height: 80rpx;font-size: 28rpx;background: white;padding: 0 10rpx;
}.order {background: white;margin-top: 20rpx;
}
.order-title {font-size: 24rpx;color: #a2a1a0;padding: 24rpx;
}
.order-list {padding: 0 30rpx;
}
.order-item {background: #fff;display: flex;font-size: 32rpx;padding: 25rpx 0;border-top: 1rpx #e3e3e3 solid;
}
.order-item-left {flex: 1;display: flex;
}
.order-item-image {width: 94rpx;height: 94rpx;margin-right: 25rpx;
}
.order-item-number {color: #a3a3a3;margin-top: 4rpx;font-size: 28rpx;
}
.order-promotion-icon {display: inline-block;background: #ff4500;padding: 2rpx 6rpx 6rpx;color: #fff;font-size: 28rpx;margin-right: 8rpx;
}
.order-promotion-price {color: #ff4500;
}
.order-total-price {font-size: 40rpx;
}
.content-comment {padding: 10rpx 30rpx 20rpx;background: white;margin-top: 20rpx;
}
.content-comment > label {font-size: 32rpx;color: #a3a3a3;
}
.content-comment > textarea {width: 95%;font-size: 24rpx;background: #f2f2f2;padding: 20rpx;height: 160rpx;margin-top: 10rpx;
}.operate {height: 110rpx;display: flex;
}
.operate-info {width: 74%;background: #353535;color: #fff;line-height: 110rpx;padding-left: 40rpx;
}
.operate-submit {width: 26%;font-size: 30rpx;text-align: center;line-height: 110rpx;background: #ff9c35;color: #fff;
}
(3)实现页面逻辑
  • 在pages/order/checkout/checkout.js文件中实现页面逻辑
const app = getApp()
const fetch = app.fetch
Page({data: {},comment: '',onLoad: function (options) {wx.showLoading({title: '努力加载中'})fetch('/food/order', {id: options.order_id}).then(data => {this.setData(data)wx.hideLoading()}, () => {this.onLoad(options)})},inputComment: function (e) {console.log(e)this.comment = e.detail.value},pay: function () {var id = this.data.idwx.showLoading({title: '正在支付'})fetch('/food/order', {id: id,comment: this.comment}, 'POST').then(() => {return fetch('/food/pay', { id }, 'POST')}).then(() => {wx.hideLoading()wx.showToast({title: '支付成功',icon: 'success',duration: 2000,success: () => {wx.navigateTo({url: '/pages/order/detail/detail?order_id=' + id})}})}).catch(() => {this.pay()})}})
(4)实现页面效果

image-20240916215430458

7、订单详情页

(1)实现页面结构
  • 在pages/order/detail/detail.wxml文件中实现页面结构
<!--pages/order/detail/detail.wxml-->
<view class="top"><view class="card" wx:if="{{ !is_taken }}"><view class="card-title">取餐号</view><view class="card-content"><view class="card-info"><text class="card-code">{{ code }}</text><text class="card-info-r">正在精心制作中…</text></view><view class="card-comment" wx:if="{{ comment }}">备注:{{ comment }}</view><view class="card-tips">美食制作中,尽快为您服务☺</view></view></view>
</view>
<view class="order"><view class="order-title">订单详情</view><view class="order-list"><!-- 订单商品列表项 --><view class="order-item" wx:for="{{ order_food }}" wx:key="id"><view class="order-item-left"><image class="order-item-image" mode="widthFix" src="{{ item.image_url }}" /><view><view class="order-item-name">{{ item.name }}</view><view class="order-item-number">x {{ item.number }}</view></view></view><view class="order-item-price">{{ priceFormat(item.price * item.number) }}</view></view><!-- 满减信息 --><view class="order-item" wx:if="{{ checkPromotion(promotion) }}"><view class="order-item-left"><i class="order-promotion-icon"></i>满减优惠</view><view class="order-promotion-price">- {{ priceFormat(promotion) }}</view></view><!-- 小计 --><view class="order-item"><view class="order-item-left">小计</view><view class="order-total-price">{{priceFormat(price)}}</view></view></view>
</view>
<view class="list"><view><text>订单号码</text><view>{{ sn }}</view></view><view><text>下单时间</text><view>{{ create_time }}</view></view><view><text>付款时间</text><view>{{ pay_time }}</view></view><view wx:if="{{ is_taken }}"><text>取餐时间</text><view>{{ taken_time }}</view></view>
</view>
<view class="tips" wx:if="{{ is_taken }}">取餐号{{ code }} 您已取餐</view>
<view class="tips" wx:else>请凭此页面至取餐柜台领取美食</view>
<wxs module="priceFormat">module.exports = function (price) {return price ? '¥ ' + parseFloat(price) : ''}
</wxs>
<wxs module="checkPromotion">module.exports = function (promotion) {return parseFloat(promotion) > 0}
</wxs>
(2)实现页面样式
  • 在pages/order/detail/detail.wxss文件中实现页面样式
/* pages/order/detail/detail.wxss */
.card {margin: 20rpx auto;width: 85%;background: #fef9f4;display: flex;font-size: 30rpx;
}
.card-title {width: 28rpx;padding: 0 30rpx;background: #de5f4b;border-left: 1rpx solid #de5f4b;font-size: 28rpx;color: #fff;display: flex;align-items: center;
}
.card-content {flex: 1;margin-left: 50rpx;
}
.card-info {margin-top: 10rpx;
}
.card-code {font-size: 60rpx;margin-right: 40rpx;
}
.card-info-r {font-size: 24rpx;color: #ff9c35;
}
.card-comment {color: #de5f4b;font-weight: 600;margin-top: 8rpx;
}
.card-tips {color: #a2a1a0;margin: 10rpx 0 20rpx;font-size: 24rpx;
}.order {background: white;margin-top: 20rpx;
}
.order-title {font-size: 24rpx;color: #a2a1a0;padding: 24rpx;
}
.order-list {padding: 0 30rpx;
}
.order-item {background: #fff;display: flex;font-size: 32rpx;padding: 25rpx 0;border-top: 1rpx #e3e3e3 solid;
}
.order-item-left {flex: 1;display: flex;
}
.order-item-image {width: 94rpx;height: 94rpx;margin-right: 25rpx;
}
.order-item-number {color: #a3a3a3;margin-top: 4rpx;font-size: 28rpx;
}
.order-promotion-icon {display: inline-block;background: #ff4500;padding: 2rpx 6rpx 6rpx;color: #fff;font-size: 28rpx;margin-right: 8rpx;
}
.order-promotion-price {color: #ff4500;
}
.order-total-price {font-size: 40rpx;
}
.list {background: #fff;margin-top: 20rpx;
}
.list > view {font-size: 30rpx;color: #d1d1d1;padding: 20rpx;border-bottom: 1rpx #e3e3e3 solid;display: flex;
}
.list > view > view {color: black;margin-left: 20rpx;
}
.tips {width: 80%;text-align: center;margin: 20rpx auto 40rpx;padding: 12rpx 20rpx;background: #ff9c35;color: #fff;font-size: 36rpx;
}
(3)实现页面逻辑
  • 在pages/order/detail/detail.js文件中实现页面逻辑
// pages/order/detail/detail.js
const app = getApp()
const fetch = app.fetch
Page({data: {},onLoad: function (options) {var id = options.order_idwx.showLoading({title: '努力加载中'})fetch('/food/order', {id: id}).then(data => {this.setData(data)wx.hideLoading()}, () => {this.onLoad(options)})},onUnload: function () {wx.reLaunch({url: '/pages/order/list/list'})}
})
(4)实现页面效果

image-20240917162105360

8、订单列表页

(1)实现页面结构
  • 在pages/order/list/list.wxml文件中实现页面结构
<!--pages/list/list.wxml-->
<view class="discount"><text class="discount-txt"></text>满{{ promotion.k }}元减{{ promotion.v }}元(在线支付专享)
</view>
<view class="content"><!-- 左侧菜单栏区域 --><scroll-view class="category" scroll-y><view wx:for="{{ foodList }}" wx:key="id" class="category-item category-{{ activeIndex == index ? 'selected' : 'unselect' }}" data-index="{{ index }}" bindtap="tapCategory"><view class="category-name">{{ item.name }}</view></view></scroll-view><!-- 右侧商品列表区域 --><scroll-view class="food" scroll-y scroll-into-view="category_{{ tapIndex }}" scroll-with-animation bindscroll="onFoodScroll"><block wx:for="{{ foodList }}" wx:for-item="category" wx:key="id" wx:for-index="category_index"><view class="food-category" id="category_{{ category_index }}">{{ category.name }}</view><view class="food-item" wx:for="{{ category.food }}" wx:for-item="food" wx:key="id"><view class="food-item-pic"><image mode="widthFix" src="{{ food.image_url }}" /></view><view class="food-item-info"><view>{{ food.name }}</view><view class="food-item-price">{{ priceFormat(food.price) }}</view></view><view class="food-item-opt"><i class="iconfont" data-category_index="{{ category_index }}" data-index="{{ index }}" bindtap="addToCart"></i></view></view></block></scroll-view>
</view>
<!-- 购物车界面 -->
<view class="shopcart" wx:if="{{ showCart }}"><view class="shopcart-mask" bindtap="showCartList" wx:if="{{ showCart }}"></view><view class="shopcart-wrap"><view class="shopcart-head"><view class="shopcart-head-title">已选商品</view><view class="shopcart-head-clean" bindtap="cartClear"><i class="iconfont"></i>清空购物车</view></view><view class="shopcart-list"><view class="shopcart-item" wx:for="{{ cartList }}" wx:key="id"><view class="shopcart-item-name">{{ item.name }}</view><view class="shopcart-item-price"><view>{{ priceFormat(item.price * item.number) }}</view></view><view class="shopcart-item-number"><i class="iconfont shopcart-icon-dec" data-id="{{ index }}" bindtap="cartNumberDec"></i><view>{{ item.number }}</view><i class="iconfont shopcart-icon-add" data-id="{{ index }}" bindtap="cartNumberAdd"></i></view></view></view></view>
</view>
<!-- 满减优惠信息 -->
<view class="promotion"><label wx:if="{{ promotion.k - cartPrice > 0 }}">满{{ promotion.k }}立减{{ promotion.v }}元,还差{{ promotion.k - cartPrice }}元</label><label wx:else>已满{{ promotion.k }}元可减{{ promotion.v }}元</label>
</view>
<!-- 小球动画 -->
<view class="operate"><view class="operate-shopcart-ball" hidden="{{ !cartBall.show }}" style="left: {{ cartBall.x }}px; top: {{ cartBall.y }}px;"></view><view class="operate-shopcart" bindtap="showCartList"><i class="iconfont operate-shopcart-icon {{ cartNumber > 0 ? 'operate-shopcart-icon-activity' : '' }}"><span wx:if="{{ cartNumber > 0 }}">{{ cartNumber }}</span></i><view class="operate-shopcart-empty" wx:if="{{ cartNumber === 0 }}">购物车是空的</view><view class="operate-shopcart-price" wx:else><block wx:if="{{ cartPrice >= promotion.k }}"><view>{{ priceFormat(cartPrice - promotion.v )}}</view><text>{{ priceFormat(cartPrice) }}</text></block><view wx:else>{{ priceFormat(cartPrice) }}</view></view></view><view class="operate-submit {{ cartNumber !== 0 ? 'operate-submit-activity' : '' }}" bindtap="order">选好了</view>
</view>
<wxs module="priceFormat">module.exports = function (price) {return '¥ ' + parseFloat(price)}
</wxs>
(2)实现页面样式
  • 在pages/order/list/list.wxss文件中实现页面样式
/* pages/list/list.wxss */
page {display: flex;flex-direction: column;height: 100%;
}
/* 折扣信息区 */
.discount {width: 100%;height: 70rpx;line-height: 70rpx;background: #fef9e6;font-size: 28rpx;text-align: center;color: #999;
}
.discount-txt {color: #fff;padding: 5rpx 10rpx;background: red;margin-right: 15rpx;
}
.content {flex: 1;display: flex;overflow: hidden;
}
.category {width: 202rpx;height: 100%;background: #fcfcfc;font-size: 28rpx;
}
/* 隐藏滚动条 */
::-webkit-scrollbar {width: 0;height: 0;color: transparent;
}
.category-item {height: 100rpx;line-height: 100rpx;text-align: center;
}
.food-category {font-size: 24rpx;background: #f3f4f6;padding: 10rpx;color: #ff9c35;
}
.food-item {display: flex;margin: 40rpx 20rpx;
}
.food-item-pic {margin-right: 20rpx;width: 94rpx;height: 94rpx;
}
.food-item-pic > image {width: 100%;height: 100%;
}
.food-item-info {flex: 1;font-size: 30rpx;margin-top: 4rpx;
}
.food-item-price {margin-top: 14rpx;color: #f05a86;
}
.food-item-opt {margin-top: 40rpx;
}
.food-item-opt > i:before {font-size: 44rpx;color: #ff9c35;content: "\e728";
}
.category-unselect {color: #6c6c6c;background: #f9f9f9;border-bottom: 1rpx solid #e3e3e3;
}
.category-selected {color: #ff9c35;background: white;border-left: 6rpx solid #ff9c35;
}
.category-selected:last-child {border-bottom: 1rpx solid #e3e3e3;
}
/* 购物车区域 */
.operate {height: 110rpx;display: flex;
}
.operate-shopcart {display: flex;width: 74%;padding: 10rpx;background: #353535;
}
/* “选好了”按钮 */
.operate-submit {width: 26%;font-size: 30rpx;background: #eee;color: #aaa;text-align: center;line-height: 110rpx;
}
.operate-submit-activity {background: #ff9c35;color: #fff;
}
/* 购物车图标 */
.operate-shopcart-icon {font-size: 80rpx;color: #87888e;margin-left: 20rpx;position: relative;
}
.operate-shopcart-icon:before {content: "\e73c";
}
.operate-shopcart-icon-activity {color: #ff9c35;
}
/* 购物车为空 */
.operate-shopcart-empty {color: #a9a9a9;line-height: 88rpx;font-size: 30rpx;margin-left: 20rpx;
}
/* 购物车中的商品购买数量 */
.operate-shopcart-icon > span {padding: 2rpx 14rpx;border-radius: 50%;background: red;color: white;font-size: 28rpx;position: absolute;top: 0px;right: -10rpx; text-align: center;
}
/* 购物车中的商品价格 */
.operate-shopcart-price {display: flex;
}
.operate-shopcart-price > view {font-size: 40rpx;line-height: 88rpx;margin-left: 25rpx;color: #fff;
}
.operate-shopcart-price > text {font-size: 24rpx;line-height: 92rpx;margin-left: 15rpx;color: #aaa;text-decoration: line-through;
}
/* 小球的样式 */
.operate-shopcart-ball {width: 36rpx;height: 36rpx;position: fixed;border-radius: 50%;left: 50%;top: 50%;background: #ff9c35;
}
/* 满减优惠区域 */
.promotion {padding: 7rpx 0 9rpx;background: #ffcd9b;color: #fff7ec;font-size: 28rpx;text-align: center;
}
.shopcart {position: fixed;top: 0;left: 0;bottom: 149rpx;right: 0;font-size: 28rpx;
}
.shopcart-wrap {position: absolute;width: 100%;max-height: 90%;bottom: 0;background: #fff;overflow: scroll;
}
.shopcart-mask {position: absolute;top: 0;right: 0;bottom: 0;left: 0;background: #000;opacity: 0.5;
}
.shopcart-head {position: fixed;width: 100%;background: #f0f0f0;color: #878787;line-height: 100rpx;font-size: 26rpx;overflow: hidden;
}
.shopcart-head-title {float: left;margin-left: 40rpx;
}
.shopcart-head-title:before {background: #ff9c35;width: 8rpx;height: 32rpx;content: "";display: inline-block;margin-right: 10rpx;position: relative;top: 6rpx;
}
.shopcart-head-clean {float: right;margin-right: 20rpx;
}
.shopcart-head-clean > i:before {content: "\e61b";position: relative;top: 2rpx;
}
.shopcart-list {margin-top: 101rpx;
}
.shopcart-item {display: flex;padding: 30rpx 20rpx;line-height: 40rpx;
}
.shopcart-item > view {margin-left: 30rpx;
}
.shopcart-item:not(:last-child) {border-bottom: 1rpx solid #e3e3e3;
}
.shopcart-item-name {flex: 1;
}
.shopcart-item-price {color: #ff9c35;
}
.shopcart-item-number {display: flex;
}
.shopcart-item-number > view {margin: 0 15rpx;
}
.shopcart-icon-dec:before {content: "\e61a";font-size: 44rpx;color: #888;
}
.shopcart-icon-add:before {content: "\e728";font-size: 44rpx;color: #ff9c35;
}
(3)实现页面逻辑
  • 在pages/order/list/list.js文件中实现页面逻辑
// pages/list/list.js
// 引入购物车动画模块
const shopcartAnimate = require('../../utils/shopcartAnimate.js')
const app = getApp()
const fetch = app.fetch
const categoryPosition = [] // 右列表各分类高度数组
Page({data: {foodList: [],promotion: {},activeIndex: 0,tapIndex: 0,cartPrice: 0, // 购物车中商品的总价格cartNumber: 0, // 购物车中商品的总数量cartList: {},  // 保存购物车数据showCart: false,},disableNextScroll: false,shopcartAnimate: null,onLoad: function () {wx.showLoading({title: '努力加载中'})fetch('/food/list').then(data => {wx.hideLoading()this.setData({foodList: data.list,promotion: data.promotion[0]}, () => {var query = wx.createSelectorQuery()var top = 0var height = 0query.select('.food').boundingClientRect(rect => {top = rect.topheight = rect.height})query.selectAll('.food-category').boundingClientRect(res => {res.forEach(rect => {categoryPosition.push(rect.top - top - height / 3)})})query.exec()})}, () => {this.onLoad()})this.shopcartAnimate = shopcartAnimate('.operate-shopcart-icon', this)},tapCategory: function (e) {this.disableNextScroll = truevar index = e.currentTarget.dataset.indexthis.setData({activeIndex: index,tapIndex: index})},onFoodScroll: function (e) {if (this.disableNextScroll) {this.disableNextScroll = falsereturn}var scrollTop = e.detail.scrollTopvar activeIndex = 0categoryPosition.forEach((item, i) => {if (scrollTop >= item) {activeIndex = i}})if (activeIndex !== this.data.activeIndex) {this.setData({ activeIndex })}},// 加入购物车addToCart: function (e) {const index = e.currentTarget.dataset.indexconst category_index = e.currentTarget.dataset.category_indexconst food = this.data.foodList[category_index].food[index]const cartList = this.data.cartListif (cartList[index]) {++cartList[index].number} else {cartList[index] = {id: food.id,name: food.name,price: parseFloat(food.price),number: 1}}this.setData({cartList,cartPrice: this.data.cartPrice + cartList[index].price,cartNumber: this.data.cartNumber + 1})this.shopcartAnimate.start(e)},showCartList: function () {if (this.data.cartNumber > 0) {this.setData({showCart: !this.data.showCart})}},cartNumberAdd: function(e) {var id = e.currentTarget.dataset.idvar cartList = this.data.cartList++cartList[id].numberthis.setData({cartList: cartList,cartNumber: ++this.data.cartNumber,cartPrice: this.data.cartPrice + cartList[id].price})},cartNumberDec: function(e) {var id = e.currentTarget.dataset.idvar cartList = this.data.cartListif (cartList[id]) {var price = cartList[id].priceif (cartList[id].number > 1) {--cartList[id].number} else {delete cartList[id]}this.setData({cartList: cartList,cartNumber: --this.data.cartNumber,cartPrice: this.data.cartPrice - price})if (this.data.cartNumber <= 0) {this.setData({showCart: false})}}},// 清空购物车cartClear: function() {this.setData({cartList: {},cartNumber: 0,cartPrice: 0,showCart: false})},// 实现跳转到订单确认页order: function() {if (this.data.cartNumber === 0) {return}wx.showLoading({title: '正在生成订单'})fetch('/food/order', {order: this.data.cartList}, 'POST').then(data => {wx.navigateTo({url: '/pages/order/checkout/checkout?order_id=' + data.order_id})}, () => {this.order()})}})
(4)实现页面效果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

9、消费记录页

(1)实现页面结构
  • 在pages/record/record.wxml文件中实现页面结构
<!--pages/record/record.wxml-->
<view class="head"><button class="avatar-wrapper" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar"><image class="avatar" src="{{ avatarUrl }}" /></button>
</view>
<view class="content"><view class="list-title">消费记录</view><view class="list-item" wx:for="{{ list }}" wx:key="id"><view class="list-item-l"><view>消费</view><view class="list-item-time">{{ item.pay_time }}</view></view><view class="list-item-r"><text>{{ priceFormat(item.price) }}</text></view></view>
</view>
<wxs module="priceFormat">module.exports = function (price) {return '¥ ' + parseFloat(price)}
</wxs>
(2)实现页面样式
  • 在pages/record/record.wxss文件中实现页面样式
/* pages/record/record.wxss */
page {background-color: #f8f8f8;font-size: 32rpx;
}
.head {width: 100%;background-color: #f7982a;height: 400rpx;display: flex;justify-content: center;align-items: center;
}
.avatar-wrapper {width: 160rpx;height: 160rpx;padding: 0;background: none;border-radius: 50%;
}
.avatar {width: 160rpx;height: 160rpx;border-radius: 50%;
}
/* 列表部分 */
.list-title {background-color: #fff;padding: 16rpx 0;text-align: center;
}
.list-item {display: flex;padding: 42rpx 20rpx;border-bottom: 1rpx solid #ececec;
}
.list-item-l {flex: 1;
}
.list-item-r {line-height: 76rpx;
}
.list-item-r > text {color: #f7982a;font-weight: 600;font-size: 32rpx;
}
.list-item-time {margin-top: 10rpx;font-size: 26rpx;color: #999;
}
(3)实现页面逻辑
  • 在pages/record/record.js文件中实现页面逻辑
// pages/record/record.js
const defaultAvatar = '/images/avatar.png'
const app = getApp()
const fetch = app.fetch
Page({data: {avatarUrl: defaultAvatar},onLoad: function () {wx.showLoading({title: '努力加载中'})fetch('/food/record').then(data => {wx.hideLoading()this.setData(data)})},onChooseAvatar: function (e) {const { avatarUrl } = e.detailthis.setData({ avatarUrl })}
})
(4)实现页面效果

image-20240917164247314


http://www.ppmy.cn/server/121608.html

相关文章

vue-入门速通

setup是最早的生命周期&#xff0c;在vue2里边的data域可以使用this调用setup里面的数据&#xff0c;但是在setup里边不能使用thisvue项目的可执行文件是index&#xff0c;另外运行前端需要npm run vue的三个模块内需要三个不同的结构&#xff0c;里边放置js代码&#xff0c;注…

软RAID 之mdadm.conf文件详解

mdadm.conf 是 mdadm 工具的主要配置文件&#xff0c;用于定义Linux系统中软件RAID&#xff08;MD&#xff09;阵列的配置。这个文件通常位于 /etc/mdadm/mdadm.conf。它包含了关于RAID阵列的详细信息&#xff0c;如阵列的设备名称、组成阵列的磁盘、阵列的级别和布局等。mdadm…

SpringBoot2:web开发常用功能实现及原理解析-@ControllerAdvice实现全局异常统一处理

文章目录 前言1、工程包结构2、POM依赖3、Java代码 前言 本篇主要针对前后端分离的项目&#xff0c;做的一个统一响应包装、统一异常捕获处理。 在Spring里&#xff0c;我们可以使用ControllerAdvice来声明一些关于controller的全局性的东西&#xff0c;其用法主要有以下三点…

最新Kali Linux超详细安装教程(附镜像包)

一、镜像下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1BfiyAMW6E1u9fhfyv8oH5Q 提取码&#xff1a;tft5 二、配置虚拟机 这里我们以最新的vm17.5为例。进行配置 1.创建新的虚拟机&#xff1a;选择自定义 2.下一步 3.选择稍后安装操作系统 4.选择Debian版本 因…

SQLServer数据分页

一.分页 将一定量的数据进行分页&#xff0c;每一页中定量存储数据 1.top分页查询 比如当前存在于数据库中的一共有40条数据&#xff0c;我们将每10条数据算作一页&#xff0c;那么一共可以分出4页&#xff0c;如果要进行查询的话&#xff0c;只需要使用如下格式即可&#x…

【高级编程】XML DOM4J解析XML文件(含案例)

文章目录 DOM4JDOM4J 解析 XML读取修改添加删除 XML&#xff08;EXtensible Markup Language&#xff09;&#xff0c;可扩展标记语言。一种用于存储和传输数据的标记语言。XML 与操作系统、编程语言的开发平台无关。实现不同系统之间的数据交换。 作用&#xff1a;数据交互&a…

python 实现double factorial recursive双阶乘递归算法

double factorial recursive双阶乘递归算法介绍 双阶乘&#xff08;Double Factorial&#xff09;是一个数学概念&#xff0c;它指的是从某个数开始&#xff0c;每隔一个数相乘直到1或指定的结束点。对于正整数n&#xff0c;其双阶乘有两种定义方式&#xff0c;取决于n的奇偶性…

用二分查询一个有序向量(或数组)中是否存在vector<T>[i]==i;

0. 下面【1】使用了类&#xff0c;【2】使用了函数 解析&#xff1a; - 因为我们的代码中不存在l mid1;r mid -1;这种可能会【跨越另一个指针】的情况&#xff0c;所以我们while条件写l1!r&#xff0c;言下之意&#xff0c;彼时你俩相差为1&#xff0c;我们就跳出循环了。…