美文网首页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