美文网首页web前端
微信小程序开发入门实战 (五)开发订单页

微信小程序开发入门实战 (五)开发订单页

作者: 侬姝沁儿 | 来源:发表于2020-06-02 14:51 被阅读0次

上篇文章 微信小程序开发入门实战 (三)项目目录与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前端。如有意见可以进行评论,每一条评论我都会认真对待。

相关文章

网友评论

    本文标题:微信小程序开发入门实战 (五)开发订单页

    本文链接:https://www.haomeiwen.com/subject/lwiyzhtx.html