Ver Fonte

优化:收益页面下拉刷新位置调整

haiyang há 1 semana atrás
pai
commit
96a1b406c4

+ 5 - 1
src/components/IncomeItem.vue

@@ -26,7 +26,7 @@
                     ¥{{ data.changeAmount }}
                 </view>
                 <view class="income-header-menu-left-time">
-                    {{ data.settleTime }}
+                    {{ type === 1 ? data.settleTime : '预估' }}
                 </view>
             </view>
         </view>
@@ -101,6 +101,10 @@ const props = defineProps({
                 margin-bottom: 18rpx;
                 text-align: right;
             }
+
+            .income-header-menu-left-time {
+                text-align: right;
+            }
         }
     }
 }

+ 30 - 30
src/components/mescroll.vue

@@ -172,73 +172,73 @@ export default {
     computed: {
     // 是否使用fixed定位 (当height有值,则不使用)
     isFixed(){
-    return !this.height && this.fixed
+        return !this.height && this.fixed
     },
     // mescroll的高度
     scrollHeight(){
-    if (this.isFixed) {
-    return "auto"
-    } else if(this.height){
-    return this.toPx(this.height) + 'px'
-    }else{
-    return "100%"
-    }
+        if (this.isFixed) {
+            return "auto"
+        } else if(this.height){
+            return this.toPx(this.height) + 'px'
+        }else{
+            return "100%"
+        }
     },
     // 下拉布局往下偏移的距离 (px)
     numTop() {
-    return this.toPx(this.top)
+        return this.toPx(this.top)
     },
     fixedTop() {
-    return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
+        return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
     },
     padTop() {
-    return !this.isFixed ? this.numTop + 'px' : 0
+        return !this.isFixed ? this.numTop + 'px' : 0
     },
     // 上拉布局往上偏移 (px)
     numBottom() {
-    return this.toPx(this.bottom)
+        return this.toPx(this.bottom)
     },
     fixedBottom() {
-    return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
+        return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
     },
     padBottom() {
-    return !this.isFixed ? this.numBottom + 'px' : 0
+        return !this.isFixed ? this.numBottom + 'px' : 0
     },
     // 是否为重置下拉的状态
     isDownReset(){
-    return this.downLoadType===3 || this.downLoadType===4
+        return this.downLoadType===3 || this.downLoadType===4
     },
     // 过渡
     transition() {
-    return this.isDownReset ? 'transform 300ms' : '';
+        return this.isDownReset ? 'transform 300ms' : '';
     },
     translateY() {
-    return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+        return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
     },
     // 列表是否可滑动
     scrollable(){
-    if(this.disableScroll) return false
-    return this.downLoadType===0 || this.isDownReset
+        if(this.disableScroll) return false
+        return this.downLoadType===0 || this.isDownReset
     },
     // 是否在加载中
     isDownLoading(){
-    return this.downLoadType === 3
+        return this.downLoadType === 3
     },
     // 旋转的角度
     downRotate(){
-    return 'rotate(' + 360 * this.downRate + 'deg)'
+        return 'rotate(' + 360 * this.downRate + 'deg)'
     },
     // 文本提示
     downText(){
-    if(!this.mescroll) return ""; // 避免头条小程序初始化时报错
-    switch (this.downLoadType){
-    case 1: return this.mescroll.optDown.textInOffset;
-    case 2: return this.mescroll.optDown.textOutOffset;
-    case 3: return this.mescroll.optDown.textLoading;
-    case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess :
-    this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
-    default: return this.mescroll.optDown.textInOffset;
-    }
+        if(!this.mescroll) return ""; // 避免头条小程序初始化时报错
+        switch (this.downLoadType){
+            case 1: return this.mescroll.optDown.textInOffset;
+            case 2: return this.mescroll.optDown.textOutOffset;
+            case 3: return this.mescroll.optDown.textLoading;
+            case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess :
+                this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
+            default: return this.mescroll.optDown.textInOffset;
+        }
     }
     },
     methods: {

+ 6 - 4
src/custom-components/custom-mescroll/mescroll-body.vue

@@ -33,17 +33,19 @@
             <view v-if="mescroll.optUp.use && !isDownLoading && upLoadType !== 3" class="mescroll-upwarp"
                 :style="{ background: mescroll.optUp.bgColor, color: mescroll.optUp.textColor }">
                 <!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
-                <view v-show="upLoadType === 1">
+                <!-- <view v-show="upLoadType === 1">
                     <view class="upwarp-progress mescroll-rotate"
                         :style="{ 'border-color': mescroll.optUp.textColor }" />
                     <view class="upwarp-tip">
                         {{ mescroll.optUp.textLoading }}
                     </view>
-                </view>
+                </view> -->
                 <!-- 无数据 -->
-                <view v-if="upLoadType === 2" class="upwarp-nodata">
+                <!-- <view v-if="upLoadType === 2" class="upwarp-nodata">
                     {{ mescroll.optUp.textNoMore }}
-                </view>
+                </view> -->
+                <up-loadmore :status="upLoadType === 1 ? 'loading' : upLoadType === 2 ? 'nomore' : 'loadmore'"
+                    :loading-text="mescroll.optUp.textLoading" :nomore-text="mescroll.optUp.textNoMore" />
             </view>
         </view>
 

+ 218 - 186
src/pages/income/income.vue

@@ -1,4 +1,5 @@
 <script lang="ts" setup>
+import { onReachBottom } from '@dcloudio/uni-app'
 import { usePagination, useRequest } from 'alova/client'
 import { storeToRefs } from 'pinia'
 import { computed, ref, watch } from 'vue'
@@ -6,6 +7,7 @@ import { getAccountCount } from '@/api/home'
 import { getCouponIssuerAccountByPageMap } from '@/api/income'
 import IncomeItem from '@/components/IncomeItem.vue'
 import MescrollUni from '@/components/mescroll.vue'
+import MescrollBody from '@/custom-components/custom-mescroll/mescroll-body.vue'
 import { useShare } from '@/hooks/useShare'
 import { useUserStore } from '@/store'
 import { useTokenStore } from '@/store/token'
@@ -16,6 +18,7 @@ definePage({
     style: {
         navigationBarTitleText: '收益',
         navigationStyle: 'custom',
+        onReachBottomDistance: 157,
     },
 })
 
@@ -37,6 +40,7 @@ const showLoading = ref(false)
 const pickerDate = ref(Date.now())
 const pickerShow = ref(false)
 const totalAmount = ref(0)
+const mescrollHeight = ref(0)
 let mescroll = null
 
 // 新增:滚动距离和透明度
@@ -81,12 +85,21 @@ watch(() => incomeData.value, (newVal) => {
     }
 })
 
+watch(() => headerOverlayOpacity.value, (newVal) => {
+    if (newVal > 0) {
+        mescroll.lockDownScroll(true)
+    }
+    else {
+        mescroll.lockDownScroll(false)
+    }
+})
+
 // ============ mescroll 配置 ============
 const downOption = reactive({
     use: true,
     auto: false,
-    bgColor: '#ffffff',
-    textColor: '#666',
+    bgColor: '#ed6b66',
+    textColor: '#fff',
     textInOffset: '下拉刷新',
     textOutOffset: '释放刷新',
     textLoading: '刷新中...',
@@ -108,8 +121,6 @@ const upOption = reactive({
         btnText: '刷新试试',
         icon: '/static/images/mescroll-empty.png',
         fixed: false,
-        top: '200rpx',
-        zIndex: 1,
     },
     textNoMore: '--- 已经到底了 ---',
     toTop: {
@@ -117,6 +128,7 @@ const upOption = reactive({
         offset: 1000,
         bottom: 120,
     },
+    isBoth: false,
     // 开启虚拟列表优化(大数据量时)
     virtual: {
         use: false,
@@ -156,6 +168,17 @@ async function upCallback() {
     page.value++
 }
 
+onReachBottom(() => {
+    console.log('上拉加载触发')
+    if (isLastPage.value) {
+        // console.log(mescroll)
+        // mescroll.endSuccess(0, false)
+        return
+    }
+    mescroll.showUpScroll()
+    page.value++
+})
+
 onSuccess((response) => {
     let isNextPage = isLastPage.value
     // 此处处理alova isLastPage默认值为true,首次请求出现的无法加载下一页问题
@@ -202,6 +225,14 @@ onShow(async () => {
     }
 })
 
+onLoad(() => {
+    // 计算mescroll最小高度
+    const systemInfo = uni.getSystemInfoSync()
+    const vh100 = systemInfo.screenHeight - systemInfo.safeAreaInsets.bottom
+    const tabbarHeight = (systemInfo.screenWidth / 750) * 100
+    mescrollHeight.value = vh100 - tabbarHeight
+})
+
 // 创建分享hook实例
 const { getShareConfig, getTimelineShareConfig } = useShare()
 
@@ -227,69 +258,71 @@ function goPage(page: string) {
 </script>
 
 <template>
-    <view class="profile-container" :style="{ minHeight: `calc(100vh - 100rpx)` }">
-        <!-- <up-pull-refresh :refreshing="refreshing" :threshold="60" @refresh="onRefresh"> -->
-        <!-- 顶部区域 -->
-        <view class="income-header"
-            :style="{ background: `url(${getImageUrl('@img/income/income-bg.png')})`, backgroundSize: '100% 110%', backgroundPosition: 'left bottom', backgroundRepeat: 'no-repeat' }">
-            <view class="income-header-avatar-info"
-                :style="{ paddingTop: `calc(${safeAreaInsets.top}px + ${menuButtonInfo.height}px)` }">
-                <view class="income-header-balance">
-                    当前账户余额(元)
-                </view>
-                <view class="income-header-balance-num">
-                    <view class="income-header-balance-num-amount">
-                        {{ accountCountData?.balance || 0 }}
+    <view class="profile-container">
+        <view class="income-header-overlay"
+            :style="{ opacity: headerOverlayOpacity, height: `calc(${menuButtonInfo.bottom + 4}px + 50rpx)` }" />
+        <mescroll-body id="mescrollContainer" :height="`${mescrollHeight}px`" :sticky="true" :down="downOption"
+            :up="upOption" :fixed="false" :is-show-empty="true" @init="mescrollInit" @down="downCallback"
+            @up="upCallback" @emptyclick="reload">
+            <!-- 顶部区域 -->
+            <view class="income-header"
+                :style="{ background: `url(${getImageUrl('@img/income/income-bg.png')})`, backgroundSize: '100% 110%', backgroundPosition: 'left bottom', backgroundRepeat: 'no-repeat' }">
+                <view class="income-header-avatar-info"
+                    :style="{ paddingTop: `calc(${safeAreaInsets.top}px + ${menuButtonInfo.height}px)` }">
+                    <view class="income-header-balance">
+                        当前账户余额(元)
                     </view>
-                    <view class="income-header-balance-num-btns">
-                        <view v-if="isUnlock" class="income-header-balance-num-btn js" @click="goPage('unlockRewards')">
-                            解锁
+                    <view class="income-header-balance-num">
+                        <view class="income-header-balance-num-amount">
+                            {{ accountCountData?.balance || 0 }}
+                        </view>
+                        <view class="income-header-balance-num-btns">
+                            <view v-if="isUnlock" class="income-header-balance-num-btn js"
+                                @click="goPage('unlockRewards')">
+                                解锁
+                            </view>
                         </view>
                     </view>
                 </view>
-            </view>
-            <view class="income-header-tips">
-                <view class="income-header-tips-item">
-                    <view class="income-header-tips-item-num">
-                        {{ accountCountData?.withdrawableAmount || 0 }}
-                    </view>
-                    <view class="income-header-tips-item-des">
-                        可提现金额
-                    </view>
-                </view>
-                <view class="income-header-tips-item">
-                    <view class="income-header-tips-item-num">
-                        {{ accountCountData?.unsettledAmount || 0 }}
-                    </view>
-                    <view class="income-header-tips-item-des">
-                        未结算金额
+                <view class="income-header-tips">
+                    <view class="income-header-tips-item">
+                        <view class="income-header-tips-item-num">
+                            {{ accountCountData?.withdrawableAmount || 0 }}
+                        </view>
+                        <view class="income-header-tips-item-des">
+                            可提现金额
+                        </view>
                     </view>
-                </view>
-                <view class="income-header-tips-item">
-                    <view class="income-header-tips-item-num">
-                        {{ accountCountData?.totalCommission || 0 }}
+                    <view class="income-header-tips-item">
+                        <view class="income-header-tips-item-num">
+                            {{ accountCountData?.unsettledAmount || 0 }}
+                        </view>
+                        <view class="income-header-tips-item-des">
+                            未结算金额
+                        </view>
                     </view>
-                    <view class="income-header-tips-item-des">
-                        累计收益
+                    <view class="income-header-tips-item">
+                        <view class="income-header-tips-item-num">
+                            {{ accountCountData?.totalCommission || 0 }}
+                        </view>
+                        <view class="income-header-tips-item-des">
+                            累计收益
+                        </view>
                     </view>
                 </view>
             </view>
-        </view>
-        <!-- 公告 -->
-        <view v-if="isUnlock" class="income-header-notice">
-            <view class="income-header-notice-icon">
-                <image :src="getImageUrl('@img/income/notice.png')" mode="aspectFit" />
-            </view>
-            <view class="income-header-notice-content">
-                在考核周期内未达标已锁定收益,<text>前往查看~</text>
+            <!-- 公告 -->
+            <view v-if="isUnlock" class="income-header-notice">
+                <view class="income-header-notice-icon">
+                    <image :src="getImageUrl('@img/income/notice.png')" mode="aspectFit" />
+                </view>
+                <view class="income-header-notice-content">
+                    在考核周期内未达标已锁定收益,<text>前往查看~</text>
+                </view>
             </view>
-        </view>
-        <!-- 菜单 -->
-        <view class="income-header-menu" :style="{ marginTop: isUnlock ? '24rpx' : '-50rpx' }">
-            <view class="income-header-overlay"
-                :style="{ opacity: headerOverlayOpacity, height: `calc(${menuButtonInfo.bottom + 4}px + 50rpx)` }" />
             <!-- 结算筛选 -->
-            <view class="income-header-menu-filter" :style="{ top: `${menuButtonInfo.bottom + 4}px` }">
+            <view class="income-header-menu-filter"
+                :style="{ marginTop: isUnlock ? '24rpx' : '-50rpx', top: `${menuButtonInfo.bottom + 4}px` }">
                 <view class="income-header-menu-filter-item" :class="[activeIndex === 0 ? 'active' : '']"
                     @click="tabChange(0)">
                     待结算
@@ -312,21 +345,15 @@ function goPage(page: string) {
                 </view>
             </view>
             <view class="income-menu-list">
-                <mescroll-uni id="mescrollContainer" :down="downOption" :up="upOption" :fixed="false"
-                    :is-show-empty="true" @init="mescrollInit" @down="downCallback" @up="upCallback"
-                    @emptyclick="reload">
-                    <view v-for="item in incomeData" :key="item.userId" class="income-content-item">
-                        <income-item :data="item" :type="activeIndex" />
-                    </view>
-                    <!-- 滚动区域内的loading遮罩 -->
-                    <view v-if="showLoading && loading" class="loading-mask">
-                        <view class="loading-spinner" />
-                        <text class="loading-text">加载中...</text>
-                    </view>
-                </mescroll-uni>
+                <view v-for="item in incomeData" :key="item.userId" class="income-content-item">
+                    <income-item :data="item" :type="activeIndex" />
+                </view>
+                <!-- 滚动区域内的loading遮罩 -->
+                <view v-if="showLoading && loading" class="loading-mask">
+                    <up-loading-icon color="#ed6b66" text="加载中" />
+                </view>
             </view>
-        </view>
-        <!-- </up-pull-refresh> -->
+        </mescroll-body>
     </view>
 </template>
 
@@ -463,145 +490,150 @@ function goPage(page: string) {
         }
     }
 
-    .income-header-menu {
-        // background: #ffffff;
+    // 新增:吸顶区域上方的覆盖元素
+    .income-header-overlay {
+        position: fixed;
+        top: 0;
+        left: 0;
+        right: 0;
+        background-color: #ed6b66;
         border-radius: 10rpx 10rpx 0rpx 0rpx;
-        margin-left: 24rpx;
-        margin-right: 23rpx;
-        margin-bottom: 20rpx;
-        flex: 1;
-        display: flex;
-        flex-direction: column;
-        min-height: 0;
+        z-index: 99;
+        pointer-events: none; // 确保不影响点击事件
+    }
 
-        // 新增:吸顶区域上方的覆盖元素
-        .income-header-overlay {
-            position: fixed;
-            top: 0;
-            left: 0;
-            right: 0;
-            background-color: #ed6b66;
-            border-radius: 10rpx 10rpx 0rpx 0rpx;
-            z-index: 99;
-            pointer-events: none; // 确保不影响点击事件
-        }
+    .income-header-menu-filter {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        font-weight: 500;
+        font-size: 32rpx;
+        color: #333333;
+        height: 90rpx;
+        background: #ffffff;
+        box-shadow: 0rpx 3rpx 7rpx 0rpx rgba(213, 213, 213, 0.29);
+        border-radius: 10rpx 10rpx 0rpx 0rpx;
+        position: sticky;
+        top: 0;
+        z-index: 100;
 
-        .income-header-menu-filter {
+        .income-header-menu-filter-item {
+            flex: 1;
             display: flex;
-            justify-content: space-between;
+            justify-content: center;
             align-items: center;
-            font-weight: 500;
-            font-size: 32rpx;
-            color: #333333;
-            height: 90rpx;
-            background: #ffffff;
-            box-shadow: 0rpx 3rpx 7rpx 0rpx rgba(213, 213, 213, 0.29);
-            border-radius: 10rpx 10rpx 0rpx 0rpx;
-            position: sticky;
-            top: 0;
-            z-index: 100;
+            position: relative;
+            height: 100%;
 
-            .income-header-menu-filter-item {
-                flex: 1;
-                display: flex;
-                justify-content: center;
-                align-items: center;
-                position: relative;
-                height: 100%;
+            &.active {
+                color: #ed6b66;
 
-                &.active {
-                    color: #ed6b66;
-
-                    &:after {
-                        content: '';
-                        position: absolute;
-                        bottom: 0;
-                        left: 50%;
-                        transform: translateX(-50%);
-                        width: 67rpx;
-                        height: 6rpx;
-                        background: #ed6b66;
-                        border-radius: 3rpx;
-                    }
+                &:after {
+                    content: '';
+                    position: absolute;
+                    bottom: 0;
+                    left: 50%;
+                    transform: translateX(-50%);
+                    width: 67rpx;
+                    height: 6rpx;
+                    background: #ed6b66;
+                    border-radius: 3rpx;
                 }
             }
         }
+    }
+
+    .income-menu-time-filter {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        font-weight: 400;
+        font-size: 24rpx;
+        color: #333333;
+        padding: 30rpx 20rpx 14rpx;
+        background: #ffffff;
+        margin-top: 4rpx;
+        position: sticky;
+        z-index: 100;
 
-        .income-menu-time-filter {
+        .income-menu-time-filter-text {
+            font-weight: 400;
+            font-size: 26rpx;
+            color: #000000;
             display: flex;
-            justify-content: space-between;
             align-items: center;
-            font-weight: 400;
-            font-size: 24rpx;
-            color: #333333;
-            padding: 30rpx 20rpx 14rpx;
-            background: #ffffff;
-            margin-top: 4rpx;
-            position: sticky;
-            z-index: 100;
-
-            .income-menu-time-filter-text {
-                font-weight: 400;
-                font-size: 26rpx;
-                color: #000000;
-                display: flex;
-                align-items: center;
-                gap: 12rpx;
-            }
+            gap: 12rpx;
         }
+    }
 
-        .income-menu-list {
-            background: #ffffff;
-            // flex: 1;
-            display: flex;
-            flex-direction: column;
-            position: relative;
+    .income-menu-list {
+        background: #ffffff;
+        // flex: 1;
+        display: flex;
+        flex-direction: column;
+        position: relative;
 
-            #mescrollContainer {
-                height: 100%;
-                flex: 1;
-            }
+        #mescrollContainer {
+            height: 100%;
+            flex: 1;
+        }
 
-            .income-content-item {
-                margin-top: 20rpx;
-            }
+        .income-content-item {
+            margin-top: 20rpx;
+        }
 
-            // loading遮罩样式
-            .loading-mask {
-                position: absolute;
-                top: 0;
-                left: 0;
-                right: 0;
-                bottom: 0;
-                background-color: rgba(255, 255, 255, 0.8);
-                display: flex;
-                flex-direction: column;
-                justify-content: center;
-                align-items: center;
-                z-index: 999;
-            }
+        // loading遮罩样式
+        .loading-mask {
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background-color: rgba(255, 255, 255, 0.8);
+            display: flex;
+            flex-direction: row;
+            flex-wrap: nowrap;
+            justify-content: center;
+            align-items: end;
+            z-index: 999;
+        }
 
-            .loading-spinner {
-                width: 40rpx;
-                height: 40rpx;
-                border: 4rpx solid #e0e0e0;
-                border-top-color: #ed6b66;
-                border-radius: 50%;
-                animation: spin 1s linear infinite;
-            }
+        .loading-spinner {
+            width: 40rpx;
+            height: 40rpx;
+            border: 4rpx solid #e0e0e0;
+            border-top-color: #ed6b66;
+            border-radius: 50%;
+            animation: spin 1s linear infinite;
+        }
 
-            .loading-text {
-                margin-top: 16rpx;
-                font-size: 24rpx;
-                color: #666;
-            }
+        .loading-text {
+            margin-left: 16rpx;
+            font-size: 24rpx;
+            color: #666;
+        }
 
-            @keyframes spin {
-                to {
-                    transform: rotate(360deg);
-                }
+        @keyframes spin {
+            to {
+                transform: rotate(360deg);
             }
         }
     }
+
+    .income-header-menu-filter,
+    .income-menu-time-filter,
+    .income-menu-list {
+        margin-left: 24rpx;
+        margin-right: 23rpx;
+    }
+
+    :deep(.mescroll-empty) {
+        width: auto;
+        margin-left: 23rpx;
+        margin-left: 24rpx;
+        background-color: #fff;
+        border-bottom-left-radius: 10rpx;
+        border-bottom-right-radius: 10rpx;
+    }
 }
 </style>