小程序:实现下拉刷新和上拉加载更多功能

文章目录

一、下拉刷新

需求

实现下拉刷新小程序

分析

scroll-view 开启

  • refresher-enabled
    • refresher-triggered="{{refresherTriggered}}"
    • bindrefresherrefresh="onRefresh"
javascript 复制代码
data: {
    orders: [],
    // 视角
    cameraAngles: ['无人机视角', '地面全景', '飞达池特写'],
    progressSteps: [
      { step:0, label: '已下单', thumb: 'https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/pic1.jpg' },
      { step:2, label: '已封装', thumb: 'https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/pic2.jpg' },
      { step:3, label: '绕飞中', thumb: 'https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/pic3.jpg' },
      { step:4, label: '投放完成', thumb: 'https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/pic4.jpg' },
    ],
    // 分页与刷新
    pageNo: 1,
    pageSize: 10,
    hasMore: true,
    loading: false,
    refresherTriggered: false,
  },
onRefresh() {
    if (this.data.loading) return
    this.setData({ pageNo: 1, hasMore: true, refresherTriggered: true })
    this.getTradeOrderList({ append: false })
  },

二、上拉加载更多

需求

实现上拉加载更多数据的功能

分析

scroll-view 开启

  • refresher-enabled
    • refresher-triggered="{{refresherTriggered}}"
    • bindrefresherrefresh="onRefresh"
javascript 复制代码
data: {
    orders: [],
    // 视角
    cameraAngles: ['无人机视角', '地面全景', '飞达池特写'],
    progressSteps: [
      { step:0, label: '已下单', thumb: 'https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/pic1.jpg' },
      { step:2, label: '已封装', thumb: 'https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/pic2.jpg' },
      { step:3, label: '绕飞中', thumb: 'https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/pic3.jpg' },
      { step:4, label: '投放完成', thumb: 'https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/pic4.jpg' },
    ],
    // 分页与刷新
    pageNo: 1,
    pageSize: 10,
    hasMore: true,
    loading: false,
    refresherTriggered: false,
  },
onLoadMore() {
    if (this.data.loading || !this.data.hasMore) return

    console.log('触底加载更多,当前页码:', this.data.pageNo);
    this.setData({
      pageNo: this.data.pageNo + 1,
      loading: true
    });

    this.loadMoreOrders();
  },

  // 加载更多订单
  async loadMoreOrders() {
    try {
      const res = await fetchTradeOrderListApi({
        pageNo: this.data.pageNo,
        pageSize: this.data.pageSize,
      });

      if (res.code === 0 && res.data.list.length > 0) {
        // 为新订单计算飞行状态
        const newOrders = res.data.list.map(order => {
          let flightState = -1;

          if (order.flightInfo && typeof order.flightInfo.state === 'number') {
            flightState = order.flightInfo.state;
          } else if (order.state === 3) {
            flightState = 0; // 已下单
          }

          return {
            ...order,
            flightState: flightState
          };
        });

        // 判断是否还有更多数据
        const hasMore = newOrders.length >= this.data.pageSize;

        this.setData({
          orders: [...this.data.orders, ...newOrders],
          hasMore: hasMore,
          loading: false
        });

        console.log('加载更多完成,新增订单数:', newOrders.length, '是否还有更多:', hasMore);
      } else {
        // 没有更多数据
        this.setData({
          hasMore: false,
          loading: false
        });
        console.log('没有更多数据了');
      }
    } catch (err) {
      console.error('加载更多订单失败:', err);
      this.setData({ loading: false });
      wx.showToast({
        title: '加载失败',
        icon: 'none'
      });
    }
  },

wxml

javascript 复制代码
<!--pages/blessing-archive/archive.wxml-->
<navigation-bar title="档案" back="{{true}}" color="#FFF5D0" background="transparent" opacity="0" transparent="true"></navigation-bar>
<image class="bg-image" src="/assets/feiyuan/bg-home.jpg" mode="aspectFill"></image>

<scroll-view
  class="page-scroll"
  scroll-y="true"
  enhanced="true"
  show-scrollbar="false"
  bindscrolltolower="onLoadMore"
  refresher-enabled
  refresher-triggered="{{refresherTriggered}}"
  bindrefresherrefresh="onRefresh"
>
  <view class="page-container" wx:if="{{orders.length > 0}}">

    <!-- 展开的订单 -->
    <block wx:for="{{orders}}" wx:key="id" wx:for-index="parentIndex">
      <view class="order-item">
        <!-- 订单头部 -->
        <view class="order-header" bindtap="toggleOrder" data-id="{{item.id}}">
          <view class="order-id">
            <view class="order-id-text">{{item.tradeOrderNo}}</view>
            <view class="order-service-text">飞达进度</view>
          </view>
          <view style="display: flex; align-items: center;">
            <view wx:if="{{item.state === 4}}" class="order-status completed" bindtap="onViewCertificate" data-id="{{item.id}}">
              飞达证书
            </view>
            <view wx:else class="order-status processing">
              <block wx:if="{{item.state == 0}}">待支付</block>
              <block wx:elif="{{item.state == 1}}">待审核</block>
              <block wx:elif="{{item.state == 2}}">审核未通过</block>
              <block wx:elif="{{item.state == 3}}">飞达中</block>
              <block wx:elif="{{item.state == 4}}">已完成</block>
              <block wx:else>已取消</block>
            </view>
          </view>
        </view>

        <!-- 订单详情 -->
        <view wx:if="{{item.state === 4}}" class="order-detail">
          <!-- 视频区域 -->
          <view class="video-area" bindtap="onPlayVideo" data-parent-index="{{parentIndex}}">
            <image
              class="video-cover"
              src="https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/home-3.jpg"
              mode="aspectFill"
            />
            <view class="video-overlay"></view>
            <!-- <view class="video-play-btn">
              <view class="video-play-icon"></view>
            </view> -->
          </view>
          <!-- 视角切换 -->
          <view class="camera-tabs">
            <view
              class="camera-tab {{item.currentAngle === angleIndex ? 'active' : ''}}"
              wx:for="{{cameraAngles}}"
              wx:key="*this"
              wx:for-item="angle"
              wx:for-index="angleIndex"
              data-index="{{angleIndex}}"
              data-parent-index="{{parentIndex}}"
              bindtap="onAngleChange"
            >
              <text class="camera-tab-text">{{angle}}</text>
            </view>
          </view>
        </view>
        <view wx:else class="order-detail">
          <!-- 飞达进度 -->
          <view class="progress-card" style="display: flex; align-items: center; justify-content: space-around;" wx:if="{{item.state === 0}}">
             <view class="submit-btn submit-state1"  bindtap="payNow" data-id="{{item.tradeInfo.payOrderId}}">
              <text class="submit-btn-text">立即支付</text>
            </view>
            <view class="submit-btn submit-state2"  bindtap="closeOrder" data-id="{{item.tradeOrderId}}">
              <text class="submit-btn-text" style="color: #FF4D4F;">取消订单</text>
            </view>
          </view>
          <view class="progress-card" wx:else>
            <!-- <text class="progress-title">飞达进度</text> -->
            <view class="progress-steps">
              <view class="step-item" wx:for="{{progressSteps}}" wx:key="label" wx:for-item="feat">
                <!-- 步骤编号圆 -->

                <view class="step-top-row">
                  <view class="step-line-left {{feat.step === 0 ? 'hidden' : ''}} {{feat.step <= item.flightState ? 'done' : ''}}"></view>
                  <view class="step-circle {{feat.step <= item.flightState ? 'done' : ''}} {{feat.step === item.flightState ? 'active' : ''}}">
                    <image wx:if="{{feat.step <= item.flightState}}" class="step-icon" src="https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/status1-1.png" mode="aspectFill" />
                    <image wx:else class="step-icon" src="https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/status2-2.png" mode="aspectFill" />
                  </view>
                  <view class="step-line-right {{feat.step === progressSteps.length - 1 ? 'hidden' : ''}} {{feat.step < item.flightState ? 'done' : ''}}"></view>
                </view>
                <!-- 步骤名称 -->
                <text class="step-label {{feat.step <= item.flightState ? 'done' : ''}}">{{feat.label}}</text>
                <!-- 缩略图占位 -->
                <view class="step-thumb {{feat.step <= item.flightState ? 'done' : ''}}">
                  <image wx:if="{{feat.thumb}}" class="step-thumb-img" src="{{feat.thumb}}" mode="aspectFill" />
                </view>
              </view>
            </view>
          </view>
        </view>
      </view>
    </block>
  </view>
  <!-- ========== 无订单 - 缺省状态 ========== -->
  <block wx:else>
    <view class="no-video-container">
      <!-- 缺省图片 -->
      <image
        class="no-video-image"
        src="/assets/feiyuan/live-videono.png"
        mode="aspectFit"
      />

      <!-- 前往飞达页面 -->
      <view class="no-video-btn" bindtap="onGoToBlessing">
        <text class="no-video-btn-text">前往飞达</text>
      </view>
    </view>
  </block>

  <!-- 加载状态提示 -->
  <view class="loading-status" wx:if="{{loading}}">
    <text>加载中...</text>
  </view>
  <view class="loading-status" wx:elif="{{!hasMore && orders.length > 0}}">
    <text>没有更多数据了</text>
  </view>
</scroll-view>

js

javascript 复制代码
// pages/blessing-archive/archive.js
import {fetchTradeOrderListApi} from '../../api/prayApi.js'
import { cancelPayOrder } from '../../api/orderListApi.js'
import { getPayOrder,getPayOrderDetail,submitPayOrder } from '../../api/packageDetailApi.js'
Page({
  data: {
    orders: [],
    // 视角
    cameraAngles: ['无人机视角', '地面全景', '飞达池特写'],
    progressSteps: [
      { step:0, label: '已下单', thumb: 'https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/pic1.jpg' },
      { step:2, label: '已封装', thumb: 'https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/pic2.jpg' },
      { step:3, label: '绕飞中', thumb: 'https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/pic3.jpg' },
      { step:4, label: '投放完成', thumb: 'https://www.aaa.com/ifly-api/admin-api/infra/file/29/get/wxMiniProDefault/feiyuan/pic4.jpg' },
    ],
    // 分页与刷新
    pageNo: 1,
    pageSize: 10,
    hasMore: true,
    loading: false,
    refresherTriggered: false,
  },

  onShow() {
    // 页面显示
    this.getTradeOrderList({ append: false }); // 获取订单列表
  },

  // ===== 展开/收起订单 =====
  toggleOrder(e) {
    const id = e.currentTarget.dataset.id;
    const orders = this.data.orders;
    const order = orders.find(o => o.id === id);
    if (order) {
      order.expanded = !order.expanded;
      this.setData({ orders });
    }
  },

  // ===== 查看证书 =====
  onViewCertificate(e) {
    const id = e.currentTarget.dataset.id;
    wx.navigateTo({
      url: `/pages/certificate-detail/detail?tradeOrderNo=${id}`
    });
  },

  // ===== 展开折叠订单 =====
  expandCollapsedOrder(e) {
    const id = e.currentTarget.dataset.id;
    const collapsed = this.data.collapsedOrders;
    const order = collapsed.find(o => o.id === id);
    
    if (order) {
      // 模拟展开逻辑:将折叠项移到主列表
      const newOrder = {
        id: order.id,
        tradeOrderNo: order.tradeOrderNo,
        service: '基础版飞达服务',
        time: '2026-02-28 20:00',
        status: 'completed',
        statusText: '飞达完成',
        expanded: true
      };
      
      this.setData({
        orders: [...this.data.orders, newOrder],
        collapsedOrders: collapsed.filter(o => o.id !== id)
      });
    }
  },
  // ===== 视角切换 =====

  onAngleChange(e) {
    const childIndex = e.currentTarget.dataset.index; // 子循环索引
    console.log(e.currentTarget.dataset);
    const parentIndex = e.currentTarget.dataset.parentIndex; // 父循环索引
    const orders = this.data.orders;
    const order = orders[parentIndex];
    if (order) {
      order.currentAngle = childIndex;
      this.setData({ orders });
    }
  },

  // ===== 视频播放 =====

  onPlayVideo() {
    console.log('点击了视频视频播放');
    wx.showToast({ title: '视频加载中...', icon: 'none' });
  },
  // ===== 获取订单列表 =====
  async getTradeOrderList({ append }) {
    if (this.data.loading || (!this.data.hasMore && append)) return
    this.setData({ loading: true })
    const { pageNo, pageSize } = this.data
    try {
      const res = await fetchTradeOrderListApi({
        pageNo,
        pageSize,
      });
      if (res.code === 0) {
        // 为每个订单计算飞行状态
        const ordersWithFlightState = res.data.list.map(order => {
          let flightState = -1;

          // 如果 flightInfo 存在,使用 flightInfo.state
          if (order.flightInfo && typeof order.flightInfo.state === 'number') {
            flightState = order.flightInfo.state;
          } else if (order.state === 3) {
            // 如果 flightInfo 为 null 但 order.state === 3,显示已下单状态
            flightState = 0; // 已下单
          }

          return {
            ...order,
            flightState: flightState
          };
        });

        // 判断是否还有更多数据
        const hasMore = ordersWithFlightState.length >= pageSize;

        this.setData({
          orders: append ? [...this.data.orders, ...ordersWithFlightState] : ordersWithFlightState,
          hasMore: append ? this.data.hasMore && hasMore : hasMore,
          loading: false,
          refresherTriggered: false
        });

        console.log(`获取订单列表成功,共${append ? this.data.orders.length + ordersWithFlightState.length : ordersWithFlightState.length}条订单,是否还有更多:${hasMore}`);
      } else {
        this.setData({
          hasMore: false,
          loading: false,
          refresherTriggered: false
        });
      }
    } catch (err) {
      console.error('获取订单列表失败:', err);
      this.setData({ loading: false, refresherTriggered: false })
      wx.showToast({
        title: '获取失败',
        icon: 'none'
      });
    }
  },
  // ===== 立即支付 =====
  async payNow(e) {
    const payOrderId = e.currentTarget?.dataset?.id
    if (!payOrderId) {
      wx.showToast({ title: '支付信息缺失', icon: 'none' })
      return
    }

    try {
      const openid = wx.getStorageSync('openId')
      const pRes = await submitPayOrder({
        id: payOrderId,
        channelCode: 'wx_wish',
        channelExtras: { openid },
        openid,
      })

      if (pRes?.code !== 0) {
        throw new Error(pRes?.msg || '支付失败')
      }

      const payData = pRes?.data || {}
      const params = JSON.parse(payData.displayContent || '{}')
      const payParams = {
        timeStamp: params.timeStamp,
        nonceStr: params.nonceStr,
        package: params.packageValue,
        signType: params.signType,
        paySign: params.paySign,
        success: async () => {
          try {
            // 先同步查询订单状态,后台会把订单状态改为已支付/待使用
            const res = await getPayOrder(payOrderId, true)
            const merchantOrderId = res?.data?.merchantOrderId
            if (merchantOrderId) {
              await getPayOrderDetail(merchantOrderId, true)
            }
          } catch (err) {
            console.warn('同步支付结果失败', err)
          }
          wx.showToast({ title: '支付成功', icon: 'success' })
          // 刷新页面列表
          this.getTradeOrderList({ append: false })
        },
        fail: err => {
          console.warn('支付失败', err)
          wx.showToast({ title: '支付已取消', icon: 'none' })
        },
      }
      wx.requestPayment(payParams)
    } catch (err) {
      console.error('支付失败', err)
      wx.showToast({ title: err?.message || '支付失败', icon: 'none' })
    }
  },
  // ===== 关闭订单 =====
  closeOrder(e) {
    const id = e?.currentTarget?.dataset?.id
    if (!id) {
      wx.showToast({ title: '订单ID错误', icon: 'none' })
      return
    }

    wx.showModal({
      title: '提示',
      content: '确定要关闭订单吗?',
      success: async res => {
        if (!res.confirm) return
        try {
          wx.showLoading({ title: '取消中...' })
          // TODO: 更换调用取消订单接口,返回结果后刷新页面列表 
          wx.showToast({ title: '该功能正在开发中', icon: 'success' })
          wx.hideLoading()
          return 
          const result = await cancelPayOrder({ id })
          wx.hideLoading()
          if (result?.code === 0) {
            wx.showToast({ title: '已取消', icon: 'success' })
            // 刷新页面列表
            this.getTradeOrderList()
          } else {
            wx.showToast({
              title: result?.msg || '关闭失败',
              icon: 'none',
            })
          }
        } catch (err) {
          wx.hideLoading()
          console.error('关闭订单失败', err)
          wx.showToast({ title: '关闭失败', icon: 'none' })
        }
      },
    })
  },
  // ===== 前往飞达页面 =====
  onGoToBlessing() {
    wx.switchTab({
      url: '/pages/pray/pray'
    });
  },
  // ===== 下拉刷新 =====
  onRefresh() {
    if (this.data.loading) return
    this.setData({ pageNo: 1, hasMore: true, refresherTriggered: true })
    this.getTradeOrderList({ append: false })
  },

  // 触底加载更多
  onLoadMore() {
    if (this.data.loading || !this.data.hasMore) return

    console.log('触底加载更多,当前页码:', this.data.pageNo);
    this.setData({
      pageNo: this.data.pageNo + 1,
      loading: true
    });

    this.loadMoreOrders();
  },

  // 加载更多订单
  async loadMoreOrders() {
    try {
      const res = await fetchTradeOrderListApi({
        pageNo: this.data.pageNo,
        pageSize: this.data.pageSize,
      });

      if (res.code === 0 && res.data.list.length > 0) {
        // 为新订单计算飞行状态
        const newOrders = res.data.list.map(order => {
          let flightState = -1;

          if (order.flightInfo && typeof order.flightInfo.state === 'number') {
            flightState = order.flightInfo.state;
          } else if (order.state === 3) {
            flightState = 0; // 已下单
          }

          return {
            ...order,
            flightState: flightState
          };
        });

        // 判断是否还有更多数据
        const hasMore = newOrders.length >= this.data.pageSize;

        this.setData({
          orders: [...this.data.orders, ...newOrders],
          hasMore: hasMore,
          loading: false
        });

        console.log('加载更多完成,新增订单数:', newOrders.length, '是否还有更多:', hasMore);
      } else {
        // 没有更多数据
        this.setData({
          hasMore: false,
          loading: false
        });
        console.log('没有更多数据了');
      }
    } catch (err) {
      console.error('加载更多订单失败:', err);
      this.setData({ loading: false });
      wx.showToast({
        title: '加载失败',
        icon: 'none'
      });
    }
  },
});
相关推荐
2501_915909065 小时前
全面解析前端开发中常用的浏览器调试工具及其使用场景
android·ios·小程序·https·uni-app·iphone·webview
王者鳜錸10 小时前
企业解决方案十一-各类小程序定制开发
图像处理·人工智能·小程序·大模型·语音处理·定制开发
互联科技报10 小时前
商城小程序选择哪家平台比较好?预算有限也能选对!
大数据·小程序
小盼江11 小时前
Uniapp小程序鲜花商城推荐系统 买家卖家双端(web+uniapp)
前端·小程序·uni-app
盈建云系统12 小时前
小程序表单提交、input 双向绑定,最简洁写法
前端·小程序·apache
空中海1 天前
微信小程序 - 03 工程实践层与综合 Demo
微信小程序·小程序·notepad++
优睿远行1 天前
微信小程序云开发环境搭建与REST API混合架构实战
微信小程序·小程序
空中海1 天前
微信小程序 - 02 基础概念层与核心能力层
微信小程序·小程序
游戏开发爱好者81 天前
使用Fiddler设置HTTPS抓包诊断Power Query网络问题
android·ios·小程序·https·uni-app·iphone·webview