Browse Source

yxy代码

yzx 9 months ago
parent
commit
77c37fa777
35 changed files with 838 additions and 403 deletions
  1. 135 35
      app/admin/controller/borrow/BorrowApp.php
  2. 4 2
      app/admin/controller/borrow/BorrowApplication.php
  3. 36 25
      uniapp/pages/index/detail.vue
  4. 36 161
      uniapp/pages/index/index.vue
  5. 48 0
      uniapp/uni_modules/z-paging/changelog.md
  6. 7 2
      uniapp/uni_modules/z-paging/components/z-paging-cell/z-paging-cell.vue
  7. 1 1
      uniapp/uni_modules/z-paging/components/z-paging-swiper-item/z-paging-swiper-item.vue
  8. 1 1
      uniapp/uni_modules/z-paging/components/z-paging-swiper/z-paging-swiper.vue
  9. 9 7
      uniapp/uni_modules/z-paging/components/z-paging/components/z-paging-refresh.vue
  10. 20 3
      uniapp/uni_modules/z-paging/components/z-paging/css/z-paging-main.css
  11. 1 0
      uniapp/uni_modules/z-paging/components/z-paging/i18n/en.json
  12. 1 0
      uniapp/uni_modules/z-paging/components/z-paging/i18n/zh-Hans.json
  13. 1 0
      uniapp/uni_modules/z-paging/components/z-paging/i18n/zh-Hant.json
  14. 2 2
      uniapp/uni_modules/z-paging/components/z-paging/js/modules/back-to-top.js
  15. 25 12
      uniapp/uni_modules/z-paging/components/z-paging/js/modules/chat-record-mode.js
  16. 43 7
      uniapp/uni_modules/z-paging/components/z-paging/js/modules/data-handle.js
  17. 4 0
      uniapp/uni_modules/z-paging/components/z-paging/js/modules/i18n.js
  18. 9 3
      uniapp/uni_modules/z-paging/components/z-paging/js/modules/load-more.js
  19. 1 1
      uniapp/uni_modules/z-paging/components/z-paging/js/modules/loading.js
  20. 23 4
      uniapp/uni_modules/z-paging/components/z-paging/js/modules/nvue.js
  21. 134 16
      uniapp/uni_modules/z-paging/components/z-paging/js/modules/refresher.js
  22. 58 13
      uniapp/uni_modules/z-paging/components/z-paging/js/modules/scroller.js
  23. 55 28
      uniapp/uni_modules/z-paging/components/z-paging/js/modules/virtual-list.js
  24. 1 1
      uniapp/uni_modules/z-paging/components/z-paging/js/z-paging-constant.js
  25. 3 2
      uniapp/uni_modules/z-paging/components/z-paging/js/z-paging-enum.js
  26. 63 14
      uniapp/uni_modules/z-paging/components/z-paging/js/z-paging-interceptor.js
  27. 25 7
      uniapp/uni_modules/z-paging/components/z-paging/js/z-paging-main.js
  28. 25 4
      uniapp/uni_modules/z-paging/components/z-paging/js/z-paging-utils.js
  29. 2 4
      uniapp/uni_modules/z-paging/components/z-paging/wxs/z-paging-renderjs.js
  30. 7 5
      uniapp/uni_modules/z-paging/components/z-paging/wxs/z-paging-wxs.wxs
  31. 28 18
      uniapp/uni_modules/z-paging/components/z-paging/z-paging.vue
  32. 12 10
      uniapp/uni_modules/z-paging/package.json
  33. 11 10
      uniapp/uni_modules/z-paging/readme.md
  34. 6 4
      web/src/views/backend/borrow/borrowApplication/popupForm.vue
  35. 1 1
      web/src/views/backend/dashboard.vue

+ 135 - 35
app/admin/controller/borrow/BorrowApp.php

@@ -36,34 +36,133 @@ class BorrowApp extends Backend
         if ($this->request->param('select')) {
             $this->select();
         }
-        $user_id = $this->auth->id;
 
+        $type = $this->request->get()['type'];
+        $user_id = $this->auth->id;
         $user_group_id = Db::name('admin_group_access')->where('uid', $user_id)->value("group_id");
         list($where, $alias, $limit, $order) = $this->queryBuilder();
-
-        if ($user_group_id == 1 || $user_group_id == 2) {
-
-            $res = $this->model
-                ->alias($alias)
-                ->where($where)
-                ->order($order)
-                ->select();
-        } else if ($user_group_id == 3) {
-            $res = $this->model
-                ->alias($alias)
-                ->where($where)
-                ->where("status", 6)
-                ->where('purpose', 0)
-                ->order($order)
-                ->select();
-        } else if ($user_group_id == 4) {
-            $res = $this->model
-                ->alias($alias)
-                ->where($where)
-                ->where('user_id', $user_id)
-                ->order($order)
-                ->select();
+        if($type == 1){
+            if ($user_group_id == 1 || $user_group_id == 2) {
+                $res = $this->model
+                    ->alias($alias)
+                    ->where($where)
+                    ->where(function($query) {
+                        $query->where([
+                            ["status", "in", [0,2,6]]
+                        ]);
+                    })
+                    ->order($order)
+                    ->paginate($limit);
+            } else if ($user_group_id == 3) {
+                $res = $this->model
+                    ->alias($alias)
+                    ->where($where)
+                    ->where(function($query) {
+                        $query->where([
+                            ["status", "=", 6],
+                            ['purpose', '=', 0]
+                        ]);
+                    })
+                    ->whereOr(function($query) {
+                        $query->where([
+                            ["status", "=", 3],
+                            ['purpose', '=', 0],
+                            ['college_leader_id', '=',  $this->auth->id]
+                        ]);
+                    })
+                    ->order($order)
+                    ->paginate($limit);
+            } else if ($user_group_id == 4) {
+                $res = $this->model
+                    ->alias($alias)
+                    ->where($where)
+                    ->where(function($query) {
+                        $query->where([
+                            ["status", "=", [0,2,6]],
+                            ['user_id', '=', $user_id]
+                        ]);
+                    })
+                    ->order($order)
+                    ->paginate($limit);
+            }
+        }else if($type == 2){
+            if ($user_group_id == 1 || $user_group_id == 2) {
+                $res = $this->model
+                    ->alias($alias)
+                    ->where($where)
+                    ->where(function($query) {
+                        $query->where([
+                            ["status", "in", [3,5]]
+                        ]);
+                    })
+                    ->order($order)
+                    ->paginate($limit);
+            } else if ($user_group_id == 3) {
+                $res = $this->model
+                    ->alias($alias)
+                    ->where($where)
+                    ->where(function($query) {
+                        $query->where([
+                            ["status", "in", [3,5]],
+                            ['purpose', '=', 0],
+                            ['college_leader_id', '=',  $this->auth->id]
+                        ]);
+                    })
+                    ->order($order)
+                    ->paginate($limit);
+            } else if ($user_group_id == 4) {
+                $res = $this->model
+                    ->alias($alias)
+                    ->where($where)
+                    ->where(function($query) {
+                        $query->where([
+                            ["status", "=", [3,5]],
+                            ['user_id', '=', $user_id]
+                        ]);
+                    })
+                    ->order($order)
+                    ->paginate($limit);
+            }
+        }else if($type == 3){
+            if ($user_group_id == 1 || $user_group_id == 2) {
+                $res = $this->model
+                    ->alias($alias)
+                    ->where($where)
+                    ->where(function($query) {
+                        $query->where([
+                            ["status", "in", [1,4,7]]
+                        ]);
+                    })
+                    ->order($order)
+                    ->paginate($limit);
+            } else if ($user_group_id == 3) {
+                $res = $this->model
+                    ->alias($alias)
+                    ->where($where)
+                    ->where(function($query) {
+                        $query->where([
+                            ["status", "in",  [1,4,7]],
+                            ['purpose', '=', 0],
+                            ['college_leader_id', '=',  $this->auth->id]
+                        ]);
+                    })
+                    ->order($order)
+                    ->paginate($limit);
+            } else if ($user_group_id == 4) {
+                $res = $this->model
+                    ->alias($alias)
+                    ->where($where)
+                    ->where(function($query) {
+                        $query->where([
+                            ["status", "=", [1,4,7]],
+                            ['user_id', '=', $user_id]
+                        ]);
+                    })
+                    ->order($order)
+                    ->paginate($limit);
+            }
         }
+
         $this->success('', [
             'list' => $res,
             'remark' => get_route_remark(),
@@ -174,8 +273,12 @@ class BorrowApp extends Backend
             $data = $this->request->post()['data'];
             $this->BorrowTools->checkRules($data);
             $data = $this->BorrowTools->checkText($data);
+            //获取用户类型
+            $user_id = $this->auth->id;
+            $user_group_id = Db::name('admin_group_access')->where('uid', $user_id)->value("group_id");
 
-            if ($data['status'] == 1  ) {
+
+            if ($data['status'] == 1 ) {
                 if(!isset($data['annotation'])){
                     $this->error("驳回时请填写批注!");
                 }
@@ -192,9 +295,6 @@ class BorrowApp extends Backend
             }
 
             $data = $this->excludeFields($data);
-            //获取用户类型
-            $user_id = $this->auth->id;
-            $user_group_id = Db::name('admin_group_access')->where('uid', $user_id)->value("group_id");
 
             $this->model->startTrans();
             $result = false;
@@ -353,7 +453,7 @@ class BorrowApp extends Backend
             $asset_userArr = [];
             $admin_idArr = [];
             $open_idArr = [];
-        
+
             //获取消息推送目标人
             foreach ($data['asset'] as $k => $v){
                 $asset_user = Db::name('asset')->where('asset_name',$data['asset'][$k]['model'])->value('user');
@@ -396,7 +496,7 @@ class BorrowApp extends Backend
                     $text['data']['const6'] = ['教学借单'];
                 }
                 $result = $this->model->save($data);
-                
+
                 $data['accessories']['borrow_id'] = $this->model->id;
                 Db::name('accessories')->insert($data['accessories']);
                 $groupAccess = [];
@@ -419,14 +519,14 @@ class BorrowApp extends Backend
                 $this->error($e->getMessage());
             }
             if ($result !== false) {
-               
+
                 if ($open_idArr !== [] && $open_idArr !== null) {
-                    
+
                     foreach ($open_idArr as $k => $v){
                         $text['open_id'] = $open_idArr[$k];
                         if ( $open_idArr[$k] !== [] &&  $open_idArr[$k] !== null) {
                             //  halt($text);
-                             $res = self::wxAuditMessage($text);
+                            $res = self::wxAuditMessage($text);
                         }
                     }
 
@@ -435,7 +535,7 @@ class BorrowApp extends Backend
                     } else {
                         $this->success("更新成功,但消息发送失败,请检查网络或联系管理员");
                     }
-                    
+
                 }else{
                     // halt(2);
                     $this->success('更新成功!但对方未配置公众号,消息提示失败!');
@@ -492,7 +592,7 @@ class BorrowApp extends Backend
             'appid' => 'wx58633590ab59b7a1',
             'pagepath' => ''
         ];
-       
+
         $result = OaService::getInstance()->sendTemplateMessage($openid, $template_id, $data, $url, $miniprogram);
 
         return $result;

+ 4 - 2
app/admin/controller/borrow/BorrowApplication.php

@@ -173,14 +173,16 @@ class BorrowApplication extends Backend
             $data = $this->request->post();
             $data = $this->BorrowTools->checkText($data);
             $data = $this->excludeFields($data);
+
             //获取用户类型
             $user_id = $this->auth->id;
-            if($user_id == $data['user_id']){
+            $user_group_id = Db::name('admin_group_access')->where('uid', $user_id)->value("group_id");
+
+            if($user_id == $data['user_id'] && $user_group_id == 4){
                 $data['annotation'] = '';
                 $data['college_annotation'] = '';
             }
 
-            $user_group_id = Db::name('admin_group_access')->where('uid', $user_id)->value("group_id");
             $open_id = Db::name('oauth_log')->where('user_id', $data['user_id'])->value('opid');
             if ($open_id !== null) {
                 $text = [

+ 36 - 25
uniapp/pages/index/detail.vue

@@ -582,37 +582,48 @@
 
 	// 审批驳回
 	async function overrule() {
-		if (userInfo.userInfo.group != 3) {
-			datailData.value.status = 1
-		} else {
-			datailData.value.status = 7
-		}
-		datailData.value.annotation = remark.value
-		console.log('datailData.value', datailData.value)
-		wx.showLoading({
-			title: '加载中',
-		})
-		const overruleData = await requestApi(
-			'admin/borrow.BorrowApp/edit?server=1', {
-				data: datailData.value
-			},
-			'POST'
-		)
-		console.log('overruleData', overruleData)
-		jiazhai()
-		if (overruleData.code == 0) {
+		
+		if(remark.value == null || remark.value ==''){
 			wx.showModal({
 				title: '提示',
-				content: overruleData.msg,
+				content: "驳回时请填写批注",
 				showCancel: false
 			})
+		}else{
+			if (userInfo.userInfo.group != 3) {
+				datailData.value.status = 1
+				datailData.value.annotation = remark.value
+			} else {
+				datailData.value.status = 7
+				datailData.value.college_annotation = remark.value
+			}
+			console.log('datailData.value', datailData.value)
+			wx.showLoading({
+				title: '加载中',
+			})
+			const overruleData = await requestApi(
+				'admin/borrow.BorrowApp/edit?server=1', {
+					data: datailData.value
+				},
+				'POST'
+			)
+			console.log('overruleData', overruleData)
+			jiazhai()
+			if (overruleData.code == 0) {
+				wx.showModal({
+					title: '提示',
+					content: overruleData.msg,
+					showCancel: false
+				})
+			}
+		
+			console.log('data', xiangqinData.value)
+			setTimeout(function() {
+				wx.hideLoading()
+			}, 1000)
 		}
-
 		
-		console.log('data', xiangqinData.value)
-		setTimeout(function() {
-			wx.hideLoading()
-		}, 1000)
+		
 	}
 
 	// 科研借出扫码

+ 36 - 161
uniapp/pages/index/index.vue

@@ -5,152 +5,19 @@
 			<uv-search placeholder="请输入搜索内容" v-model="keyword"></uv-search>
 		</view> -->
 		<view class="">
+			   
 		<!-- 使用z-paging-swiper为根节点可以免计算高度 -->
-		<z-paging-swiper>
-			<!-- 需要固定在顶部不滚动的view放在slot="top"的view中 -->
-			<!-- 注意!此处的z-tabs为独立的组件,可替换为第三方的tabs,若需要使用z-tabs,请在插件市场搜索z-tabs并引入,否则会报插件找不到的错误 -->
-			<template #top>
+		 <z-paging-swiper>
+  			<template #top>
 				<z-tabs ref="tabs" :list="tabList" :current="current" @change="tabsChange" />
 			</template>
 			<!-- swiper必须设置height:100%,因为swiper有默认的高度,只有设置高度100%才可以铺满页面  -->
-			<swiper class="swiper" :current="current" @transition="swiperTransition"
-				@animationfinish="swiperAnimationfinish">
+			<swiper class="swiper" :current="current" @transition="swiperTransition" @animationfinish="swiperAnimationfinish">
 				<swiper-item class="swiper-item" v-for="(item, index) in tabList" :key="index">
 					<!-- 这里的swiper-list-item为demo中为演示用定义的组件,列表及分页代码在swiper-list-item组件内 -->
 					<!-- 请注意,swiper-list-item非z-paging内置组件,在自己的项目中必须自己创建,若未创建则会报组件不存在的错误 -->
-					<swiper-list-item :tabIndex="index" :currentIndex="current">
-						<block v-if="index==0">
-							<view class="" style="overflow-y: scroll !important;height: 94vh;">
-
-								<block v-if="data.some(item => item.status==0 || item.status==2 || item.status==6)">
-								<block v-for="(items, indexs) in data" :key="indexs">
-									<block v-if="items.status==0 || items.status==2 || items.status==6">
-										<view class="jie_card" @click="detail(items.id)">
-											<view class="jie_card_title">
-												<text class="jie_card_title_text">借单号:{{items.encoding}}</text>
-												<text class="jie_tags" v-if="items.purpose==1">教学借单</text>
-												<text class="jie_tags jie_tags2" v-if="items.purpose==0">科研借单</text>
-											</view>
-											<uv-line></uv-line>
-											<view class="jie_contend">
-												<view class="jie_contend_text">
-													<text class="jie_contend_text1">借取人:</text>
-													<text class="jie_contend_text2">{{items.username}}</text>
-												</view>
-												<view class="jie_contend_text">
-													<text class="jie_contend_text1">联系电话:</text>
-													<text class="jie_contend_text2">{{items.mobile}}</text>
-												</view>
-												<view class="jie_contend_text">
-													<text class="jie_contend_text1">申请时间:</text>
-													<text class="jie_contend_text2">{{items.create_time}}</text>
-												</view>
-											</view>
-											<view class="jie_card_ztai"
-												v-if="items.status==6">
-												学院审批</view>
-											<view class="jie_card_ztai" v-if="items.status==0">待审批
-											</view>
-											<view class="jie_card_ztai jie_card_ztai2" v-if="items.status==2 ">待使用
-											</view>
-										</view>
-									</block>
-								</block>
-								</block>
-								<view class=""  v-else>
-									<uv-empty text="暂无数据"  icon="https://cdn.uviewui.com/uview/empty/list.png"></uv-empty>
-								</view>
-								<view style="width: 100%; height: 5vh;">
-
-								</view>
-							</view>
-						</block>
-						<block v-else-if="index==1">
-							<view class="" style="overflow-y: scroll !important;height: 90vh;">
-								<block v-if="data.some(item => item.status === 3 || item.status === 5)">
-								<block v-for="(items, indexs) in data" :key="indexs">
-									<block v-if="items.status==3 || items.status==5">
-										<view class="jie_card" @click="detail(items.id)">
-											<view class="jie_card_title">
-												<text class="jie_card_title_text">借单号:{{items.encoding}}</text>
-
-												<text class="jie_tags" v-if="items.purpose==1">教学借单</text>
-												<text class="jie_tags jie_tags2" v-if="items.purpose==0">科研借单</text>
-											</view>
-											<uv-line></uv-line>
-											<view class="jie_contend">
-												<view class="jie_contend_text">
-													<text class="jie_contend_text1">借取人:</text>
-													<text class="jie_contend_text2">{{items.username}}</text>
-												</view>
-												<view class="jie_contend_text">
-													<text class="jie_contend_text1">联系电话:</text>
-													<text class="jie_contend_text2">{{items.mobile}}</text>
-												</view>
-												<view class="jie_contend_text">
-													<text class="jie_contend_text1">申请时间:</text>
-													<text class="jie_contend_text2">{{items.create_time}}</text>
-												</view>
-											</view>
-											<view class="jie_card_ztai" v-if="items.status==5">已逾期</view>
-											<view class="jie_card_ztai jie_card_ztai2" v-if="items.status==3">使用中</view>
-										</view>
-									</block>
-								</block>
-								</block>
-								<view class="" v-else>
-									<uv-empty text="暂无数据"  icon="https://cdn.uviewui.com/uview/empty/list.png"></uv-empty>
-								</view>
-								<view style="width: 100%; height: 5vh;">
-
-								</view>
-							</view>
-						</block>
-						<block v-else-if="index==2">
-							<view class="" style="overflow-y: scroll !important;height: 90vh;">
-								
-								<block v-if="data.some(item => item.status==1 || item.status==4 || item.status==7)">
-								<block v-for="(items, indexs) in data" :key="indexs">
-									<block v-if="items.status==1 || items.status==4 || items.status==7 ">
-										<view class="jie_card" @click="detail(items.id)">
-											<view class="jie_card_title">
-												<text class="jie_card_title_text">借单号:{{items.encoding}}</text>
-
-												<text class="jie_tags" v-if="items.purpose==1">教学借单</text>
-												<text class="jie_tags jie_tags2" v-if="items.purpose==0">科研借单</text>
-											</view>
-											<uv-line></uv-line>
-											<view class="jie_contend">
-												<view class="jie_contend_text">
-													<text class="jie_contend_text1">借取人:</text>
-													<text class="jie_contend_text2">{{items.username}}</text>
-												</view>
-												<view class="jie_contend_text">
-													<text class="jie_contend_text1">联系电话:</text>
-													<text class="jie_contend_text2">{{items.mobile}}</text>
-												</view>
-												<view class="jie_contend_text">
-													<text class="jie_contend_text1">申请时间:</text>
-													<text class="jie_contend_text2">{{items.create_time}}</text>
-												</view>
-											</view>
-											<view class="jie_card_ztai" v-if="items.status==1">已驳回</view>
-											<view class="jie_card_ztai" v-if="items.status==7">领导驳回</view>
-											<view class="jie_card_ztai jie_card_ztai2" v-if="items.status==4">已归还</view>
-										</view>
-									</block>
-								</block>
-								</block>
-								<view class=""  v-else>
-									<uv-empty text="暂无数据"  icon="https://cdn.uviewui.com/uview/empty/list.png"></uv-empty>
-								</view>
-								<view style="width: 100%; height: 5vh;">
-								
-								</view>
-							</view>
-						</block>
+					<swiper-list-item ref="listItem" :tabIndex="index" :currentIndex="current"> 
 					</swiper-list-item>
-
 				</swiper-item>
 			</swiper>
 		</z-paging-swiper>
@@ -174,15 +41,10 @@
 			<image src="../../static/img/sqi.png" mode="widthFix" style="width: 50rpx;margin-bottom: -12rpx;"></image>
 			仪器借用
 		</view>
-		
-			<!-- <web-view v-if="stuts" src="http://yxy.glut.cc/api/xmwechat.Offiaccount/oauth?server=1"></web-view> -->
-			<!-- <image :show-menu-by-longpress="true" src="../../static/img/erweima.jpg" mode="widthFix" style="width: 100%;"></image> -->
-			<!-- <button @click="wxgz" type="primary" style="background-color: #2d8cf0; margin-bottom: 30rpx;">关注公众号</button> -->
 </template>
 <script setup>
-	import {
-		ref
-	} from 'vue'
+    import { ref } from 'vue';
+
 	import {
 		requestApi
 	} from '@/api/request.js'
@@ -194,21 +56,13 @@
 	let wxpopup = ref(null)
 	let userInfo =wx.getStorageSync('userInfos')
 	let stuts = ref(false)
+	let current =ref(0);
 	// console.log('w',userInfo)
 	// 获取后端需要的token认证
 	// 使用wx.getStorageSync('userInfo')从本地存储中获取用户令牌信息并将其赋值给user_Token变量。
 	const data = ref([])
 	onShow(async () => {
-		
-		wx.showLoading({
-			title: '加载中',
-		})
-
-		const res = await requestApi('index.php/admin/borrow.borrowApp/apply')
-		wx.hideLoading()
-		// console.log(res)
-		data.value = res.data.list
-		// console.log('1',data.value)
+		// reloadCurrentList()
 		const ress= await requestApi(
 		'api/xmwechat.Offiaccount/isBind?server=1',
 		{
@@ -223,7 +77,26 @@
 			wxpopup.value.open('top');
 		}
 	})
-	
+	const paging = ref(null);
+	const dataList = ref([])
+	// let pageNo = 1;
+	// let pageSize = 30;
+	const listItem = ref();
+	// const queryList = async (pageNo, pageSize) => {
+	// 	console.log("执行力”")
+	// 	// 此处请求仅为演示,请替换为自己项目中的请求
+	// 	await requestApi('index.php/admin/borrow.borrowApp/apply',{
+	// 		page:pageNo,
+	// 		limit:pageSize
+	// 	}).then(res => {
+	// 		// 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
+ //            paging.value.complete(res.data.list.data);
+	// 		console.log(res.data.list.data)
+ //        }).catch(res => { 
+	// 		console.log(res)
+	// 		paging.value.complete(false);
+	// 	})
+	// }
 	// 进详情页
 	function detail(id) {
 		wx.navigateTo({
@@ -236,11 +109,11 @@
 			url: '/pages/teacher/application'
 		})
 	}
-	const paging = ref(null);
-	const tabs = ref(null);
-	const current = ref(0);
+	
+	const tabs = ref(null); 
 	const tabList = ref(['待审批/待使用', '使用中/逾期', '已驳回/已归还']);
-
+	
+	
 	// tabs通知swiper切换
 	const tabsChange = (index) => {
 		current.value = index;
@@ -256,7 +129,9 @@
 		current.value = e.detail.current;
 		tabs.value.unlockDx();
 	}
-
+	const reloadCurrentList = () => {
+		listItem.value[current.value].reload();
+	}
 </script>
 
 <style lang="less">

+ 48 - 0
uniapp/uni_modules/z-paging/changelog.md

@@ -1,3 +1,51 @@
+## 2.7.12(2024-09-22)
+1.`新增` 虚拟列表+吸顶演示(一般写法)演示&`@virtualPlaceholderTopHeight`,用于监听虚拟列表顶部占位高度改变。  
+2.`新增` 聊天记录模式+虚拟列表演示&虚拟列表兼容说明。  
+3.`新增` 聊天记录模式中长按cell显示操作pop的示例和说明。  
+4.`修复` nvue中底部固定加载更多高度未跟随unit适配的问题。  
+5.`修复` 在iOS中可能出现的本地分页空白的问题。  
+6.`修复` `to-bottom-loading-more-enabled` 无效的问题。  
+7.`优化` 在聊天记录模式中,点击返回顶部按钮调整为点击返回底部。  
+8.`优化`  `refresher-enabled`设置为false时调用`reload(true)`也允许显示下拉刷新view。  
+## 2.7.11(2024-06-28)
+1.`新增` 方法`updateVirtualListRender`,支持手动触发虚拟列表渲染更新。  
+2.`新增` 延迟加载列表演示。  
+3.`修复` v2.7.8引出的vue3+npm+微信小程序中,`uni.$zp`配置失效的问题。  
+4.`修复` 在本地分页+虚拟列表情况下,虚拟列表cell未被正常销毁的问题。  
+5.`修复` 打开调试模式下无法获取getApp导致的`cannot read property 'zp_handleQueryCallback' of undefined`的报错。  
+6.`修复` 极小概率出现的分页请求较快且快速滚动到底部时未能加载更多的问题。  
+## 2.7.10(2024-05-10)
+1.`修复` v2.7.8引出的vue3+npm+微信小程序中,uni.$zp配置失效的问题。  
+## 2.7.9(2024-05-10)
+1.`修复` 在新版HbuilderX+vue3+微信小程序中可能出现的加载第二页数据后返回顶部无法下拉的问题。  
+2.`修复` 在vue3+抖音小程序中可能出现的首次加载reload没有触发的问题。  
+3.`优化` ts类型中,`ZPagingInstance`泛型不必填,默认为`any`。  
+## 2.7.8(2024-05-09)
+1.`新增` typescript类型声明文件,感谢小何同学提供。  
+2.`新增` 添加极简写法fetch相关配置及示例。  
+3.`新增` `fixed-cellHeight`配置,支持在虚拟列表中自定义固定的cell高度。  
+4.`新增` `refresher-refreshing-scrollable`配置,支持自定义下拉刷新中是否允许列表滚动。  
+5.`修复` 在新版HubilderX+vue3+h5中,`swiper-demo`模式`slot=top`被导航栏遮挡的问题。  
+6.`修复` 下拉进入二楼偶现的二楼高度未铺满全屏的问题。  
+7.`修复` 虚拟列表中complete若传相同对象会导致key重复的问题。  
+8.`修复` 在虚拟列表中调用refresh后缓存高度原始数据未清空的问题。  
+9.`修复` 聊天记录模式删除记录时顶部显示loading的问题。  
+9.`优化` `scrollIntoViewByIndex`支持在虚拟列表中滚动到指定cell。  
+10.`优化` `content-z-index`默认值修改为1。  
+## 2.7.7(2024-04-01)
+1.`新增` 下拉进入二楼功能及相关配置&demo。  
+2.`新增` 虚拟列表写法添加【非内置列表】写法,可良好兼容vue3中的各平台并有较优的性能表现。  
+3.`新增` `z-paging-cell`补充`@touchstart`事件。  
+4.`修复` 页面滚动模式设置了`auto-full-height`后可能出现的依然有异常空白占位的问题和下拉刷新时列表数据被切割的问题。  
+## 2.7.6(2024-02-29)
+1.`新增` `max-width`,支持设置`z-paging`的最大宽度,默认`z-paging`宽度铺满窗口。  
+2.`新增` `chat-adjust-position-offset`,支持设置使用聊天记录模式中键盘弹出时占位高度偏移距离。    
+3.`修复` 由于renderjs中聊天记录模式判断不准确导致的可能出现的从聊天记录页面跳转到其他页面后返回页面无法滚动的问题。  
+4.`修复` 聊天记录模式首次加载失败后,发送消息顶部会显示加载失败文字的问题。  
+5.`修复` 聊天记录模式nvue可能出现的键盘弹出无法滚动到底部的问题。  
+6.`修复` 聊天记录模式+nvue滚动条无法展示的问题&底部会显示加载中的问题。  
+7.`修复` 聊天记录模式监听键盘弹出可能出现的无法正常销毁的问题。  
+8.`修复` 直接修改dataList的值组件内部的值未更新的问题。  
 ## 2.7.5(2024-01-23)
 1.`新增` props:`chat-loading-more-default-as-loading`,支持设置在聊天记录模式中滑动到顶部状态为默认状态时,以加载中的状态展示。  
 2.`新增` slots:`chatNoMore`,支持自定义聊天记录模式没有更多数据view。  

+ 7 - 2
uniapp/uni_modules/z-paging/components/z-paging-cell/z-paging-cell.vue

@@ -6,12 +6,12 @@
 <!-- z-paging-cell,用于在nvue中使用cell包裹,vue中使用view包裹 -->
 <template>
 	<!-- #ifdef APP-NVUE -->
-	<cell :style="[cellStyle]">
+	<cell :style="[cellStyle]" @touchstart="onTouchstart">
 		<slot />
 	</cell>
 	<!-- #endif -->
 	<!-- #ifndef APP-NVUE -->
-	<view :style="[cellStyle]">
+	<view :style="[cellStyle]" @touchstart="onTouchstart">
 		<slot />
 	</view>
 	<!-- #endif -->
@@ -28,6 +28,11 @@
                     return {}
                 }
 			}
+		},
+		methods: {
+			onTouchstart(e) {
+				this.$emit('touchstart', e);
+			}
 		}
 	}
 </script>

+ 1 - 1
uniapp/uni_modules/z-paging/components/z-paging-swiper-item/z-paging-swiper-item.vue

@@ -9,7 +9,7 @@
 		<z-paging ref="paging" :fixed="false" 
 			:auto="false" :useVirtualList="useVirtualList" :useInnerList="useInnerList" :cellKeyName="cellKeyName" :innerListStyle="innerListStyle" 
 			:preloadPage="preloadPage" :cellHeightMode="cellHeightMode" :virtualScrollFps="virtualScrollFps" :virtualListCol="virtualListCol"
-			@query="_queryList" @listChange="_updateList" style="height: 100%;">
+			@query="_queryList" @listChange="_updateList">
 			<slot />
 			<template #header>
 				<slot name="header"/>

+ 1 - 1
uniapp/uni_modules/z-paging/components/z-paging-swiper/z-paging-swiper.vue

@@ -72,7 +72,7 @@
 		},
 		computed: {
 			finalSwiperStyle() {
-				const swiperStyle = this.swiperStyle;
+				const swiperStyle = { ...this.swiperStyle };
 				if (!this.systemInfo) return swiperStyle;
 				const windowTop = this.windowTop;
 				const windowBottom = this.systemInfo.windowBottom;

+ 9 - 7
uniapp/uni_modules/z-paging/components/z-paging/components/z-paging-refresh.vue

@@ -50,7 +50,7 @@
 				}
 			};
 		},
-		props: ['status', 'defaultThemeStyle', 'defaultText', 'pullingText', 'refreshingText', 'completeText', 'defaultImg', 'pullingImg', 
+		props: ['status', 'defaultThemeStyle', 'defaultText', 'pullingText', 'refreshingText', 'completeText', 'goF2Text', 'defaultImg', 'pullingImg', 
 			'refreshingImg', 'completeImg', 'refreshingAnimated', 'showUpdateTime', 'updateTimeKey', 'imgStyle', 'titleStyle', 'updateTimeStyle', 'updateTimeTextMap', 'unit'
 		],
 		computed: {
@@ -60,7 +60,7 @@
 			// 当前状态数组
 			statusTextArr() {
 				this.updateTime();
-				return [this.defaultText, this.pullingText, this.refreshingText, this.completeText];
+				return [this.defaultText, this.pullingText, this.refreshingText, this.completeText, this.goF2Text];
 			},
 			// 当前状态文字
 			currentTitle() {
@@ -85,16 +85,18 @@
 				if (status === R.Default) {
 					if (!!this.defaultImg) return this.defaultImg;
 					return this.zTheme.arrow[this.ts];
-				} else if (status  === R.ReleaseToRefresh) {
+				} else if (status === R.ReleaseToRefresh) {
 					if (!!this.pullingImg) return this.pullingImg;
 					if (!!this.defaultImg) return this.defaultImg;
 					return this.zTheme.arrow[this.ts];
-				} else if (status  === R.Loading) {
+				} else if (status === R.Loading) {
 					if (!!this.refreshingImg) return this.refreshingImg;
 					return this.zTheme.flower[this.ts];;
-				} else if (status  === R.Complete) {
+				} else if (status === R.Complete) {
 					if (!!this.completeImg) return this.completeImg;
 					return this.zTheme.success[this.ts];;
+				} else if (status === R.GoF2) {
+					return this.zTheme.arrow[this.ts];
 				}
 				return '';
 			},
@@ -197,10 +199,10 @@
 
 	.zp-r-right-time-text-rpx {
 		margin-top: 10rpx;
-		font-size: 24rpx;
+		font-size: 26rpx;
 	}
 	.zp-r-right-time-text-px {
 		margin-top: 5px;
-		font-size: 12px;
+		font-size: 13px;
 	}
 </style>

+ 20 - 3
uniapp/uni_modules/z-paging/components/z-paging/css/z-paging-main.css

@@ -2,13 +2,18 @@
 
 .z-paging-content {
 	position: relative;
+	flex-direction: column;
+	/* #ifndef APP-NVUE */
+	overflow: hidden;
+	/* #endif */
+}
+
+.z-paging-content-full {
 	/* #ifndef APP-NVUE */
 	display: flex;
 	width: 100%;
 	height: 100%;
-	overflow: hidden;
 	/* #endif */
-	flex-direction: column;
 }
 
 .z-paging-content-fixed, .zp-loading-fixed {
@@ -23,6 +28,14 @@
 	right: 0;
 }
 
+.zp-f2-content {
+	width: 100%;
+	position: fixed;
+	top: 0;
+	left: 0;
+	background-color: white;
+}
+
 .zp-page-top, .zp-page-bottom {
 	/* #ifndef APP-NVUE */
 	width: auto;
@@ -33,7 +46,7 @@
 	z-index: 999;
 }
 
-.zp-page-left,.zp-page-right {
+.zp-page-left, .zp-page-right {
 	/* #ifndef APP-NVUE */
 	height: 100%;
 	/* #endif */
@@ -180,6 +193,10 @@
 	z-index: 999;
 }
 
+.zp-back-to-top-img-inversion {
+	transform: rotate(180deg);
+}
+
 .zp-empty-view {
 	/* #ifdef APP-NVUE */
 	height: 100%;

+ 1 - 0
uniapp/uni_modules/z-paging/components/z-paging/i18n/en.json

@@ -3,6 +3,7 @@
 	"zp.refresher.pulling": "Release to refresh",
 	"zp.refresher.refreshing": "Refreshing...",
 	"zp.refresher.complete": "Refresh succeeded",
+	"zp.refresher.f2": "Refresh to enter 2f",
 	
 	"zp.loadingMore.default": "Click to load more",
 	"zp.loadingMore.loading": "Loading...",

+ 1 - 0
uniapp/uni_modules/z-paging/components/z-paging/i18n/zh-Hans.json

@@ -3,6 +3,7 @@
 	"zp.refresher.pulling": "松开立即刷新",
 	"zp.refresher.refreshing": "正在刷新...",
 	"zp.refresher.complete": "刷新成功",
+	"zp.refresher.f2": "松手进入二楼",
 	
 	"zp.loadingMore.default": "点击加载更多",
 	"zp.loadingMore.loading": "正在加载...",

+ 1 - 0
uniapp/uni_modules/z-paging/components/z-paging/i18n/zh-Hant.json

@@ -3,6 +3,7 @@
 	"zp.refresher.pulling": "鬆開立即重繪",
 	"zp.refresher.refreshing": "正在重繪...",
 	"zp.refresher.complete": "重繪成功",
+	"zp.refresher.f2": "鬆手進入二樓",
 	
 	"zp.loadingMore.default": "點擊加載更多",
 	"zp.loadingMore.loading": "正在加載...",

+ 2 - 2
uniapp/uni_modules/z-paging/components/z-paging/js/modules/back-to-top.js

@@ -89,10 +89,10 @@ export default {
 				!callbacked && this._handleToTop();
 			})
 		},
-		// 处理滚动到顶部
+		// 处理滚动到顶部(聊天记录模式中为滚动到底部)
 		_handleToTop() {
 			!this.backToTopWithAnimate && this._checkShouldShowBackToTop(0);
-			this.scrollToTop(this.backToTopWithAnimate);
+			!this.useChatRecordMode ? this.scrollToTop(this.backToTopWithAnimate) : this.scrollToBottom(this.backToTopWithAnimate);
 		},
 		// 判断是否要显示返回顶部按钮
 		_checkShouldShowBackToTop(scrollTop) {

+ 25 - 12
uniapp/uni_modules/z-paging/components/z-paging/js/modules/chat-record-mode.js

@@ -23,6 +23,11 @@ export default {
 			type: Boolean,
 			default: u.gc('autoAdjustPositionWhenChat', true)
 		},
+		// 使用聊天记录模式中键盘弹出时占位高度偏移距离。默认0rpx。单位px
+		chatAdjustPositionOffset: {
+			type: [Number, String],
+			default: u.gc('chatAdjustPositionOffset', '0rpx')
+		},
 		// 使用聊天记录模式中键盘弹出时是否自动滚动到底部,默认为否
 		autoToBottomWhenChat: {
 			type: Boolean,
@@ -51,6 +56,9 @@ export default {
 		finalChatRecordMoreOffset() {
 			return u.convertToPx(this.chatRecordMoreOffset);
 		},
+		finalChatAdjustPositionOffset() {
+			return u.convertToPx(this.chatAdjustPositionOffset);
+		},
 		// 聊天记录模式旋转180度style
 		chatRecordRotateStyle() {
 			let cellStyle;
@@ -106,18 +114,7 @@ export default {
 		// 监听键盘高度变化(H5、百度小程序、抖音小程序、飞书小程序不支持)
 		// #ifndef H5 || MP-BAIDU || MP-TOUTIAO
 		if (this.useChatRecordMode) {
-			uni.onKeyboardHeightChange(res => {
-				this.$emit('keyboardHeightChange', res);
-				if (this.autoAdjustPositionWhenChat) {
-					this.isKeyboardHeightChanged = true;
-					this.keyboardHeight = res.height;
-				}
-				if (this.autoToBottomWhenChat && this.keyboardHeight > 0) {
-					u.delay(() => {
-						this.scrollToBottom(false);
-					})
-				} 
-			})
+			uni.onKeyboardHeightChange(this._handleKeyboardHeightChange);
 		}
 		// #endif
 	},
@@ -131,6 +128,22 @@ export default {
 		// 手动触发滚动到顶部加载更多,聊天记录模式时有效
 		doChatRecordLoadMore() {
 			this.useChatRecordMode && this._onLoadingMore('click');
+		},
+		// 处理键盘高度变化
+		_handleKeyboardHeightChange(res) {
+			this.$emit('keyboardHeightChange', res);
+			if (this.autoAdjustPositionWhenChat) {
+				this.isKeyboardHeightChanged = true;
+				this.keyboardHeight = res.height > 0 ? res.height + this.finalChatAdjustPositionOffset : res.height;
+			}
+			if (this.autoToBottomWhenChat && this.keyboardHeight > 0) {
+				u.delay(() => {
+					this.scrollToBottom(false);
+					u.delay(() => {
+						this.scrollToBottom(false);
+					})
+				})
+			} 
 		}
 	}
 }

+ 43 - 7
uniapp/uni_modules/z-paging/components/z-paging/js/modules/data-handle.js

@@ -53,6 +53,16 @@ export default {
 			type: String,
 			default: u.gc('autowireQueryName', '')
 		},
+		// 获取分页数据Function,功能与@query类似。若设置了fetch则@query将不再触发
+		fetch: {
+			type: Function,
+			default: null
+		},
+		// fetch的附加参数,fetch配置后有效
+		fetchParams: {
+			type: Object,
+			default: u.gc('fetchParams', null)
+		},
 		// z-paging mounted后自动调用reload方法(mounted后自动调用接口),默认为是
 		auto: {
 			type: Boolean,
@@ -146,7 +156,8 @@ export default {
 			queryFrom: '',
 			listRendering: false,
 			isHandlingRefreshToPage: false,
-			isFirstPageAndNoMore: false
+			isFirstPageAndNoMore: false,
+			totalDataChangeThrow: true
 		}
 	},
 	computed: {
@@ -171,7 +182,8 @@ export default {
 	},
 	watch: {
 		totalData(newVal, oldVal) {
-			this._totalDataChange(newVal, oldVal);
+			this._totalDataChange(newVal, oldVal, this.totalDataChangeThrow);
+			this.totalDataChangeThrow = true;
 		},
 		currentData(newVal, oldVal) {
 			this._currentDataChange(newVal, oldVal);
@@ -183,14 +195,22 @@ export default {
 		},
 		value: {
 			handler(newVal) {
-				this.realTotalData = newVal;
+				// 当v-model绑定的数据源被更改时,此时数据源改变不emit input事件,避免循环调用
+				if (newVal !== this.totalData) {
+					this.totalDataChangeThrow = false;
+					this.totalData = newVal;
+				}
 			},
 			immediate: true
 		},
 		// #ifdef VUE3
 		modelValue: {
 			handler(newVal) {
-				this.realTotalData = newVal;
+				// 当v-model绑定的数据源被更改时,此时数据源改变不emit input事件,避免循环调用
+				if (newVal !== this.totalData) {
+					this.totalDataChangeThrow = false;
+					this.totalData = newVal;
+				}
 			},
 			immediate: true
 		}
@@ -486,13 +506,17 @@ export default {
 					this.loadingStatus = Enum.More.Default;
 				}
 				if (isLocal) {
+					// 如果当前是本地分页,则必然是由setLocalPaging方法触发,此时直接本地加载第一页数据即可。后续本地分页加载更多方法由滚动到底部加载更多事件处理
 					this.totalLocalPagingList = data;
 					const localPageNo = this.defaultPageNo;
 					const localPageSize = this.queryFrom !== Enum.QueryFrom.Refresh ? this.defaultPageSize : this.currentRefreshPageSize;
 					this._localPagingQueryList(localPageNo, localPageSize, 0, res => {
-						this.completeByTotal(res, this.totalLocalPagingList.length);
+						u.delay(() => {
+							this.completeByTotal(res, this.totalLocalPagingList.length);;
+						}, 0)
 					})
 				} else {
+					// 如果当前不是本地分页,则按照正常分页逻辑进行数据处理&emit数据
 					let dataChangeDelayTime = 0;
 					// #ifdef APP-NVUE
 					if (this.privateShowRefresherWhenReload && this.finalNvueListIs === 'waterfall') {
@@ -614,7 +638,7 @@ export default {
 				this.privateConcat = false;
 				const totalPageSize = pageNo * this.pageSize;
 				this.currentRefreshPageSize = totalPageSize;
-				// 如果是本地分页,则在组件内部自己处理分页逻辑,不emit query相关事件
+				// 如果调用refresh时是本地分页,则在组件内部自己处理分页逻辑,不emit query相关事件
 				if (this.isLocalPaging && this.isHandlingRefreshToPage) {
 					this._localPagingQueryList(this.defaultPageNo, totalPageSize, 0, res => {
 						this.complete(res);
@@ -676,7 +700,19 @@ export default {
 			this.queryFrom = from;
 			this.requestTimeStamp = u.getTime();
 			const [lastItem] = this.realTotalData.slice(-1);
-			this.$emit('query', ...interceptor._handleQuery(pageNo, pageSize, from, lastItem || null));
+			if (this.fetch) {
+				const fetchParams = interceptor._handleFetchParams({pageNo, pageSize, from, lastItem: lastItem || null}, this.fetchParams);
+				const fetchResult = this.fetch(fetchParams);
+				if (!interceptor._handleFetchResult(fetchResult, this, fetchParams)) {
+					u.isPromise(fetchResult) ? fetchResult.then(res => {
+						this.complete(res);
+					}).catch(err => {
+						this.complete(false);
+					}) : this.complete(fetchResult)
+				}
+			} else {
+				this.$emit('query', ...interceptor._handleQuery(pageNo, pageSize, from, lastItem || null));
+			}
 		},
 		// 触发数据改变promise
 		_callDataPromise(success, totalList) {

+ 4 - 0
uniapp/uni_modules/z-paging/components/z-paging/js/modules/i18n.js

@@ -50,6 +50,10 @@ export default {
 				yesterday: t('zp.refresherUpdateTime.yesterday')
 			};
 		},
+		// 最终的继续下拉进入二楼文字
+		finalRefresherGoF2Text() {
+			return this._getI18nText('zp.refresher.f2', this.refresherGoF2Text);
+		},
 		// 最终的底部加载更多默认状态文字
 		finalLoadingMoreDefaultText() {
 			return this._getI18nText('zp.loadingMore.default', this.loadingMoreDefaultText);

+ 9 - 3
uniapp/uni_modules/z-paging/components/z-paging/js/modules/load-more.js

@@ -179,12 +179,16 @@ export default {
 		// 是否显示自定义状态下的底部加载更多
 		showLoadingMoreCustom() {
 			return this._showLoadingMore('Custom');
-		}
+		},
+		// 底部加载更多固定高度
+		loadingMoreFixedHeight() {
+			return u.addUnit('80rpx', this.unit);
+		},
 	},
 	methods: {
 		// 页面滚动到底部时通知z-paging进行进一步处理
 		pageReachBottom() {
-			!this.useChatRecordMode && this._onLoadingMore('toBottom');
+			!this.useChatRecordMode && this.toBottomLoadingMoreEnabled && this._onLoadingMore('toBottom');
 		},
 		// 手动触发上拉加载更多(非必须,可依据具体需求使用)
 		doLoadMore(type) {
@@ -271,12 +275,14 @@ export default {
 			if (this.pageNo >= this.defaultPageNo && this.loadingStatus !== Enum.More.NoMore) {
 				this.pageNo ++;
 				this._startLoading(false);
-				// 如果是本地分页,则在组件内部对数据进行分页处理,不触发@query事件
 				if (this.isLocalPaging) {
+					// 如果是本地分页,则在组件内部对数据进行分页处理,不触发@query事件
 					this._localPagingQueryList(this.pageNo, this.defaultPageSize, this.localPagingLoadingTime, res => {
 						this.completeByTotal(res, this.totalLocalPagingList.length);
+						this.queryFrom = Enum.QueryFrom.LoadingMore;
 					})
 				} else {
+					// emit @query相关加载更多事件
 					this._emitQuery(this.pageNo, this.defaultPageSize, Enum.QueryFrom.LoadingMore);
 					this._callMyParentQuery();
 				}

+ 1 - 1
uniapp/uni_modules/z-paging/components/z-paging/js/modules/loading.js

@@ -44,7 +44,7 @@ export default {
 				this.loadingStatusAfterRender = newVal;
 			})
 			if (this.useChatRecordMode) {
-				if (this.isFirstPage && newVal === Enum.More.NoMore) {
+				if (this.isFirstPage && (newVal === Enum.More.NoMore || newVal === Enum.More.Fail)) {
 					this.isFirstPageAndNoMore = true;
 					return;
 				}

+ 23 - 4
uniapp/uni_modules/z-paging/components/z-paging/js/modules/nvue.js

@@ -67,6 +67,7 @@ export default {
 			nShowRefresherRevealHeight: 0,
 			nOldShowRefresherRevealHeight: -1,
 			nRefresherWidth: uni.upx2px(750),
+			nF2Opacity: 0
 		}
 	},
 	computed: {
@@ -141,16 +142,34 @@ export default {
 		// 下拉刷新刷新中
 		_nOnRrefresh() {
 			if (this.nShowRefresherReveal) return;
+			// 进入刷新状态
 			this.nRefresherLoading = true;
-			this.refresherStatus = Enum.Refresher.Loading;
-			this._doRefresherLoad();
+			if (this.refresherStatus === Enum.Refresher.GoF2) {
+				this._handleGoF2();
+				this.$nextTick(() => {
+					this._nRefresherEnd();
+				})
+			} else {
+				this.refresherStatus = Enum.Refresher.Loading;
+				this._doRefresherLoad();
+			}
+			
 		},
 		// 下拉刷新下拉中
 		_nOnPullingdown(e) {
 			if (this.refresherStatus === Enum.Refresher.Loading || (this.isIos && !this.nListIsDragging)) return;
 			this._emitTouchmove(e);
-			const { viewHeight, pullingDistance } = e;
-			this.refresherStatus = pullingDistance >= viewHeight ? Enum.Refresher.ReleaseToRefresh : Enum.Refresher.Default;
+			let { viewHeight, pullingDistance } = e;
+			// 更新下拉刷新状态
+			// 下拉刷新距离超过阈值
+			if (pullingDistance >= viewHeight) {
+				// 如果开启了下拉进入二楼并且下拉刷新距离超过进入二楼阈值,则当前下拉刷新状态为松手进入二楼,否则为松手立即刷新
+				// (pullingDistance - viewHeight) + this.finalRefresherThreshold 不等同于pullingDistance,此处是为了兼容不同平台下拉相同距离pullingDistance不一致的问题,pullingDistance仅与viewHeight互相关联
+				this.refresherStatus = this.refresherF2Enabled && (pullingDistance - viewHeight) + this.finalRefresherThreshold >= this.finalRefresherF2Threshold ? Enum.Refresher.GoF2 : Enum.Refresher.ReleaseToRefresh;
+			} else {
+				// 下拉刷新距离未超过阈值,显示默认状态
+				this.refresherStatus = Enum.Refresher.Default;
+			}
 		},
 		// 下拉刷新结束
 		_nRefresherEnd(doEnd = true) {

+ 134 - 16
uniapp/uni_modules/z-paging/components/z-paging/js/modules/refresher.js

@@ -3,6 +3,9 @@ import u from '.././z-paging-utils'
 import c from '.././z-paging-constant'
 import Enum from '.././z-paging-enum'
 
+// #ifdef APP-NVUE
+const weexAnimation = weex.requireModule('animation');
+// #endif
 export default {
 	props: {
 		// 下拉刷新的主题样式,支持black,white,默认black
@@ -55,6 +58,11 @@ export default {
 			type: [Number, String],
 			default: u.gc('refresherCompleteDuration', 300)
 		},
+		// 自定义下拉刷新中是否允许列表滚动,默认为是
+		refresherRefreshingScrollable: {
+			type: Boolean,
+			default: u.gc('refresherRefreshingScrollable', true)
+		},
 		// 自定义下拉刷新结束状态下是否允许列表滚动,默认为否
 		refresherCompleteScrollable: {
 			type: Boolean,
@@ -100,6 +108,11 @@ export default {
 			type: [String, Object],
 			default: u.gc('refresherCompleteText', null)
 		},
+		// 自定义继续下拉进入二楼文字
+		refresherGoF2Text: {
+			type: [String, Object],
+			default: u.gc('refresherGoF2Text', null)
+		},
 		// 自定义下拉刷新默认状态下的图片
 		refresherDefaultImg: {
 			type: String,
@@ -165,6 +178,26 @@ export default {
 			type: Number,
 			default: u.gc('refresherOutRate', 0.65)
 		},
+		// 是否开启下拉进入二楼功能,默认为否
+		refresherF2Enabled: {
+			type: Boolean,
+			default: u.gc('refresherF2Enabled', false)
+		},
+		// 下拉进入二楼阈值,默认为200rpx
+		refresherF2Threshold: {
+			type: [Number, String],
+			default: u.gc('refresherF2Threshold', '200rpx')
+		},
+		// 下拉进入二楼动画时间,单位为毫秒,默认为200毫秒
+		refresherF2Duration: {
+			type: [Number, String],
+			default: u.gc('refresherF2Duration', 200)
+		},
+		// 下拉进入二楼状态松手后是否弹出二楼,默认为是
+		showRefresherF2: {
+			type: Boolean,
+			default: u.gc('showRefresherF2', true)
+		},
 		// 设置自定义下拉刷新下拉时实际下拉位移与用户下拉距离的比值,默认为0.75,即代表若用户下拉10px,则实际位移为7.5px(nvue无效)
 		refresherPullRate: {
 			type: Number,
@@ -180,7 +213,7 @@ export default {
 			type: String,
 			default: u.gc('refresherUpdateTimeKey', 'default')
 		},
-		// 下拉刷新时下拉到“松手立即刷新”状态时是否使手机短振动,默认为否(h5无效)
+		// 下拉刷新时下拉到“松手立即刷新”或“松手进入二楼”状态时是否使手机短振动,默认为否(h5无效)
 		refresherVibrate: {
 			type: Boolean,
 			default: u.gc('refresherVibrate', false)
@@ -222,6 +255,8 @@ export default {
 			showCustomRefresher: false,
 			doRefreshAnimateAfter: false,
 			isRefresherInComplete: false,
+			showF2: false,
+			f2Transform: '',
 			pullDownTimeStamp: 0,
 			moveDis: 0,
 			oldMoveDis: 0,
@@ -245,7 +280,7 @@ export default {
 		},
 		refresherStatus(newVal) {
 			newVal === Enum.Refresher.Loading && this._cleanRefresherEndTimeout();
-			this.refresherVibrate && newVal === Enum.Refresher.ReleaseToRefresh && this._doVibrateShort();
+			this.refresherVibrate && (newVal === Enum.Refresher.ReleaseToRefresh || newVal === Enum.Refresher.GoF2) && this._doVibrateShort();
 			this.$emit('refresherStatusChange', newVal);
 			this.$emit('update:refresherStatus', newVal);
 		},
@@ -279,6 +314,9 @@ export default {
 			if (idDefault && this.customRefresherHeight > 0) return this.customRefresherHeight + this.finalRefresherThresholdPlaceholder;
 			return u.convertToPx(refresherThreshold) + this.finalRefresherThresholdPlaceholder;
 		},
+		finalRefresherF2Threshold() {
+			return u.convertToPx(u.addUnit(this.refresherF2Threshold, this.unit));
+		},
 		finalRefresherThresholdPlaceholder() {
 			return this.useRefresherStatusBarPlaceholder ? this.statusBarHeight : 0;
 		},
@@ -311,7 +349,7 @@ export default {
 			return this.refresherTriggered;
 		},
 		showRefresher() {
-			const showRefresher = this.finalRefresherEnabled && this.useCustomRefresher;
+			const showRefresher = this.finalRefresherEnabled || this.useCustomRefresher && !this.useChatRecordMode;
 			// #ifndef APP-NVUE
 			this.active && this.customRefresherHeight === -1 && showRefresher && this.updateCustomRefresherHeight();
 			// #endif
@@ -345,6 +383,10 @@ export default {
 		updateCustomRefresherHeight() {
 			u.delay(() => this.$nextTick(this._updateCustomRefresherHeight));
 		},
+		// 关闭二楼
+		closeF2() {
+			this._handleCloseF2();
+		},
 		// 自定义下拉刷新被触发
 		_onRefresh(fromScrollView = false, isUserPullDown = true) {
 			if (fromScrollView && !(this.finalRefresherEnabled && !this.useCustomRefresher)) return;
@@ -465,7 +507,14 @@ export default {
 			this.isTouchmoving = true;
 			this.isTouchEnded = false;
 			// 更新下拉刷新状态
-			this.refresherStatus = moveDis >= this.finalRefresherThreshold ? Enum.Refresher.ReleaseToRefresh : this.refresherStatus = Enum.Refresher.Default;
+			// 下拉刷新距离超过阈值
+			if (moveDis >= this.finalRefresherThreshold) {
+				// 如果开启了下拉进入二楼并且下拉刷新距离超过进入二楼阈值,则当前下拉刷新状态为松手进入二楼,否则为松手立即刷新
+				this.refresherStatus = this.refresherF2Enabled && moveDis >= this.finalRefresherF2Threshold ? Enum.Refresher.GoF2 : Enum.Refresher.ReleaseToRefresh;
+			} else {
+				// 下拉刷新距离未超过阈值,显示默认状态
+				this.refresherStatus = Enum.Refresher.Default;
+			}
 			// #ifndef APP-VUE || MP-WEIXIN || MP-QQ  || H5
 			// this.scrollEnable = false;
 			// 通过transform控制下拉刷新view垂直偏移
@@ -497,17 +546,24 @@ export default {
 			this.refresherReachMaxAngle = true;
 			this.isTouchEnded = true;
 			const refresherThreshold = this.finalRefresherThreshold;
-			if (moveDis >= refresherThreshold && this.refresherStatus === Enum.Refresher.ReleaseToRefresh) {
-				// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
-				this.refresherTransform = `translateY(${refresherThreshold}px)`;
-				this.refresherTransition = 'transform .1s linear';
-				// #endif
-				u.delay(() => {
-					this._emitTouchmove({ pullingDistance: refresherThreshold, dy: this.moveDis - refresherThreshold });
-				}, 0.1);
-				this.moveDis = refresherThreshold;
-				this.refresherStatus = Enum.Refresher.Loading;
-				this._doRefresherLoad();
+			if (moveDis >= refresherThreshold && (this.refresherStatus === Enum.Refresher.ReleaseToRefresh || this.refresherStatus === Enum.Refresher.GoF2)) {
+				// 如果是松手进入二楼状态,则触发进入二楼
+				if (this.refresherStatus === Enum.Refresher.GoF2) {
+					this._handleGoF2();
+					this._refresherEnd();
+				} else {
+					// 如果是松手立即刷新状态,则触发下拉刷新
+					// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
+					this.refresherTransform = `translateY(${refresherThreshold}px)`;
+					this.refresherTransition = 'transform .1s linear';
+					// #endif
+					u.delay(() => {
+						this._emitTouchmove({ pullingDistance: refresherThreshold, dy: this.moveDis - refresherThreshold });
+					}, 0.1);
+					this.moveDis = refresherThreshold;
+					this.refresherStatus = Enum.Refresher.Loading;
+					this._doRefresherLoad();
+				}
 			} else {
 				this._refresherEnd();
 				this.isTouchmovingTimeout = u.delay(() => {
@@ -613,10 +669,72 @@ export default {
 				}, refresherCompleteDelay);
 			}
 			if (setLoading) {
-				u.delay(() => this.loading = false, shouldEndLoadingDelay ? c.delayTime : 0);
+				u.delay(() => this.loading = false, shouldEndLoadingDelay ? 10 : 0);
 				isUserPullDown && this._onRestore();
 			}
 		},
+		// 处理进入二楼
+		_handleGoF2() {
+			if (this.showF2 || !this.refresherF2Enabled) return;
+			this.$emit('refresherF2Change', 'go');
+			
+			if (!this.showRefresherF2) return;
+			// #ifndef APP-NVUE
+			this.f2Transform = `translateY(${-this.superContentHeight}px)`;
+			this.showF2 = true;
+			u.delay(() => {
+				this.f2Transform = 'translateY(0px)';
+			}, 100, 'f2ShowDelay')
+			// #endif
+			
+			// #ifdef APP-NVUE
+			this.showF2 = true;
+			this.$nextTick(() => {
+				weexAnimation.transition(this.$refs['zp-n-f2'], {
+					styles: { transform: `translateY(${-this.superContentHeight}px)` },
+					duration: 0,
+					timingFunction: 'linear',
+					needLayout: true,
+					delay: 0
+				})
+				this.nF2Opacity = 1;
+			})
+			u.delay(() => {
+				weexAnimation.transition(this.$refs['zp-n-f2'], {
+					styles: { transform: 'translateY(0px)' },
+					duration: this.refresherF2Duration,
+					timingFunction: 'linear',
+					needLayout: true,
+					delay: 0
+				})
+			}, 10, 'f2GoDelay')
+			// #endif
+		},
+		// 处理退出二楼
+		_handleCloseF2() {
+			if (!this.showF2 || !this.refresherF2Enabled) return;
+			this.$emit('refresherF2Change', 'close');
+			
+			if (!this.showRefresherF2) return;
+			// #ifndef APP-NVUE
+			this.f2Transform = `translateY(${-this.superContentHeight}px)`;
+			// #endif
+			
+			// #ifdef APP-NVUE
+			weexAnimation.transition(this.$refs['zp-n-f2'], {
+				styles: { transform: `translateY(${-this.superContentHeight}px)` },
+				duration: this.refresherF2Duration,
+				timingFunction: 'linear',
+				needLayout: true,
+				delay: 0
+			})
+			// #endif
+			
+			u.delay(() => {
+				this.showF2 = false;
+				this.nF2Opacity = 0;
+			}, this.refresherF2Duration, 'f2CloseDelay')
+		},
 		// 模拟用户手动触发下拉刷新
 		_doRefresherRefreshAnimate() {
 			this._cleanRefresherCompleteTimeout();

+ 58 - 13
uniapp/uni_modules/z-paging/components/z-paging/js/modules/scroller.js

@@ -59,7 +59,8 @@ export default {
 			pageScrollTop: -1,
 			scrollEnable: true,
 			privateScrollWithAnimation: -1,
-			cacheScrollNodeHeight: -1
+			cacheScrollNodeHeight: -1,
+			superContentHeight: 0,
 		}
 	},
 	watch: {
@@ -87,14 +88,12 @@ export default {
 		},
 		finalScrollTop(newVal) {
 			this.renderPropScrollTop = newVal < 6 ? 0 : 10;
-		},
+		}
 	},
 	computed: {
 		finalScrollWithAnimation() {
 			if (this.privateScrollWithAnimation !== -1) {
-				const scrollWithAnimation = this.privateScrollWithAnimation === 1;
-				this.privateScrollWithAnimation = -1;
-				return scrollWithAnimation;
+				return this.privateScrollWithAnimation === 1;
 			}
 			return this.scrollWithAnimation;
 		},
@@ -108,8 +107,15 @@ export default {
 		finalScrollTop() {
 			return this.usePageScroll ? this.pageScrollTop : this.oldScrollTop;
 		},
+		// 当前是否是旧版webview
 		finalIsOldWebView() {
 			return this.isOldWebView && !this.usePageScroll;
+		},
+		// 当前scroll-view/list-view是否允许滚动
+		finalScrollable() {
+			return this.scrollable && !this.usePageScroll && this.scrollEnable 
+			&& (this.refresherCompleteScrollable ? true : this.refresherStatus !== Enum.Refresher.Complete)
+			&& (this.refresherRefreshingScrollable ? true : this.refresherStatus !== Enum.Refresher.Loading);
 		}
 	},
 	methods: {
@@ -167,9 +173,31 @@ export default {
 				this._scrollToY(y, offset, animate);
 			})
 		},
-		// 滚动到指定view(nvue中有效)。index为需要滚动的view的index(第几个);offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否
+		// 滚动到指定view(nvue中和虚拟列表中有效)。index为需要滚动的view的index(第几个,从0开始);offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否
 		scrollIntoViewByIndex(index, offset, animate) {
-			this._scrollIntoView(index, offset, animate);
+			if (index >= this.realTotalData.length) {
+				u.consoleErr('当前滚动的index超出已渲染列表长度,请先通过refreshToPage加载到对应index页并等待渲染成功后再调用此方法!')
+				return;
+			}
+			this.$nextTick(() => {
+				// #ifdef APP-NVUE
+				// 在nvue中,根据index获取对应节点信息并滚动到此节点位置
+				this._scrollIntoView(index, offset, animate);
+				// #endif
+				// #ifndef APP-NVUE
+				if (this.finalUseVirtualList) {
+					const isCellFixed = this.cellHeightMode === Enum.CellHeightMode.Fixed;
+					u.delay(() => {
+						if (this.finalUseVirtualList) {
+							// 虚拟列表 + 每个cell高度完全相同模式下,此时滚动到对应index的cell就是滚动到scrollTop = cellHeight * index的位置
+							// 虚拟列表 + 高度是动态非固定的模式下,此时滚动到对应index的cell就是滚动到scrollTop = 缓存的cell高度数组中第index个的lastTotalHeight的位置
+							const scrollTop = isCellFixed ? this.virtualCellHeight * index : this.virtualHeightCacheList[index].lastTotalHeight;
+							this.scrollToY(scrollTop, offset, animate);
+						}
+					}, isCellFixed ? 0 : 100)
+				}
+				// #endif
+			})
 		},
 		// 滚动到指定view(nvue中有效)。view为需要滚动的view(通过`this.$refs.xxx`获取),不包含"#";offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否
 		scrollIntoViewByView(view, offset, animate) {
@@ -194,7 +222,7 @@ export default {
 		},
 		// 更新z-paging内置scroll-view的scrollTop
 		updateScrollViewScrollTop(scrollTop, animate = true) {
-			this.privateScrollWithAnimation = animate ? 1 : 0;
+			this._updatePrivateScrollWithAnimation(animate);
 			this.scrollTop = this.oldScrollTop;
 			this.$nextTick(() => {
 				this.scrollTop = scrollTop;
@@ -212,7 +240,9 @@ export default {
 		},
 		// 当滚动到底部时
 		_onScrollToLower(e) {
-			(!e.detail || !e.detail.direction || e.detail.direction === 'bottom') && this._onLoadingMore(this.useChatRecordMode ? 'click' : 'toBottom')
+			(!e.detail || !e.detail.direction || e.detail.direction === 'bottom') 
+			&& this.toBottomLoadingMoreEnabled
+			&& this._onLoadingMore(this.useChatRecordMode ? 'click' : 'toBottom')
 		},
 		// 滚动到顶部
 		_scrollToTop(animate = true, isPrivate = true) {
@@ -254,7 +284,7 @@ export default {
 				});
 				return;
 			}
-			this.privateScrollWithAnimation = animate ? 1 : 0;
+			this._updatePrivateScrollWithAnimation(animate);
 			this.scrollTop = this.oldScrollTop;
 			this.$nextTick(() => {
 				this.scrollTop = 0;
@@ -286,7 +316,7 @@ export default {
 				return;
 			}
 			try {
-				this.privateScrollWithAnimation = animate ? 1 : 0;
+				this._updatePrivateScrollWithAnimation(animate);
 				const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container');
 				const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
 				const pagingContainerH = pagingContainerNode ? pagingContainerNode[0].height : 0;
@@ -353,7 +383,7 @@ export default {
 		},
 		// 滚动到指定位置
 		_scrollToY(y, offset = 0, animate = false, addScrollTop = false) {
-			this.privateScrollWithAnimation = animate ? 1 : 0;
+			this._updatePrivateScrollWithAnimation(animate);
 			u.delay(() => {
 				if (this.usePageScroll) {
 					if (addScrollTop && this.pageScrollTop !== -1) {
@@ -385,6 +415,14 @@ export default {
 			// 在非ios平台滚动中,再次验证一下是否滚动到了底部。因为在一些安卓设备中,有概率滚动到底部不触发@scrolltolower事件,因此添加双重检测逻辑
 			!this.isIos && this._checkScrolledToBottom(scrollDiff);
 		},
+		// 更新内置的scroll-view是否启用滚动动画
+		_updatePrivateScrollWithAnimation(animate) {
+			this.privateScrollWithAnimation = animate ? 1 : 0;
+			u.delay(() => this.$nextTick(() => {
+				// 在滚动结束后将滚动动画状态设置回初始状态
+				this.privateScrollWithAnimation = -1;
+			}), 100, 'updateScrollWithAnimationDelay')
+		},
 		// 检测scrollView是否要铺满屏幕
 		_doCheckScrollViewShouldFullHeight(totalData) {
 			if (this.autoFullHeight && this.usePageScroll && this.isTotalChangeFromAddData) {
@@ -421,12 +459,19 @@ export default {
 				callback(null, null);
 			}
 		},
+		// 更新缓存中z-paging整个内容容器高度
+		async _updateCachedSuperContentHeight() {
+			const superContentNode = await this._getNodeClientRect('.z-paging-content');
+			if (superContentNode) {
+				this.superContentHeight = superContentNode[0].height;
+			}
+		},
 		// scrollTop改变时触发
 		_scrollTopChange(newVal, isPageScrollTop){
 			this.$emit('scrollTopChange', newVal);
 			this.$emit('update:scrollTop', newVal);
 			this._checkShouldShowBackToTop(newVal);
-			// 之前在安卓中scroll-view有概率滚动到顶部时scrollTop不为0导致下拉刷新判断异常,经过测试在HX3.98+已修复,因此暂时关闭此容错判断
+			// 之前在安卓中scroll-view有概率滚动到顶部时scrollTop不为0导致下拉刷新判断异常,因此判断scrollTop在105之内都允许下拉刷新,但此方案会导致某些情况(例如滚动到距离顶部10px处)下拉抖动,因此改为通过获取zp-scroll-view的节点信息中的scrollTop进行验证的方案
 			// const scrollTop = this.isIos ? (newVal > 5 ? 6 : 0) : (newVal > 105 ? 106 : (newVal > 5 ? 6 : 0));
 			const scrollTop = newVal > 5 ? 6 : 0;
 			if (isPageScrollTop && this.wxsPageScrollTop !== scrollTop) {

+ 55 - 28
uniapp/uni_modules/z-paging/components/z-paging/js/modules/virtual-list.js

@@ -59,6 +59,11 @@ export default {
 			type: String,
 			default: u.gc('cellHeightMode', Enum.CellHeightMode.Fixed)
 		},
+		// 固定的cell高度,cellHeightMode=fixed才有效,若设置了值,则不计算第一个cell高度而使用设置的cell高度
+		fixedCellHeight: {
+			type: [Number, String],
+			default: u.gc('fixedCellHeight', 0)
+		},
 		// 虚拟列表列数,默认为1。常用于每行有多列的情况,例如每行有2列数据,需要将此值设置为2
 		virtualListCol: {
 			type: [Number, String],
@@ -98,23 +103,17 @@ export default {
 	},
 	watch: {
 		// 监听总数据的改变,刷新虚拟列表布局
-		realTotalData(newVal) {
-			// #ifndef APP-NVUE
-			if (this.finalUseVirtualList) {
-				this.updateVirtualListFromDataChange = true;
-				this.$nextTick(() => {
-					this.getCellHeightRetryCount.fixed = 0;
-					!newVal.length && this._resetDynamicListState(!this.isUserPullDown);
-					newVal.length && this.cellHeightMode === Enum.CellHeightMode.Fixed && this.isFirstPage && this._updateFixedCellHeight();
-					this._updateVirtualScroll(this.oldScrollTop);
-				})
-			}
-			// #endif
+		realTotalData() {
+			this.updateVirtualListRender();
 		},
 		// 监听虚拟列表渲染数组的改变并emit
 		virtualList(newVal){
 			this.$emit('update:virtualList', newVal);
 			this.$emit('virtualListChange', newVal);
+		},
+		// 监听虚拟列表顶部占位高度改变并emit
+		virtualPlaceholderTopHeight(newVal) {
+			this.$emit('virtualTopHeightChange', newVal);
 		}
 	},
 	computed: {
@@ -141,6 +140,9 @@ export default {
 		finalVirtualPageHeight(){
 			return this.virtualPageHeight > 0 ? this.virtualPageHeight : this.windowHeight;
 		},
+		finalFixedCellHeight() {
+			return u.convertToPx(this.fixedCellHeight);
+		},
 		virtualRangePageHeight(){
 			return this.finalVirtualPageHeight * this.preloadPage;
 		},
@@ -227,6 +229,23 @@ export default {
 			// 将当前cell的高度信息从高度缓存数组中删除
 			this.virtualHeightCacheList.splice(index, 1);
 		},
+		// 手动触发虚拟列表渲染更新,可用于解决例如修改了虚拟列表数组中元素,但展示未更新的情况
+		updateVirtualListRender() {
+			// #ifndef APP-NVUE
+			if (this.finalUseVirtualList) {
+				this.updateVirtualListFromDataChange = true;
+				this.$nextTick(() => {
+					this.getCellHeightRetryCount.fixed = 0;
+					if (this.realTotalData.length) {
+						this.cellHeightMode === Enum.CellHeightMode.Fixed && this.isFirstPage && this._updateFixedCellHeight()
+					} else {
+						this._resetDynamicListState(!this.isUserPullDown);
+					}
+					this._updateVirtualScroll(this.oldScrollTop);
+				})
+			}
+			// #endif
+		},
 		// 初始化虚拟列表
 		_virtualListInit() {
 			this.$nextTick(() => {
@@ -243,21 +262,25 @@ export default {
 		},
 		// cellHeightMode为fixed时获取第一个cell高度
 		_updateFixedCellHeight() {
-			this.$nextTick(() => {
-				u.delay(() => {
-					this._getNodeClientRect(`#zp-id-${0}`,this.finalUseInnerList).then(cellNode => {
-						if (!cellNode) {
-							if (this.getCellHeightRetryCount.fixed > 10) return;
-							this.getCellHeightRetryCount.fixed ++;
-							// 如果获取第一个cell的节点信息失败,则重试(不超过10次)
-							this._updateFixedCellHeight();
-						} else {
-							this.virtualCellHeight = cellNode[0].height;
-							this._updateVirtualScroll(this.oldScrollTop);
-						}
-					});
-				}, c.delayTime, 'updateFixedCellHeightDelay');
-			})
+			if (!this.finalFixedCellHeight) {
+				this.$nextTick(() => {
+					u.delay(() => {
+						this._getNodeClientRect(`#zp-id-${0}`,this.finalUseInnerList).then(cellNode => {
+							if (!cellNode) {
+								if (this.getCellHeightRetryCount.fixed > 10) return;
+								this.getCellHeightRetryCount.fixed ++;
+								// 如果获取第一个cell的节点信息失败,则重试(不超过10次)
+								this._updateFixedCellHeight();
+							} else {
+								this.virtualCellHeight = cellNode[0].height;
+								this._updateVirtualScroll(this.oldScrollTop);
+							}
+						});
+					}, c.delayTime, 'updateFixedCellHeightDelay');
+				})
+			} else {
+				this.virtualCellHeight = this.finalFixedCellHeight;
+			}
 		},
 		// cellHeightMode为dynamic时获取每个cell高度
 		_updateDynamicCellHeight(list, dataFrom = 'bottom') {
@@ -309,6 +332,7 @@ export default {
 		_setCellIndex(list, dataFrom = 'bottom') {
 			let currentItemIndex = 0;
 			const cellIndexKey = this.virtualCellIndexKey;
+			([Enum.QueryFrom.Refresh, Enum.QueryFrom.Reload].indexOf(this.queryFrom) >= 0) && this._resetDynamicListState();
 			if (this.totalData.length) {
 				if (dataFrom === 'bottom') {
 					currentItemIndex = this.realTotalData.length;
@@ -322,7 +346,7 @@ export default {
 						currentItemIndex = firstItem[cellIndexKey] - list.length;
 					}
 				}
-			} else {			
+			} else {
 				this._resetDynamicListState();
 			}
 			for (let i = 0; i < list.length; i++) {
@@ -330,6 +354,9 @@ export default {
 				if (!item || Object.prototype.toString.call(item) !== '[object Object]') {
 					item = { item };
 				}
+				if (item[c.listCellIndexUniqueKey]) {
+					item = u.deepCopy(item);
+				}
 				item[cellIndexKey] = currentItemIndex + i;
 				item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`;
 				list[i] = item;

+ 1 - 1
uniapp/uni_modules/z-paging/components/z-paging/js/z-paging-constant.js

@@ -2,7 +2,7 @@
 
 export default {
 	// 当前版本号
-	version: '2.7.5',
+	version: '2.7.12',
 	// 延迟操作的通用时间
 	delayTime: 100,
 	// 请求失败时候全局emit使用的key

+ 3 - 2
uniapp/uni_modules/z-paging/components/z-paging/js/z-paging-enum.js

@@ -6,12 +6,13 @@ export default {
 		Refresher: 0,
 		LoadingMore: 1
 	},
-	// 下拉刷新状态 0.默认状态 1.松手立即刷新 2.刷新中 3.刷新结束
+	// 下拉刷新状态 0.默认状态 1.松手立即刷新 2.刷新中 3.刷新结束 4.松手进入二楼
 	Refresher: {
 		Default: 0,
 		ReleaseToRefresh: 1,
 		Loading: 2,
-		Complete: 3
+		Complete: 3,
+		GoF2: 4
 	},
 	// 底部加载更多状态 0.默认状态 1.加载中 2.没有更多数据 3.加载失败
 	More: {

+ 63 - 14
uniapp/uni_modules/z-paging/components/z-paging/js/z-paging-interceptor.js

@@ -1,32 +1,56 @@
 // [z-paging]拦截器
 
+const queryKey = 'Query';
+const fetchParamsKey = 'FetchParams';
+const fetchResultKey = 'FetchResult';
+const language2LocalKey = 'Language2Local';
+
 // 拦截&处理@query事件
 function handleQuery(callback) {
-	try {
-		setTimeout(function() {
-			_getApp().globalData.zp_handleQueryCallback = callback;
-		}, 1);
-	} catch (e) {}
+	_addHandleByKey(queryKey, callback);
+	return this;
 }
 
 // 拦截&处理@query事件(私有,请勿调用)
-function _handleQuery(pageNo, pageSize, from, lastItem){
-	const callback = _getApp().globalData.zp_handleQueryCallback;
+function _handleQuery(pageNo, pageSize, from, lastItem) {
+	const callback = _getHandleByKey(queryKey);
 	return callback ? callback(pageNo, pageSize, from, lastItem) : [pageNo, pageSize, from];
 }
 
+// 拦截&处理:fetch参数
+function handleFetchParams(callback) {
+	_addHandleByKey(fetchParamsKey, callback);
+	return this;
+}
+
+// 拦截&处理:fetch参数(私有,请勿调用)
+function _handleFetchParams(parmas, extraParams) {
+	const callback = _getHandleByKey(fetchParamsKey);
+	return callback ? callback(parmas, extraParams || {}) : { pageNo: parmas.pageNo, pageSize: parmas.pageSize, ...(extraParams || {}) };
+}
+
+// 拦截&处理:fetch结果
+function handleFetchResult(callback) {
+	_addHandleByKey(fetchResultKey, callback);
+	return this;
+}
+
+// 拦截&处理:fetch结果(私有,请勿调用)
+function _handleFetchResult(result, paging, params) {
+	const callback = _getHandleByKey(fetchResultKey);
+	callback && callback(result, paging, params);
+	return callback ? true : false;
+}
+
 // 拦截&处理系统language转i18n local
 function handleLanguage2Local(callback) {
-	try {
-		setTimeout(function() {
-			_getApp().globalData.zp_handleLanguage2LocalCallback = callback;
-		}, 1);
-	} catch (e) {}
+	_addHandleByKey(language2LocalKey, callback);
+	return this;
 }
 
 // 拦截&处理系统language转i18n local(私有,请勿调用)
-function _handleLanguage2Local(language, local){
-	const callback = _getApp().globalData.zp_handleLanguage2LocalCallback;
+function _handleLanguage2Local(language, local) {
+	const callback = _getHandleByKey(language2LocalKey);
 	return callback ? callback(language, local) : local;
 }
 
@@ -40,9 +64,34 @@ function _getApp(){
 	// #endif
 }
 
+// 是否可以访问globalData
+function _hasGlobalData() {
+	return _getApp() && _getApp().globalData;
+}
+
+// 添加处理函数
+function _addHandleByKey(key, callback) {
+	try {
+		setTimeout(function() {
+			if (_hasGlobalData()) {
+				_getApp().globalData[`zp_handle${key}Callback`] = callback;
+			}
+		}, 1);
+	} catch (_) {}
+}
+
+// 获取处理回调函数
+function _getHandleByKey(key) {
+	return _hasGlobalData() ? _getApp().globalData[`zp_handle${key}Callback`] : null;
+}
+
 export default {
 	handleQuery,
 	_handleQuery,
+	handleFetchParams,
+	_handleFetchParams,
+	handleFetchResult,
+	_handleFetchResult,
 	handleLanguage2Local,
 	_handleLanguage2Local
 };

+ 25 - 7
uniapp/uni_modules/z-paging/components/z-paging/js/z-paging-main.js

@@ -107,6 +107,11 @@ export default {
 			type: String,
 			default: u.gc('width', '')
 		},
+		// z-paging的最大宽度,优先级低于pagingStyle中设置的max-width;传字符串,如100px、100rpx、100%。默认为空,也就是铺满窗口宽度,若设置了特定值则会自动添加margin: 0 auto
+		maxWidth: {
+			type: String,
+			default: u.gc('maxWidth', '')
+		},
 		// z-paging的背景色,优先级低于pagingStyle中设置的background。传字符串,如"#ffffff"
 		bgColor: {
 			type: String,
@@ -162,10 +167,15 @@ export default {
 			type: Number,
 			default: u.gc('superContentZIndex', 1)
 		},
-		// z-paging内容容器部分的z-index,默认为10
+		// z-paging内容容器部分的z-index,默认为1
 		contentZIndex: {
 			type: Number,
-			default: u.gc('contentZIndex', 10)
+			default: u.gc('contentZIndex', 1)
+		},
+		// z-paging二楼的z-index,默认为100
+		f2ZIndex: {
+			type: Number,
+			default: u.gc('f2ZIndex', 100)
 		},
 		// 使用页面滚动时,是否在不满屏时自动填充满屏幕,默认为是
 		autoFullHeight: {
@@ -196,7 +206,7 @@ export default {
 		this.renderJsIgnore;
 		if (!this.createdReload && !this.refresherOnly && this.auto) {
 			// 开始预加载
-			this.$nextTick(this._preReload);
+			u.delay(() => this.$nextTick(this._preReload), 0);
 		}
 		// 如果开启了列表缓存,在初始化的时候通过缓存数据填充列表数据
 		this.finalUseCache && this._setListByLocalCache();
@@ -210,8 +220,12 @@ export default {
 			// 初始化z-paging高度
 			!this.usePageScroll && this.autoHeight && this._setAutoHeight();
 			this.loaded = true;
-			// 更新fixed模式下z-paging的布局,主要是更新windowTop、windowBottom
-			u.delay(this.updateFixedLayout);
+			u.delay(() => {
+				// 更新fixed模式下z-paging的布局,主要是更新windowTop、windowBottom
+				this.updateFixedLayout();
+				// 更新缓存中z-paging整个内容容器高度
+				this._updateCachedSuperContentHeight();
+			});
 		})
 		// 初始化页面滚动模式下slot="top"、slot="bottom"高度
 		this.updatePageScrollTopHeight();
@@ -237,8 +251,8 @@ export default {
 		this.finalUseVirtualList && this._virtualListInit();
 		// #endif
 		// #ifndef APP-PLUS
-		// 非app平台中,在通过获取css设置的底部安全区域占位view高度设置bottom距离后,更新页面滚动底部高度
 		this.$nextTick(() => {
+			// 非app平台中,在通过获取css设置的底部安全区域占位view高度设置bottom距离后,更新页面滚动底部高度
 			setTimeout(() => {
 				this._getCssSafeAreaInsetBottom(() => this.safeAreaInsetBottom && this.updatePageScrollBottomHeight());
 			}, delay)
@@ -292,6 +306,10 @@ export default {
 			if (this.width.length && !pagingStyle['width']) {
 				pagingStyle['width'] = this.width;
 			}
+			if (this.maxWidth.length && !pagingStyle['max-width']) {
+				pagingStyle['max-width'] = this.maxWidth;
+				pagingStyle['margin'] = '0 auto';
+			}
 			return pagingStyle;
 		},
 		// 当前z-paging内容的样式
@@ -420,7 +438,7 @@ export default {
 			this._offEmit();
 			// 取消监听键盘高度变化事件(H5、百度小程序、抖音小程序、飞书小程序、QQ小程序、快手小程序不支持)
 			// #ifndef H5 || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-KUAISHOU
-			this.useChatRecordMode && uni.offKeyboardHeightChange(() => {});
+			this.useChatRecordMode && uni.offKeyboardHeightChange(this._handleKeyboardHeightChange);
 			// #endif
 		},
 		// 触发更新是否超出页面状态

+ 25 - 4
uniapp/uni_modules/z-paging/components/z-paging/js/z-paging-utils.js

@@ -19,7 +19,7 @@ function gc(key, defaultValue) {
 		const value = config[key];
 		// 如果全局配置存在但对应的配置项不存在,则返回默认值;反之返回配置项
 		return value === undefined ? defaultValue : value;
-	}
+	};
 }
 
 // 获取最终的touch位置
@@ -32,7 +32,7 @@ function getTouch(e) {
 	} else if (e.datail && e.datail != {}) {
 		touch = e.datail;
 	} else {
-		return {touchX: 0, touchY: 0}
+		return { touchX: 0, touchY: 0 }
 	}
 	return {
 		touchX: touch.clientX,
@@ -45,10 +45,12 @@ function getTouchFromZPaging(target) {
 	if (target && target.tagName && target.tagName !== 'BODY' && target.tagName !== 'UNI-PAGE-BODY') {
 		const classList = target.classList;
 		if (classList && classList.contains('z-paging-content')) {
+			// 此处额外记录当前z-paging是否是页面滚动、是否滚动到了顶部、是否是聊天记录模式以传给renderjs。避免不同z-paging组件renderjs内部判断数据互相影响导致的各种问题
 			return {
 				isFromZp: true,
 				isPageScroll: classList.contains('z-paging-content-page'),
-				isReachedTop: classList.contains('z-paging-reached-top')
+				isReachedTop: classList.contains('z-paging-reached-top'),
+				isUseChatRecordMode: classList.contains('z-paging-use-chat-record-mode')
 			};
 		} else {
 			return getTouchFromZPaging(target.parentNode);
@@ -145,6 +147,11 @@ function wait(ms) {
 	});
 }
 
+// 是否是promise
+function isPromise(func) {
+	return Object.prototype.toString.call(func) === '[object Promise]';
+}
+
 // 添加单位
 function addUnit(value, unit) {
 	if (Object.prototype.toString.call(value) === '[object String]') {
@@ -158,6 +165,18 @@ function addUnit(value, unit) {
 	return unit === 'rpx' ? value + 'rpx' : (value / 2) + 'px';
 }
 
+// 深拷贝
+function deepCopy(obj) {
+	if (typeof obj !== 'object' || obj === null) return obj;
+	let newObj = Array.isArray(obj) ? [] : {};
+	for (let key in obj) {
+		if (obj.hasOwnProperty(key)) {
+			newObj[key] = deepCopy(obj[key]);
+		}
+	}
+	return newObj;
+}
+
 // ------------------ 私有方法 ------------------------
 // 处理全局配置
 function _handleDefaultConfig() {
@@ -245,5 +264,7 @@ export default {
 	consoleErr,
 	delay,
 	wait,
-	addUnit
+	isPromise,
+	addUnit,
+	deepCopy
 };

+ 2 - 4
uniapp/uni_modules/z-paging/components/z-paging/wxs/z-paging-renderjs.js

@@ -26,10 +26,7 @@ export default {
 			if (newVal === -1) return;
 			data.isIosAndH5 = newVal;
 		},
-		// 接收逻辑层发送的数据(是否是聊天记录模式)
-		renderPropUseChatRecordModeChange(newVal) {
-			data.useChatRecordMode = newVal;
-		},
+
 		// 拦截处理touch事件
 		_handleTouch() {
 			if (!window.$zPagingRenderJsInited) {
@@ -46,6 +43,7 @@ export default {
 			data.isTouchFromZPaging = touchResult.isFromZp;
 			data.isUsePageScroll = touchResult.isPageScroll;
 			data.isReachedTop = touchResult.isReachedTop;
+			data.useChatRecordMode = touchResult.isUseChatRecordMode;
 		},
 		// 处理touch中
 		_handleTouchmove(e) {

+ 7 - 5
uniapp/uni_modules/z-paging/components/z-paging/wxs/z-paging-wxs.wxs

@@ -56,6 +56,8 @@ function touchmove(e, ownerIns) {
 	var ins = _getIns(ownerIns);
 	var dataset = ins.getDataset();
 	var refresherThreshold = dataset.refresherthreshold;
+	var refresherF2Threshold = dataset.refresherf2threshold;
+	var refresherF2Enabled = _isTrue(dataset.refresherf2enabled);
 	var isIos = _isTrue(dataset.isios);
 	var state = ins.getState();
 	var watchTouchDirectionChange = _isTrue(dataset.watchtouchdirectionchange);
@@ -107,7 +109,7 @@ function touchmove(e, ownerIns) {
 	var oldIsTouchmoving = _isTrue(dataset.oldistouchmoving);
 	var hasTouchmove = _isTrue(dataset.hastouchmove);
 	var isTouchmoving = state.isTouchmoving;
-	state.refresherStatus = moveDis >= refresherThreshold ? 1 : 0;
+	state.refresherStatus = moveDis >= refresherThreshold ? (refresherF2Enabled && moveDis > refresherF2Threshold ? 'goF2' : 'releaseToRefresh') : 'default';
 	if (!isTouchmoving) {
 		state.isTouchmoving = true;
 		isTouchmoving = true;
@@ -117,7 +119,7 @@ function touchmove(e, ownerIns) {
 	}
 	// 如果需要实时监听下拉位置偏移,则需要实时通知js层,此操作会使wxs层与js层频繁通信从而导致在一些性能较差设备中下拉刷新卡顿
 	if (hasTouchmove) {
-		ownerIns.callMethod('_handleWxsPullingDown', { moveDis:moveDis, diffDis:moveDisObj.diffDis });
+		ownerIns.callMethod('_handleWxsPullingDown', { moveDis: moveDis, diffDis: moveDisObj.diffDis });
 	}
 	// 在下拉刷新状态改变时通知js层
 	if (oldRefresherStatus == undefined || oldRefresherStatus != state.refresherStatus || oldIsTouchmoving != isTouchmoving) {
@@ -145,9 +147,9 @@ function touchend(e, ownerIns) {
 	if (!state.isTouchmoving) return;
 	var oldRefresherStatus = state.refresherStatus;
 	var oldMoveDis = state.moveDis;
-	var refresherThreshold = ins.getDataset().refresherthreshold
+	var refresherThreshold = ins.getDataset().refresherthreshold;
 	var moveDis = _getMoveDis(e, ins).currentDis;
-	if (!(moveDis >= refresherThreshold && oldRefresherStatus === 1)) {
+	if (!(moveDis >= refresherThreshold && oldRefresherStatus === 'releaseToRefresh')) {
 		state.isTouchmoving = false;
 	}
 	// 通知js层touch结束
@@ -294,7 +296,7 @@ function _touchDisabled(e, ins, processTag) {
 	var scrollTop = parseFloat(dataset.scrolltop);
 	var finalScrollTop = usePageScroll ? pageScrollTop : scrollTop;
 	var fixedIsTop = false;
-	// 是否要处理滚动到顶部scrollTop不为0时候的容错,之前在安卓中scroll-view有概率滚动到顶部时scrollTop不为0导致下拉刷新判断异常,经过测试在HX3.98+已修复,因此暂时关闭此容错判断
+	// 是否要处理滚动到顶部scrollTop不为0时候的容错,为解决在安卓中scroll-view有概率滚动到顶部时scrollTop不为0导致下拉刷新判断异常,但此方案会导致某些情况(例如滚动到距离顶部10px处)下拉抖动,因此改为通过获取zp-scroll-view的节点信息中的scrollTop进行验证的方案
 	var handleFaultTolerantMove = false;
 	if (handleFaultTolerantMove && finalScrollTop == (state.startScrollTop || 0) && finalScrollTop <= 105) {
 		fixedIsTop = true;

+ 28 - 18
uniapp/uni_modules/z-paging/components/z-paging/z-paging.vue

@@ -4,7 +4,7 @@
   / /_____| |_) | (_| | (_| | | | | | (_| |
  /___|    | .__/ \__,_|\__, |_|_| |_|\__, |
           |_|          |___/         |___/ 
-v2.7.5 (2024-01-23)
+v2.7.12 (2024-09-22)
 by ZXLee
 -->
 <!-- 文档地址:https://z-paging.zxlee.cn -->
@@ -14,10 +14,14 @@ by ZXLee
 
 <template name="z-paging">
 	<!-- #ifndef APP-NVUE -->
-	<view :class="{'z-paging-content':true,'z-paging-content-fixed':!usePageScroll&&fixed,'z-paging-content-page':usePageScroll,'z-paging-reached-top':renderPropScrollTop<1}" :style="[finalPagingStyle]">
+	<view :class="{'z-paging-content':true,'z-paging-content-full':!usePageScroll,'z-paging-content-fixed':!usePageScroll&&fixed,'z-paging-content-page':usePageScroll,'z-paging-reached-top':renderPropScrollTop<1,'z-paging-use-chat-record-mode':useChatRecordMode}" :style="[finalPagingStyle]">
 		<!-- #ifndef APP-PLUS -->
 		<view v-if="cssSafeAreaInsetBottom===-1" class="zp-safe-area-inset-bottom"></view>
 		<!-- #endif -->
+		<!-- 二楼view -->
+		<view v-if="showF2 && showRefresherF2" @touchmove.stop.prevent class="zp-f2-content" :style="[{'transform': f2Transform, 'transition': `transform .2s linear`, 'height': superContentHeight + 'px', 'z-index': f2ZIndex}]">
+			<slot name="f2"/>
+		</view>
 		<!-- 顶部固定的slot -->
 		<slot v-if="!usePageScroll&&zSlots.top" name="top" />
 		<view class="zp-page-top" @touchmove.stop.prevent v-else-if="usePageScroll&&zSlots.top" :style="[{'top':`${windowTop}px`,'z-index':topZIndex}]">
@@ -31,7 +35,7 @@ by ZXLee
 				<scroll-view
 					ref="zp-scroll-view" :class="{'zp-scroll-view':true,'zp-scroll-view-absolute':!usePageScroll,'zp-scroll-view-hide-scrollbar':!showScrollbar}" :style="[chatRecordRotateStyle]"
 					:scroll-top="scrollTop" :scroll-x="scrollX"
-					:scroll-y="scrollable&&!usePageScroll&&scrollEnable&&(refresherCompleteScrollable?true:refresherStatus!==R.Complete)" :enable-back-to-top="finalEnableBackToTop"
+					:scroll-y="finalScrollable" :enable-back-to-top="finalEnableBackToTop"
 					:show-scrollbar="showScrollbar" :scroll-with-animation="finalScrollWithAnimation"
 					:scroll-into-view="scrollIntoView" :lower-threshold="finalLowerThreshold" :upper-threshold="5"
 					:refresher-enabled="finalRefresherEnabled&&!useCustomRefresher" :refresher-threshold="finalRefresherThreshold"
@@ -52,7 +56,7 @@ by ZXLee
 						<view class="zp-paging-main" :style="[scrollViewInStyle,{'transform': finalRefresherTransform,'transition': refresherTransition}]"
 						<!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 -->
 						:change:prop="pagingWxs.propObserver" :prop="wxsPropType"
-						:data-refresherThreshold="finalRefresherThreshold" :data-isIos="isIos"
+						:data-refresherThreshold="finalRefresherThreshold" :data-refresherF2Enabled="refresherF2Enabled" :data-refresherF2Threshold="finalRefresherF2Threshold" :data-isIos="isIos"
 						:data-loading="loading||isRefresherInComplete" :data-useChatRecordMode="useChatRecordMode" 
 						:data-refresherEnabled="refresherEnabled" :data-useCustomRefresher="useCustomRefresher" :data-pageScrollTop="wxsPageScrollTop"
 						:data-scrollTop="wxsScrollTop" :data-refresherMaxAngle="refresherMaxAngle" :data-refresherNoTransform="refresherNoTransform"
@@ -61,7 +65,6 @@ by ZXLee
 						<!-- #endif -->
 						<!-- #ifdef APP-VUE || H5 -->
 						:change:renderPropIsIosAndH5="pagingRenderjs.renderPropIsIosAndH5Change" :renderPropIsIosAndH5="isIosAndH5"
-						:change:renderPropUseChatRecordMode="pagingRenderjs.renderPropUseChatRecordModeChange" :renderPropUseChatRecordMode="useChatRecordMode"
 						<!-- #endif -->
 						>	
 							<view v-if="showRefresher" class="zp-custom-refresher-view" :style="[{'margin-top': `-${finalRefresherThreshold+refresherThresholdUpdateTag}px`,'background': refresherBackground,'opacity': isTouchmoving ? 1 : 0}]">
@@ -69,12 +72,13 @@ by ZXLee
 									<view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" />
 									<!-- 下拉刷新view -->
 									<view class="zp-custom-refresher-slot-view">
-										<slot v-if="!(zSlots.refresherComplete&&refresherStatus===R.Complete)" :refresherStatus="refresherStatus" name="refresher" />
+										<slot v-if="!(zSlots.refresherComplete&&refresherStatus===R.Complete)&&!(zSlots.refresherF2&&refresherStatus===R.GoF2)" :refresherStatus="refresherStatus" name="refresher" />
 									</view>
 									<slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" />
+									<slot v-else-if="zSlots.refresherF2&&refresherStatus===R.GoF2" name="refresherF2" />
 									<z-paging-refresh ref="refresh" v-else-if="!showCustomRefresher" class="zp-custom-refresher-refresh" :style="[{'height': `${finalRefresherThreshold - finalRefresherThresholdPlaceholder}px`}]" :status="refresherStatus"
 										:defaultThemeStyle="finalRefresherThemeStyle" :defaultText="finalRefresherDefaultText"
-										:pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText"
+										:pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" :goF2Text="finalRefresherGoF2Text"
 										:defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated"
 										:showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap"
 										:imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" :unit="unit" />
@@ -92,7 +96,7 @@ by ZXLee
 										<view class="zp-list-container" :style="[innerListStyle]">
 											<template v-if="finalUseVirtualList">
 												<view class="zp-list-cell" :style="[innerCellStyle]" :id="`zp-id-${item[virtualCellIndexKey]}`" v-for="(item,index) in virtualList" :key="item['zp_unique_index']" @click="_innerCellClick(item,virtualTopRangeIndex+index)">
-													<view v-if="useCompatibilityMode">使用兼容模式请在组件源码z-paging.vue第95行中注释这一行,并打开下面一行注释</view>
+													<view v-if="useCompatibilityMode">使用兼容模式请在组件源码z-paging.vue第99行中注释这一行,并打开下面一行注释</view>
 													<!-- <zp-public-virtual-cell v-if="useCompatibilityMode" :extraData="extraData" :item="item" :index="virtualTopRangeIndex+index" /> -->
 													<slot v-else name="cell" :item="item" :index="virtualTopRangeIndex+index"/>
 												</view>
@@ -106,7 +110,7 @@ by ZXLee
 										<slot name="footer"/>
 									</template>
 									<!-- 聊天记录模式加载更多loading -->
-									<template v-if="useChatRecordMode&&(loadingStatus!==M.NoMore||zSlots.chatNoMore)&&(realTotalData.length||(showChatLoadingWhenReload&&showLoading))">
+									<template v-if="useChatRecordMode&&realTotalData.length>=defaultPageSize&&(loadingStatus!==M.NoMore||zSlots.chatNoMore)&&(realTotalData.length||(showChatLoadingWhenReload&&showLoading))&&!isFirstPageAndNoMore">
 										<view :style="[chatRecordRotateStyle]">
 											<slot v-if="loadingStatus===M.NoMore&&zSlots.chatNoMore" name="chatNoMore" />
 											<template v-else>
@@ -166,7 +170,7 @@ by ZXLee
 		<!-- 点击返回顶部view -->
 		<view v-if="showBackToTopClass" :class="finalBackToTopClass" :style="[finalBackToTopStyle]" @click.stop="_backToTopClick">
 			<slot v-if="zSlots.backToTop" name="backToTop" />
-			<image v-else class="zp-back-to-top-img" :src="backToTopImg.length?backToTopImg:base64BackToTop" />
+			<image v-else class="zp-back-to-top-img" :class="{'zp-back-to-top-img-inversion': useChatRecordMode&&!backToTopImg.length}" :src="backToTopImg.length?backToTopImg:base64BackToTop" />
 		</view>
 		<!-- 全屏Loading(铺满z-paging并固定) -->
 		<view v-if="showLoading&&zSlots.loading&&loadingFullFixed" class="zp-loading-fixed">
@@ -175,7 +179,11 @@ by ZXLee
 	</view>
 	<!-- #endif -->
 	<!-- #ifdef APP-NVUE -->
-	<component :is="finalNvueSuperListIs" :style="[finalPagingStyle]" :class="{'z-paging-content-fixed':fixed&&!usePageScroll}" :scrollable="false">
+	<component ref="z-paging-content" :is="finalNvueSuperListIs" :style="[finalPagingStyle]" :class="{'z-paging-content-fixed':fixed&&!usePageScroll}" :scrollable="false">
+		<!-- 二楼view -->
+		<view v-if="showF2 && showRefresherF2" ref="zp-n-f2" class="zp-f2-content" @touchmove.stop.prevent :style="[{'height': superContentHeight + 'px', 'width': nRefresherWidth + 'px', 'opacity': nF2Opacity}]">
+			<slot name="f2"/>
+		</view>
 		<!-- 顶部固定的slot -->
 		<view ref="zp-page-top" v-if="zSlots.top" :class="{'zp-page-top':usePageScroll}" :style="[usePageScroll?{'top':`${windowTop}px`,'z-index':topZIndex}:{}]">
 			<slot name="top" />
@@ -190,8 +198,8 @@ by ZXLee
 				<slot name="left" />
 			</view>
 			<component :is="finalNvueListIs" ref="zp-n-list" :id="nvueListId" :style="[{'flex': 1,'top':isIos?'0px':'-1px'},usePageScroll?scrollViewStyle:{},chatRecordRotateStyle]" :alwaysScrollableVertical="true"
-				:fixFreezing="nFixFreezing" :show-scrollbar="showScrollbar&&!useChatRecordMode" :loadmoreoffset="finalLowerThreshold" :enable-back-to-top="enableBackToTop"
-				:scrollable="scrollable&&scrollEnable&&(refresherCompleteScrollable?true:refresherStatus!==R.Complete)" :bounce="nvueBounce" :column-count="nWaterfallColumnCount" :column-width="nWaterfallColumnWidth"
+				:fixFreezing="nFixFreezing" :show-scrollbar="showScrollbar" :loadmoreoffset="finalLowerThreshold" :enable-back-to-top="enableBackToTop"
+				:scrollable="finalScrollable" :bounce="nvueBounce" :column-count="nWaterfallColumnCount" :column-width="nWaterfallColumnWidth"
 				:column-gap="nWaterfallColumnGap" :left-gap="nWaterfallLeftGap" :right-gap="nWaterfallRightGap" :pagingEnabled="nvuePagingEnabled" :offset-accuracy="offsetAccuracy"
 				@loadmore="_nOnLoadmore" @scroll="_nOnScroll" @scrollend="_nOnScrollend">
 				<refresh v-if="(zSlots.top?cacheTopHeight!==-1:true)&&finalNvueRefresherEnabled" class="zp-n-refresh" :style="[nvueRefresherStyle]" :display="nRefresherLoading?'show':'hide'" @refresh="_nOnRrefresh" @pullingdown="_nOnPullingdown">
@@ -199,9 +207,10 @@ by ZXLee
 						<view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" />
 						<!-- 下拉刷新view -->
 						<slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" />
+						<slot v-else-if="zSlots.refresherF2&&refresherStatus===R.GoF2" name="refresherF2" />
 						<slot v-else-if="(nScopedSlots?nScopedSlots:zSlots).refresher" :refresherStatus="refresherStatus" name="refresher" />
 						<z-paging-refresh ref="refresh" v-else :status="refresherStatus" :defaultThemeStyle="finalRefresherThemeStyle"
-							:defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText"
+							:defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" :goF2Text="finalRefresherGoF2Text"
 							:defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated"
 							:showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap"
 							:imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" :unit="unit" />
@@ -212,9 +221,10 @@ by ZXLee
 					<view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" />
 					<!-- 下拉刷新view -->
 					<slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" />
+					<slot v-else-if="zSlots.refresherF2&&refresherStatus===R.GoF2" name="refresherF2" />
 					<slot v-else-if="(nScopedSlots?nScopedSlots:$slots).refresher" :refresherStatus="R.Loading" name="refresher" />
 					<z-paging-refresh ref="refresh" v-else :status="R.Loading" :defaultThemeStyle="finalRefresherThemeStyle"
-						:defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" 
+						:defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" :goF2Text="finalRefresherGoF2Text"
 						:defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated"
 						:showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap"
 						:imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" :unit="unit" />
@@ -241,7 +251,7 @@ by ZXLee
 				<!-- 上拉加载更多view -->
 				<component :is="nViewIs" v-if="!refresherOnly&&loadingMoreEnabled&&!showEmpty">
 					<!-- 聊天记录模式加载更多loading(滚动到顶部加载更多或无更多数据时显示) -->
-					<template v-if="useChatRecordMode&&(loadingStatus!==M.NoMore||zSlots.chatNoMore)">
+					<template v-if="useChatRecordMode&&realTotalData.length>=defaultPageSize&&(loadingStatus!==M.NoMore||zSlots.chatNoMore)&&realTotalData.length&&isChatRecordModeAndInversion">
 						<view :style="[chatRecordRotateStyle]">
 							<slot v-if="loadingStatus===M.NoMore&&zSlots.chatNoMore" name="chatNoMore" />
 							<template v-else>
@@ -251,7 +261,7 @@ by ZXLee
 						</view>
 					</template>
 					
-					<view :style="nLoadingMoreFixedHeight?{height:loadingMoreCustomStyle&&loadingMoreCustomStyle.height?loadingMoreCustomStyle.height:'80rpx'}:{}">
+					<view :style="nLoadingMoreFixedHeight?{height:loadingMoreCustomStyle&&loadingMoreCustomStyle.height?loadingMoreCustomStyle.height:loadingMoreFixedHeight}:{}">
 						<slot v-if="showLoadingMoreDefault" name="loadingMoreDefault" />
 						<slot v-else-if="showLoadingMoreLoading" name="loadingMoreLoading" />
 						<slot v-else-if="showLoadingMoreNoMore" name="loadingMoreNoMore" />
@@ -288,7 +298,7 @@ by ZXLee
 		<!-- 点击返回顶部view -->
 		<view v-if="showBackToTopClass" :class="finalBackToTopClass" :style="[finalBackToTopStyle]" @click.stop="_backToTopClick">
 			<slot v-if="zSlots.backToTop" name="backToTop" />
-			<image v-else class="zp-back-to-top-img" :src="backToTopImg.length?backToTopImg:base64BackToTop" />
+			<image v-else class="zp-back-to-top-img" :class="{'zp-back-to-top-img-inversion': useChatRecordMode&&!backToTopImg.length}" :src="backToTopImg.length?backToTopImg:base64BackToTop" />
 		</view>
 		<!-- 全屏Loading(铺满z-paging并固定) -->
 		<view v-if="showLoading&&zSlots.loading&&loadingFullFixed" class="zp-loading-fixed">

+ 12 - 10
uniapp/uni_modules/z-paging/package.json

@@ -2,8 +2,8 @@
   "id": "z-paging",
   "name": "z-paging",
   "displayName": "【z-paging下拉刷新、上拉加载】高性能,全平台兼容。支持虚拟列表,分页全自动处理",
-  "version": "2.7.5",
-  "description": "超简单、低耦合!使用wxs+renderjs实现。支持自定义下拉刷新、上拉加载更多、虚拟列表(轻松渲染万级列表)、自动管理空数据图、无闪动聊天分页、本地分页、国际化等100+项配置",
+  "version": "2.7.12",
+  "description": "超简单、低耦合!使用wxs+renderjs实现。支持自定义下拉刷新、上拉加载更多、虚拟列表、下拉进入二楼、自动管理空数据图、无闪动聊天分页、本地分页、国际化等100+项配置",
   "keywords": [
     "下拉刷新",
     "上拉加载",
@@ -12,10 +12,11 @@
     "虚拟列表"
 ],
   "repository": "https://github.com/SmileZXLee/uni-z-paging",
+  "types": "global.d.ts",
   "engines": {
     "HBuilderX": "^3.0.7"
   },
-"dcloudext": {
+  "dcloudext": {
     "sale": {
       "regular": {
         "price": "0.00"
@@ -41,7 +42,8 @@
     "platforms": {
       "cloud": {
         "tcb": "y",
-        "aliyun": "y"
+        "aliyun": "y",
+        "alipay": "n"
       },
       "client": {
         "App": {
@@ -67,18 +69,18 @@
           "百度": "y",
           "字节跳动": "y",
           "QQ": "y",
-		  "钉钉": "y",
-		  "快手": "y",
-		  "飞书": "y",
-		  "京东": "y"
+          "钉钉": "y",
+          "快手": "y",
+          "飞书": "y",
+          "京东": "y"
         },
         "快应用": {
           "华为": "y",
           "联盟": "y"
         },
         "Vue": {
-            "vue2": "y",
-            "vue3": "y"
+          "vue2": "y",
+          "vue3": "y"
         }
       }
     }

+ 11 - 10
uniapp/uni_modules/z-paging/readme.md

@@ -1,11 +1,11 @@
 # z-paging
 
 <p align="center">
-    <img alt="logo" src="https://z-paging.zxlee.cn/img/title-logo.png" height="100" style="margin-bottom: 50px;">
+    <img alt="logo" src="https://z-paging.zxlee.cn/img/title-logo.png" height="100" style="margin-bottom: 50px;" />
 </p>
 
-[![version](https://img.shields.io/badge/version-2.7.5-blue)](https://github.com/SmileZXLee/uni-z-paging)
-[![license](https://img.shields.io/github/license/SmileZXLee/uni-z-paging)](https://en.wikipedia.org/wiki/MIT_License)
+[![version](https://img.shields.io/badge/version-2.7.12-blue)](https://github.com/SmileZXLee/uni-z-paging) [![license](https://img.shields.io/github/license/SmileZXLee/uni-z-paging)](https://en.wikipedia.org/wiki/MIT_License)
+<img height="0" width="0" src="https://api.z-notify.zxlee.cn/v1/public/statistics/8293556910106066944/addOnly?from=uni" />
 
 `z-paging-x`现已支持uniapp x,持续完善中,插件地址👉🏻 [https://ext.dcloud.net.cn/plugin?name=z-paging-x](https://ext.dcloud.net.cn/plugin?name=z-paging-x)  
 
@@ -18,7 +18,7 @@
 * 【配置简单】仅需两步(绑定网络请求方法、绑定分页结果数组)轻松完成完整下拉刷新,上拉加载更多功能。
 * 【低耦合,低侵入】分页自动管理。在page中无需处理任何分页相关逻辑,无需在data中定义任何分页相关变量,全由z-paging内部处理。
 * 【超灵活,支持各种类型自定义】支持自定义下拉刷新,自定义上拉加载更多等各种自定义效果;支持使用内置自动分页,同时也支持通过监听下拉刷新和滚动到底部事件自行处理;支持使用自带全屏布局规范,同时也支持将z-paging自由放在任意容器中。
-* 【功能丰富】支持国际化,支持自定义且自动管理空数据图,支持主题模式切换,支持本地分页,支持无闪动聊天分页模式,支持展示最后更新时间,支持吸顶效果,支持内部scroll-view滚动与页面滚动,支持一键滚动到顶部等诸多功能。
+* 【功能丰富】支持国际化,支持自定义且自动管理空数据图,支持主题模式切换,支持本地分页,支持无闪动聊天分页模式,支持展示最后更新时间,支持吸顶效果,支持内部scroll-view滚动与页面滚动,支持一键滚动到顶部,支持下拉进入二楼等诸多功能。
 * 【全平台兼容】支持vue、nvue,vue2、vue3,支持h5、app及各家小程序。
 * 【高性能】在app-vue、h5、微信小程序、QQ小程序上使用wxs+renderjs从视图层实现下拉刷新;支持虚拟列表,轻松渲染万级数据!
 
@@ -34,13 +34,14 @@
 
 ***
 
-|                 自定义下拉刷新效果演示                  |                      滑动切换选项卡+吸顶演示                       |
-| :----------------------------------------------------------: | :----------------------------------------------------------: |
-| ![](https://z-paging.zxlee.cn/public/img/z-paging-demo5.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo6.gif) |
+|                    自定义下拉刷新效果演示                    |                   滑动切换选项卡+吸顶演示                    | 聊天记录模式演示                                             |
+| :----------------------------------------------------------: | :----------------------------------------------------------: | ------------------------------------------------------------ |
+| ![](https://z-paging.zxlee.cn/public/img/z-paging-demo5.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo6.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo7.gif) |
+
+|                 虚拟列表(流畅渲染1万+条)演示                 |                       下拉进入二楼演示                       | 在弹窗内使用演示                                             |
+| :----------------------------------------------------------: | :----------------------------------------------------------: | ------------------------------------------------------------ |
+| ![](https://z-paging.zxlee.cn/public/img/z-paging-demo8.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo9.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo10.gif) |
 
-|                   聊天记录模式演示                    |                    虚拟列表(流畅渲染1万+条)演示                     |
-| :----------------------------------------------------------: | :----------------------------------------------------------: |
-| ![](https://z-paging.zxlee.cn/public/img/z-paging-demo7.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo8.gif) |
 
 ### 在线demo体验地址:
 

+ 6 - 4
web/src/views/backend/borrow/borrowApplication/popupForm.vue

@@ -125,22 +125,24 @@
                         :input-attr="{ maxlength: 200, clearable: true, 'show-password': true ,readonly: true}"
                     />
                     <FormItem
-                        :label="t('borrow.borrowapplication.annotation')"
+                        label="实验室批注"
                         prop="annotation"
                         type="textarea"
                         v-model="baTable.form.items!.annotation"
-                        :required="baTable.form.items!.status == 1"
+                        :required="baTable.form.items!.status == 0"
                         message="驳回时批注为必填项!"
                         :input-attr="{ maxlength: 200, clearable: true, 'show-password': true }"
+                        v-if="baTable.form.items!.status == 0"
                     />
                     <FormItem
-                        :label="t('borrow.borrowapplication.college_annotation')"
+                        label="学院批注"
                         prop="college_annotation"
                         type="textarea"
                         v-model="baTable.form.items!.college_annotation"
-                        :required="baTable.form.items!.status == 1"
+                        :required="baTable.form.items!.status == 6"
                         message="驳回时批注为必填项!"
                         :input-attr="{ maxlength: 200, clearable: true, 'show-password': true }"
+                        v-if="baTable.form.items!.status == 6"
                     />
                     <el-form-item label="配件">
                         <div style="width: 100% ;">

+ 1 - 1
web/src/views/backend/dashboard.vue

@@ -233,7 +233,7 @@ const initUserGrowthChart = () => {
     // prettier-ignore
     let dataAxis = [
         t('dashboard.Jan'), t('dashboard.Feb'), t('dashboard.Mar'), t('dashboard.Apr'), t('dashboard.May'), t('dashboard.Jun')
-        , t('dashboard.Jul'), t('dashboard.Aug'), t('dashboard.Sept'), t('dashboard.Oct'), t('dashboard.Nov'), t('dashboard.Jan'),];
+        , t('dashboard.Jul'), t('dashboard.Aug'), t('dashboard.Sept'), t('dashboard.Oct'), t('dashboard.Nov'), t('dashboard.Dec'),];
     // prettier-ignore
     let data = [dbdata.value.mq['01'], dbdata.value.mq['02'], dbdata.value.mq['03'], dbdata.value.mq['04'], dbdata.value.mq['05'],
         dbdata.value.mq['06'], dbdata.value.mq['07'], dbdata.value.mq['08'], dbdata.value.mq['09'], dbdata.value.mq['10'], dbdata.value.mq['11'], dbdata.value.mq['12']];