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

devtools/2024/11/14 15:07:53/

零、文章目录

小程序>微信小程序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/devtools/114893.html

相关文章

将阮一峰老师的《ES6入门教程》的源码拷贝本地运行和发布

你好同学&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏、评论和关注。 阮一峰老师的《ES6入门教程》应该是很多同学学习 ES6 知识的重要参考吧&#xff0c;应该也有很多同学在看该文档的时候&#xff0c;想知道这个教程的前端源码是怎么实现的&#xff0c;也可能有同学下载…

用SpringBoot进行通义千问接口调用同步方法和异步流式多轮回复方法

同步效果就不展示了,这里展示更常用的异步,多轮异步流式效果展示如下: 第一轮回答,此时没有会话id,需要雪花算法生成插入数据库 第二轮问题以及结果内容组合 1、同步版本环境准备以及代码 需要开通阿里大模型服务,如果没有开通服务,单独的去生成 key 是无效的。 阿里…

代码随想录刷题day32丨动态规划理论基础,509. 斐波那契数, 70. 爬楼梯, 746. 使用最小花费爬楼梯

代码随想录刷题day32丨动态规划理论基础&#xff0c;509. 斐波那契数&#xff0c; 70. 爬楼梯&#xff0c; 746. 使用最小花费爬楼梯 1.动态规划理论基础 动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题…

企业如何使用数据分析管理系统

在数字化时代&#xff0c;数据成为企业发展新的增长方向&#xff0c;如何利用数据分析管理系统高效管理和运用这些数据&#xff0c;已成为企业决策者们亟待解决的关键所在。数聚股份将通过多年的实践经验来深入探讨企业如何通过数据分析管理系统实现智能决策&#xff0c;增强竞…

[网络][知识]TCP-IP各协议的RFC编号和RFC原始文档的获取地址

TCP/IP协议族包括很多个子协议,下面是TCP/IP 协议和支持服务所支持的 RFC。 RFC768 用户数据报协议 (UDP) RFC783 简单文件传输协议 (TFTP) RFC791 Internet 协议 (IP) RFC792 Internet 控制消息协议 (ICMP) RFC793 传输控制协议 (TCP) RFC816 故障隔离和恢复 RFC…

Python Flask网页开发基本框架

注&#xff1a;Flask详细学习请见Flask学习合集。 直接上代码: app.py from flask import Flaskapp Flask(__name__)app.route("/") def hello():return "Hello, World!"if __name__ "__init__":app.run(host "127.0.0.1", port…

LDD学习2--Scull(TODO)

《Linux Device Drivers》&#xff08;LDD&#xff09;书籍中的 scull&#xff08;Simple Character Utility for Loading Localities&#xff09;是一个用于演示 Linux 字符设备驱动程序编写的示例代码。它为理解 Linux 内核模块和字符设备驱动程序的编写提供了基础实践平台&a…

SpringBoot 数据库表结构文档生成

官方地址&#xff1a;https://github.com/pingfangushi/screw screw 螺丝钉&#xff0c;支持以下数据库 MySQL MariaDB TIDB Oracle SqlServer PostgreSQL Cache DB&#xff08;2016&#xff09; 生产文档支持 html word markdown 开始 添加依赖 <!-- 螺丝钉 --><…