原生小程序如何进行下拉刷新 + 上划加载更多 + 时分秒倒计时功能 + 支付功能

199 阅读4分钟

概述:废话不多说,直接上代码

6c65d173620c8774b794e65f66620c4.png

DOM


<wxs src="../../../utils/common.wxs" module="model" />
<view class="main-container">
  <van-tabs
    swipeable
    color="#02BA4B"
    bind:change="tabChange"
    active="{{currentTab}}"
  >
    <van-tab title="待支付" name="1" />
    <van-tab title="已支付" name="3" />
    <van-tab title="已取消" name="5" />
    <van-tab title="全部" name="66" />
  </van-tabs>
  <scroll-view
    scroll-y
    refresher-enabled="{{true}}"
    refresher-triggered="{{loading}}"
    bindrefresherrefresh="onRefresh"
    bindscrolltolower="onLoadMore"
    style="height: calc(100vh - 100rpx)">
    <view class="m24-card-box pr25 pl25 pt25 pb25" wx:for="{{orderMyList}}" wx:key="index">
      <view class="box content-between align-items-center mb25">
        <view class="order-no">{{item.no}}</view>
        <view class="box">
          <!-- 待支付 -->
          <view wx:if="{{item.payStatus == 1 && item.remainPayTime > 0}}" class="remaining-time">
            <text class="mr20">剩余时间</text><text>{{item.countdownStr}}</text>
          </view>
          <!-- 支付完成 -->
          <view wx:if="{{item.payStatus == 3}}" class="pay-status">{{item.payNameStatus}}({{item.ticketNameStatus}})</view>
          <!--  5 已取消 -->
          <view wx:else class="{{ item.payStatus == 5 ? 'cancel-status' : 'pay-status' }}">{{item.payNameStatus}}</view>
        </view>
      </view>
      <view class="content-box">
        <view class="number-name">{{item.gradeName}}</view>
        <view class="detail-item">有效期:{{item.validTime}}个月</view>
        <view class="box content-between align-items-center">
          <view class="detail-item">下单时间:{{model.formatTimeCommon(item.payTime, "YYYY-MM-DD HH:MM")}}</view>
          <view>
             <text class="total-unit"></text><text class="total-money">{{model.toFixed(item.amount)}}</text>
          </view>
        </view>
      </view>
      <view wx:if="{{item.payStatus == 1}}" class="box content-end" bind:tap="clickSubmit" data-item="{{item}}">
        <view class="pay-btn btn">立即支付</view>
      </view>
    </view>
    <van-empty wx:if="{{orderMyList.length == 0 && !loading}}" description="暂无数据" />
    <block wx:else>
      <view wx:if="{{!loading && !hasMore}}" class="no-more">没有更多数据了</view>
      <view wx:else class="loading">加载中...</view>
    </block>
  </scroll-view>
</view>

js

// pages/test/index.js
// import { getPermit } from "@utils/permit.js"; // 引入权限模块
import { formatTime, throttle } from "@utils/util";
import { orderList, payOrder, bindWxUser } from "@service-api/mine/index";
const app = getApp();
Page({
  /**
   * 页面的初始数据
   */
  data: {
    openId: null,
    currentData: {},
    loading: false,
    orderMyList: [],
    currentTab: '1',
    queryParams: {
      page: 1,
      rows: 10,
      payStatus: '1',
      payPlatform: 2
    },
    hasMore: true, // 是否还有更多数据
    timer: null // 新增定时器
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
    this.setData({
      openId: options.openId,
    });
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady() {},

  /**
   * 生命周期函数--监听页面显示
   */
  onShow() {
    // getPermit(this); // 获取并设置页面权限
    this.getInit()
  },
  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide() {},

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload() {
    if (this.data.timer) {
      clearInterval(this.data.timer);
    }
  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh() {},

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom() {},

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage() {},

  // 切换tab
  tabChange(event) {
    this.setData({
      loading: true,
      orderMyList: [],
      'queryParams.page': 1,
      'queryParams.payStatus': event.detail.name == 66 ? '' : event.detail.name
    });
    this.getInit();
  },

  // 下拉刷新方法
  async onRefresh() {
    this.data.queryParams.page = 1; // 重置页码
    this.setData({ loading: true, orderMyList: [] });
    this.getInit();
  },

  // 上拉加载更多方法
  async onLoadMore() {
    if (!this.data.hasMore) return;
    this.data.queryParams.page += 1; // 增加页码
    this.getInit();
  },

  async getInit() {
    const { code, data } = await orderList(this.data.queryParams);
    if (code == 0) {
      if (data.rows) {
        let processedRows = data.rows.map((item) => {
          // payStatus 支付状态:1待支付, 3已支付,4支付失败,5支付关闭(取消),6已退款
          // ticketStatus  开票状态0:未开票1:已开票
          return {
            payNameStatus: 
              item.payStatus == 1 ? '待支付' : 
              item.payStatus == 3 ? '已支付' : 
              item.payStatus == 4 ? '支付失败' : 
              item.payStatus == 5 ? '支付关闭(取消)' : 
              item.payStatus == 6 ? '已退款' : '',
            ticketNameStatus: item.ticketStatus == 0 ? '未开票' : '已开票',
            ...item,
            countdown: item.remainPayTime,
            isCountdownEnd: false // 倒计时结束标记
          };
        });
        this.setData({
          loading: false,
          hasMore: data.rows.length < 10 ? false : true,
          orderMyList: this.data.queryParams.page == 1 ? processedRows : this.data.orderMyList.concat(processedRows)
        }, () => {
          this.startCountdown();
        });
      } else {
        this.setData({
          loading: false,
          hasMore: false,
        });
      }
    }
  },

  // 新增倒计时方法
  startCountdown() {
    if (this.data.timer) {
      clearInterval(this.data.timer);
    }
    
    const timer = setInterval(() => {
      const updatedList = this.data.orderMyList.map(item => {
        if (item.countdown > 0 && !item.isCountdownEnd) {
          const newCountdown = item.countdown - 1;
          // 修改:使用剩余秒数直接转换
          const hours = Math.floor(newCountdown / 3600);
          const minutes = Math.floor((newCountdown % 3600) / 60);
          const seconds = newCountdown % 60;
          const countdownStr = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
          
          return {
            ...item,
            countdown: newCountdown,
            countdownStr,
            isCountdownEnd: newCountdown <= 0
          };
        }
        return item;
      });
      this.setData({
        orderMyList: updatedList
      });
      
      // 检查是否所有倒计时都结束
      const allEnded = updatedList.every(item => item.countdown <= 0 || item.isCountdownEnd);
      if (allEnded) {
        clearInterval(timer);
      }
    }, 1000);
    
    this.setData({
      timer
    });
  },

  // 立即支付
  clickSubmit: throttle(function (e) {
    wx.showLoading({
      title: "支付中...",
    });
    this.setData({ currentData: e.currentTarget.dataset.item })
    this.getCreateOrder()
  }, 3000), // 设置节流间隔为 3000ms

  // 创建订单
  async getCreateOrder() {
    const params = {
      wxCode: '',
      payPlatform: 2,
      id: this.data.currentData.id
    }
    const { code, data } = await payOrder(params);
    if (code == 0) {
      this.getPayment(data)
    } else {
      wx.hideLoading()
    }
  },

  // 去支付
  async getPayment(data) {
    wx.requestPayment({
      timeStamp: data.jsapiResult.timeStamp,
      nonceStr: data.jsapiResult.nonceStr,
      package: data.jsapiResult.packageValue,
      signType: data.jsapiResult.signType,
      paySign: data.jsapiResult.paySign,
      success: () => {
        wx.showToast({
          title: '支付成功',
          icon: 'none',
          duration: 2000
        })
        this.data.queryParams.page = 1
        this.getInit()
        setTimeout(() => {
          this.getInit();
        }, 1000);
      },
      fail(err) {
        console.log(err)
        if (err.errMsg != "requestPayment:fail cancel") {
          wx.showToast({
            title: '支付失败:' + err.errMsg,
            icon: 'none',
            duration: 2000
          })
        } else {
          wx.showToast({
            title: '取消支付',
            icon: 'none',
            duration: 2000
          })
        }
      },
      complete: () => {}
    })
    wx.hideLoading()
  }
})

css

.loading, .no-more {
  text-align: center;
  padding: 20rpx 0;
  color: #999;
  font-size: 24rpx;
}
.main-container {
  overflow: hidden;
  height: 100vh;
}
.m24-card-box {
  .order-no {
    font-size: 28rpx;
    color: #83878EFF;
  }
  .remaining-time {
    font-size: 24rpx;
    color: #ed3b3bff;
    padding: 4rpx 8rpx;
    border-radius: 4px;
    background: #fdebebff;
    border: 1px solid #ed3b3bff;
  }
  .pay-status {
    font-size: 28rpx;
    color: #ed3b3bff;
    margin-left: 16rpx;
  }
  .cancel-status {
    font-size: 28rpx;
    margin-left: 16rpx;
    color: #83878EFF;
  }
  .content-box {
    padding: 24rpx;
    border-radius: 8px;
    background-color: #F7F8FCFF;
    .number-name {
      font-size: 32rpx;
      color: #303133ff;
      font-weight: 600;
      margin-bottom: 16rpx;
    }
    .detail-item {
      color: #83878eff;
      font-size: 28rpx;
    }
    .total-unit {
      font-size: 28rpx;
      color: #303133FF;
    }
    .total-money {
      font-size: 40rpx;
      color: #303133FF;
      font-weight: 600;
    }
  }
  .btn {
    font-size: 28rpx;
    padding: 8rpx 16rpx;
    border-radius: 8px;
    margin-top: 24rpx;
  }
  .cancel-btn {
    color: #83878eff;
    border: 2px solid #e5e6ebff;
  }
  .pay-btn {
    color: #165dffff;
    border: 2px solid #165dffff;
    margin-left: 24rpx;
  }
}

json

{
	"navigationBarTitleText": "我的订单",
	"navigationBarBackgroundColor": "#ffffff",
	"navigationBarTextStyle": "black",
	"usingComponents": {
		"van-icon": "@vant/weapp/icon/index",
		"van-action-sheet": "@vant/weapp/action-sheet/index",
		"van-popup": "@vant/weapp/popup/index",
		"van-dialog": "@vant/weapp/dialog/index",
		"van-picker": "@vant/weapp/picker/index",
		"van-tab": "@vant/weapp/tab/index",
		"van-tabs": "@vant/weapp/tabs/index",
		"van-empty": "@vant/weapp/empty/index"
	}
}

END...