上篇文章 微信小程序开发入门实战 (三)项目目录与tabBar 配置 中,我们完成了项目目录的构建和 tabBar 的配置,下来我们首先对订单页面进行开发。
设计稿如图:
订单-ui基础准备与分析
从设计稿我们分析得出,我们需要进行开发的功能包括:
1.数据列表
2.下拉刷新
3.上拉加载
4.取消订单
5.列表子项点击进入订单详情
6.列表没有数据时的 empty
首先,我们把列表子项设为一个组件 order-item
,把下拉刷新和上拉加载设为一个组件 global-list
,把列表没有数据时的empty设为组件 is-empty
。
然后我们根据组件构建相关目录文件,如图:
相关组件分析并做完基础工作后,我们直接上代码开干。
代码
app.wxss
全局默认样式
/**app.wxss**/
page {
/* 颜色变量 */
--scm-color-blue: #4856dc;
--scm-color-red: #f93636;
--scm-color-dark: #2e2d2d;
--scm-color-gray: #a1a1a1;
--scm-color-bg: #f5f5f5;
--scm-color-white: #fff;
--scm-color-border: #f9f9f9;
/* 全局样式 */
background-color: var(--scm-color-bg);
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: var(--scm-color-dark);
font-size: 30rpx;
}
组件 components/order-item/order-item
order-item.wxml
<!--components/order-item/order-item.wxml-->
<view class="order-item" bindtap="bindtapOrderItem" data-item="{{ item }}">
<!-- header -->
<view class="order-item__header">
<!-- header-img -->
<view class="order-item__header-img" data-item="{{ item }}" catchtap="gotoShop">
<image src="{{ item.shopLogo ? item.shopLogo : 'https://fscdn.zto.com/fs8/M03/B0/81/wKhBD16mvIeAOA2rAAAKnSirTdY139.png' }}"></image>
</view>
<!-- header-goods -->
<view class="order-item__header-goods">
<view class="order-item__header-goods-title">{{ item.shopName }}</view>
<view class="order-item__header-goods-info">
<!-- <view class="tag">仅自提</view> -->
<view class="time">
{{ item.gmtCreate }}
</view>
</view>
</view>
<!-- status -->
<view class="order-item__header-status">
{{ item.statusCode }}
</view>
</view>
<!-- goods -->
<view class="order-item__goods">
<scroll-view scroll-x class="order-item__goods-scroll">
<view class="order-item__goods-warp">
<view
class="order-item__goods-item"
wx:for="{{ showShopList }}"
wx:for-item="shop"
wx:key="index"
>
<image src="{{ shop.imgUrl ? shop.imgUrl : 'https://fscdn.zto.com/fs8/M03/B0/81/wKhBD16mvIeAOA2rAAAKnSirTdY139.png' }}"></image>
<view>{{ shop.qty }}</view>
</view>
</view>
</scroll-view>
<view class="order-item__goods-more" wx:if="{{ showShopList.length > 4 }}">
<image src="https://fscdn.zto.com/fs8/M03/45/C9/wKhBD16hPk6ADPt3AAAAzsfvHR0974.png"></image>
</view>
</view>
<!-- footer -->
<view class="order-item__footer">
<view>共 <text>{{ item.totalQty }}</text> 件商品,合计:<text>¥{{ item.totalAmount }}</text></view>
</view>
<!-- button -->
<view class="order-item__btns">
<slot></slot>
</view>
</view>
order-item.wxss
/* components/order-item/order-item.wxss */
.order-item {
position: relative;
display: block;
width: 710rpx;
margin: 20rpx auto 0;
padding: 30rpx;
box-sizing: border-box;
border-radius: 32rpx;
background-color: #fff;
}
/* header */
.order-item__header {
display: flex;
}
/* header-img */
.order-item__header .order-item__header-img {
width: 80rpx;
height: 80rpx;
margin-right: 20rpx;
border-radius: 50%;
overflow: hidden;
}
.order-item__header .order-item__header-img image {
display: block;
width: 100%;
height: 100%;
}
/* header-goods */
.order-item__header .order-item__header-goods {
flex: 1;
max-width: 420rpx;
}
.order-item__header .order-item__header-goods .order-item__header-goods-title {
color: #2E2D2D;
font-size: 32rpx;
line-height: 36rpx;
font-weight: 400;
margin-bottom: 10rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.order-item__header .order-item__header-goods .order-item__header-goods-info {
line-height: 30rpx;
}
.order-item__header .order-item__header-goods .order-item__header-goods-info .tag {
display: inline-block;
padding: 3rpx 10rpx;
border: 1rpx solid rgba(72,104,220,0.5);
border-radius: 4rpx;
color: rgba(72,86,220,1);
font-size: 24rpx;
font-weight: 400;
margin-right: 20rpx;
line-height: 30rpx;
box-sizing: border-box;
}
.order-item__header .order-item__header-goods .order-item__header-goods-info .time {
display: inline-block;
color:rgba(46,45,45,0.5);
font-size: 20rpx;
font-weight: 400;
}
/* status */
.order-item__header .order-item__header-status {
color: #2E2D2D;
font-size: 28rpx;
font-weight: 400;
margin-left: 20rpx;
max-width: 150rpx;
/*overflow: hidden;*/
white-space: nowrap;
text-overflow: ellipsis;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
height: 80rpx;
line-height: 1;
}
/* goods */
.order-item__goods {
position: relative;
display: flex;
width: 100%;
height: 120rpx;
padding-right: 80rpx;
box-sizing: border-box;
margin-top: 20rpx;
margin-bottom: 34rpx;
overflow: hidden;
}
.order-item__goods-scroll {
display: block;
width: 100%;
padding-bottom: 10rpx;
height: 130rpx;
}
.order-item__goods-warp {
display: flex;
}
.order-item__goods-item {
flex-shrink: 0;
position: relative;
display: block;
width: 120rpx;
height: 120rpx;
margin-right: 24rpx;
border-radius: 16rpx;
overflow: hidden;
}
.order-item__goods-item image {
display: block;
width: 120rpx;
height: 120rpx;
}
.order-item__goods-item view {
position: absolute;
left: 0;
right: 0;
bottom: 0;
font-size: 22rpx;
font-weight: 400;
line-height: 30rpx;
color: #fff;
text-align: center;
background-color: rgba(0,0,0,0.3);
}
.order-item__goods-more {
position: absolute;
top: 0;
right: 0;
display: block;
padding: 54rpx 22rpx;
background-color: #fff;
}
.order-item__goods-more image {
display: block;
width: 36rpx;
height: 12rpx;
}
/* footer */
.order-item__footer {
color: #646464;
font-size: 24rpx;
font-weight: 400;
line-height: 33rpx;
margin-bottom: 13rpx;
}
.order-item__footer text {
color: #F93636;
}
/* button */
.order-item__btns {
position: absolute;
right: 30rpx;
bottom: 30rpx;
}
order-item.js
// components/order-item/order-item.js
Component({
/**
* 组件的属性列表
*/
properties: {
item: {
type: Object,
value: {}
}
},
/**
* 组件的初始数据
*/
data: {
shopList: [], // 购买的商品列表
showShopList: [] // 显示的商品列表,即购买的商品列表的1-10
},
/**
* 在组件实例进入页面节点树时执行
*/
attached: function() {},
/**
* 在组件在视图层布局完成后执行
*/
ready: function() {},
// 数据监听器
observers: {
// 在 item 被设置时,执行这个函数
'item': function(item) {
if (item.productInfo) {
this.setData({
shopList: [...item.productInfo],
showShopList: item.productInfo.slice(0, 10)
})
}
}
},
/**
* 组件的方法列表
*/
methods: {
// 点击前往订单详情
bindtapOrderItem(event) {
console.log('bindtapOrderItem', event, )
this.triggerEvent('bindtapOrderItemEvent', event)
},
// 前往店铺详情
gotoShop(event) {
console.log('点击前往店铺详情', event, event.currentTarget.dataset)
}
}
})
order-item.json
{
"component": true,
"usingComponents": {}
}
组件 components/global-list/global-list
global-list.wxml
<!--components/global-list/global-list.wxml-->
<scroll-view
class="global-list"
scroll-y="{{ scrollY }}"
scroll-top="{{ scrollTop }}"
lower-threshold="{{ lowerThreshold }}"
bind:scroll="scrollHandler"
bindscrolltolower="scrollToLower"
bindscrolltoupper="scrollToupper"
bind:touchstart="touchStart"
bind:touchmove="touchMove"
bind:touchend="touchEnd"
>
<!-- 刷新 -->
<view wx:if="{{ showRefresh }}" class="global-list__refresh">{{ refreshText }}</view>
<slot></slot>
<!-- 加载-提示文字 -->
<view wx:if="{{ isLoading }}" class="global-list__loading">{{ loadingText }}</view>
<!-- 加载完成-提示文字 -->
<view wx:if="{{ !isLoading && isFinished }}" class="global-list__finished">{{ finishedText }}</view>
</scroll-view>
global-list.wxss
/* components/global-list/global-list.wxss */
.global-list {
width: 100%;
height: 100%;
}
.global-list__refresh {
height: 120rpx;
line-height: 120rpx;
text-align: center;
font-size: 30rpx;
font-weight: 400;
}
.global-list__loading,
.global-list__finished {
padding: 30rpx 0;
font-size: 26rpx;
color: rgba(46, 45, 45, 1);
line-height: 38rpx;
text-align: center;
}
global-list.js
// components/global-list/global-list.js
Component({
properties: {
isLoading: {
type: Boolean,
value: false
}, // 是否开启加载
loadingText: {
type: String,
value: '加载中...'
}, // 加载提示
isFinished: {
type: Boolean,
value: false
}, // 是否加载完成
finishedText: {
type: String,
value: '没有更多了...'
}, // 加载完成提示
lowerThreshold: {
type: Number,
value: 50
}, // 距底部/右边多远时,触发 scrolltolower 事件
scrollY: {
type: Boolean,
value: true
}, // 允许纵向滚动
scrollTop: {
type: Number,
value: 0
}, // 设置竖向滚动条位置
// 刷新 ------------------
isOpenRefresh: {
type: Boolean,
value: false
}, // 是否开启刷新
refreshDistance: {
type: Number,
value: 25
}, // 刷新下拉距离
refreshTime: {
type: Number,
value: 500
}, // 下拉刷新执行几秒
refreshText: {
type: String,
value: '刷新中...'
}
// 刷新 ------------------
},
data: {
isToTop: false, // 是否到达顶部
freshStatus: 'more', // 当前刷新的状态 more-继续下拉刷新\release-释放刷新\refreshing-刷新中
startY: 0, //
showRefresh: false, // 是否显示下拉刷新组件
},
methods: {
// 滚动到底部/右边时触发
scrollToLower: function (e) {
console.log('滚动到底部/右边时触发', e)
console.log(this.data.isLoading, this.data.isFinished)
if (!this.data.isLoading && !this.data.isFinished) {
this.triggerEvent('load', {})
}
},
// 滚动时触发
scrollHandler: function (e) {
// console.log('滚动时触发', e)
this.triggerEvent('scroll', { ...e.detail })
this.setData({
isToTop: false
})
},
// 下拉刷新 -----------------------------------------------
// 滚动到顶部/左边时触发
scrollToupper(e) {
console.log('滚动到顶部/左边时触发', e)
this.setData({
isToTop: true
})
},
// 触摸屏幕
touchStart(event) {
if (!this.properties.isOpenRefresh) {
return
}
// console.log('触摸屏幕', event, this)
this.setData({
startY: event.changedTouches[0].pageY,
freshStatus: 'more'
})
},
// 触摸屏幕后移动
touchMove(event) {
if (!this.properties.isOpenRefresh) {
return
}
// console.log('触摸屏幕后移动', event, this.data.isToTop)
let endY = event.changedTouches[0].pageY
let startY = this.data.startY
let dis = endY - startY
// console.log('触摸屏幕---移动', dis)
if (dis <= 0 && this.data.isToTop) {
return
}
if (dis > this.properties.refreshDistance) {
this.setData({
showRefresh: true
}, () => {
if (dis > this.properties.refreshDistance + 25) {
this.setData({
freshStatus: 'release'
})
} else {
this.setData({
freshStatus: 'more'
})
}
})
} else {
this.setData({
showRefresh: false
})
}
},
// 触摸屏幕-松手
touchEnd(event) {
if (!this.properties.isOpenRefresh) {
return
}
// console.log('触摸屏幕-松手', event)
if (this.data.freshStatus === 'release') {
this.setData({
freshStatus: 'refreshing'
})
// 此处需要节流防抖,防止请求速度过快不显示
setTimeout(() => {
this.triggerEvent('refresh')
// 触发事件后,刷新结束,数据重置
setTimeout(() => {
this.setData({
freshStatus: 'more',
showRefresh: false,
isToTop: false,
startY: 0
})
}, this.properties.refreshTime)
}, 500)
} else {
this.setData({
showRefresh: false
})
}
},
// 下拉刷新 -----------------------------------------------
}
})
global-list.json
{
"component": true,
"usingComponents": {}
}
页面 pages/order/order
order.wxml
<!--pages/order/order.wxml-->
<view class="m-order" style="height: calc(100vh - {{ statusBarHeight }}px )">
<empty wx:if="{{ list.length === 0 && listData.finished }}" message="还没有订单信息哦"></empty>
<!-- 类似于 vant 的 list 组件:https://youzan.github.io/vant/#/zh-CN/list -->
<global-list
wx:if="{{ list.length !== 0 }}"
isLoading="{{ listData.loading }}"
isFinished="{{ listData.finished }}"
isOpenRefresh="{{ true }}"
scrollTop="{{scrollTop}}"
finishedText="已无更多订单~"
bind:load="scrollLoadEvent"
bind:refresh="refreshEvent"
>
<!-- 列表子项 -->
<order-item
wx:for="{{ list }}"
wx:for-item="order"
wx:key="index"
item="{{ order }}"
data-item="{{ order }}"
bind:bindtapOrderItemEvent="gotoOrderDetails"
>
<van-button
round
plain
custom-class="cancel-btn"
data-item="{{ order }}"
catchtap="handleCancelBtnEvent"
>取消订单</van-button>
</order-item>
</global-list>
</view>
order.js
// pages/order/order.js
// import Toast from '../../miniprogram_npm/@vant/weapp/toast/toast'
import OrderRequest from '../../config/order'
console.log(OrderRequest)
Page({
/**
* 页面的初始数据
*/
data: {
// list: [], // 列表数据
list: OrderRequest.result.list, // 列表数据
pagination: {
total: 0,
pageIndex: 1,
pageSize: 10
}, // 分页数据
listData: {
loading: false,
finished: false
}, // 列表辅助数据:loading-加载、finished-是否已加载完成
// other
statusBarHeight: wx.getSystemInfoSync()['statusBarHeight'],
scrollTop: 0
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
// 设置底部 tabbar
this.getTabBar().setData({
selected: 1
})
// 重置默认数据
this.setData({
'pagination': {
total: 0,
pageIndex: 1,
pageSize: 10
},
listData: {
loading: false,
finished: false
}
})
// 重置列表
// this.setData({
// list: [],
// }, () => {
// // this.getOrderList()
// })
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
// 监听用户下拉刷新事件
refreshEvent(event) {
},
// 上拉滚动加载
scrollLoadEvent() {
var nextPage = this.data.pagination.pageIndex + 1
if (nextPage <= this.data.pagination.total) {
this.setData({
'pagination.pageIndex': nextPage
}, () => {
this.getOrderList()
})
} else {
// 已经到底了
}
},
// 获取订单列表
getOrderList() {},
// 点击前往订单详情
gotoOrderDetails(event) {},
// 取消订单
handleCancelBtnEvent(event) {}
})
order.json
{
"usingComponents": {
"order-item": "/components/order-item/order-item",
"global-list": "/components/global-list/global-list",
"is-empty": "/components/is-empty/is-empty"
},
"navigationBarTitleText": "订单"
}
order.wxss
/* pages/order/order.wxss */
.m-order {
box-sizing: border-box;
}
config/order
由于我们显示列表需要数据,但是此案例并不提供后端接口,所以我们需要造个假数据。因此我们在 config 文件下,新建一个名为 order.js
的文件,内容如下:
export default {
"result": {
"total": 2,
"list": [
{
"totalQty": 1,
"shopLogo": "https://baike.baidu.com/pic/%E5%AE%89%E5%A6%AE%C2%B7%E6%B5%B7%E7%91%9F%E8%96%87/1127490/0/21a4462309f790529822d56771bbc0ca7bcb0a467f1b?fr=lemma&ct=single",
"surplusOverTime": null,
"soNo": "SO20200515000067",
"shopName": "言墨儿的小店",
"gmtCreate": 1589529277000,
"productInfo": [
{
"imgUrl": "https://baike.baidu.com/pic/%E5%AE%89%E5%A6%AE%C2%B7%E6%B5%B7%E7%91%9F%E8%96%87/1127490/0/21a4462309f790529822d56771bbc0ca7bcb0a467f1b?fr=lemma&ct=single",
"qty": 1,
"name": "哈根达斯草莓味冰淇淋",
"barcode": "074570022021"
}
],
"totalAmount": 1880,
"deliveryMode": 1,
"statusName": "交易取消",
"shopNo": "15013125889",
"deliveryStatus": 0,
"statusCode": "cancel"
},
{
"totalQty": 2,
"shopLogo": "https://baike.baidu.com/pic/%E5%AE%89%E5%A6%AE%C2%B7%E6%B5%B7%E7%91%9F%E8%96%87/1127490/0/21a4462309f790529822d56771bbc0ca7bcb0a467f1b?fr=lemma&ct=single",
"surplusOverTime": null,
"soNo": "SO20200515000063",
"shopName": "侬姝沁儿的小店",
"gmtCreate": 1589528910000,
"productInfo": [
{
"imgUrl": "",
"qty": 1,
"name": "蒙牛随变香草",
"barcode": "6923644251697"
},
{
"imgUrl": "https://baike.baidu.com/pic/%E5%AE%89%E5%A6%AE%C2%B7%E6%B5%B7%E7%91%9F%E8%96%87/1127490/0/21a4462309f790529822d56771bbc0ca7bcb0a467f1b?fr=lemma&ct=single",
"qty": 1,
"name": "光明三色冰淇淋",
"barcode": "6922214710015"
}
],
"totalAmount": 600,
"deliveryMode": 1,
"statusName": "交易完成",
"shopNo": "15013125889",
"deliveryStatus": 15,
"statusCode": "finish"
}
]
},
"message": "成功",
"statusCode": "200",
"status": true
}
结语
至此代码基本完成了。我们切换到订单页面,显示结果如下:
订单页提示:后面还有精彩敬请期待,请大家关注我的专题:web前端。如有意见可以进行评论,每一条评论我都会认真对待。
网友评论