From 8f2ce34669a93fed2656f117da6a97af6811bc38 Mon Sep 17 00:00:00 2001 From: duanshuwen Date: Tue, 29 Jul 2025 09:01:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=A2=E5=8D=95=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.js | 28 +- package.json | 3 +- .../components/CustomEmpty/images/empty.png | Bin 0 -> 12308 bytes pages/order/components/CustomEmpty/index.vue | 26 + .../components/CustomEmpty/styles/index.scss | 19 + .../CustomNoMore/images/no_more.png | Bin 0 -> 79568 bytes pages/order/components/CustomNoMore/index.vue | 14 + .../components/CustomNoMore/styles/index.scss | 17 + .../images/refresher_loading.gif | Bin 0 -> 99597 bytes .../components/CustomRefresher/index.vue | 39 + .../CustomRefresher/styles/index.scss | 21 + pages/order/components/OrderList/demo.vue | 322 --- pages/order/components/OrderList/index.vue | 265 -- .../components/OrderList/styles/index.scss | 298 --- pages/order/components/OrderList/test.vue | 195 -- pages/order/list.vue | 288 +-- pages/order/styles/list.scss | 12 - request/api/OrderApi.js | 12 + uni_modules/z-paging/changelog.md | 51 + .../z-paging-cell/z-paging-cell.vue | 47 + .../z-paging-empty-view.vue | 209 ++ .../z-paging-swiper-item.vue | 160 ++ .../z-paging-swiper/z-paging-swiper.vue | 176 ++ .../components/z-paging-load-more.vue | 182 ++ .../z-paging/components/z-paging-refresh.vue | 214 ++ .../components/z-paging/config/index.js | 3 + .../components/z-paging/css/z-paging-main.css | 241 ++ .../z-paging/css/z-paging-static.css | 50 + .../z-paging/components/z-paging/i18n/en.json | 23 + .../components/z-paging/i18n/index.js | 8 + .../components/z-paging/i18n/zh-Hans.json | 23 + .../components/z-paging/i18n/zh-Hant.json | 23 + .../z-paging/js/hooks/useZPaging.js | 25 + .../z-paging/js/hooks/useZPagingComp.js | 25 + .../z-paging/js/modules/back-to-top.js | 125 + .../z-paging/js/modules/chat-record-mode.js | 153 ++ .../z-paging/js/modules/common-layout.js | 152 ++ .../z-paging/js/modules/data-handle.js | 744 ++++++ .../components/z-paging/js/modules/empty.js | 144 ++ .../components/z-paging/js/modules/i18n.js | 113 + .../z-paging/js/modules/load-more.js | 374 +++ .../components/z-paging/js/modules/loading.js | 95 + .../components/z-paging/js/modules/nvue.js | 299 +++ .../z-paging/js/modules/refresher.js | 835 +++++++ .../z-paging/js/modules/scroller.js | 589 +++++ .../z-paging/js/modules/virtual-list.js | 539 +++++ .../z-paging/js/z-paging-constant.js | 19 + .../components/z-paging/js/z-paging-enum.js | 45 + .../z-paging/js/z-paging-interceptor.js | 97 + .../components/z-paging/js/z-paging-main.js | 537 +++++ .../components/z-paging/js/z-paging-mixin.js | 22 + .../components/z-paging/js/z-paging-static.js | 13 + .../components/z-paging/js/z-paging-utils.js | 322 +++ .../z-paging/wxs/z-paging-renderjs.js | 67 + .../components/z-paging/wxs/z-paging-wxs.wxs | 382 +++ .../z-paging/components/z-paging/z-paging.vue | 656 +++++ uni_modules/z-paging/package.json | 89 + uni_modules/z-paging/readme.md | 57 + uni_modules/z-paging/types/comps.d.ts | 11 + uni_modules/z-paging/types/comps/_common.d.ts | 9 + .../z-paging/types/comps/z-paging-cell.d.ts | 29 + .../types/comps/z-paging-empty-view.d.ts | 95 + .../types/comps/z-paging-swiper-item.d.ts | 95 + .../z-paging/types/comps/z-paging-swiper.d.ts | 89 + .../z-paging/types/comps/z-paging.d.ts | 2131 +++++++++++++++++ uni_modules/z-paging/types/index.d.ts | 24 + 66 files changed, 10650 insertions(+), 1320 deletions(-) create mode 100644 pages/order/components/CustomEmpty/images/empty.png create mode 100644 pages/order/components/CustomEmpty/index.vue create mode 100644 pages/order/components/CustomEmpty/styles/index.scss create mode 100644 pages/order/components/CustomNoMore/images/no_more.png create mode 100644 pages/order/components/CustomNoMore/index.vue create mode 100644 pages/order/components/CustomNoMore/styles/index.scss create mode 100644 pages/order/components/CustomRefresher/images/refresher_loading.gif create mode 100644 pages/order/components/CustomRefresher/index.vue create mode 100644 pages/order/components/CustomRefresher/styles/index.scss delete mode 100644 pages/order/components/OrderList/demo.vue delete mode 100644 pages/order/components/OrderList/index.vue delete mode 100644 pages/order/components/OrderList/styles/index.scss delete mode 100644 pages/order/components/OrderList/test.vue create mode 100644 request/api/OrderApi.js create mode 100644 uni_modules/z-paging/changelog.md create mode 100644 uni_modules/z-paging/components/z-paging-cell/z-paging-cell.vue create mode 100644 uni_modules/z-paging/components/z-paging-empty-view/z-paging-empty-view.vue create mode 100644 uni_modules/z-paging/components/z-paging-swiper-item/z-paging-swiper-item.vue create mode 100644 uni_modules/z-paging/components/z-paging-swiper/z-paging-swiper.vue create mode 100644 uni_modules/z-paging/components/z-paging/components/z-paging-load-more.vue create mode 100644 uni_modules/z-paging/components/z-paging/components/z-paging-refresh.vue create mode 100644 uni_modules/z-paging/components/z-paging/config/index.js create mode 100644 uni_modules/z-paging/components/z-paging/css/z-paging-main.css create mode 100644 uni_modules/z-paging/components/z-paging/css/z-paging-static.css create mode 100644 uni_modules/z-paging/components/z-paging/i18n/en.json create mode 100644 uni_modules/z-paging/components/z-paging/i18n/index.js create mode 100644 uni_modules/z-paging/components/z-paging/i18n/zh-Hans.json create mode 100644 uni_modules/z-paging/components/z-paging/i18n/zh-Hant.json create mode 100644 uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js create mode 100644 uni_modules/z-paging/components/z-paging/js/hooks/useZPagingComp.js create mode 100644 uni_modules/z-paging/components/z-paging/js/modules/back-to-top.js create mode 100644 uni_modules/z-paging/components/z-paging/js/modules/chat-record-mode.js create mode 100644 uni_modules/z-paging/components/z-paging/js/modules/common-layout.js create mode 100644 uni_modules/z-paging/components/z-paging/js/modules/data-handle.js create mode 100644 uni_modules/z-paging/components/z-paging/js/modules/empty.js create mode 100644 uni_modules/z-paging/components/z-paging/js/modules/i18n.js create mode 100644 uni_modules/z-paging/components/z-paging/js/modules/load-more.js create mode 100644 uni_modules/z-paging/components/z-paging/js/modules/loading.js create mode 100644 uni_modules/z-paging/components/z-paging/js/modules/nvue.js create mode 100644 uni_modules/z-paging/components/z-paging/js/modules/refresher.js create mode 100644 uni_modules/z-paging/components/z-paging/js/modules/scroller.js create mode 100644 uni_modules/z-paging/components/z-paging/js/modules/virtual-list.js create mode 100644 uni_modules/z-paging/components/z-paging/js/z-paging-constant.js create mode 100644 uni_modules/z-paging/components/z-paging/js/z-paging-enum.js create mode 100644 uni_modules/z-paging/components/z-paging/js/z-paging-interceptor.js create mode 100644 uni_modules/z-paging/components/z-paging/js/z-paging-main.js create mode 100644 uni_modules/z-paging/components/z-paging/js/z-paging-mixin.js create mode 100644 uni_modules/z-paging/components/z-paging/js/z-paging-static.js create mode 100644 uni_modules/z-paging/components/z-paging/js/z-paging-utils.js create mode 100644 uni_modules/z-paging/components/z-paging/wxs/z-paging-renderjs.js create mode 100644 uni_modules/z-paging/components/z-paging/wxs/z-paging-wxs.wxs create mode 100644 uni_modules/z-paging/components/z-paging/z-paging.vue create mode 100644 uni_modules/z-paging/package.json create mode 100644 uni_modules/z-paging/readme.md create mode 100644 uni_modules/z-paging/types/comps.d.ts create mode 100644 uni_modules/z-paging/types/comps/_common.d.ts create mode 100644 uni_modules/z-paging/types/comps/z-paging-cell.d.ts create mode 100644 uni_modules/z-paging/types/comps/z-paging-empty-view.d.ts create mode 100644 uni_modules/z-paging/types/comps/z-paging-swiper-item.d.ts create mode 100644 uni_modules/z-paging/types/comps/z-paging-swiper.d.ts create mode 100644 uni_modules/z-paging/types/comps/z-paging.d.ts create mode 100644 uni_modules/z-paging/types/index.d.ts diff --git a/main.js b/main.js index 6b23d8b..2fb4430 100644 --- a/main.js +++ b/main.js @@ -1,24 +1,22 @@ -import App from './App' +import App from "./App"; // #ifndef VUE3 -import Vue from 'vue' -import './uni.promisify.adaptor' -Vue.config.productionTip = false -App.mpType = 'app' +import Vue from "vue"; +import "./uni.promisify.adaptor"; +Vue.config.productionTip = false; +App.mpType = "app"; const app = new Vue({ - ...App -}) -app.$mount() + ...App, +}); +app.$mount(); // #endif // #ifdef VUE3 -import { createSSRApp } from 'vue' -import zPaging from 'z-paging/components/z-paging/z-paging.vue' +import { createSSRApp } from "vue"; export function createApp() { - const app = createSSRApp(App) - app.component('z-paging', zPaging) + const app = createSSRApp(App); return { - app - } + app, + }; } -// #endif \ No newline at end of file +// #endif diff --git a/package.json b/package.json index 048d7d2..d74cb86 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { "dependencies": { "lottie-web": "^5.13.0", - "vue-element-plus-x": "^1.3.0", - "z-paging": "^2.8.7" + "vue-element-plus-x": "^1.3.0" } } diff --git a/pages/order/components/CustomEmpty/images/empty.png b/pages/order/components/CustomEmpty/images/empty.png new file mode 100644 index 0000000000000000000000000000000000000000..14829e9f788b4641635c95cefc389bef9c6e38a8 GIT binary patch literal 12308 zcmV+vFze5WP)1^@s6ky^J%00001b5ch_0Itp) z=>PyA07*naRCr$PeF>N(M|I|VSyx}vGt)CY)2*JPduCd5N+Tf&kg=8(k|p79mjza< zU1K4<;t~d%!`K{a7Rv^+i#XN>?*ay4e*){;^;$NpF`tbjcCkShLTGfgq&a$~@2k4H zy6eh}^~*CNGqN(Xj_&H7%nvcuRgrPLh(F#RFJ8PrXwb8?#QGpnIjShi+Zf;xWUP+? z^aFs>pd#XI8Y_Vc0*nH~Q5D$t(~9yyIt^zKaj~(*oFBPiz0D*5VN!boFm@{<9%le; z_2wDBzXlROSOCWU9ue+oPba>Mh=nz%#kwuBE+A#ItS6CH|A(UBzh(^D*R2%diaQcO zm_?@imvb5T(lBB%7EL$_)C{Ctj`b=j_5%QREL>4>s`VlP1SlfHXWP=sA0T3Rz0`Xx z>sb?!mY0`zgVOdT2JGLgW#!|tnc!uB>rA zTDthiKw2rW|EOu&oh{v}xN;4X00N#uhF7<@?lMq(BkH31W>DJO-izN!1n#RQ&u4l)>=UV?l-SDL`rM#8 z*VEp0`);g}0*7g&@!3f8H)9M|ie;!&G^dhCxHPMzf2POyV118Pjrp{cO8*|sf9Y?8 zRD%AF)Wkr6wMv4Ds)DLAC|4?=(r1kUV;V39pfRSm+YkvtQc)n0F#by>5|BimY`g&*r1^gh5}~R|7@8OkaRiOazscd3Z&C1=xooxrp|U9TveYKNKF8e zsy=3cdRz5X3(sIJa-*wo$zsbm5YiG(RrOK$OoY>?ceYb07D zHQ?ca#M3g1iaGyUVB!I0fW^^V28H3j(}9$-fYg8ruTmBSmZjwaD10#+qg85HE$Z! z#Qe*nt>ggHsa;Z8S8JsC>=Mjob5O3RKvb@tN5qcP&3+)#nSfa!O-ZOIkVz$BdrvoX zWYUjzwkKm3p(-5`9Y_tPcGirm(>WU~Mz;!UAX@`jn#W0FX(MZ0W#q*JsdcGcMXf-7 zr35qcSt!<_ecXKlsJm z*s8~~YT|M+GvWrwJRshW74<8Z93A}WMg>Hz?Wzq()?0N<^qR(ifK)1*3uw!_WEs6> zG|x6jSe7Vf(JW9ulL}^%rk1Otp3SgRs1*&yXXapOr2r&{Mm>IXPJolE$EBpt-g0#s!YTxeN)$fYF(}wap~1p)4TT z`$(;UB+^35g%V86EI_H;_;7K4bWYSu++s-l==|l?M($BkLBM;M2ETvlk=-K^hR}vh zTAM~{fH-JSIY4r07~Z(xGDHaLC~niyw%D0#DFp%2Ewmx4X_{_Gn9DAKs&Sjp4UnbY zcE#8qQmEnP0*K0_DTL{pwT$C-RtmmwQ4RSG3};&oDeuP+wcC#m@A)1g)^1*GfDG0; zi`s!iZ@S%Ztf?==_cpbwXyy*QvAHUk!nw^>Yqqt4ODlB7z4WtnDsi!SL@6v!C zo1TN*a-m^sp|(s4I@>bPo=NNfC6mUcD(}gmZ_}Ed4&(tr8@jZaOaGELga9W$I>)c0 zyef}=olgLMx|o~zD0~`^wxfQEuTpvZxbF?~>=n*z_zEinWh1Y6Ag<_ISEG+o7|g4bsVE zS&0FNeej!2>&d34OLM! zSjgoeo6EcW`=rKlw~{OY;s=rk2EK6=vDA=8tboZ;{&GDB?1Owuuj`rAtYvW<8l>rYgV)~|#PqI~nYyzVs1Zy4KyMlyQz zuNdZ0flue!*nj(l^JL8w2}nfkN+z|7VdN|i_#salt@DfdC>YY6M0f4z(T_GXYT6*r z4x)2W;79qEOkA5~9O_vIhZ1=5x7k%r4|_Q&nxR zX|xrR3Z&2c0|B078ve?~2M3-DRf%<+CE6|iJbWn9 zxp0j$j1B&-N(7{2g8lYvcKN_;cIjQ^ih3cRvoVMq^a#Hp{AEW^$F2K&dcw~z;i}zI zscHaHc-8%gRhwUc)*x&2JeE`;iP0#Q$Q z7xZo4Vtc)ZdNCkTEp#qO6LCe3w564g1Hf?r*jG{2wsVsUS1zp-`ZSIC`W9J(ivdh4 z2>;>e{(%P(X;*mTJu#5#>{Hsv9G#c}RSou^Gw575Z133=S#wmYW;5uVNYC(8CWU_i z0JJ{Rx4Da@%GSxb#VfN*E1LxmdHI$x`GI{q&sN^IQ8#OA08&ittU*Q=tuy1(p*?+R<;M0Y<{fDpDhYvW$=!?G;T-&xi8nCJ%*OecMboAD>D@ zD4TMyHd~{-7J<$gL1c`*sJTP}2}qu2d8IToIk$LOex;b=53AS-Z6N6%ew1O$*KEXd zOi}s(8o5TI**w2ehEhcy8OOV37M8X~Kk>2ete*3DF`G6e->}isgMBnVn5(#+A;h}-MgXXjK_IrqKbDAUq$r=#WXp6G-hk7u> z>=Jo@J8|teWQeY&T+BKbclQiR$2ZIvtiBtFt{ubZZZE$ zQ*%pQ{JAH|)JMrrv}55l`}gnPQ0DMDfK=a{K?3&z$;{L8{1#BdMPc=5AAV4|>m}Dn zGG|Qh%x=}k$(E|M9qIR=&0HUiMDFbbqEl8GD=Ve0(doGll*^hl zm?W?98G~QhNDi$n2BZkoPO#2#T13JL-3A9k`*@q~J_#yej!-IcsMA)`1R9V?5OU^<^B0~}D@l$5|8Hq2L;&0GnPmI2@i060hNk&^?So|wHMpD*6Z z05T$#_Ne3sj_wXhcpp^#2@Ty47D@j+L`oBSc~x8<7fJIRbMcN%dB=5we#_6 zarm7wZ%dW6(!4|y8w z>FOx8d41Cf>6 z3umT2QZ83s;}0Yov~NGUf8fq=wP@{Bq58>cLcY{EAW@!m#)*$6*+?z*W=*eCaQm~9 zbcDS632Hj1x2Ioj@(xpJLsNuF>7{bUy&l`gn#@ThdC7O)Xq8pIiQf zKbUPx>Rm_nU2sdZmbPZpYpUO8PmbTi7$a8>K`RUEP6p4kE5*x(hK8b@ z%Wpv}MFdh~HBt-8qiU5Xb^&RmYLD&?w0#ToZ0?HOyddkDEW>7MB8xR>ntdKAbel#_ zPkpScR<3jd$s%n8?MgzlS}UxexT>LJ_c8p+*#+w^NCqdcS~ zqB@0#1rc?!?yV51w~ftQvH&s$bRLNg_Jw{VxsaGP6tww4j0?^V5-I1!gOuQ1$2BRmu!E3nMSBoFO zy>1KNy^kZN;Y{E54*+M!r#P1zc|Ufxx52>9ZLaa+O-m(^rqH#8a~>pca1e-ew;yYt zfJ2ehCNh850#M4zhb~42QZ8TEHa3&}&zh_J89T{CR4d`}`Wkn)An=CR57k5n}6m|JK_3pqLNz{$j zNbBhs1>I>hF-2P_)t{ctmVcd@P68wXk+|9FpvbR7ED$-kmIIz92)aBVVpzK%FY(Ag zDiliHqmv6CuBhrI?pDvV)FNjQNI8FD6j6kHXi)?JV}8ilU8-FvV4SGBS`#CAIU+ z({&16A9 zvhdQob+dl{$f=N{Lkv2{m+MbSt@n9NB-2J#Q)s%d3=3gggsl9<&0OCuZ3vKDn#d|F z)=2drNx%BibJX0Ko!y`@era8eWC5vXvs@#|HIXGjIkj_x$Vn~Af%c#b5Lr?R)=2s> z3uBKc%o7<%E2UD`xygkOlq;%zq(auG0Pq)BT)gejp+mVa6=~HpYqmy`11SQvTjgUE zM1RJorak>=3()+qWs~LgZSyeQ{Ka}$nursML<<_BvWz___hZyrR%Q*F$jUr2kaEj~ z9ph8kPisuu>1~T$Z}!gy%ufyP?fX4M3=UP>TrAX;d(A+qt5oylko-JSQIQ%Hl(Q={ zJ*w(TQ4DqG8YR4okSvf$wIV}U&-B-8@yn|NL{wyCAZ3?|0~0gzUu2B687kMA$}F)m z1{FnNA2~R9!54zMqSb$oH0~O?kJRddk=1W(Y96vndC$pNN27us=wxW17uqu3fG5>K z#0M(zPZ{+r5#>Z$TUEnZ6?{sL45X=29-qBs zrBJ%j5srk0GMHuvHys}8f2iKRS?l*>AVuf_I%0lyg7R#{8A6K#w z7yQQu2m4%y|7xgR3%pV@kQz~U=LT&jw@4M@D-p#fM(oefO(C=N_|zQGok&*XBN>n~ zDPx_KO8Kvo^tSuismx62pPJT0=2ez;(m7r(k$^NhG55y&O7T{ifkEKG0CyZ3?Ejt6 z9niuFBaK@!HZ*FDWZXt!9HS8M;CH0JwED;@Z2Tnt;z=j<-;;;=GU~(8HuEFZd0}zM zzJWppBBLq@yLWDbO`V3p%KcEJAH#Rf3R3T?yZI-%6IBZ!BFkGpVbOSVXlNj*s+xTH z+{~v{O*`zXk*vN@)oTy#-$kLl*8Q`lK;m75jG^m2OW;^djjGB9Z`E5JWPW-k@Z@0g zii97`v`)(9mYl)rN^bqx)zJof`nP)q2pxl{m=-j@GgL-o)#xD7!*MaXQZLv=DB{Qs z4WyZD?%?b~{^P)4tEUW3(=rCRZC!l>y}h`uU4yJPwiuAiYH=giuAodK*&V|*gX=6o zvxL-0K_2CmiVAZJ3(h)8B0>6!w9?)SdZD8&ZI34&M1ER07)0`}BLEpk!;SMuH8Nd4 ze1!s%s;Y^xnd}?#E9IL3SV}s$oXz(CerT}&y%7Q3vdLQvNFEgMG~4%ttZZiIh?$|u z+%2uqqnub11&J(`N*)&;H@VcWy4+z*%>E!U^{hF-Qc1&Wq52~x(7hp;6CGi#fK(`y zx1ODz|6NtphGp8wsx@O!21UF2(B6R`1+{d&{2th4d_x+&Mhc>Kjy7_`LgaY<)Xx9F zoHUMspg(=l!_Cls6q6+A*`b&D#L&!U6Wx@0j zU@5;0bWf6-&WVHCh?=^yw+AS?koPgp5k&)$ag!~%{MbrPnDtkGjYO8Z(-ZT*Q7%`m z_Gn%A2xBm*q4MU#gFF8*XqI13zqbsK^osbIuzigW2I@G4WsPG;8%e2!JVcdsFwWL8 zGWBOxQ&m-%o#%`W+yvkjYFj1){X2R*<3czw>r+qnOGFUKVP3FsyDp{HFHyFA^BXc8 zs~Q`o<`yqoSX}uK17^5$__jr_)ju5??E8g=)T)Iti4LTS@`ych`7;a=Khr>1E|h_+ zVEiMsHc>4p{FHB`9kJ)6jjD3ILZD}EREmzv25`~Xt69t~L2hZ;+z+ZAz@&RqCv4{^ z9<7P!-XmFi{W?W=J~d6;vNYBjDd=Ed4dc%jNclo}>-hBihg4NN9;l4~SCXLIxPNfR z|EZx4E&a-x0g0xj2JXB!RmdJ8>A(g(SmfB`oPMXQ|14}&O;Hrx2X9kHyWxB)Jxk=@ zj5?1&)GHrfAe|bUeQ&W`xds?>+&H8Y9<43eod*Z|-i?UC*B#bd<5hk?I*=N4_2nwJ zvuVYVNBCUU*i-Zqrlob#sTAz#+YXs@B659bO|*s+j|WJq3OF`9|I5qE#dnL8!Yv0| zDkn9@Ubhh(RArqP9Y{p&tWmR8-lHv4uEsPN9iM?xr4k4v8e$Y&pKhzjq{E$8GBiXdCtw# zsoC-AIjN7E+kipxri;k>d$&Ma0GQU8&hbM-0MhJy?#S%o@^3N5dhJ9~1B1P4dqM+t zpvsO;?8hi|*-Z5zb z3CCx%SLT*h-p?2~Cl{;5${P>n?Yqc9S#n zP%Ih2aNL0^-3=naQ=pU`J)5CDlM>SFRE~?R+5*BDNhOqzj?d0tpI<5642*SDUl%#F zj@qdpoKgYa@v}WUAG&Fs2g~>Cix@}^9Hn$lX<~W~Xx-%SKT*VPMMc3<$SmI7)vo*4 zg+1)oz-k9(rD^Q+^!&o(#Zu*3#u#1IZah`!ob|Igp^zqe9vQyP?D~fe3+gtSBeU>rMnY)w{)+iWllN>C|nxRR!h_JAo7_a zY;P3FES*ync+I|DJD*0}STrxzNa5y?(5Yync0RWR3yXP=@j(~1MB>(xyrVr0Tf4iU zJ(G@nIauflhD=ydHCQPWA(vkkVh{QQNu+ar$2bt+6au_s-=3Wht@k^Tcu~ec3OTT% zBqL|m`Nbv3=JGrPs&eeYS*<4$if+~0*1ZW*sp$JnQR?ZJmaLFj0#Lc^jSViPbIuGw zKL?zJVvMmD6i{y6NHni@Aep=S_DE@=y&ZklZ-M&E%x7USx9p>ZIMKU$!5EAPy61Rz z7bq01wGjxZk_TA{iN4J(=755{NXrWm#Wuo$!knt&FCHA)@$-#9^CI<+u#eQ_j@<~^ zQ@pzQYz`Ll%W|5>4_6;t5uoVUI$f!mP9^lSO%VobRWXw0q%}~nSkeQ!6m*NDJ2p3$ zMHOGi5Sqwa7sgI2h(z-q^rLwvPOx-$_iXR3UE3yFnF_{0YBFLqP19)-?J?2%$q~35 zIi3zNzfh2Fv{3X&u9-{<+R~|-)>5?gDHJK_Oj*}Jtd#u)sA>;nGB|!?Pr}M^`!t% zF!W3nSHmz8&S@%Mb>YyCr!1Ps7*oznWN$=-d(9PCu8i!ep&eW7G+{Dctu!%^R!hi97P;M~EnqZ&OlB~ak^!D$vn=Q<0%3Cy<-~a#*!bwCyR2NKh z0PMS|r1E)HWp@C;>*%8PI>dRJ0lss1&yF8F1CZD>vG5i^q~Q8?G$Ku9FD@2}Z@zec z-_PqPrzx+-Kx&Fu{WGNlf!U>HVcq0UrTn|c-@NQ=R`VJVs?j;K39^_K0PF7gPgk?nd zUlmAv=*VF2IA8DQo}1p3Xjgv-0Cd1F(zxRDNhA}wWFjR5PA7Uw`*QlZFTCdFo(B77LCRbXz`aoab zRJ8^&#_;8{(+8NQ-pK$X(L4fCB9Rn4I%uL<$Qd&6JPcGo_$SnqU%ULGJulTH_y)eT zW=0AdDNw z9ofj&DuUJ8Q-YVyRoNrr+l3S!ECHLkI-xC{rk$lnyE~Hh?c_mCF=$@pVF0`&jF=2f zBt}_s(>dQV5nf1_D_0#qexNGIgX&!G?~%p=Q9HGwJYv06kKZW`l-yDgigXz>Z|1cy zz(+zgXHk9dQs5CYb`T$4m>+0X3^`}=HQ`~?CO%M!8O+4Yo40n^D-Rx*XrxexyU_nN z!T**3v|K2{O0fv#a>ev@lON^riyto6M8>zWPAAhjosFX7@P;`(r3;(VMg?gvXM}(Y%^~WOY(KUN{6KinL1Sci_b)`$Nc!Z$7`}9NdcUTr-$Wmp7n#oa z;s`WU;3uF~Zn^B(ffwu5F15TL8A$d~^IA&d-qC3cX(T(=ff#O!k4!3l<_3jS!#E}< zLnArbsG5dxD4T<3Yp}||%WI@MMG@(B0$!<9hC-=iYO9KI{>hBWB+@x6YojG}UlUO_ z@{X{;6Q=N5G?JC?)D-K=u1q|Ps9Zvutk(BZI;YZ_$g$3asTdbT_U)Vu z^KK*4LJcomA}du`z4}z4WV;p zAtI3Uz?hFGQt-~>2lhU>nlx|S0Z9%Hhn@ukiA&3CEE}XDHBu`9sYY}}c&#oV5zQMN zo!VDo+BcO%@~TMIxhj?Pf=ExS0pcT&!na;_VE3xgyqbYzE7TcBFtveq=u=tXl}H*)-gU7 zJ7<4`2SBy8l_7D*dT0LST3dOnd%SjX`MX&FHk?E2mBPAtzb=h5GBT1$ZR&lif^Y{g z*kZNXf3vl^T=@Q!#eTPO2Kaa)slDss1ABkkY?>EoC|Kt2Ms$A^AXQ(tL;;f9@W2@( zc$y~znu%gdU@?%wkAlfJCh<(}t|3N%l2-PmtLn7B?up%@AiTzGwV{@kcT#2Op^pKhj!1Wlb9C!3Q5q z4jnxH?-fP)R^#X#r*8yM@f+ms3k`52)26=m$lkpZ(4?R%YYL>+A3t#oO$pQlq%&jL z>p@}P27qm=Nau{ATyrrh@Hk@PT^Ap?peZyj22zZbFcPQ3J2gGspUaj%S}2y@ z4gliq6}1=^xV4cpfUB&1T?=MJ_5>=)cU`o<|A|I{DF#vuk}wje3rJMJgC|aObwcND z2tdcuLp{P?A^(Q6XD-A|mJMcpgWkp;>dqgJvn2fLrTcd`gyzLS3e~0OeWJR7WJ&PR zr%!$m5I+N;2TYMZ{Jx%*K->%Dz7;^l(>dWy{SS{N7hyt4C0=)IZ~t@kYNi-S=RHP3 z37}y>`p41H_T*ya1IX|{(Y#!WPtcglEE!Uh6kRgwP#}> z)hfC3>%E2o$r9a9o<8|O1>rLQsMEY4E#fvTxD*T*fmTQ<9A%bmx*L=aCorX4b97JN zv$fE?7)a+AAZl%GQ-DM?FP&B20}6f&fNeJ_XADWVnOg*n+(H)wL?L(C+6wV`VA^fR z4($42RGKFM>8X*)D-kPK0ApP;iFbBnhPH3r`i@$ABYtmvNxxf}Fh@V+1Wk3I_!-pdW zZ+!H`sn;utau+aWxD$CahTDJ6td9M44Rp?Gkt;MbB2GXOuf6ZiuRMGE?YD|9rYr9Pa3N}u#F(eQcNB~$yfNv%%sSh7I)SHXa`o|u7 zEQMX$Z$b?}&k*bgtAQJ?ZV+(b5I;J|6lj?JwYbVbWG^rVw;kO-@NgKKhd&=V_entf zT>wUr`KmrKkb)$%F@FyaBwdr_5#WYn`v?ACRB1fMSV}o@^7Twf+zkjFYX>5O<^jTU ziiU4JeBgq|gW7^mzj*Em25@OmLi#-hQWz<1yfl%3WHkTt7abV*aIKPf{JE1i08_q& zfHuu@TgBXl1)U9Yi3F2{@20KS*2d>SaW2~1GE1N&uYcs>@BQSan{F~Z3Z zMN8qF;^+L50G(!z;Zb^_mLzqG`>u@)NwdWv)N`*;&~c z(I5~ZLR(uKP~1QBVF+*r671J@_ilYiu8~GYe-Bal1T$hD#!oDPIwgP=)GD_eyKv9l zb!Mc$PBc%!Bw6#e3lVwE%-yDNTN8O`p7DYkgvNJHO?5h#$w)2oji94Rk#ZPE^kD`<2=r5322P5@1!bl>){67Uu4j{(>I}_(4}LhHIE~1F3#q@ zU@edmxvWB;NL3GUis9~0=SmbBnxX-_@?ZdyS_Q5-I^2K4Q(iNL5a%6Bpk4_O z&HL%d=)X%a_@ZUaGX}9}-;QFTBM{lvIj({tos(aOg9`jGkxbuwU|`#**N|RsYvcE0 z2{cgxI_y6v2GMgmu)!YL+&ti_W4v0z~te zrrZs{$PpXE(K-2=NVL$2_iQ<^(?89}Kw6#tjZ0Z23FtKMl@r%9Rr|7{C__~@TgxV$ zGtGG}gPi+5Ku>^4D?I}KCPdiT-gBp?UYSlN{wkGB?r@jvY_`hjoLdvw zsbn7I^qmHO7z1hb`Zq3Vl_l`P=*+9q3FU4@Q3i?T>25#Pnn-7JoEJIA)f`Z8zq=h| zZ3_gJwX*t}7)Yz!xp8r;Cjp{)uefslx?}>rjEb^PKME0=&N*f|`I#pRi0ol3!A&uc zR1_c4~Z^xWhXpyC4+RW)c{$TbnCwCtMOfFX1-$RWV{_V3y8tr$pa(7ADu zt1p2QCnsK|fOaNGFNu;2?K$Lp2i-Q5Gl#aoAmhvR*u)$h`{#I=yXefQm$IR473 zu44?o3;_FtI(Xtbh@J}$$>Si%;D2h#_V*kd=$;XNUyH`YWyTU%O$q3x@hi?R158zXOE(x3npL-P`L8#~d3Q*03|B{|XUa$29B*giQc2 uryxF~Y3$*oqI@6K)Ki0lTe5z4C;fl + + + {{ statusText }} + + + + + + diff --git a/pages/order/components/CustomEmpty/styles/index.scss b/pages/order/components/CustomEmpty/styles/index.scss new file mode 100644 index 0000000..359c684 --- /dev/null +++ b/pages/order/components/CustomEmpty/styles/index.scss @@ -0,0 +1,19 @@ +.empty-container { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + height: 100%; + flex-direction: column; + align-items: center; +} + +.empty-image { + height: 130px; + width: 130px; +} + +.empty-text { + margin-top: 10px; + font-size: 14px; + color: #666666; +} \ No newline at end of file diff --git a/pages/order/components/CustomNoMore/images/no_more.png b/pages/order/components/CustomNoMore/images/no_more.png new file mode 100644 index 0000000000000000000000000000000000000000..8bca5fedb43c82dcc608024e4566782da8dc55ea GIT binary patch literal 79568 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%0AQ{*`^) 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

V|NqUzL^{ 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 + + + 已经到达宇宙尽头啦~ + + + + \ No newline at end of file diff --git a/pages/order/components/CustomNoMore/styles/index.scss b/pages/order/components/CustomNoMore/styles/index.scss new file mode 100644 index 0000000..d67b2ff --- /dev/null +++ b/pages/order/components/CustomNoMore/styles/index.scss @@ -0,0 +1,17 @@ +.nomore { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + align-items: center; + padding: 10px 0; +} +.nomore-image { + width: 100px; + height: 65px; +} +.nomore-text { + margin-top: 5px; + font-size: 12px; + color: #222963; +} \ No newline at end of file diff --git a/pages/order/components/CustomRefresher/images/refresher_loading.gif b/pages/order/components/CustomRefresher/images/refresher_loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..1ee754f3112499b7b8dd8dbbda5a42a5705da4ca GIT binary patch literal 99597 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

^ULdC>s+h9K%Soo+^A_+;{oUT1>(^udL; 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

=^FnIIhF8W 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 literal 0 HcmV?d00001 diff --git a/pages/order/components/CustomRefresher/index.vue b/pages/order/components/CustomRefresher/index.vue new file mode 100644 index 0000000..b2b87db --- /dev/null +++ b/pages/order/components/CustomRefresher/index.vue @@ -0,0 +1,39 @@ + + + + + + + diff --git a/pages/order/components/CustomRefresher/styles/index.scss b/pages/order/components/CustomRefresher/styles/index.scss new file mode 100644 index 0000000..c153bae --- /dev/null +++ b/pages/order/components/CustomRefresher/styles/index.scss @@ -0,0 +1,21 @@ +.refresher-container { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + height: 150rpx; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.refresher-image { + margin-top: 10rpx; + height: 45px; + width: 45px; +} + +.refresher-text { + margin-top: 10rpx; + font-size: 24rpx; + color: #666666; +} \ No newline at end of file diff --git a/pages/order/components/OrderList/demo.vue b/pages/order/components/OrderList/demo.vue deleted file mode 100644 index 9379a35..0000000 --- a/pages/order/components/OrderList/demo.vue +++ /dev/null @@ -1,322 +0,0 @@ - - - - - \ No newline at end of file diff --git a/pages/order/components/OrderList/index.vue b/pages/order/components/OrderList/index.vue deleted file mode 100644 index e331c78..0000000 --- a/pages/order/components/OrderList/index.vue +++ /dev/null @@ -1,265 +0,0 @@ - - - - - \ No newline at end of file diff --git a/pages/order/components/OrderList/styles/index.scss b/pages/order/components/OrderList/styles/index.scss deleted file mode 100644 index f4a1a3e..0000000 --- a/pages/order/components/OrderList/styles/index.scss +++ /dev/null @@ -1,298 +0,0 @@ -.order-list-container { - width: 100%; - min-height: 100vh; - background-color: #f5f5f5; - padding-top: 120rpx; /* 为固定导航栏留出空间 */ - - .z-paging-container { - width: 100%; - min-height: calc(100vh - 120rpx); - } - - // z-paging内部列表项样式 - :deep(.z-paging-content) { - padding: 0 32rpx; - - .order-card { - margin-bottom: 24rpx; - - &:last-child { - margin-bottom: 0; - } - } - } - - // 非虚拟列表内容区域样式 - .order-list-content { - padding: 0 32rpx; - - .order-card { - margin-bottom: 24rpx; - - &:last-child { - margin-bottom: 0; - } - } - } -} - - - -// 空状态样式(z-paging内置) -:deep(.z-paging-empty-view) { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 120rpx 60rpx; - text-align: center; - - .z-paging-empty-view-img { - width: 200rpx; - height: 200rpx; - margin-bottom: 40rpx; - opacity: 0.6; - } - - .z-paging-empty-view-text { - font-size: 28rpx; - color: #999999; - margin-bottom: 40rpx; - line-height: 1.5; - } - - .z-paging-empty-view-reload-btn { - padding: 20rpx 40rpx; - background-color: #007aff; - color: white; - border: none; - border-radius: 12rpx; - font-size: 28rpx; - transition: all 0.3s ease; - - &:active { - background-color: #0056cc; - transform: scale(0.95); - } - } -} - -// 自定义空状态样式 -.custom-empty-state { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 120rpx 60rpx; - text-align: center; - - .empty-icon { - width: 200rpx; - height: 200rpx; - margin-bottom: 40rpx; - opacity: 0.6; - } - - .empty-text { - font-size: 28rpx; - color: #999999; - margin-bottom: 40rpx; - line-height: 1.5; - } - - .refresh-btn { - padding: 20rpx 40rpx; - background-color: #007aff; - color: white; - border: none; - border-radius: 12rpx; - font-size: 28rpx; - transition: all 0.3s ease; - - &:active { - background-color: #0056cc; - transform: scale(0.95); - } - } -} - -// z-paging加载更多样式 -:deep(.z-paging-load-more) { - padding: 40rpx 0; - text-align: center; - - .z-paging-load-more-line { - display: flex; - align-items: center; - justify-content: center; - gap: 20rpx; - - .z-paging-load-more-loading { - width: 32rpx; - height: 32rpx; - border: 4rpx solid #e5e5e5; - border-top: 4rpx solid #007aff; - border-radius: 50%; - animation: spin 1s linear infinite; - } - - .z-paging-load-more-text { - font-size: 26rpx; - color: #666666; - } - } - - .z-paging-load-more-no-more-line { - .z-paging-load-more-text { - font-size: 26rpx; - color: #999999; - } - } -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - - - -// z-paging刷新器样式 -:deep(.z-paging-refresh) { - .z-paging-refresh-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 20rpx; - - .z-paging-refresh-loading { - width: 40rpx; - height: 40rpx; - border: 4rpx solid #e5e5e5; - border-top: 4rpx solid #007aff; - border-radius: 50%; - animation: spin 1s linear infinite; - margin-bottom: 10rpx; - } - - .z-paging-refresh-text { - font-size: 26rpx; - color: #666666; - } - } -} - -/* 下拉刷新样式优化 */ -:deep(.uni-scroll-view-refresher) { - background: rgba(255, 255, 255, 0.9); - backdrop-filter: blur(10px); -} - -/* 滚动条样式 */ -.scroll-area::-webkit-scrollbar { - width: 0; - background: transparent; -} - -/* 响应式设计 */ -@media (max-width: 375px) { - .order-list { - padding: 8px 0; - } - - .empty-state { - padding: 60px 16px; - min-height: 50vh; - } - - .empty-icon { - width: 100px; - height: 100px; - margin-bottom: 16px; - } - - .empty-text { - font-size: 15px; - margin-bottom: 20px; - } - - .refresh-btn { - padding: 8px 20px; - font-size: 13px; - } -} - -/* 暗色模式支持 */ -@media (prefers-color-scheme: dark) { - .order-list-container { - background: linear-gradient(180deg, #1a1a1a 0%, #2d2d2d 50%, #404040 100%); - } - - .empty-text { - color: #cccccc; - } - - .loading-text { - color: #cccccc; - } - - .no-more-text { - color: #999999; - } - - .loading-spinner { - border-color: #404040; - border-top-color: #409EFF; - } -} - -/* 列表项动画 */ -.order-list view { - animation: fadeInUp 0.3s ease-out; -} - -@keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* 加载状态过渡 */ -.global-loading { - transition: opacity 0.3s ease; -} - -.load-more { - transition: all 0.3s ease; -} - -/* 空状态动画 */ -.empty-state { - animation: fadeIn 0.5s ease-out; -} - -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -/* 刷新状态优化 */ -.scroll-area[refresher-triggered="true"] { - .order-list { - transition: transform 0.3s ease; - } -} \ No newline at end of file diff --git a/pages/order/components/OrderList/test.vue b/pages/order/components/OrderList/test.vue deleted file mode 100644 index 428d2b3..0000000 --- a/pages/order/components/OrderList/test.vue +++ /dev/null @@ -1,195 +0,0 @@ - - - - - \ No newline at end of file diff --git a/pages/order/list.vue b/pages/order/list.vue index 7c18719..ecceed3 100644 --- a/pages/order/list.vue +++ b/pages/order/list.vue @@ -1,7 +1,11 @@ + + + + + + - + diff --git a/uni_modules/z-paging/components/z-paging-swiper-item/z-paging-swiper-item.vue b/uni_modules/z-paging/components/z-paging-swiper-item/z-paging-swiper-item.vue new file mode 100644 index 0000000..bf40f14 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging-swiper-item/z-paging-swiper-item.vue @@ -0,0 +1,160 @@ + + + + + + + + + + + diff --git a/uni_modules/z-paging/components/z-paging-swiper/z-paging-swiper.vue b/uni_modules/z-paging/components/z-paging-swiper/z-paging-swiper.vue new file mode 100644 index 0000000..41e391c --- /dev/null +++ b/uni_modules/z-paging/components/z-paging-swiper/z-paging-swiper.vue @@ -0,0 +1,176 @@ + + + + + + + + + + + diff --git a/uni_modules/z-paging/components/z-paging/components/z-paging-load-more.vue b/uni_modules/z-paging/components/z-paging/components/z-paging-load-more.vue new file mode 100644 index 0000000..4c2a6e8 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/components/z-paging-load-more.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/uni_modules/z-paging/components/z-paging/components/z-paging-refresh.vue b/uni_modules/z-paging/components/z-paging/components/z-paging-refresh.vue new file mode 100644 index 0000000..305c5c5 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/components/z-paging-refresh.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/uni_modules/z-paging/components/z-paging/config/index.js b/uni_modules/z-paging/components/z-paging/config/index.js new file mode 100644 index 0000000..15a37e2 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/config/index.js @@ -0,0 +1,3 @@ +// z-paging全局配置文件,注意避免更新时此文件被覆盖,若被覆盖,可在此文件中右键->点击本地历史记录,找回覆盖前的配置 + +export default {} \ No newline at end of file diff --git a/uni_modules/z-paging/components/z-paging/css/z-paging-main.css b/uni_modules/z-paging/components/z-paging/css/z-paging-main.css new file mode 100644 index 0000000..9825869 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/css/z-paging-main.css @@ -0,0 +1,241 @@ +/* [z-paging]公共css*/ + +.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%; + /* #endif */ +} + +.z-paging-content-fixed, .zp-loading-fixed { + position: fixed; + /* #ifndef APP-NVUE */ + height: auto; + width: auto; + /* #endif */ + top: 0; + left: 0; + bottom: 0; + 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; + /* #endif */ + position: fixed; + left: 0; + right: 0; + z-index: 999; +} + +.zp-page-left, .zp-page-right { + /* #ifndef APP-NVUE */ + height: 100%; + /* #endif */ +} + +.zp-scroll-view-super { + flex: 1; + overflow: hidden; + position: relative; +} + +.zp-view-super { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; +} + +.zp-scroll-view-container, .zp-scroll-view { + position: relative; + /* #ifndef APP-NVUE */ + height: 100%; + width: 100%; + /* #endif */ +} + +.zp-absoulte { + /* #ifndef APP-NVUE */ + position: absolute; + top: 0; + width: auto; + /* #endif */ +} + +.zp-scroll-view-absolute { + position: absolute; + top: 0; + left: 0; +} + +/* #ifndef APP-NVUE */ +.zp-scroll-view-hide-scrollbar ::-webkit-scrollbar { + display: none; + -webkit-appearance: none; + width: 0 !important; + height: 0 !important; + background: transparent; +} +/* #endif */ + +.zp-paging-touch-view { + width: 100%; + height: 100%; + position: relative; +} + +.zp-fixed-bac-view { + position: absolute; + width: 100%; + top: 0; + left: 0; + height: 200px; +} + +.zp-paging-main { + height: 100%; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; +} + +.zp-paging-container { + flex: 1; + position: relative; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; +} + +.zp-chat-record-loading-custom-image { + width: 35rpx; + height: 35rpx; + /* #ifndef APP-NVUE */ + animation: loading-flower 1s linear infinite; + /* #endif */ +} + +.zp-page-bottom-keyboard-placeholder-animate { + transition-property: height; + transition-duration: 0.15s; + /* #ifndef APP-NVUE */ + will-change: height; + /* #endif */ +} + +.zp-custom-refresher-container { + overflow: hidden; +} + +.zp-custom-refresher-refresh { + /* #ifndef APP-NVUE */ + display: block; + /* #endif */ +} + +.zp-back-to-top { + z-index: 999; + position: absolute; + bottom: 0rpx; + transition-duration: .3s; + transition-property: opacity; +} +.zp-back-to-top-rpx { + width: 76rpx; + height: 76rpx; + bottom: 0rpx; + right: 25rpx; +} +.zp-back-to-top-px { + width: 38px; + height: 38px; + bottom: 0px; + right: 13px; +} + +.zp-back-to-top-show { + opacity: 1; +} + +.zp-back-to-top-hide { + opacity: 0; +} + +.zp-back-to-top-img { + /* #ifndef APP-NVUE */ + width: 100%; + height: 100%; + /* #endif */ + /* #ifdef APP-NVUE */ + flex: 1; + /* #endif */ + z-index: 999; +} + +.zp-back-to-top-img-inversion { + transform: rotate(180deg); +} + +.zp-empty-view { + /* #ifdef APP-NVUE */ + height: 100%; + /* #endif */ + flex: 1; +} + +.zp-empty-view-center { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + align-items: center; + justify-content: center; +} + +.zp-loading-fixed { + z-index: 9999; +} + +.zp-safe-area-inset-bottom { + position: absolute; + /* #ifndef APP-PLUS */ + height: env(safe-area-inset-bottom); + /* #endif */ +} + +.zp-n-refresh-container { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + justify-content: center; + width: 750rpx; +} + +.zp-n-list-container{ + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + flex: 1; +} diff --git a/uni_modules/z-paging/components/z-paging/css/z-paging-static.css b/uni_modules/z-paging/components/z-paging/css/z-paging-static.css new file mode 100644 index 0000000..de445c2 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/css/z-paging-static.css @@ -0,0 +1,50 @@ +/* [z-paging]公用的静态css资源 */ + +.zp-line-loading-image { + /* #ifndef APP-NVUE */ + animation: loading-flower 1s steps(12) infinite; + /* #endif */ + color: #666666; +} +.zp-line-loading-image-rpx { + margin-right: 8rpx; + width: 34rpx; + height: 34rpx; +} +.zp-line-loading-image-px { + margin-right: 4px; + width: 17px; + height: 17px; +} + +.zp-loading-image-ios-rpx { + width: 40rpx; + height: 40rpx; +} +.zp-loading-image-ios-px { + width: 20px; + height: 20px; +} + +.zp-loading-image-android-rpx { + width: 34rpx; + height: 34rpx; +} +.zp-loading-image-android-px { + width: 17px; + height: 17px; +} + +/* #ifndef APP-NVUE */ +@keyframes loading-flower { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + to { + -webkit-transform: rotate(1turn); + transform: rotate(1turn); + } +} +/* #endif */ + diff --git a/uni_modules/z-paging/components/z-paging/i18n/en.json b/uni_modules/z-paging/components/z-paging/i18n/en.json new file mode 100644 index 0000000..48fbe60 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/i18n/en.json @@ -0,0 +1,23 @@ +{ + "zp.refresher.default": "Pull down to refresh", + "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...", + "zp.loadingMore.noMore": "No more data", + "zp.loadingMore.fail": "Load failed,click to reload", + + "zp.emptyView.title": "No data", + "zp.emptyView.reload": "Reload", + "zp.emptyView.error": "Sorry,load failed", + + "zp.refresherUpdateTime.title": "Last update: ", + "zp.refresherUpdateTime.none": "None", + "zp.refresherUpdateTime.today": "Today", + "zp.refresherUpdateTime.yesterday": "Yesterday", + + "zp.systemLoading.title": "Loading..." +} diff --git a/uni_modules/z-paging/components/z-paging/i18n/index.js b/uni_modules/z-paging/components/z-paging/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uni_modules/z-paging/components/z-paging/i18n/zh-Hans.json b/uni_modules/z-paging/components/z-paging/i18n/zh-Hans.json new file mode 100644 index 0000000..440ca63 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/i18n/zh-Hans.json @@ -0,0 +1,23 @@ +{ + "zp.refresher.default": "继续下拉刷新", + "zp.refresher.pulling": "松开立即刷新", + "zp.refresher.refreshing": "正在刷新...", + "zp.refresher.complete": "刷新成功", + "zp.refresher.f2": "松手进入二楼", + + "zp.loadingMore.default": "点击加载更多", + "zp.loadingMore.loading": "正在加载...", + "zp.loadingMore.noMore": "没有更多了", + "zp.loadingMore.fail": "加载失败,点击重新加载", + + "zp.emptyView.title": "没有数据哦~", + "zp.emptyView.reload": "重新加载", + "zp.emptyView.error": "很抱歉,加载失败", + + "zp.refresherUpdateTime.title": "最后更新:", + "zp.refresherUpdateTime.none": "无", + "zp.refresherUpdateTime.today": "今天", + "zp.refresherUpdateTime.yesterday": "昨天", + + "zp.systemLoading.title": "加载中..." +} diff --git a/uni_modules/z-paging/components/z-paging/i18n/zh-Hant.json b/uni_modules/z-paging/components/z-paging/i18n/zh-Hant.json new file mode 100644 index 0000000..b3d5502 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/i18n/zh-Hant.json @@ -0,0 +1,23 @@ +{ + "zp.refresher.default": "繼續下拉重繪", + "zp.refresher.pulling": "鬆開立即重繪", + "zp.refresher.refreshing": "正在重繪...", + "zp.refresher.complete": "重繪成功", + "zp.refresher.f2": "鬆手進入二樓", + + "zp.loadingMore.default": "點擊加載更多", + "zp.loadingMore.loading": "正在加載...", + "zp.loadingMore.noMore": "沒有更多了", + "zp.loadingMore.fail": "加載失敗,點擊重新加載", + + "zp.emptyView.title": "沒有數據哦~", + "zp.emptyView.reload": "重新加載", + "zp.emptyView.error": "很抱歉,加載失敗", + + "zp.refresherUpdateTime.title": "最後更新:", + "zp.refresherUpdateTime.none": "無", + "zp.refresherUpdateTime.today": "今天", + "zp.refresherUpdateTime.yesterday": "昨天", + + "zp.systemLoading.title": "加載中..." +} diff --git a/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js b/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js new file mode 100644 index 0000000..adb6e1b --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js @@ -0,0 +1,25 @@ +// [z-paging]useZPaging hooks + +import { onPageScroll, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app'; + +function useZPaging(paging) { + const cPaging = !!paging ? paging.value || paging : null; + + onPullDownRefresh(() => { + if (!cPaging || !cPaging.value) return; + cPaging.value.reload().catch(() => {}); + }) + + onPageScroll(e => { + if (!cPaging || !cPaging.value) return; + cPaging.value.updatePageScrollTop(e.scrollTop); + e.scrollTop < 10 && cPaging.value.doChatRecordLoadMore(); + }) + + onReachBottom(() => { + if (!cPaging || !cPaging.value) return; + cPaging.value.pageReachBottom(); + }) +} + +export default useZPaging \ No newline at end of file diff --git a/uni_modules/z-paging/components/z-paging/js/hooks/useZPagingComp.js b/uni_modules/z-paging/components/z-paging/js/hooks/useZPagingComp.js new file mode 100644 index 0000000..f9bc5ab --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/hooks/useZPagingComp.js @@ -0,0 +1,25 @@ +// [z-paging]useZPagingComp hooks + +function useZPagingComp(paging) { + const cPaging = !!paging ? paging.value || paging : null; + + const reload = () => { + if (!cPaging || !cPaging.value) return; + cPaging.value.reload().catch(() => {}); + } + const updatePageScrollTop = scrollTop => { + if (!cPaging || !cPaging.value) return; + cPaging.value.updatePageScrollTop(scrollTop); + } + const doChatRecordLoadMore = () => { + if (!cPaging || !cPaging.value) return; + cPaging.value.doChatRecordLoadMore(); + } + const pageReachBottom = () => { + if (!cPaging || !cPaging.value) return; + cPaging.value.pageReachBottom(); + } + return { reload, updatePageScrollTop, doChatRecordLoadMore, pageReachBottom }; +} + +export default useZPagingComp \ No newline at end of file diff --git a/uni_modules/z-paging/components/z-paging/js/modules/back-to-top.js b/uni_modules/z-paging/components/z-paging/js/modules/back-to-top.js new file mode 100644 index 0000000..4e484d0 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/back-to-top.js @@ -0,0 +1,125 @@ +// [z-paging]点击返回顶部view模块 +import u from '.././z-paging-utils' + +export default { + props: { + // 自动显示点击返回顶部按钮,默认为否 + autoShowBackToTop: { + type: Boolean, + default: u.gc('autoShowBackToTop', false) + }, + // 点击返回顶部按钮显示/隐藏的阈值(滚动距离),单位为px,默认为400rpx + backToTopThreshold: { + type: [Number, String], + default: u.gc('backToTopThreshold', '400rpx') + }, + // 点击返回顶部按钮的自定义图片地址,默认使用z-paging内置的图片 + backToTopImg: { + type: String, + default: u.gc('backToTopImg', '') + }, + // 点击返回顶部按钮返回到顶部时是否展示过渡动画,默认为是 + backToTopWithAnimate: { + type: Boolean, + default: u.gc('backToTopWithAnimate', true) + }, + // 点击返回顶部按钮与底部的距离,注意添加单位px或rpx,默认为160rpx + backToTopBottom: { + type: [Number, String], + default: u.gc('backToTopBottom', '160rpx') + }, + // 点击返回顶部按钮的自定义样式 + backToTopStyle: { + type: Object, + default: u.gc('backToTopStyle', {}), + }, + // iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只支持竖向,默认为是 + enableBackToTop: { + type: Boolean, + default: u.gc('enableBackToTop', true) + }, + }, + data() { + return { + // 点击返回顶部的class + backToTopClass: 'zp-back-to-top zp-back-to-top-hide', + // 上次点击返回顶部的时间 + lastBackToTopShowTime: 0, + // 点击返回顶部显示的class是否在展示中,使得按钮展示/隐藏过度效果更自然 + showBackToTopClass: false, + } + }, + computed: { + backToTopThresholdUnitConverted() { + return u.addUnit(this.backToTopThreshold, this.unit); + }, + backToTopBottomUnitConverted() { + return u.addUnit(this.backToTopBottom, this.unit); + }, + finalEnableBackToTop() { + return this.usePageScroll ? false : this.enableBackToTop; + }, + finalBackToTopThreshold() { + return u.convertToPx(this.backToTopThresholdUnitConverted); + }, + finalBackToTopStyle() { + const backToTopStyle = this.backToTopStyle; + if (!backToTopStyle.bottom) { + backToTopStyle.bottom = this.windowBottom + u.convertToPx(this.backToTopBottomUnitConverted) + 'px'; + } + if(!backToTopStyle.position){ + backToTopStyle.position = this.usePageScroll ? 'fixed': 'absolute'; + } + return backToTopStyle; + }, + finalBackToTopClass() { + return `${this.backToTopClass} zp-back-to-top-${this.unit}`; + } + }, + methods: { + // 点击了返回顶部 + _backToTopClick() { + let callbacked = false; + this.$emit('backToTopClick', toTop => { + (toTop === undefined || toTop === true) && this._handleToTop(); + callbacked = true; + }); + // 如果用户没有禁止默认的返回顶部事件,则触发滚动到顶部 + this.$nextTick(() => { + !callbacked && this._handleToTop(); + }) + }, + // 处理滚动到顶部(聊天记录模式中为滚动到底部) + _handleToTop() { + !this.backToTopWithAnimate && this._checkShouldShowBackToTop(0); + !this.useChatRecordMode ? this.scrollToTop(this.backToTopWithAnimate) : this.scrollToBottom(this.backToTopWithAnimate); + }, + // 判断是否要显示返回顶部按钮 + _checkShouldShowBackToTop(scrollTop) { + if (!this.autoShowBackToTop) { + this.showBackToTopClass = false; + return; + } + if (scrollTop > this.finalBackToTopThreshold) { + if (!this.showBackToTopClass) { + // 记录当前点击返回顶部按钮显示的class生效了 + this.showBackToTopClass = true; + this.lastBackToTopShowTime = new Date().getTime(); + // 当滚动到需要展示返回顶部的阈值内,则延迟300毫秒展示返回到顶部按钮 + u.delay(() => { + this.backToTopClass = 'zp-back-to-top zp-back-to-top-show'; + }, 300) + } + } else { + // 如果当前点击返回顶部按钮显示的class是生效状态并且滚动小于触发阈值,则隐藏返回顶部按钮 + if (this.showBackToTopClass) { + this.backToTopClass = 'zp-back-to-top zp-back-to-top-hide'; + u.delay(() => { + this.showBackToTopClass = false; + }, new Date().getTime() - this.lastBackToTopShowTime < 500 ? 0 : 300) + } + } + }, + } +} + diff --git a/uni_modules/z-paging/components/z-paging/js/modules/chat-record-mode.js b/uni_modules/z-paging/components/z-paging/js/modules/chat-record-mode.js new file mode 100644 index 0000000..12b2b83 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/chat-record-mode.js @@ -0,0 +1,153 @@ +// [z-paging]聊天记录模式模块 +import u from '.././z-paging-utils' + +export default { + props: { + // 使用聊天记录模式,默认为否 + useChatRecordMode: { + type: Boolean, + default: u.gc('useChatRecordMode', false) + }, + // 使用聊天记录模式时滚动到顶部后,列表垂直移动偏移距离。默认0rpx。单位px(暂时无效) + chatRecordMoreOffset: { + type: [Number, String], + default: u.gc('chatRecordMoreOffset', '0rpx') + }, + // 使用聊天记录模式时是否自动隐藏键盘:在用户触摸列表时候自动隐藏键盘,默认为是 + autoHideKeyboardWhenChat: { + type: Boolean, + default: u.gc('autoHideKeyboardWhenChat', true) + }, + // 使用聊天记录模式中键盘弹出时是否自动调整slot="bottom"高度,默认为是 + autoAdjustPositionWhenChat: { + type: Boolean, + default: u.gc('autoAdjustPositionWhenChat', true) + }, + // 使用聊天记录模式中键盘弹出时占位高度偏移距离。默认0rpx。单位px + chatAdjustPositionOffset: { + type: [Number, String], + default: u.gc('chatAdjustPositionOffset', '0rpx') + }, + // 使用聊天记录模式中键盘弹出时是否自动滚动到底部,默认为否 + autoToBottomWhenChat: { + type: Boolean, + default: u.gc('autoToBottomWhenChat', false) + }, + // 使用聊天记录模式中reload时是否显示chatLoading,默认为否 + showChatLoadingWhenReload: { + type: Boolean, + default: u.gc('showChatLoadingWhenReload', false) + }, + // 在聊天记录模式中滑动到顶部状态为默认状态时,以加载中的状态展示,默认为是。若设置为否,则默认会显示【点击加载更多】,然后才会显示loading + chatLoadingMoreDefaultAsLoading: { + type: Boolean, + default: u.gc('chatLoadingMoreDefaultAsLoading', true) + }, + }, + data() { + return { + // 键盘高度 + keyboardHeight: 0, + // 键盘高度是否未改变,此时占位高度变化不需要动画效果 + isKeyboardHeightChanged: false, + } + }, + computed: { + finalChatRecordMoreOffset() { + return u.convertToPx(this.chatRecordMoreOffset); + }, + finalChatAdjustPositionOffset() { + return u.convertToPx(this.chatAdjustPositionOffset); + }, + // 聊天记录模式旋转180度style + chatRecordRotateStyle() { + let cellStyle; + // 在vue中,直接将列表倒置,因此在vue的cell中,也直接写style="transform: scaleY(-1)"转回来即可。 + // #ifndef APP-NVUE + cellStyle = this.useChatRecordMode ? { transform: 'scaleY(-1)' } : {}; + // #endif + + // 在nvue中,需要考虑数据量不满一页的情况,因为nvue中的list无法通过flex-end修改不满一页的起始位置,会导致不满一页时列表数据从底部开始,因此需要特别判断 + // 当数据不满一屏的时候,不进行列表倒置 + // #ifdef APP-NVUE + cellStyle = this.useChatRecordMode ? { transform: this.isFirstPageAndNoMore ? 'scaleY(1)' : 'scaleY(-1)' } : {}; + // #endif + + this.$emit('update:cellStyle', cellStyle); + this.$emit('cellStyleChange', cellStyle); + + // 在聊天记录模式中,如果列表没有倒置并且当前是第一页,则需要自动滚动到最底部 + this.$nextTick(() => { + if (this.isFirstPage && this.isChatRecordModeAndNotInversion) { + this.$nextTick(() => { + // 这里多次触发滚动到底部是为了避免在某些情况下,即使是在nextTick但是cell未渲染完毕导致滚动到底部位置不正确的问题 + this._scrollToBottom(false); + u.delay(() => { + this._scrollToBottom(false); + u.delay(() => { + this._scrollToBottom(false); + }, 50) + }, 50) + }) + } + }) + return cellStyle; + }, + // 是否是聊天记录列表并且有配置transform + isChatRecordModeHasTransform() { + return this.useChatRecordMode && this.chatRecordRotateStyle && this.chatRecordRotateStyle.transform; + }, + // 是否是聊天记录列表并且列表未倒置 + isChatRecordModeAndNotInversion() { + return this.isChatRecordModeHasTransform && this.chatRecordRotateStyle.transform === 'scaleY(1)'; + }, + // 是否是聊天记录列表并且列表倒置 + isChatRecordModeAndInversion() { + return this.isChatRecordModeHasTransform && this.chatRecordRotateStyle.transform === 'scaleY(-1)'; + }, + // 最终的聊天记录模式中底部安全区域的高度,如果开启了底部安全区域并且键盘未弹出,则添加底部区域高度 + chatRecordModeSafeAreaBottom() { + return this.safeAreaInsetBottom && !this.keyboardHeight ? this.safeAreaBottom : 0; + } + }, + mounted() { + this.addKeyboardHeightChangeListener(); + }, + methods: { + // 添加聊天记录 + addChatRecordData(data, toBottom = true, toBottomWithAnimate = true) { + if (!this.useChatRecordMode) return; + this.isTotalChangeFromAddData = true; + this.addDataFromTop(data, toBottom, toBottomWithAnimate); + }, + // 手动触发滚动到顶部加载更多,聊天记录模式时有效 + doChatRecordLoadMore() { + this.useChatRecordMode && this._onLoadingMore('click'); + }, + // 手动添加键盘高度变化监听 + addKeyboardHeightChangeListener() { + // 监听键盘高度变化(H5、百度小程序、抖音小程序、飞书小程序不支持) + // #ifndef H5 || MP-BAIDU || MP-TOUTIAO + if (this.useChatRecordMode) { + uni.onKeyboardHeightChange(this._handleKeyboardHeightChange); + } + // #endif + }, + // 处理键盘高度变化 + _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); + }) + }) + } + } + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/common-layout.js b/uni_modules/z-paging/components/z-paging/js/modules/common-layout.js new file mode 100644 index 0000000..777240e --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/common-layout.js @@ -0,0 +1,152 @@ +// [z-paging]通用布局相关模块 +import u from '.././z-paging-utils' + +// #ifdef APP-NVUE +const weexDom = weex.requireModule('dom'); +// #endif + +export default { + data() { + return { + systemInfo: null, + cssSafeAreaInsetBottom: -1, + isReadyDestroy: false, + } + }, + computed: { + // 顶部可用距离 + windowTop() { + if (!this.systemInfo) return 0; + // 暂时修复vue3中隐藏系统导航栏后windowTop获取不正确的问题,具体bug详见https://ask.dcloud.net.cn/question/141634 + // 感谢litangyu!!https://github.com/SmileZXLee/uni-z-paging/issues/25 + // #ifdef VUE3 && H5 + const pageHeadNode = document.getElementsByTagName("uni-page-head"); + if (!pageHeadNode.length) return 0; + // #endif + return this.systemInfo.windowTop || 0; + }, + // 底部安全区域高度 + safeAreaBottom() { + if (!this.systemInfo) return 0; + let safeAreaBottom = 0; + // #ifdef APP-PLUS + safeAreaBottom = this.systemInfo.safeAreaInsets.bottom || 0 ; + // #endif + // #ifndef APP-PLUS + safeAreaBottom = Math.max(this.cssSafeAreaInsetBottom, 0); + // #endif + return safeAreaBottom; + }, + // 是否是比较老的webview,在一些老的webview中,需要进行一些特殊处理 + isOldWebView() { + // #ifndef APP-NVUE || MP-KUAISHOU + try { + const systemInfos = u.getSystemInfoSync(true).system.split(' '); + const deviceType = systemInfos[0]; + const version = parseInt(systemInfos[1]); + if ((deviceType === 'iOS' && version <= 10) || (deviceType === 'Android' && version <= 6)) { + return true; + } + } catch(e) { + return false; + } + // #endif + return false; + }, + // 当前组件的$slots,兼容不同平台 + zSlots() { + // #ifdef VUE2 + + // #ifdef MP-ALIPAY + return this.$slots; + // #endif + + return this.$scopedSlots || this.$slots; + // #endif + + return this.$slots; + }, + }, + beforeDestroy() { + this.isReadyDestroy = true; + }, + // #ifdef VUE3 + unmounted() { + this.isReadyDestroy = true; + }, + // #endif + methods: { + // 更新fixed模式下z-paging的布局 + updateFixedLayout() { + this.fixed && this.$nextTick(() => { + this.systemInfo = u.getSystemInfoSync(); + }) + }, + // 获取节点尺寸 + _getNodeClientRect(select, inDom = true, scrollOffset = false) { + if (this.isReadyDestroy) { + return Promise.resolve(false); + }; + // nvue中获取节点信息 + // #ifdef APP-NVUE + select = select.replace(/[.|#]/g, ''); + const ref = this.$refs[select]; + return new Promise((resolve, reject) => { + if (ref) { + weexDom.getComponentRect(ref, option => { + resolve(option && option.result ? [option.size] : false); + }) + } else { + resolve(false); + } + }); + return; + // #endif + + // vue中获取节点信息 + //#ifdef MP-ALIPAY + inDom = false; + //#endif + + /* + inDom可能是true、false,也可能是具体的dom节点 + 如果inDom不为false,则使用uni.createSelectorQuery().in()进行查询,如果inDom为true,则in中的是this,否则in中的为具体的dom + 如果inDom为false,则使用uni.createSelectorQuery()进行查询 + */ + let res = !!inDom ? uni.createSelectorQuery().in(inDom === true ? this : inDom) : uni.createSelectorQuery(); + scrollOffset ? res.select(select).scrollOffset() : res.select(select).boundingClientRect(); + return new Promise((resolve, reject) => { + res.exec(data => { + resolve((data && data != '' && data != undefined && data.length) ? data : false); + }); + }); + }, + // 获取slot="left"和slot="right"宽度并且更新布局 + _updateLeftAndRightWidth(targetStyle, parentNodePrefix) { + this.$nextTick(() => { + let delayTime = 0; + // #ifdef MP-BAIDU + delayTime = 10; + // #endif + setTimeout(() => { + ['left','right'].map(position => { + this._getNodeClientRect(`.${parentNodePrefix}-${position}`).then(res => { + this.$set(targetStyle, position, res ? res[0].width + 'px' : '0px'); + }); + }) + }, delayTime) + }) + }, + // 通过获取css设置的底部安全区域占位view高度设置bottom距离(直接通过systemInfo在部分平台上无法获取到底部安全区域) + _getCssSafeAreaInsetBottom(success) { + this._getNodeClientRect('.zp-safe-area-inset-bottom').then(res => { + this.cssSafeAreaInsetBottom = res ? res[0].height : -1; + res && success && success(); + }); + }, + // 同步获取系统信息,兼容不同平台(供z-paging-swiper使用) + _getSystemInfoSync(useCache = false) { + return u.getSystemInfoSync(useCache); + } + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/data-handle.js b/uni_modules/z-paging/components/z-paging/js/modules/data-handle.js new file mode 100644 index 0000000..4ceaf0b --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/data-handle.js @@ -0,0 +1,744 @@ +// [z-paging]数据处理模块 +import u from '.././z-paging-utils' +import c from '.././z-paging-constant' +import Enum from '.././z-paging-enum' +import interceptor from '../z-paging-interceptor' + +export default { + props: { + // 自定义初始的pageNo,默认为1 + defaultPageNo: { + type: Number, + default: u.gc('defaultPageNo', 1), + observer: function(newVal) { + this.pageNo = newVal; + }, + }, + // 自定义pageSize,默认为10 + defaultPageSize: { + type: Number, + default: u.gc('defaultPageSize', 10), + validator: (value) => { + if (value <= 0) u.consoleErr('default-page-size必须大于0!'); + return value > 0; + } + }, + // 为保证数据一致,设置当前tab切换时的标识key,并在complete中传递相同key,若二者不一致,则complete将不会生效 + dataKey: { + type: [Number, String, Object], + default: u.gc('dataKey', null), + }, + // 使用缓存,若开启将自动缓存第一页的数据,默认为否。请注意,因考虑到切换tab时不同tab数据不同的情况,默认仅会缓存组件首次加载时第一次请求到的数据,后续的下拉刷新操作不会更新缓存。 + useCache: { + type: Boolean, + default: u.gc('useCache', false) + }, + // 使用缓存时缓存的key,用于区分不同列表的缓存数据,useCache为true时必须设置,否则缓存无效 + cacheKey: { + type: String, + default: u.gc('cacheKey', null) + }, + // 缓存模式,默认仅会缓存组件首次加载时第一次请求到的数据,可设置为always,即代表总是缓存,每次列表刷新(下拉刷新、调用reload等)都会更新缓存 + cacheMode: { + type: String, + default: u.gc('cacheMode', Enum.CacheMode.Default) + }, + // 自动注入的list名,可自动修改父view(包含ref="paging")中对应name的list值 + autowireListName: { + type: String, + default: u.gc('autowireListName', '') + }, + // 自动注入的query名,可自动调用父view(包含ref="paging")中的query方法 + autowireQueryName: { + 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, + default: u.gc('auto', true) + }, + // 用户下拉刷新时是否触发reload方法,默认为是 + reloadWhenRefresh: { + type: Boolean, + default: u.gc('reloadWhenRefresh', true) + }, + // reload时自动滚动到顶部,默认为是 + autoScrollToTopWhenReload: { + type: Boolean, + default: u.gc('autoScrollToTopWhenReload', true) + }, + // reload时立即自动清空原list,默认为是,若立即自动清空,则在reload之后、请求回调之前页面是空白的 + autoCleanListWhenReload: { + type: Boolean, + default: u.gc('autoCleanListWhenReload', true) + }, + // 列表刷新时自动显示下拉刷新view,默认为否 + showRefresherWhenReload: { + type: Boolean, + default: u.gc('showRefresherWhenReload', false) + }, + // 列表刷新时自动显示加载更多view,且为加载中状态,默认为否 + showLoadingMoreWhenReload: { + type: Boolean, + default: u.gc('showLoadingMoreWhenReload', false) + }, + // 组件created时立即触发reload(可解决一些情况下先看到页面再看到loading的问题),auto为true时有效。为否时将在mounted+nextTick后触发reload,默认为否 + createdReload: { + type: Boolean, + default: u.gc('createdReload', false) + }, + // 本地分页时上拉加载更多延迟时间,单位为毫秒,默认200毫秒 + localPagingLoadingTime: { + type: [Number, String], + default: u.gc('localPagingLoadingTime', 200) + }, + // 自动拼接complete中传过来的数组(使用聊天记录模式时无效) + concat: { + type: Boolean, + default: u.gc('concat', true) + }, + // 请求失败是否触发reject,默认为是 + callNetworkReject: { + type: Boolean, + default: u.gc('callNetworkReject', true) + }, + // 父组件v-model所绑定的list的值 + value: { + type: Array, + default: function() { + return []; + } + }, + // #ifdef VUE3 + modelValue: { + type: Array, + default: function() { + return []; + } + } + // #endif + }, + data (){ + return { + currentData: [], + totalData: [], + realTotalData: [], + totalLocalPagingList: [], + dataPromiseResultMap: { + reload: null, + complete: null, + localPaging: null + }, + isSettingCacheList: false, + pageNo: 1, + currentRefreshPageSize: 0, + isLocalPaging: false, + isAddedData: false, + isTotalChangeFromAddData: false, + privateConcat: true, + myParentQuery: -1, + firstPageLoaded: false, + pagingLoaded: false, + loaded: false, + isUserReload: true, + fromEmptyViewReload: false, + queryFrom: '', + listRendering: false, + isHandlingRefreshToPage: false, + isFirstPageAndNoMore: false, + totalDataChangeThrow: true, + addDataFromTopBufferedInsert: u.useBufferedInsert(this._addDataFromTop) + } + }, + computed: { + pageSize() { + return this.defaultPageSize; + }, + finalConcat() { + return this.concat && this.privateConcat; + }, + finalUseCache() { + if (this.useCache && !this.cacheKey) { + u.consoleErr('use-cache为true时,必须设置cache-key,否则缓存无效!'); + } + return this.useCache && !!this.cacheKey; + }, + finalCacheKey() { + return this.cacheKey ? `${c.cachePrefixKey}-${this.cacheKey}` : null; + }, + isFirstPage() { + return this.pageNo === this.defaultPageNo; + } + }, + watch: { + totalData(newVal, oldVal) { + this._totalDataChange(newVal, oldVal, this.totalDataChangeThrow); + this.totalDataChangeThrow = true; + }, + currentData(newVal, oldVal) { + this._currentDataChange(newVal, oldVal); + }, + useChatRecordMode(newVal, oldVal) { + if (newVal) { + this.nLoadingMoreFixedHeight = false; + } + }, + value: { + handler(newVal) { + // 当v-model绑定的数据源被更改时,此时数据源改变不emit input事件,避免循环调用 + if (newVal !== this.totalData) { + this.totalDataChangeThrow = false; + this.totalData = newVal; + } + }, + immediate: true + }, + // #ifdef VUE3 + modelValue: { + handler(newVal) { + // 当v-model绑定的数据源被更改时,此时数据源改变不emit input事件,避免循环调用 + if (newVal !== this.totalData) { + this.totalDataChangeThrow = false; + this.totalData = newVal; + } + }, + immediate: true + } + // #endif + }, + methods: { + // 请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为是否成功(默认为是) + complete(data, success = true) { + this.customNoMore = -1; + return this.addData(data, success); + }, + //【保证数据一致】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为dataKey,需与:data-key绑定的一致,第三个参数为是否成功(默认为是) + completeByKey(data, dataKey = null, success = true) { + if (dataKey !== null && this.dataKey !== null && dataKey !== this.dataKey) { + this.isFirstPage && this.endRefresh(); + return new Promise(resolve => resolve()); + } + this.customNoMore = -1; + return this.addData(data, success); + }, + //【通过total判断是否有更多数据】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为total(列表总数),第三个参数为是否成功(默认为是) + completeByTotal(data, total, success = true) { + if (total == 'undefined') { + this.customNoMore = -1; + } else { + const dataTypeRes = this._checkDataType(data, success, false); + data = dataTypeRes.data; + success = dataTypeRes.success; + if (total >= 0 && success) { + return new Promise((resolve, reject) => { + this.$nextTick(() => { + let nomore = false; + const realTotalDataCount = this.pageNo == this.defaultPageNo ? 0 : this.realTotalData.length; + const dataLength = this.privateConcat ? data.length : 0; + let exceedCount = realTotalDataCount + dataLength - total; + // 没有更多数据了 + if (exceedCount >= 0) { + nomore = true; + // 仅截取total内部分的数据 + exceedCount = this.defaultPageSize - exceedCount; + if (this.privateConcat && exceedCount > 0 && exceedCount < data.length) { + data = data.splice(0, exceedCount); + } + } + this.completeByNoMore(data, nomore, success).then(res => resolve(res)).catch(() => reject()); + }) + }); + } + } + return this.addData(data, success); + }, + //【自行判断是否有更多数据】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为是否没有更多数据,第三个参数为是否成功(默认是是) + completeByNoMore(data, nomore, success = true) { + if (nomore != 'undefined') { + this.customNoMore = nomore == true ? 1 : 0; + } + return this.addData(data, success); + }, + // 请求结束且请求失败时调用,支持传入请求失败原因 + completeByError(errorMsg) { + this.customerEmptyViewErrorText = errorMsg; + return this.complete(false); + }, + // 与上方complete方法功能一致,新版本中设置服务端回调数组请使用complete方法 + addData(data, success = true) { + if (!this.fromCompleteEmit) { + this.disabledCompleteEmit = true; + this.fromCompleteEmit = false; + } + const currentTimeStamp = u.getTime(); + const disTime = currentTimeStamp - this.requestTimeStamp; + let minDelay = this.minDelay; + if (this.isFirstPage && this.finalShowRefresherWhenReload) { + minDelay = Math.max(400, minDelay); + } + const addDataDalay = (this.requestTimeStamp > 0 && disTime < minDelay) ? minDelay - disTime : 0; + this.$nextTick(() => { + u.delay(() => { + this._addData(data, success, false); + }, this.delay > 0 ? this.delay : addDataDalay) + }) + + return new Promise((resolve, reject) => { + this.dataPromiseResultMap.complete = { resolve, reject }; + }); + }, + // 从顶部添加数据,不会影响分页的pageNo和pageSize + addDataFromTop(data, toTop = true, toTopWithAnimate = true) { + // 如果使用了虚拟列表,则需要对短时间内的大量数据进行整合然后一次性添加,避免设置虚拟列表cellIndex时候key冲突的问题,否则正常调用 + (this.finalUseVirtualList ? this.addDataFromTopBufferedInsert : this._addDataFromTop)( + data, toTop, toTopWithAnimate + ); + }, + // 重新设置列表数据,调用此方法不会影响pageNo和pageSize,也不会触发请求。适用场景:当需要删除列表中某一项时,将删除对应项后的数组通过此方法传递给z-paging。(当出现类似的需要修改列表数组的场景时,请使用此方法,请勿直接修改page中:list.sync绑定的数组) + resetTotalData(data) { + this.isTotalChangeFromAddData = true; + data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : data; + this.totalData = data; + }, + // 设置本地分页数据,请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging作分页处理(若调用了此方法,则上拉加载更多时内部会自动分页,不会触发@query所绑定的事件) + setLocalPaging(data, success = true) { + this.isLocalPaging = true; + this.$nextTick(() => { + this._addData(data, success, true); + }) + return new Promise((resolve, reject) => { + this.dataPromiseResultMap.localPaging = { resolve, reject }; + }); + }, + // 重新加载分页数据,pageNo会恢复为默认值,相当于下拉刷新的效果(animate为true时会展示下拉刷新动画,默认为false) + reload(animate = this.showRefresherWhenReload) { + if (animate) { + this.privateShowRefresherWhenReload = animate; + this.isUserPullDown = true; + } + if (!this.showLoadingMoreWhenReload) { + this.listRendering = true; + } + this.$nextTick(() => { + this._preReload(animate, false); + }) + return new Promise((resolve, reject) => { + this.dataPromiseResultMap.reload = { resolve, reject }; + }); + }, + // 刷新列表数据,pageNo和pageSize不会重置,列表数据会重新从服务端获取。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致 + refresh() { + return this._handleRefreshWithDisPageNo(this.pageNo - this.defaultPageNo + 1); + }, + // 刷新列表数据至指定页,例如pageNo=5时则代表刷新列表至第5页,此时pageNo会变为5,列表会展示前5页的数据。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致 + refreshToPage(pageNo) { + this.isHandlingRefreshToPage = true; + return this._handleRefreshWithDisPageNo(pageNo + this.defaultPageNo - 1); + }, + // 手动更新列表缓存数据,将自动截取v-model绑定的list中的前pageSize条覆盖缓存,请确保在list数据更新到预期结果后再调用此方法 + updateCache() { + if (this.finalUseCache && this.totalData.length) { + this._saveLocalCache(this.totalData.slice(0, Math.min(this.totalData.length, this.pageSize))); + } + }, + // 清空分页数据 + clean() { + this._reload(true); + this._addData([], true, false); + }, + // 清空分页数据 + clear() { + this.clean(); + }, + // reload之前的一些处理 + _preReload(animate = this.showRefresherWhenReload, isFromMounted = true, retryCount = 0) { + const showRefresher = this.finalRefresherEnabled && this.useCustomRefresher; + // #ifndef APP-NVUE + // 如果获取slot="refresher"高度失败,则不触发reload,直到获取slot="refresher"高度成功 + if (this.customRefresherHeight === -1 && showRefresher) { + u.delay(() => { + retryCount ++; + // 如果重试次数是10的倍数(也就是每500毫秒),尝试重新获取一下slot="refresher"高度 + // 此举是为了解决在某些特殊情况下,z-paging组件mounted了,但是未展示在用户面前,(比如在tabbar页面中,未切换到对应tabbar但是通过代码让z-paging展示了,此时控制台会报Error: Not Found:Page,因为这时候去获取dom节点信息获取不到) + // 当用户在某个时刻让此z-paging展示在面前时,即可顺利获取到slot="refresher"高度,递归停止 + if (retryCount % 10 === 0) { + this._updateCustomRefresherHeight(); + } + this._preReload(animate, isFromMounted, retryCount); + }, c.delayTime / 2); + return; + } + // #endif + this.isUserReload = true; + this.loadingType = Enum.LoadingType.Refresher; + if (animate) { + this.privateShowRefresherWhenReload = animate; + // #ifndef APP-NVUE + if (this.useCustomRefresher) { + this._doRefresherRefreshAnimate(); + } else { + this.refresherTriggered = true; + } + // #endif + // #ifdef APP-NVUE + this.refresherStatus = Enum.Refresher.Loading; + this.refresherRevealStackCount ++; + u.delay(() => { + this._getNodeClientRect('zp-n-refresh-container', false).then((node) => { + if (node) { + let nodeHeight = node[0].height; + this.nShowRefresherReveal = true; + this.nShowRefresherRevealHeight = nodeHeight; + u.delay(() => { + this._nDoRefresherEndAnimation(0, -nodeHeight, false, false); + u.delay(() => { + this._nDoRefresherEndAnimation(nodeHeight, 0); + }, 10) + }, 10) + } + this._reload(false, isFromMounted); + this._doRefresherLoad(false); + }); + }, this.pagingLoaded ? 10 : 100) + return; + // #endif + } else { + this._refresherEnd(false, false, false, false); + } + this._reload(false, isFromMounted); + }, + // 重新加载分页数据 + _reload(isClean = false, isFromMounted = false, isUserPullDown = false) { + this.isAddedData = false; + this.insideOfPaging = -1; + this.cacheScrollNodeHeight = -1; + this.pageNo = this.defaultPageNo; + this._cleanRefresherEndTimeout(); + !this.privateShowRefresherWhenReload && !isClean && this._startLoading(true); + this.firstPageLoaded = true; + this.isTotalChangeFromAddData = false; + if (!this.isSettingCacheList) { + this.totalData = []; + } + if (!isClean) { + this._emitQuery(this.pageNo, this.defaultPageSize, isUserPullDown ? Enum.QueryFrom.UserPullDown : Enum.QueryFrom.Reload); + let delay = 0; + // #ifdef MP-TOUTIAO + delay = 5; + // #endif + u.delay(this._callMyParentQuery, delay); + if (!isFromMounted && this.autoScrollToTopWhenReload) { + let checkedNRefresherLoading = true; + // #ifdef APP-NVUE + checkedNRefresherLoading = !this.nRefresherLoading; + // #endif + checkedNRefresherLoading && this._scrollToTop(false); + } + } + // #ifdef APP-NVUE + this.$nextTick(() => { + this.nShowBottom = this.realTotalData.length > 0; + }) + // #endif + }, + // 处理服务端返回的数组 + _addData(data, success, isLocal) { + this.isAddedData = true; + this.fromEmptyViewReload = false; + this.isTotalChangeFromAddData = true; + this.refresherTriggered = false; + this._endSystemLoadingAndRefresh(); + const tempIsUserPullDown = this.isUserPullDown; + if (this.showRefresherUpdateTime && this.isFirstPage) { + u.setRefesrherTime(u.getTime(), this.refresherUpdateTimeKey); + this.$refs.refresh && this.$refs.refresh.updateTime(); + } + if (!isLocal && tempIsUserPullDown && this.isFirstPage) { + this.isUserPullDown = false; + } + this.listRendering = true; + this.$nextTick(() => { + u.delay(() => this.listRendering = false); + }) + let dataTypeRes = this._checkDataType(data, success, isLocal); + data = dataTypeRes.data; + success = dataTypeRes.success; + let delayTime = c.delayTime; + if (this.useChatRecordMode) delayTime = 0; + this.loadingForNow = false; + u.delay(() => { + this.pagingLoaded = true; + this.$nextTick(()=>{ + !isLocal && this._refresherEnd(delayTime > 0, true, tempIsUserPullDown); + }) + }) + if (this.isFirstPage) { + this.isLoadFailed = !success; + this.$emit('isLoadFailedChange', this.isLoadFailed); + if (this.finalUseCache && success && (this.cacheMode === Enum.CacheMode.Always ? true : this.isSettingCacheList)) { + this._saveLocalCache(data); + } + } + this.isSettingCacheList = false; + if (success) { + if (!(this.privateConcat === false && !this.isHandlingRefreshToPage && this.loadingStatus === Enum.More.NoMore)) { + 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 => { + u.delay(() => { + this.completeByTotal(res, this.totalLocalPagingList.length);; + }, 0) + }) + } else { + // 如果当前不是本地分页,则按照正常分页逻辑进行数据处理&emit数据 + let dataChangeDelayTime = 0; + // #ifdef APP-NVUE + if (this.privateShowRefresherWhenReload && this.finalNvueListIs === 'waterfall') { + dataChangeDelayTime = 150; + } + // #endif + u.delay(() => { + this._currentDataChange(data, this.currentData); + this._callDataPromise(true, this.totalData); + }, dataChangeDelayTime) + } + if (this.isHandlingRefreshToPage) { + this.isHandlingRefreshToPage = false; + this.pageNo = this.defaultPageNo + Math.ceil(data.length / this.pageSize) - 1; + if (data.length % this.pageSize !== 0) { + this.customNoMore = 1; + } + } + } else { + this._currentDataChange(data, this.currentData); + this._callDataPromise(false); + this.loadingStatus = Enum.More.Fail; + this.isHandlingRefreshToPage = false; + if (this.loadingType === Enum.LoadingType.LoadMore) { + this.pageNo --; + } + } + }, + // 所有数据改变时调用 + _totalDataChange(newVal, oldVal, eventThrow=true) { + if ((!this.isUserReload || !this.autoCleanListWhenReload) && this.firstPageLoaded && !newVal.length && oldVal.length) { + return; + } + this._doCheckScrollViewShouldFullHeight(newVal); + if(!this.realTotalData.length && !newVal.length){ + eventThrow = false; + } + this.realTotalData = newVal; + // emit列表更新事件 + if (eventThrow) { + this.$emit('input', newVal); + // #ifdef VUE3 + this.$emit('update:modelValue', newVal); + // #endif + this.$emit('update:list', newVal); + this.$emit('listChange', newVal); + this._callMyParentList(newVal); + } + this.firstPageLoaded = false; + this.isTotalChangeFromAddData = false; + this.$nextTick(() => { + u.delay(()=>{ + // emit z-paging内容区域高度改变事件 + this._getNodeClientRect('.zp-paging-container-content').then(res => { + res && this.$emit('contentHeightChanged', res[0].height); + }); + }, c.delayTime * (this.isIos ? 1 : 3)) + // #ifdef APP-NVUE + // 在nvue中延时600毫秒展示底部加载更多,避免底部加载更多太早加载闪一下的问题 + u.delay(() => { + this.nShowBottom = true; + }, c.delayTime * 6, 'nShowBottomDelay'); + // #endif + }) + }, + // 当前数据改变时调用 + _currentDataChange(newVal, oldVal) { + newVal = [...newVal]; + // #ifndef APP-NVUE + this.finalUseVirtualList && this._setCellIndex(newVal, 'bottom'); + // #endif + if (this.isFirstPage && this.finalConcat) { + this.totalData = []; + } + // customNoMore:-1代表交由z-paging自行判断;1代表没有更多了;0代表还有更多数据 + if (this.customNoMore !== -1) { + // 如果customNoMore等于1 或者 customNoMore不是0并且新增数组长度为0(也就是不是明确的还有更多数据并且新增的数组长度为0),则没有更多数据了 + if (this.customNoMore === 1 || (this.customNoMore !== 0 && !newVal.length)) { + this.loadingStatus = Enum.More.NoMore; + } + } else { + // 如果新增的数据数组长度为0 或者 新增的数组长度小于默认的pageSize,则没有更多数据了 + if (!newVal.length || (newVal.length && newVal.length < this.defaultPageSize)) { + this.loadingStatus = Enum.More.NoMore; + } + } + if (!this.totalData.length) { + // #ifdef APP-NVUE + // 如果在聊天记录模式+nvue中,并且数据不满一页时需要将列表倒序,因为此时没有将列表旋转180度,数组中第0条数据应当在最底下显示 + if (this.useChatRecordMode && this.finalConcat && this.isFirstPage && this.loadingStatus === Enum.More.NoMore) { + newVal.reverse(); + } + // #endif + this.totalData = newVal; + } else { + if (this.finalConcat) { + const currentScrollTop = this.oldScrollTop; + this.totalData = [...this.totalData, ...newVal]; + // 此处是为了解决在微信小程序中,在某些情况下滚动到底部加载更多后滚动位置直接变为最底部的问题,因此需要通过代码强制滚动回加载更多前的位置 + // #ifdef MP-WEIXIN + if (!this.isIos && !this.isOnly && !this.usePageScroll && newVal.length) { + this.loadingMoreTimeStamp = u.getTime(); + this.$nextTick(() => { + this.scrollToY(currentScrollTop); + }) + } + // #endif + } else { + this.totalData = newVal; + } + } + this.privateConcat = true; + }, + // 根据pageNo处理refresh操作 + _handleRefreshWithDisPageNo(pageNo) { + if (!this.isHandlingRefreshToPage && !this.realTotalData.length) return this.reload(); + if (pageNo >= 1) { + this.loading = true; + this.privateConcat = false; + const totalPageSize = pageNo * this.pageSize; + this.currentRefreshPageSize = totalPageSize; + // 如果调用refresh时是本地分页,则在组件内部自己处理分页逻辑,不emit query相关事件 + if (this.isLocalPaging && this.isHandlingRefreshToPage) { + this._localPagingQueryList(this.defaultPageNo, totalPageSize, 0, res => { + this.complete(res); + }) + } else { + // emit query相关事件 + this._emitQuery(this.defaultPageNo, totalPageSize, Enum.QueryFrom.Refresh); + this._callMyParentQuery(this.defaultPageNo, totalPageSize); + } + } + return new Promise((resolve, reject) => { + this.dataPromiseResultMap.reload = { resolve, reject }; + }); + }, + // 本地分页请求 + _localPagingQueryList(pageNo, pageSize, localPagingLoadingTime, callback) { + pageNo = Math.max(1, pageNo); + pageSize = Math.max(1, pageSize); + const totalPagingList = [...this.totalLocalPagingList]; + const pageNoIndex = (pageNo - 1) * pageSize; + const finalPageNoIndex = Math.min(totalPagingList.length, pageNoIndex + pageSize); + const resultPagingList = totalPagingList.splice(pageNoIndex, finalPageNoIndex - pageNoIndex); + u.delay(() => callback(resultPagingList), localPagingLoadingTime) + }, + // 从顶部添加数据,不会影响分页的pageNo和pageSize + _addDataFromTop(data, toTop = true, toTopWithAnimate = true) { + // 数据是否拼接到顶部,如果是聊天记录模式并且列表没有倒置,则应该拼接在底部 + let addFromTop = !this.isChatRecordModeAndNotInversion; + data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : (addFromTop ? data.reverse() : data); + // #ifndef APP-NVUE + this.finalUseVirtualList && this._setCellIndex(data, 'top') + // #endif + + this.totalData = addFromTop ? [...data, ...this.totalData] : [...this.totalData, ...data]; + if (toTop) { + u.delay(() => this.useChatRecordMode ? this.scrollToBottom(toTopWithAnimate) : this.scrollToTop(toTopWithAnimate)); + } + }, + // 存储列表缓存数据 + _saveLocalCache(data) { + uni.setStorageSync(this.finalCacheKey, data); + }, + // 通过缓存数据填充列表数据 + _setListByLocalCache() { + this.totalData = uni.getStorageSync(this.finalCacheKey) || []; + this.isSettingCacheList = true; + }, + // 修改父view的list + _callMyParentList(newVal) { + if (this.autowireListName.length) { + const myParent = u.getParent(this.$parent); + if (myParent && myParent[this.autowireListName]) { + myParent[this.autowireListName] = newVal; + } + } + }, + // 调用父view的query + _callMyParentQuery(customPageNo = 0, customPageSize = 0) { + if (this.autowireQueryName) { + if (this.myParentQuery === -1) { + const myParent = u.getParent(this.$parent); + if (myParent && myParent[this.autowireQueryName]) { + this.myParentQuery = myParent[this.autowireQueryName]; + } + } + if (this.myParentQuery !== -1) { + customPageSize > 0 ? this.myParentQuery(customPageNo, customPageSize) : this.myParentQuery(this.pageNo, this.defaultPageSize); + } + } + }, + // emit query事件 + _emitQuery(pageNo, pageSize, from){ + this.queryFrom = from; + this.requestTimeStamp = u.getTime(); + const [lastItem] = this.realTotalData.slice(-1); + 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) { + for (const key in this.dataPromiseResultMap) { + const obj = this.dataPromiseResultMap[key]; + if (!obj) continue; + success ? obj.resolve({ totalList, noMore: this.loadingStatus === Enum.More.NoMore }) : this.callNetworkReject && obj.reject(`z-paging-${key}-error`); + } + }, + // 检查complete data的类型 + _checkDataType(data, success, isLocal) { + const dataType = Object.prototype.toString.call(data); + if (dataType === '[object Boolean]') { + success = data; + data = []; + } else if (dataType !== '[object Array]') { + data = []; + if (dataType !== '[object Undefined]' && dataType !== '[object Null]') { + u.consoleErr(`${isLocal ? 'setLocalPaging' : 'complete'}参数类型不正确,第一个参数类型必须为Array!`); + } + } + return { data, success }; + }, + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/empty.js b/uni_modules/z-paging/components/z-paging/js/modules/empty.js new file mode 100644 index 0000000..736fd84 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/empty.js @@ -0,0 +1,144 @@ +// [z-paging]空数据图view模块 +import u from '.././z-paging-utils' + +export default { + props: { + // 是否强制隐藏空数据图,默认为否 + hideEmptyView: { + type: Boolean, + default: u.gc('hideEmptyView', false) + }, + // 空数据图描述文字,默认为“没有数据哦~” + emptyViewText: { + type: [String, Object], + default: u.gc('emptyViewText', null) + }, + // 是否显示空数据图重新加载按钮(无数据时),默认为否 + showEmptyViewReload: { + type: Boolean, + default: u.gc('showEmptyViewReload', false) + }, + // 加载失败时是否显示空数据图重新加载按钮,默认为是 + showEmptyViewReloadWhenError: { + type: Boolean, + default: u.gc('showEmptyViewReloadWhenError', true) + }, + // 空数据图点击重新加载文字,默认为“重新加载” + emptyViewReloadText: { + type: [String, Object], + default: u.gc('emptyViewReloadText', null) + }, + // 空数据图图片,默认使用z-paging内置的图片 + emptyViewImg: { + type: String, + default: u.gc('emptyViewImg', '') + }, + // 空数据图“加载失败”描述文字,默认为“很抱歉,加载失败” + emptyViewErrorText: { + type: [String, Object], + default: u.gc('emptyViewErrorText', null) + }, + // 空数据图“加载失败”图片,默认使用z-paging内置的图片 + emptyViewErrorImg: { + type: String, + default: u.gc('emptyViewErrorImg', '') + }, + // 空数据图样式 + emptyViewStyle: { + type: Object, + default: u.gc('emptyViewStyle', {}) + }, + // 空数据图容器样式 + emptyViewSuperStyle: { + type: Object, + default: u.gc('emptyViewSuperStyle', {}) + }, + // 空数据图img样式 + emptyViewImgStyle: { + type: Object, + default: u.gc('emptyViewImgStyle', {}) + }, + // 空数据图描述文字样式 + emptyViewTitleStyle: { + type: Object, + default: u.gc('emptyViewTitleStyle', {}) + }, + // 空数据图重新加载按钮样式 + emptyViewReloadStyle: { + type: Object, + default: u.gc('emptyViewReloadStyle', {}) + }, + // 空数据图片是否铺满z-paging,默认为否,即填充满z-paging内列表(滚动区域)部分。若设置为否,则为填铺满整个z-paging + emptyViewFixed: { + type: Boolean, + default: u.gc('emptyViewFixed', false) + }, + // 空数据图片是否垂直居中,默认为是,若设置为否即为从空数据容器顶部开始显示。emptyViewFixed为false时有效 + emptyViewCenter: { + type: Boolean, + default: u.gc('emptyViewCenter', true) + }, + // 加载中时是否自动隐藏空数据图,默认为是 + autoHideEmptyViewWhenLoading: { + type: Boolean, + default: u.gc('autoHideEmptyViewWhenLoading', true) + }, + // 用户下拉列表触发下拉刷新加载中时是否自动隐藏空数据图,默认为是 + autoHideEmptyViewWhenPull: { + type: Boolean, + default: u.gc('autoHideEmptyViewWhenPull', true) + }, + // 空数据view的z-index,默认为9 + emptyViewZIndex: { + type: Number, + default: u.gc('emptyViewZIndex', 9) + }, + }, + data() { + return { + customerEmptyViewErrorText: '' + } + }, + computed: { + finalEmptyViewImg() { + return this.isLoadFailed ? this.emptyViewErrorImg : this.emptyViewImg; + }, + finalShowEmptyViewReload() { + return this.isLoadFailed ? this.showEmptyViewReloadWhenError : this.showEmptyViewReload; + }, + // 是否展示空数据图 + showEmpty() { + if (this.isOnly || this.hideEmptyView || this.realTotalData.length) return false; + if (this.autoHideEmptyViewWhenLoading) { + if (this.isAddedData && !this.firstPageLoaded && !this.loading) return true; + } else { + return true; + } + return !this.autoHideEmptyViewWhenPull && !this.isUserReload; + }, + }, + methods: { + // 点击了空数据view重新加载按钮 + _emptyViewReload() { + let callbacked = false; + this.$emit('emptyViewReload', reload => { + if (reload === undefined || reload === true) { + this.fromEmptyViewReload = true; + this.reload().catch(() => {}); + } + callbacked = true; + }); + // 如果用户没有禁止默认的点击重新加载刷新列表事件,则触发列表重新刷新 + this.$nextTick(() => { + if (!callbacked) { + this.fromEmptyViewReload = true; + this.reload().catch(() => {}); + } + }) + }, + // 点击了空数据view + _emptyViewClick() { + this.$emit('emptyViewClick'); + }, + } +} \ No newline at end of file diff --git a/uni_modules/z-paging/components/z-paging/js/modules/i18n.js b/uni_modules/z-paging/components/z-paging/js/modules/i18n.js new file mode 100644 index 0000000..2fd92a5 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/i18n.js @@ -0,0 +1,113 @@ +// [z-paging]i18n模块 +import { initVueI18n } from '@dcloudio/uni-i18n' +import messages from '../../i18n/index.js' +const { t } = initVueI18n(messages) + +import u from '.././z-paging-utils' +import c from '.././z-paging-constant' +import interceptor from '../z-paging-interceptor' + +export default { + computed: { + finalLanguage() { + try { + const local = uni.getLocale(); + const language = this.systemInfo.appLanguage; + return local === 'auto' ? interceptor._handleLanguage2Local(language, this._language2Local(language)) : local; + } catch (e) { + // 如果获取系统本地语言异常,则默认返回中文,uni.getLocale在部分低版本HX或者cli中可能报找不到的问题 + return 'zh-Hans'; + } + }, + // 最终的下拉刷新默认状态的文字 + finalRefresherDefaultText() { + return this._getI18nText('zp.refresher.default', this.refresherDefaultText); + }, + // 最终的下拉刷新下拉中的文字 + finalRefresherPullingText() { + return this._getI18nText('zp.refresher.pulling', this.refresherPullingText); + }, + // 最终的下拉刷新中文字 + finalRefresherRefreshingText() { + return this._getI18nText('zp.refresher.refreshing', this.refresherRefreshingText); + }, + // 最终的下拉刷新完成文字 + finalRefresherCompleteText() { + return this._getI18nText('zp.refresher.complete', this.refresherCompleteText); + }, + // 最终的下拉刷新上次更新时间文字 + finalRefresherUpdateTimeTextMap() { + return { + title: t('zp.refresherUpdateTime.title'), + none: t('zp.refresherUpdateTime.none'), + today: t('zp.refresherUpdateTime.today'), + yesterday: t('zp.refresherUpdateTime.yesterday') + }; + }, + // 最终的继续下拉进入二楼文字 + finalRefresherGoF2Text() { + return this._getI18nText('zp.refresher.f2', this.refresherGoF2Text); + }, + // 最终的底部加载更多默认状态文字 + finalLoadingMoreDefaultText() { + return this._getI18nText('zp.loadingMore.default', this.loadingMoreDefaultText); + }, + // 最终的底部加载更多加载中文字 + finalLoadingMoreLoadingText() { + return this._getI18nText('zp.loadingMore.loading', this.loadingMoreLoadingText); + }, + // 最终的底部加载更多没有更多数据文字 + finalLoadingMoreNoMoreText() { + return this._getI18nText('zp.loadingMore.noMore', this.loadingMoreNoMoreText); + }, + // 最终的底部加载更多加载失败文字 + finalLoadingMoreFailText() { + return this._getI18nText('zp.loadingMore.fail', this.loadingMoreFailText); + }, + // 最终的空数据图title + finalEmptyViewText() { + return this.isLoadFailed ? this.finalEmptyViewErrorText : this._getI18nText('zp.emptyView.title', this.emptyViewText); + }, + // 最终的空数据图reload title + finalEmptyViewReloadText() { + return this._getI18nText('zp.emptyView.reload', this.emptyViewReloadText); + }, + // 最终的空数据图加载失败文字 + finalEmptyViewErrorText() { + return this.customerEmptyViewErrorText || this._getI18nText('zp.emptyView.error', this.emptyViewErrorText); + }, + // 最终的系统loading title + finalSystemLoadingText() { + return this._getI18nText('zp.systemLoading.title', this.systemLoadingText); + }, + }, + methods: { + // 获取当前z-paging的语言 + getLanguage() { + return this.finalLanguage; + }, + // 获取国际化转换后的文本 + _getI18nText(key, value) { + const dataType = Object.prototype.toString.call(value); + if (dataType === '[object Object]') { + const nextValue = value[this.finalLanguage]; + if (nextValue) return nextValue; + } else if (dataType === '[object String]') { + return value; + } + return t(key); + }, + // 系统language转i18n local + _language2Local(language) { + const formatedLanguage = language.toLowerCase().replace(new RegExp('_', ''), '-'); + if (formatedLanguage.indexOf('zh') !== -1) { + if (formatedLanguage === 'zh' || formatedLanguage === 'zh-cn' || formatedLanguage.indexOf('zh-hans') !== -1) { + return 'zh-Hans'; + } + return 'zh-Hant'; + } + if (formatedLanguage.indexOf('en') !== -1) return 'en'; + return language; + } + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/load-more.js b/uni_modules/z-paging/components/z-paging/js/modules/load-more.js new file mode 100644 index 0000000..164f4be --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/load-more.js @@ -0,0 +1,374 @@ +// [z-paging]滚动到底部加载更多模块 +import u from '.././z-paging-utils' +import Enum from '.././z-paging-enum' + +export default { + props: { + // 自定义底部加载更多样式 + loadingMoreCustomStyle: { + type: Object, + default: u.gc('loadingMoreCustomStyle', {}) + }, + // 自定义底部加载更多文字样式 + loadingMoreTitleCustomStyle: { + type: Object, + default: u.gc('loadingMoreTitleCustomStyle', {}) + }, + // 自定义底部加载更多加载中动画样式 + loadingMoreLoadingIconCustomStyle: { + type: Object, + default: u.gc('loadingMoreLoadingIconCustomStyle', {}) + }, + // 自定义底部加载更多加载中动画图标类型,可选flower或circle,默认为flower + loadingMoreLoadingIconType: { + type: String, + default: u.gc('loadingMoreLoadingIconType', 'flower') + }, + // 自定义底部加载更多加载中动画图标图片 + loadingMoreLoadingIconCustomImage: { + type: String, + default: u.gc('loadingMoreLoadingIconCustomImage', '') + }, + // 底部加载更多加载中view是否展示旋转动画,默认为是 + loadingMoreLoadingAnimated: { + type: Boolean, + default: u.gc('loadingMoreLoadingAnimated', true) + }, + // 是否启用加载更多数据(含滑动到底部加载更多数据和点击加载更多数据),默认为是 + loadingMoreEnabled: { + type: Boolean, + default: u.gc('loadingMoreEnabled', true) + }, + // 是否启用滑动到底部加载更多数据,默认为是 + toBottomLoadingMoreEnabled: { + type: Boolean, + default: u.gc('toBottomLoadingMoreEnabled', true) + }, + // 滑动到底部状态为默认状态时,以加载中的状态展示,默认为否。若设置为是,可避免滚动到底部看到默认状态然后立刻变为加载中状态的问题,但分页数量未超过一屏时,不会显示【点击加载更多】 + loadingMoreDefaultAsLoading: { + type: Boolean, + default: u.gc('loadingMoreDefaultAsLoading', false) + }, + // 滑动到底部"默认"文字,默认为【点击加载更多】 + loadingMoreDefaultText: { + type: [String, Object], + default: u.gc('loadingMoreDefaultText', null) + }, + // 滑动到底部"加载中"文字,默认为【正在加载...】 + loadingMoreLoadingText: { + type: [String, Object], + default: u.gc('loadingMoreLoadingText', null) + }, + // 滑动到底部"没有更多"文字,默认为【没有更多了】 + loadingMoreNoMoreText: { + type: [String, Object], + default: u.gc('loadingMoreNoMoreText', null) + }, + // 滑动到底部"加载失败"文字,默认为【加载失败,点击重新加载】 + loadingMoreFailText: { + type: [String, Object], + default: u.gc('loadingMoreFailText', null) + }, + // 当没有更多数据且分页内容未超出z-paging时是否隐藏没有更多数据的view,默认为否 + hideNoMoreInside: { + type: Boolean, + default: u.gc('hideNoMoreInside', false) + }, + // 当没有更多数据且分页数组长度少于这个值时,隐藏没有更多数据的view,默认为0,代表不限制。 + hideNoMoreByLimit: { + type: Number, + default: u.gc('hideNoMoreByLimit', 0) + }, + // 是否显示默认的加载更多text,默认为是 + showDefaultLoadingMoreText: { + type: Boolean, + default: u.gc('showDefaultLoadingMoreText', true) + }, + // 是否显示没有更多数据的view + showLoadingMoreNoMoreView: { + type: Boolean, + default: u.gc('showLoadingMoreNoMoreView', true) + }, + // 是否显示没有更多数据的分割线,默认为是 + showLoadingMoreNoMoreLine: { + type: Boolean, + default: u.gc('showLoadingMoreNoMoreLine', true) + }, + // 自定义底部没有更多数据的分割线样式 + loadingMoreNoMoreLineCustomStyle: { + type: Object, + default: u.gc('loadingMoreNoMoreLineCustomStyle', {}) + }, + // 当分页未满一屏时,是否自动加载更多,默认为否(nvue无效) + insideMore: { + type: Boolean, + default: u.gc('insideMore', false) + }, + // 距底部/右边多远时(单位px),触发 scrolltolower 事件,默认为100rpx + lowerThreshold: { + type: [Number, String], + default: u.gc('lowerThreshold', '100rpx') + }, + }, + data() { + return { + M: Enum.More, + // 底部加载更多状态 + loadingStatus: Enum.More.Default, + // 在渲染之后的底部加载更多状态 + loadingStatusAfterRender: Enum.More.Default, + // 底部加载更多时间戳 + loadingMoreTimeStamp: 0, + // 底部加载更多slot + loadingMoreDefaultSlot: null, + // 是否展示底部加载更多 + showLoadingMore: false, + // 是否是开发者自定义的加载更多,-1代表交由z-paging自行判断;1代表没有更多了;0代表还有更多数据 + customNoMore: -1, + } + }, + computed: { + // 底部加载更多配置 + zLoadMoreConfig() { + return { + status: this.loadingStatusAfterRender, + defaultAsLoading: this.loadingMoreDefaultAsLoading || (this.useChatRecordMode && this.chatLoadingMoreDefaultAsLoading), + defaultThemeStyle: this.finalLoadingMoreThemeStyle, + customStyle: this.loadingMoreCustomStyle, + titleCustomStyle: this.loadingMoreTitleCustomStyle, + iconCustomStyle: this.loadingMoreLoadingIconCustomStyle, + loadingIconType: this.loadingMoreLoadingIconType, + loadingIconCustomImage: this.loadingMoreLoadingIconCustomImage, + loadingAnimated: this.loadingMoreLoadingAnimated, + showNoMoreLine: this.showLoadingMoreNoMoreLine, + noMoreLineCustomStyle: this.loadingMoreNoMoreLineCustomStyle, + defaultText: this.finalLoadingMoreDefaultText, + loadingText: this.finalLoadingMoreLoadingText, + noMoreText: this.finalLoadingMoreNoMoreText, + failText: this.finalLoadingMoreFailText, + hideContent: !this.loadingMoreDefaultAsLoading && this.listRendering, + unit: this.unit, + isChat: this.useChatRecordMode, + chatDefaultAsLoading: this.chatLoadingMoreDefaultAsLoading + }; + }, + // 最终的底部加载更多主题 + finalLoadingMoreThemeStyle() { + return this.loadingMoreThemeStyle.length ? this.loadingMoreThemeStyle : this.defaultThemeStyle; + }, + // 最终的底部加载更多触发阈值 + finalLowerThreshold() { + return u.convertToPx(this.lowerThreshold); + }, + // 是否显示默认状态下的底部加载更多 + showLoadingMoreDefault() { + return this._showLoadingMore('Default'); + }, + // 是否显示加载中状态下的底部加载更多 + showLoadingMoreLoading() { + return this._showLoadingMore('Loading'); + }, + // 是否显示没有更多了状态下的底部加载更多 + showLoadingMoreNoMore() { + return this._showLoadingMore('NoMore'); + }, + // 是否显示加载失败状态下的底部加载更多 + showLoadingMoreFail() { + return this._showLoadingMore('Fail'); + }, + // 是否显示自定义状态下的底部加载更多 + showLoadingMoreCustom() { + return this._showLoadingMore('Custom'); + }, + // 底部加载更多固定高度 + loadingMoreFixedHeight() { + return u.addUnit('80rpx', this.unit); + }, + }, + methods: { + // 页面滚动到底部时通知z-paging进行进一步处理 + pageReachBottom() { + !this.useChatRecordMode && this.toBottomLoadingMoreEnabled && this._onLoadingMore('toBottom'); + }, + // 手动触发上拉加载更多(非必须,可依据具体需求使用) + doLoadMore(type) { + this._onLoadingMore(type); + }, + // 通过@scroll事件检测是否滚动到了底部(顺带检测下是否滚动到了顶部) + _checkScrolledToBottom(scrollDiff, checked = false) { + // 如果当前scroll-view高度未获取,则获取其高度 + if (this.cacheScrollNodeHeight === -1) { + // 获取当前scroll-view高度 + this._getNodeClientRect('.zp-scroll-view').then((res) => { + if (res) { + const scrollNodeHeight = res[0].height; + // 缓存当前scroll-view高度,如果获取过了不再获取 + this.cacheScrollNodeHeight = scrollNodeHeight; + // // scrollDiff - this.cacheScrollNodeHeight = 当前滚动区域的顶部与内容底部的距离 - scroll-view高度 = 当前滚动区域的底部与内容底部的距离(也就是最终的与底部的距离) + if (scrollDiff - scrollNodeHeight <= this.finalLowerThreshold) { + // 如果与底部的距离小于阈值,则判断为滚动到了底部,触发滚动到底部事件 + this._onLoadingMore('toBottom'); + } + } + }); + } else { + // scrollDiff - this.cacheScrollNodeHeight = 当前滚动区域的顶部与内容底部的距离 - scroll-view高度 = 当前滚动区域的底部与内容底部的距离(也就是最终的与底部的距离) + if (scrollDiff - this.cacheScrollNodeHeight <= this.finalLowerThreshold) { + // 如果与底部的距离小于阈值,则判断为滚动到了底部,触发滚动到底部事件 + this._onLoadingMore('toBottom'); + } else if (scrollDiff - this.cacheScrollNodeHeight <= 500 && !checked) { + // 如果与底部的距离小于500px,则获取当前滚动的位置,延迟150毫秒重复上述步骤再次检测(避免@scroll触发时获取的scrollTop不正确导致的其他问题,此时获取的scrollTop不一定可信)。防止因为部分性能较差安卓设备@scroll采样率过低导致的滚动到底部但是依然没有触发的问题 + u.delay(() => { + this._getNodeClientRect('.zp-scroll-view', true, true).then((res) => { + if (res) { + this.oldScrollTop = res[0].scrollTop; + const newScrollDiff = res[0].scrollHeight - this.oldScrollTop; + this._checkScrolledToBottom(newScrollDiff, true); + } + }) + }, 150, 'checkScrolledToBottomDelay') + } + // 检测一下是否已经滚动到了顶部了,因为在安卓中滚动到顶部时scrollTop不一定为0(和滚动到底部一样的原因),所以需要在scrollTop小于150px时,通过获取.zp-scroll-view的scrollTop再判断一下 + if (this.oldScrollTop <= 150 && this.oldScrollTop !== 0) { + u.delay(() => { + // 这里再判断一下是否确实已经滚动到顶部了,如果已经滚动到顶部了,则不用再判断了,再次判断的原因是可能150毫秒之后oldScrollTop才是0 + if (this.oldScrollTop !== 0) { + this._getNodeClientRect('.zp-scroll-view', true, true).then((res) => { + // 如果150毫秒后.zp-scroll-view的scrollTop为0,则认为已经滚动到了顶部了 + if (res && res[0].scrollTop === 0 && this.oldScrollTop !== 0) { + this._onScrollToUpper(); + } + }) + } + }, 150, 'checkScrolledToTopDelay') + } + } + }, + // 触发加载更多时调用,from:toBottom-滑动到底部触发;click-点击加载更多触发 + _onLoadingMore(from = 'click') { + // 如果是ios并且是滚动到底部的,则在滚动到底部时候尝试将列表设置为禁止滚动然后设置为允许滚动,以禁止底部bounce的效果 + if (this.isIos && from === 'toBottom' && !this.scrollToBottomBounceEnabled && this.scrollEnable) { + this.scrollEnable = false; + this.$nextTick(() => { + this.scrollEnable = true; + }) + } + // emit scrolltolower + this._emitScrollEvent('scrolltolower'); + // 如果是只使用布局或下拉刷新 或者 禁用底部加载更多 或者 底部加载更多不是默认状态或加载失败状态 或者 是加载中状态 或者 空数据图已经展示了,则return,不触发内部加载更多逻辑 + if (this.isOnly || !this.loadingMoreEnabled || !(this.loadingStatus === Enum.More.Default || this.loadingStatus === Enum.More.Fail) || this.loading || this.showEmpty) return; + // #ifdef MP-WEIXIN + if (!this.isIos && !this.isOnly && !this.usePageScroll) { + const currentTimestamp = u.getTime(); + // 在非ios平台+scroll-view中节流处理 + if (this.loadingMoreTimeStamp > 0 && currentTimestamp - this.loadingMoreTimeStamp < 100) { + this.loadingMoreTimeStamp = 0; + return; + } + } + // #endif + // 处理加载更多数据 + this._doLoadingMore(); + }, + // 处理开始加载更多 + _doLoadingMore() { + if (this.pageNo >= this.defaultPageNo && this.loadingStatus !== Enum.More.NoMore) { + this.pageNo ++; + this._startLoading(false); + if (this.isLocalPaging) { + // 如果是本地分页,则在组件内部对数据进行分页处理,不触发@query事件 + this._localPagingQueryList(this.pageNo, this.defaultPageSize, this.localPagingLoadingTime, res => { + this.completeByTotal(res, this.totalLocalPagingList.length); + this.queryFrom = Enum.QueryFrom.LoadMore; + }) + } else { + // emit @query相关加载更多事件 + this._emitQuery(this.pageNo, this.defaultPageSize, Enum.QueryFrom.LoadMore); + this._callMyParentQuery(); + } + // 设置当前加载状态为底部加载更多状态 + this.loadingType = Enum.LoadingType.LoadMore; + } + }, + // (预处理)判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view + _preCheckShowNoMoreInside(newVal, scrollViewNode, pagingContainerNode) { + if (this.loadingStatus === Enum.More.NoMore && this.hideNoMoreByLimit > 0 && newVal.length) { + this.showLoadingMore = newVal.length > this.hideNoMoreByLimit; + } else if ((this.loadingStatus === Enum.More.NoMore && this.hideNoMoreInside && newVal.length) || (this.insideMore && this.insideOfPaging !== false && newVal.length)) { + this.$nextTick(() => { + this._checkShowNoMoreInside(newVal, scrollViewNode, pagingContainerNode); + }) + if (this.insideMore && this.insideOfPaging !== false && newVal.length) { + this.showLoadingMore = newVal.length; + } + } else { + this.showLoadingMore = newVal.length; + } + }, + // 判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view + async _checkShowNoMoreInside(totalData, oldScrollViewNode, oldPagingContainerNode) { + try { + const scrollViewNode = oldScrollViewNode || await this._getNodeClientRect('.zp-scroll-view'); + // 在页面滚动模式下 + if (this.usePageScroll) { + if (scrollViewNode) { + // 获取滚动内容总高度 + const scrollViewTotalH = scrollViewNode[0].top + scrollViewNode[0].height; + // 如果滚动内容总高度小于窗口高度,则认为内容未超出z-paging + this.insideOfPaging = scrollViewTotalH < this.windowHeight; + // 如果需要没有更多数据时,隐藏底部加载更多view,并且内容未超过z-paging,则隐藏底部加载更多 + if (this.hideNoMoreInside) { + this.showLoadingMore = !this.insideOfPaging; + } + // 如果需要内容未超过z-paging时自动加载更多,则触发加载更多 + this._updateInsideOfPaging(); + } + } else { + // 在scroll-view滚动模式下 + const pagingContainerNode = oldPagingContainerNode || await this._getNodeClientRect('.zp-paging-container-content'); + // 获取滚动内容总高度 + const pagingContainerH = pagingContainerNode ? pagingContainerNode[0].height : 0; + // 获取z-paging内置scroll-view高度 + const scrollViewH = scrollViewNode ? scrollViewNode[0].height : 0; + // 如果滚动内容总高度小于z-paging内置scroll-view高度,则认为内容未超出z-paging + this.insideOfPaging = pagingContainerH < scrollViewH; + if (this.hideNoMoreInside) { + this.showLoadingMore = !this.insideOfPaging; + } + // 如果需要内容未超过z-paging时自动加载更多,则触发加载更多 + this._updateInsideOfPaging(); + } + } catch (e) { + // 如果发生了异常,判断totalData数组长度为0,则认为内容未超出z-paging + this.insideOfPaging = !totalData.length; + if (this.hideNoMoreInside) { + this.showLoadingMore = !this.insideOfPaging; + } + // 如果需要内容未超过z-paging时自动加载更多,则触发加载更多 + this._updateInsideOfPaging(); + } + }, + // 是否要展示上拉加载更多view + _showLoadingMore(type) { + if (!this.showLoadingMoreWhenReload && (!(this.loadingStatus === Enum.More.Default ? this.nShowBottom : true) || !this.realTotalData.length)) return false; + if (((!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading) && !this.showLoadingMore) || + (!this.loadingMoreEnabled && (!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading)) || this.isOnly) { + return false; + } + if (this.useChatRecordMode && type !== 'Loading') return false; + if (!this.zSlots) return false; + if (type === 'Custom') { + return this.showDefaultLoadingMoreText && !(this.loadingStatus === Enum.More.NoMore && !this.showLoadingMoreNoMoreView); + } + const res = this.loadingStatus === Enum.More[type] && this.zSlots[`loadingMore${type}`] && (type === 'NoMore' ? this.showLoadingMoreNoMoreView : true); + if (res) { + // #ifdef APP-NVUE + if (!this.isIos) { + this.nLoadingMoreFixedHeight = false; + } + // #endif + } + return res; + }, + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/loading.js b/uni_modules/z-paging/components/z-paging/js/modules/loading.js new file mode 100644 index 0000000..19596ce --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/loading.js @@ -0,0 +1,95 @@ +// [z-paging]loading相关模块 +import u from '.././z-paging-utils' +import Enum from '.././z-paging-enum' + +export default { + props: { + // 第一次加载后自动隐藏loading slot,默认为是 + autoHideLoadingAfterFirstLoaded: { + type: Boolean, + default: u.gc('autoHideLoadingAfterFirstLoaded', true) + }, + // loading slot是否铺满屏幕并固定,默认为否 + loadingFullFixed: { + type: Boolean, + default: u.gc('loadingFullFixed', false) + }, + // 是否自动显示系统Loading:即uni.showLoading,若开启则将在刷新列表时(调用reload、refresh时)显示,下拉刷新和滚动到底部加载更多不会显示,默认为false。 + autoShowSystemLoading: { + type: Boolean, + default: u.gc('autoShowSystemLoading', false) + }, + // 显示系统Loading时是否显示透明蒙层,防止触摸穿透,默认为是(H5、App、微信小程序、百度小程序有效) + systemLoadingMask: { + type: Boolean, + default: u.gc('systemLoadingMask', true) + }, + // 显示系统Loading时显示的文字,默认为"加载中" + systemLoadingText: { + type: [String, Object], + default: u.gc('systemLoadingText', null) + }, + }, + data() { + return { + loading: false, + loadingForNow: false, + } + }, + watch: { + // loading状态 + loadingStatus(newVal) { + this.$emit('loadingStatusChange', newVal); + this.$nextTick(() => { + this.loadingStatusAfterRender = newVal; + }) + if (this.useChatRecordMode) { + if (this.isFirstPage && (newVal === Enum.More.NoMore || newVal === Enum.More.Fail)) { + this.isFirstPageAndNoMore = true; + return; + } + } + this.isFirstPageAndNoMore = false; + }, + loading(newVal){ + if (newVal) { + this.loadingForNow = newVal; + } + }, + }, + computed: { + // 是否显示loading + showLoading() { + if (this.firstPageLoaded || !this.loading || !this.loadingForNow) return false; + if (this.finalShowSystemLoading) { + // 显示系统loading + uni.showLoading({ + title: this.finalSystemLoadingText, + mask: this.systemLoadingMask + }) + } + return this.autoHideLoadingAfterFirstLoaded ? (this.fromEmptyViewReload ? true : !this.pagingLoaded) : this.loadingType === Enum.LoadingType.Refresher; + }, + // 最终的是否显示系统loading + finalShowSystemLoading() { + return this.autoShowSystemLoading && this.loadingType === Enum.LoadingType.Refresher; + } + }, + methods: { + // 处理开始加载更多状态 + _startLoading(isReload = false) { + if ((this.showLoadingMoreWhenReload && !this.isUserPullDown) || !isReload) { + this.loadingStatus = Enum.More.Loading; + } + this.loading = true; + }, + // 停止系统loading和refresh + _endSystemLoadingAndRefresh(){ + this.finalShowSystemLoading && uni.hideLoading(); + !this.useCustomRefresher && uni.stopPullDownRefresh(); + // #ifdef APP-NVUE + this.usePageScroll && uni.stopPullDownRefresh(); + // #endif + } + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/nvue.js b/uni_modules/z-paging/components/z-paging/js/modules/nvue.js new file mode 100644 index 0000000..3ae5687 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/nvue.js @@ -0,0 +1,299 @@ +// [z-paging]nvue独有部分模块 +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: { + // #ifdef APP-NVUE + // nvue中修改列表类型,可选值有list、waterfall和scroller,默认为list + nvueListIs: { + type: String, + default: u.gc('nvueListIs', 'list') + }, + // nvue waterfall配置,仅在nvue中且nvueListIs=waterfall时有效,配置参数详情参见:https://uniapp.dcloud.io/component/waterfall + nvueWaterfallConfig: { + type: Object, + default: u.gc('nvueWaterfallConfig', {}) + }, + // nvue 控制是否回弹效果,iOS不支持动态修改 + nvueBounce: { + type: Boolean, + default: u.gc('nvueBounce', true) + }, + // nvue中通过代码滚动到顶部/底部时,是否加快动画效果(无滚动动画时无效),默认为否 + nvueFastScroll: { + type: Boolean, + default: u.gc('nvueFastScroll', false) + }, + // nvue中list的id + nvueListId: { + type: String, + default: u.gc('nvueListId', '') + }, + // nvue中refresh组件的样式 + nvueRefresherStyle: { + type: Object, + default: u.gc('nvueRefresherStyle', {}) + }, + // nvue中是否按分页模式(类似竖向swiper)显示List,默认为false + nvuePagingEnabled: { + type: Boolean, + default: u.gc('nvuePagingEnabled', false) + }, + // 是否隐藏nvue列表底部的tagView,此view用于标识滚动到底部位置,若隐藏则滚动到底部功能将失效,在nvue中实现吸顶+swiper功能时需将最外层z-paging的此属性设置为true。默认为否 + hideNvueBottomTag: { + type: Boolean, + default: u.gc('hideNvueBottomTag', false) + }, + // nvue中控制onscroll事件触发的频率:表示两次onscroll事件之间列表至少滚动了10px。注意,将该值设置为较小的数值会提高滚动事件采样的精度,但同时也会降低页面的性能 + offsetAccuracy: { + type: Number, + default: u.gc('offsetAccuracy', 10) + }, + // #endif + }, + data() { + return { + nRefresherLoading: false, + nListIsDragging: false, + nShowBottom: true, + nFixFreezing: false, + nShowRefresherReveal: false, + nLoadingMoreFixedHeight: false, + nShowRefresherRevealHeight: 0, + nOldShowRefresherRevealHeight: -1, + nRefresherWidth: u.rpx2px(750), + nListHeight: 0, + nF2Opacity: 0 + } + }, + computed: { + // #ifdef APP-NVUE + nScopedSlots() { + // #ifdef VUE2 + return this.$scopedSlots; + // #endif + // #ifdef VUE3 + return null; + // #endif + }, + nWaterfallColumnCount() { + if (this.finalNvueListIs !== 'waterfall') return 0; + return this._nGetWaterfallConfig('column-count', 2); + }, + nWaterfallColumnWidth() { + return this._nGetWaterfallConfig('column-width', 'auto'); + }, + nWaterfallColumnGap() { + return this._nGetWaterfallConfig('column-gap', 'normal'); + }, + nWaterfallLeftGap() { + return this._nGetWaterfallConfig('left-gap', 0); + }, + nWaterfallRightGap() { + return this._nGetWaterfallConfig('right-gap', 0); + }, + nViewIs() { + const is = this.finalNvueListIs; + return is === 'scroller' || is === 'view' ? 'view' : is === 'waterfall' ? 'header' : 'cell'; + }, + nSafeAreaBottomHeight() { + return this.safeAreaInsetBottom ? this.safeAreaBottom : 0; + }, + finalNvueListIs() { + if (this.usePageScroll) return 'view'; + const nvueListIsLowerCase = this.nvueListIs.toLowerCase(); + if (['list','waterfall','scroller'].indexOf(nvueListIsLowerCase) !== -1) return nvueListIsLowerCase; + return 'list'; + }, + finalNvueSuperListIs() { + return this.usePageScroll ? 'view' : 'scroller'; + }, + finalNvueRefresherEnabled() { + return this.finalNvueListIs !== 'view' && this.finalRefresherEnabled && !this.nShowRefresherReveal && !this.useChatRecordMode; + }, + // #endif + }, + mounted(){ + // #ifdef APP-NVUE + //旋转屏幕时更新宽度 + uni.onWindowResize((res) => { + // this._nUpdateRefresherWidth(); + }) + // #endif + }, + methods: { + // #ifdef APP-NVUE + // 列表滚动时触发 + _nOnScroll(e) { + this.$emit('scroll', e); + const scrollTop = -e.contentOffset.y; + const scrollHeight = e.contentSize.height; + + if (this.watchScrollDirectionChange) { + // 计算scroll-view滚动方向,正常情况下上次滚动的oldScrollTop大于当前scrollTop即为向上滚动,反之为向下滚动 + let direction = this.oldScrollTop > scrollTop ? 'top' : 'bottom'; + // 此处为解决在iOS中,滚动到顶部因bounce的影响回弹导致滚动方向为bottom的问题:如果滚动到顶部了并且scrollTop小于顶部滚动区域,则强制设置direction为top + if (scrollTop <= 0) { + direction = 'top'; + } + // 此处为解决在iOS中,滚动到底部因bounce的影响回弹导致滚动方向为top的问题:如果滚动到底部了并且scrollTop超过底部滚动区域,则强制设置direction为bottom + if (scrollTop > this.lastScrollHeight - this.nListHeight - 1) { + direction = 'bottom'; + } + // emit 列表滚动方向改变事件 + if (direction !== this.lastScrollDirection) { + this.$emit('scrollDirectionChange', direction); + this.lastScrollDirection = direction; + } + // 当scrollHeight变化时,需要延迟100毫秒设置lastScrollHeight,如果直接根据scrollHeight的话,因为此时数据还未改变,会导致滚动方向从bottom变为top + if (this.lastScrollHeight !== scrollHeight && !this.setContentHeightPending) { + // 因此处会多次触发,因此加个标识确保在延时期间仅触发一次 + this.setContentHeightPending = true; + u.delay(() => { + this.lastScrollHeight = scrollHeight; + this.setContentHeightPending = false; + }) + } + } + + this.oldScrollTop = scrollTop; + this.nListIsDragging = e.isDragging; + this._checkShouldShowBackToTop(scrollTop, scrollTop - 1); + }, + // 列表滚动结束 + _nOnScrollend(e) { + this.$emit('scrollend', e); + + // 判断是否滚动到顶部了 + if (e?.contentOffset?.y >= 0) { + this._emitScrollEvent('scrolltoupper'); + } + // 判断是否滚动到底部了 + this._getNodeClientRect('.zp-n-list').then(node => { + if (node) { + this.nListHeight = node[0].height; + if (e?.contentSize?.height + e?.contentOffset?.y <= node[0].height) { + this._emitScrollEvent('scrolltolower'); + } + } + }) + }, + // 下拉刷新刷新中 + _nOnRrefresh() { + if (this.nShowRefresherReveal) return; + // 进入刷新状态 + this.nRefresherLoading = true; + 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); + 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) { + if (doEnd) { + this._nDoRefresherEndAnimation(0, -this.nShowRefresherRevealHeight); + !this.usePageScroll && this.$refs['zp-n-list'].resetLoadmore(); + this.nRefresherLoading = false; + } + }, + // 执行主动触发下拉刷新动画 + _nDoRefresherEndAnimation(height, translateY, animate = true, checkStack = true) { + // 清除下拉刷新相关timeout + this._cleanRefresherCompleteTimeout(); + this._cleanRefresherEndTimeout(); + + if (!this.finalShowRefresherWhenReload) { + // 如果reload不需要自动展示下拉刷新view,则在complete duration结束后再把下拉刷新状态设置回默认 + this.refresherEndTimeout = u.delay(() => { + this.refresherStatus = Enum.Refresher.Default; + }, this.refresherCompleteDuration); + return; + } + // 用户处理用户在短时间内多次调用reload的情况,此时下拉刷新view不需要重复显示,只需要保证最后一次reload对应的请求结束后收回下拉刷新view即可 + const stackCount = this.refresherRevealStackCount; + if (height === 0 && checkStack) { + this.refresherRevealStackCount --; + if (stackCount > 1) return; + this.refresherEndTimeout = u.delay(() => { + this.refresherStatus = Enum.Refresher.Default; + }, this.refresherCompleteDuration); + } + if (stackCount > 1) { + this.refresherStatus = Enum.Refresher.Loading; + } + + const duration = animate ? 200 : 0; + if (this.nOldShowRefresherRevealHeight !== height) { + if (height > 0) { + this.nShowRefresherReveal = true; + } + // 展示下拉刷新view + weexAnimation.transition(this.$refs['zp-n-list-refresher-reveal'], { + styles: { + height: `${height}px`, + transform: `translateY(${translateY}px)`, + }, + duration, + timingFunction: 'linear', + needLayout: true, + delay: 0 + }) + } + u.delay(() => { + if (animate) { + this.nShowRefresherReveal = height > 0; + } + }, duration > 0 ? duration - 60 : 0); + this.nOldShowRefresherRevealHeight = height; + }, + // 滚动到底部加载更多 + _nOnLoadmore() { + if (this.nShowRefresherReveal || !this.totalData.length) return; + this.useChatRecordMode ? this.doChatRecordLoadMore() : this._onLoadingMore('toBottom'); + }, + // 获取nvue waterfall单项配置 + _nGetWaterfallConfig(key, defaultValue) { + return this.nvueWaterfallConfig[key] || defaultValue; + }, + // 更新nvue 下拉刷新view容器的宽度 + _nUpdateRefresherWidth() { + u.delay(() => { + this.$nextTick(()=>{ + this._getNodeClientRect('.zp-n-list').then(node => { + if (node) { + this.nRefresherWidth = node[0].width || this.nRefresherWidth; + } + }) + }) + }) + } + // #endif + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/refresher.js b/uni_modules/z-paging/components/z-paging/js/modules/refresher.js new file mode 100644 index 0000000..35e04e7 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/refresher.js @@ -0,0 +1,835 @@ +// [z-paging]下拉刷新view模块 +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 + refresherThemeStyle: { + type: String, + default: u.gc('refresherThemeStyle', '') + }, + // 自定义下拉刷新中左侧图标的样式 + refresherImgStyle: { + type: Object, + default: u.gc('refresherImgStyle', {}) + }, + // 自定义下拉刷新中右侧状态描述文字的样式 + refresherTitleStyle: { + type: Object, + default: u.gc('refresherTitleStyle', {}) + }, + // 自定义下拉刷新中右侧最后更新时间文字的样式(show-refresher-update-time为true时有效) + refresherUpdateTimeStyle: { + type: Object, + default: u.gc('refresherUpdateTimeStyle', {}) + }, + // 在微信小程序和QQ小程序中,是否实时监听下拉刷新中进度,默认为否 + watchRefresherTouchmove: { + type: Boolean, + default: u.gc('watchRefresherTouchmove', false) + }, + // 底部加载更多的主题样式,支持black,white,默认black + loadingMoreThemeStyle: { + type: String, + default: u.gc('loadingMoreThemeStyle', '') + }, + // 是否只使用下拉刷新,设置为true后将关闭mounted自动请求数据、关闭滚动到底部加载更多,强制隐藏空数据图。默认为否 + refresherOnly: { + type: Boolean, + default: u.gc('refresherOnly', false) + }, + // 自定义下拉刷新默认状态下回弹动画时间,单位为毫秒,默认为100毫秒,nvue无效 + refresherDefaultDuration: { + type: [Number, String], + default: u.gc('refresherDefaultDuration', 100) + }, + // 自定义下拉刷新结束以后延迟回弹的时间,单位为毫秒,默认为0 + refresherCompleteDelay: { + type: [Number, String], + default: u.gc('refresherCompleteDelay', 0) + }, + // 自定义下拉刷新结束回弹动画时间,单位为毫秒,默认为300毫秒(refresherEndBounceEnabled为false时,refresherCompleteDuration为设定值的1/3),nvue无效 + refresherCompleteDuration: { + type: [Number, String], + default: u.gc('refresherCompleteDuration', 300) + }, + // 自定义下拉刷新中是否允许列表滚动,默认为是 + refresherRefreshingScrollable: { + type: Boolean, + default: u.gc('refresherRefreshingScrollable', true) + }, + // 自定义下拉刷新结束状态下是否允许列表滚动,默认为否 + refresherCompleteScrollable: { + type: Boolean, + default: u.gc('refresherCompleteScrollable', false) + }, + // 是否使用自定义的下拉刷新,默认为是,即使用z-paging的下拉刷新。设置为false即代表使用uni scroll-view自带的下拉刷新,h5、App、微信小程序以外的平台不支持uni scroll-view自带的下拉刷新 + useCustomRefresher: { + type: Boolean, + default: u.gc('useCustomRefresher', true) + }, + // 自定义下拉刷新下拉帧率,默认为40,过高可能会出现抖动问题 + refresherFps: { + type: [Number, String], + default: u.gc('refresherFps', 40) + }, + // 自定义下拉刷新允许触发的最大下拉角度,默认为40度,当下拉角度小于设定值时,自定义下拉刷新动画不会被触发 + refresherMaxAngle: { + type: [Number, String], + default: u.gc('refresherMaxAngle', 40) + }, + // 自定义下拉刷新的角度由未达到最大角度变到达到最大角度时,是否继续下拉刷新手势,默认为否 + refresherAngleEnableChangeContinued: { + type: Boolean, + default: u.gc('refresherAngleEnableChangeContinued', false) + }, + // 自定义下拉刷新默认状态下的文字 + refresherDefaultText: { + type: [String, Object], + default: u.gc('refresherDefaultText', null) + }, + // 自定义下拉刷新松手立即刷新状态下的文字 + refresherPullingText: { + type: [String, Object], + default: u.gc('refresherPullingText', null) + }, + // 自定义下拉刷新刷新中状态下的文字 + refresherRefreshingText: { + type: [String, Object], + default: u.gc('refresherRefreshingText', null) + }, + // 自定义下拉刷新刷新结束状态下的文字 + refresherCompleteText: { + type: [String, Object], + default: u.gc('refresherCompleteText', null) + }, + // 自定义继续下拉进入二楼文字 + refresherGoF2Text: { + type: [String, Object], + default: u.gc('refresherGoF2Text', null) + }, + // 自定义下拉刷新默认状态下的图片 + refresherDefaultImg: { + type: String, + default: u.gc('refresherDefaultImg', null) + }, + // 自定义下拉刷新松手立即刷新状态下的图片,默认与refresherDefaultImg一致 + refresherPullingImg: { + type: String, + default: u.gc('refresherPullingImg', null) + }, + // 自定义下拉刷新刷新中状态下的图片 + refresherRefreshingImg: { + type: String, + default: u.gc('refresherRefreshingImg', null) + }, + // 自定义下拉刷新刷新结束状态下的图片 + refresherCompleteImg: { + type: String, + default: u.gc('refresherCompleteImg', null) + }, + // 自定义下拉刷新刷新中状态下是否展示旋转动画 + refresherRefreshingAnimated: { + type: Boolean, + default: u.gc('refresherRefreshingAnimated', true) + }, + // 是否开启自定义下拉刷新刷新结束回弹效果,默认为是 + refresherEndBounceEnabled: { + type: Boolean, + default: u.gc('refresherEndBounceEnabled', true) + }, + // 是否开启自定义下拉刷新,默认为是 + refresherEnabled: { + type: Boolean, + default: u.gc('refresherEnabled', true) + }, + // 设置自定义下拉刷新阈值,默认为80rpx + refresherThreshold: { + type: [Number, String], + default: u.gc('refresherThreshold', '80rpx') + }, + // 设置系统下拉刷新默认样式,支持设置 black,white,none,none 表示不使用默认样式,默认为black + refresherDefaultStyle: { + type: String, + default: u.gc('refresherDefaultStyle', 'black') + }, + // 设置自定义下拉刷新区域背景 + refresherBackground: { + type: String, + default: u.gc('refresherBackground', 'transparent') + }, + // 设置固定的自定义下拉刷新区域背景 + refresherFixedBackground: { + type: String, + default: u.gc('refresherFixedBackground', 'transparent') + }, + // 设置固定的自定义下拉刷新区域高度,默认为0 + refresherFixedBacHeight: { + type: [Number, String], + default: u.gc('refresherFixedBacHeight', 0) + }, + // 设置自定义下拉刷新下拉超出阈值后继续下拉位移衰减的比例,范围0-1,值越大代表衰减越多。默认为0.65(nvue无效) + refresherOutRate: { + 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, + default: u.gc('refresherPullRate', 0.75) + }, + // 是否显示最后更新时间,默认为否 + showRefresherUpdateTime: { + type: Boolean, + default: u.gc('showRefresherUpdateTime', false) + }, + // 如果需要区别不同页面的最后更新时间,请为不同页面的z-paging的`refresher-update-time-key`设置不同的字符串 + refresherUpdateTimeKey: { + type: String, + default: u.gc('refresherUpdateTimeKey', 'default') + }, + // 下拉刷新时下拉到“松手立即刷新”或“松手进入二楼”状态时是否使手机短振动,默认为否(h5无效) + refresherVibrate: { + type: Boolean, + default: u.gc('refresherVibrate', false) + }, + // 下拉刷新时是否禁止下拉刷新view跟随用户触摸竖直移动,默认为否。注意此属性只是禁止下拉刷新view移动,其他下拉刷新逻辑依然会正常触发 + refresherNoTransform: { + type: Boolean, + default: u.gc('refresherNoTransform', false) + }, + // 是否开启下拉刷新状态栏占位,适用于隐藏导航栏时,下拉刷新需要避开状态栏高度的情况,默认为否 + useRefresherStatusBarPlaceholder: { + type: Boolean, + default: u.gc('useRefresherStatusBarPlaceholder', false) + }, + }, + data() { + return { + R: Enum.Refresher, + //下拉刷新状态 + refresherStatus: Enum.Refresher.Default, + refresherTouchstartY: 0, + lastRefresherTouchmove: null, + refresherReachMaxAngle: true, + refresherTransform: 'translateY(0px)', + refresherTransition: '', + finalRefresherDefaultStyle: 'black', + refresherRevealStackCount: 0, + refresherCompleteTimeout: null, + refresherCompleteSubTimeout: null, + refresherEndTimeout: null, + isTouchmovingTimeout: null, + refresherTriggered: false, + isTouchmoving: false, + isTouchEnded: false, + isUserPullDown: false, + privateRefresherEnabled: -1, + privateShowRefresherWhenReload: false, + customRefresherHeight: -1, + showCustomRefresher: false, + doRefreshAnimateAfter: false, + isRefresherInComplete: false, + showF2: false, + f2Transform: '', + pullDownTimeStamp: 0, + moveDis: 0, + oldMoveDis: 0, + currentDis: 0, + oldCurrentMoveDis: 0, + oldRefresherTouchmoveY: 0, + oldTouchDirection: '', + oldEmitedTouchDirection: '', + oldPullingDistance: -1, + refresherThresholdUpdateTag: 0 + } + }, + watch: { + refresherDefaultStyle: { + handler(newVal) { + if (newVal.length) { + this.finalRefresherDefaultStyle = newVal; + } + }, + immediate: true + }, + refresherStatus(newVal) { + newVal === Enum.Refresher.Loading && this._cleanRefresherEndTimeout(); + this.refresherVibrate && (newVal === Enum.Refresher.ReleaseToRefresh || newVal === Enum.Refresher.GoF2) && this._doVibrateShort(); + this.$emit('refresherStatusChange', newVal); + this.$emit('update:refresherStatus', newVal); + }, + // 监听当前下拉刷新启用/禁用状态 + refresherEnabled(newVal) { + // 当禁用下拉刷新时,强制收回正在展示的下拉刷新view + !newVal && this.endRefresh(); + } + }, + computed: { + pullDownDisTimeStamp() { + return 1000 / this.refresherFps; + }, + refresherThresholdUnitConverted() { + return u.addUnit(this.refresherThreshold, this.unit); + }, + finalRefresherEnabled() { + if (this.layoutOnly || this.useChatRecordMode) return false; + if (this.privateRefresherEnabled === -1) return this.refresherEnabled; + return this.privateRefresherEnabled === 1; + }, + finalRefresherThreshold() { + let refresherThreshold = this.refresherThresholdUnitConverted; + let idDefault = false; + if (refresherThreshold === u.addUnit(80, this.unit)) { + idDefault = true; + if (this.showRefresherUpdateTime) { + refresherThreshold = u.addUnit(120, this.unit); + } + } + 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; + }, + finalRefresherFixedBacHeight() { + return u.convertToPx(this.refresherFixedBacHeight); + }, + finalRefresherThemeStyle() { + return this.refresherThemeStyle.length ? this.refresherThemeStyle : this.defaultThemeStyle; + }, + finalRefresherOutRate() { + let rate = this.refresherOutRate; + rate = Math.max(0,rate); + rate = Math.min(1,rate); + return rate; + }, + finalRefresherPullRate() { + let rate = this.refresherPullRate; + rate = Math.max(0,rate); + return rate; + }, + finalRefresherTransform() { + if (this.refresherNoTransform || this.refresherTransform === 'translateY(0px)') return 'none'; + return this.refresherTransform; + }, + finalShowRefresherWhenReload() { + return this.showRefresherWhenReload || this.privateShowRefresherWhenReload; + }, + finalRefresherTriggered() { + if (!(this.finalRefresherEnabled && !this.useCustomRefresher)) return false; + return this.refresherTriggered; + }, + showRefresher() { + const showRefresher = this.finalRefresherEnabled || this.useCustomRefresher && !this.useChatRecordMode; + // #ifndef APP-NVUE + this.active && this.customRefresherHeight === -1 && showRefresher && this.updateCustomRefresherHeight(); + // #endif + return showRefresher; + }, + hasTouchmove() { + // #ifdef VUE2 + // #ifdef APP-VUE || H5 + if (this.$listeners && !this.$listeners.refresherTouchmove) return false; + // #endif + // #ifdef MP-WEIXIN || MP-QQ + return this.watchRefresherTouchmove; + // #endif + return true; + // #endif + return this.watchRefresherTouchmove; + }, + }, + methods: { + // 终止下拉刷新状态 + endRefresh() { + this.totalData = this.realTotalData; + this._refresherEnd(); + this._endSystemLoadingAndRefresh(); + this._handleScrollViewBounce({ bounce: true }); + this.$nextTick(() => { + this.refresherTriggered = false; + }) + }, + // 手动更新自定义下拉刷新view高度 + updateCustomRefresherHeight() { + u.delay(() => this.$nextTick(this._updateCustomRefresherHeight)); + }, + // 进入二楼 + goF2() { + this._handleGoF2(); + }, + // 关闭二楼 + closeF2() { + this._handleCloseF2(); + }, + // 自定义下拉刷新被触发 + _onRefresh(fromScrollView = false, isUserPullDown = true) { + if (fromScrollView && !(this.finalRefresherEnabled && !this.useCustomRefresher)) return; + this.$emit('onRefresh'); + this.$emit('Refresh'); + // #ifdef APP-NVUE + if (this.loading) { + u.delay(this._nRefresherEnd, 500) + return; + } + // #endif + if (this.loading || this.isRefresherInComplete) return; + this.loadingType = Enum.LoadingType.Refresher; + if (this.nShowRefresherReveal) return; + this.isUserPullDown = isUserPullDown; + this.isUserReload = !isUserPullDown; + this._startLoading(true); + this.refresherTriggered = true; + if (this.reloadWhenRefresh && isUserPullDown) { + this.useChatRecordMode ? this._onLoadingMore('click') : this._reload(false, false, isUserPullDown); + } + }, + // 自定义下拉刷新被复位 + _onRestore() { + this.refresherTriggered = 'restore'; + this.$emit('onRestore'); + this.$emit('Restore'); + }, + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + // touch开始 + _refresherTouchstart(e) { + this._handleListTouchstart(); + if (this._touchDisabled()) return; + this._handleRefresherTouchstart(u.getTouch(e)); + }, + // #endif + // 进一步处理touch开始结果 + _handleRefresherTouchstart(touch) { + if (!this.loading && this.isTouchEnded) { + this.isTouchmoving = false; + } + this.loadingType = Enum.LoadingType.Refresher; + this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); + this.isTouchEnded = false; + this.refresherTransition = ''; + this.refresherTouchstartY = touch.touchY; + this.$emit('refresherTouchstart', this.refresherTouchstartY); + this.lastRefresherTouchmove = touch; + this._cleanRefresherCompleteTimeout(); + this._cleanRefresherEndTimeout(); + }, + + // 非app-vue或微信小程序或QQ小程序或h5平台,使用js控制下拉刷新 + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + // touch中 + _refresherTouchmove(e) { + const currentTimeStamp = u.getTime(); + let touch = null; + let refresherTouchmoveY = 0; + if (this.watchTouchDirectionChange) { + // 检测下拉刷新方向改变 + touch = u.getTouch(e); + refresherTouchmoveY = touch.touchY; + const direction = refresherTouchmoveY > this.oldRefresherTouchmoveY ? 'top' : 'bottom'; + // 只有在方向改变的时候才emit相关事件 + if (direction === this.oldTouchDirection && direction !== this.oldEmitedTouchDirection) { + this._handleTouchDirectionChange({ direction }); + this.oldEmitedTouchDirection = direction; + } + this.oldTouchDirection = direction; + this.oldRefresherTouchmoveY = refresherTouchmoveY; + } + // 节流处理,在pullDownDisTimeStamp时间内的下拉刷新中事件不进行处理 + if (this.pullDownTimeStamp && currentTimeStamp - this.pullDownTimeStamp <= this.pullDownDisTimeStamp) return; + // 如果不允许下拉,则return + if (this._touchDisabled()) return; + this.pullDownTimeStamp = Number(currentTimeStamp); + touch = u.getTouch(e); + refresherTouchmoveY = touch.touchY; + // 获取当前touch的y - 初始touch的y,计算它们的差 + let moveDis = refresherTouchmoveY - this.refresherTouchstartY; + if (moveDis < 0) return; + // 对下拉刷新的角度进行限制 + if (this.refresherMaxAngle >= 0 && this.refresherMaxAngle <= 90 && this.lastRefresherTouchmove && this.lastRefresherTouchmove.touchY <= refresherTouchmoveY) { + if (!moveDis && !this.refresherAngleEnableChangeContinued && this.moveDis < 1 && !this.refresherReachMaxAngle) return; + const x = Math.abs(touch.touchX - this.lastRefresherTouchmove.touchX); + const y = Math.abs(refresherTouchmoveY - this.lastRefresherTouchmove.touchY); + const z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); + if ((x || y) && x > 1) { + // 获取下拉刷新前后两次位移的角度 + const angle = Math.asin(y / z) / Math.PI * 180; + // 如果角度小于配置要求,则return + if (angle < this.refresherMaxAngle) { + this.lastRefresherTouchmove = touch; + this.refresherReachMaxAngle = false; + return; + } + } + } + // 获取最终的moveDis + moveDis = this._getFinalRefresherMoveDis(moveDis); + // 处理下拉刷新位移 + this._handleRefresherTouchmove(moveDis, touch); + // 下拉刷新时,禁止页面滚动以防止页面向下滚动和下拉刷新同时作用导致下拉刷新位置偏移超过预期 + if (!this.disabledBounce) { + // #ifndef MP-LARK + this._handleScrollViewBounce({ bounce: false }); + // #endif + this.disabledBounce = true; + } + this._emitTouchmove({ pullingDistance: moveDis, dy: this.moveDis - this.oldMoveDis }); + }, + // #endif + // 进一步处理touch中结果 + _handleRefresherTouchmove(moveDis, touch) { + this.refresherReachMaxAngle = true; + this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); + this.isTouchmoving = true; + this.isTouchEnded = false; + // 更新下拉刷新状态 + // 下拉刷新距离超过阈值 + 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垂直偏移 + this.refresherTransform = `translateY(${moveDis}px)`; + this.lastRefresherTouchmove = touch; + // #endif + this.moveDis = moveDis; + }, + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + // touch结束 + _refresherTouchend(e) { + // 下拉刷新用户手离开屏幕,允许列表滚动 + this._handleScrollViewBounce({bounce: true}); + if (this._touchDisabled() || !this.isTouchmoving) return; + const touch = u.getTouch(e); + let refresherTouchendY = touch.touchY; + let moveDis = refresherTouchendY - this.refresherTouchstartY; + moveDis = this._getFinalRefresherMoveDis(moveDis); + this._handleRefresherTouchend(moveDis); + this.disabledBounce = false; + }, + // #endif + // 进一步处理touch结束结果 + _handleRefresherTouchend(moveDis) { + // #ifndef APP-PLUS || H5 || MP-WEIXIN + if (!this.isTouchmoving) return; + // #endif + this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); + this.refresherReachMaxAngle = true; + this.isTouchEnded = true; + const refresherThreshold = this.finalRefresherThreshold; + if (moveDis >= refresherThreshold && [Enum.Refresher.ReleaseToRefresh, Enum.Refresher.GoF2].indexOf(this.refresherStatus) >= 0) { + // 如果是松手进入二楼状态,则触发进入二楼 + 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(() => { + this.isTouchmoving = false; + }, this.refresherDefaultDuration); + } + this.scrollEnable = true; + this.$emit('refresherTouchend', moveDis); + }, + // 处理列表触摸开始事件 + _handleListTouchstart() { + if (this.useChatRecordMode && this.autoHideKeyboardWhenChat) { + uni.hideKeyboard(); + this.$emit('hidedKeyboard'); + } + }, + // 处理scroll-view bounce是否生效 + _handleScrollViewBounce({ bounce }) { + if (!this.usePageScroll && !this.scrollToTopBounceEnabled) { + if (this.wxsScrollTop <= 5) { + // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 + this.refresherTransition = ''; + // #endif + this.scrollEnable = bounce; + } else if (bounce) { + this.scrollEnable = bounce; + } + } + }, + // wxs正在下拉状态改变处理 + _handleWxsPullingDownStatusChange(onPullingDown) { + this.wxsOnPullingDown = onPullingDown; + if (onPullingDown && !this.useChatRecordMode) { + this.renderPropScrollTop = 0; + } + }, + // wxs正在下拉处理 + _handleWxsPullingDown({ moveDis, diffDis }){ + this._emitTouchmove({ pullingDistance: moveDis,dy: diffDis }); + }, + // wxs触摸方向改变 + _handleTouchDirectionChange({ direction }) { + this.$emit('touchDirectionChange',direction); + }, + // wxs通知更新其props + _handlePropUpdate(){ + this.wxsPropType = u.getTime().toString(); + }, + // 下拉刷新结束 + _refresherEnd(shouldEndLoadingDelay = true, fromAddData = false, isUserPullDown = false, setLoading = true) { + if (this.loadingType === Enum.LoadingType.Refresher) { + // 计算当前下拉刷新结束需要延迟的时间 + const refresherCompleteDelay = (fromAddData && (isUserPullDown || this.showRefresherWhenReload)) ? this.refresherCompleteDelay : 0; + // 如果延迟时间大于0,则展示刷新结束状态,否则直接展示默认状态 + const refresherStatus = refresherCompleteDelay > 0 ? Enum.Refresher.Complete : Enum.Refresher.Default; + if (this.finalShowRefresherWhenReload) { + const stackCount = this.refresherRevealStackCount; + this.refresherRevealStackCount --; + if (stackCount > 1) return; + } + this._cleanRefresherEndTimeout(); + this.refresherEndTimeout = u.delay(() => { + // 更新下拉刷新状态 + this.refresherStatus = refresherStatus; + // 如果当前下拉刷新状态不是刷新结束,则认为其不在刷新结束状态 + if (refresherStatus !== Enum.Refresher.Complete) { + this.isRefresherInComplete = false; + } + }, this.refresherStatus !== Enum.Refresher.Default && refresherStatus === Enum.Refresher.Default ? this.refresherCompleteDuration : 0); + + // #ifndef APP-NVUE + if (refresherCompleteDelay > 0) { + this.isRefresherInComplete = true; + } + // #endif + this._cleanRefresherCompleteTimeout(); + this.refresherCompleteTimeout = u.delay(() => { + let animateDuration = 1; + const animateType = this.refresherEndBounceEnabled && fromAddData ? 'cubic-bezier(0.19,1.64,0.42,0.72)' : 'linear'; + if (fromAddData) { + animateDuration = this.refresherEndBounceEnabled ? this.refresherCompleteDuration / 1000 : this.refresherCompleteDuration / 3000; + } + this.refresherTransition = `transform ${fromAddData ? animateDuration : this.refresherDefaultDuration / 1000}s ${animateType}`; + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + this.refresherTransform = 'translateY(0px)'; + this.currentDis = 0; + // #endif + // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 + this.wxsPropType = this.refresherTransition + 'end' + u.getTime(); + // #endif + // #ifdef APP-NVUE + this._nRefresherEnd(); + // #endif + this.moveDis = 0; + // #ifndef APP-NVUE + if (refresherStatus === Enum.Refresher.Complete) { + if (this.refresherCompleteSubTimeout) { + clearTimeout(this.refresherCompleteSubTimeout); + this.refresherCompleteSubTimeout = null; + } + this.refresherCompleteSubTimeout = u.delay(() => { + this.$nextTick(() => { + this.refresherStatus = Enum.Refresher.Default; + this.isRefresherInComplete = false; + }) + }, animateDuration * 800); + } + // #endif + this._emitTouchmove({ pullingDistance: 0, dy: this.moveDis }); + }, refresherCompleteDelay); + } + if (setLoading) { + 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(); + // 用户处理用户在短时间内多次调用reload的情况,此时下拉刷新view不需要重复显示,只需要保证最后一次reload对应的请求结束后收回下拉刷新view即可 + // #ifndef APP-NVUE + const doRefreshAnimateAfter = !this.doRefreshAnimateAfter && (this.finalShowRefresherWhenReload) && this + .customRefresherHeight === -1 && this.refresherThreshold === u.addUnit(80, this.unit); + if (doRefreshAnimateAfter) { + this.doRefreshAnimateAfter = true; + return; + } + // #endif + this.refresherRevealStackCount ++; + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + this.refresherTransform = `translateY(${this.finalRefresherThreshold}px)`; + // #endif + // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 + this.wxsPropType = 'begin' + u.getTime(); + // #endif + this.moveDis = this.finalRefresherThreshold; + this.refresherStatus = Enum.Refresher.Loading; + this.isTouchmoving = true; + this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); + this._doRefresherLoad(false); + }, + // 触发下拉刷新 + _doRefresherLoad(isUserPullDown = true) { + this._onRefresh(false, isUserPullDown); + this.loading = true; + }, + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + // 获取处理后的moveDis + _getFinalRefresherMoveDis(moveDis) { + let diffDis = moveDis - this.oldCurrentMoveDis; + this.oldCurrentMoveDis = moveDis; + if (diffDis > 0) { + // 根据配置的下拉刷新用户手势位移与实际需要的位移比率计算最终的diffDis + diffDis = diffDis * this.finalRefresherPullRate; + if (this.currentDis > this.finalRefresherThreshold) { + diffDis = diffDis * (1 - this.finalRefresherOutRate); + } + } + // 控制diffDis过大的情况,比如进入页面突然猛然下拉,此时diffDis不应进行太大的偏移 + diffDis = diffDis > 100 ? diffDis / 100 : diffDis; + this.currentDis += diffDis; + this.currentDis = Math.max(0, this.currentDis); + return this.currentDis; + }, + // 判断touch手势是否要触发 + _touchDisabled() { + const checkOldScrollTop = this.oldScrollTop > 5; + return this.loading || this.isRefresherInComplete || this.useChatRecordMode || this.layoutOnly || !this.refresherEnabled || !this.useCustomRefresher || (this.usePageScroll && this.useCustomRefresher && this.pageScrollTop > 10) || (!(this.usePageScroll && this.useCustomRefresher) && checkOldScrollTop); + }, + // #endif + // 更新自定义下拉刷新view高度 + _updateCustomRefresherHeight() { + this._getNodeClientRect('.zp-custom-refresher-slot-view').then((res) => { + this.customRefresherHeight = res ? res[0].height : 0; + this.showCustomRefresher = this.customRefresherHeight > 0; + if (this.doRefreshAnimateAfter) { + this.doRefreshAnimateAfter = false; + this._doRefresherRefreshAnimate(); + } + }); + }, + // emit pullingDown事件 + _emitTouchmove(e) { + // #ifndef APP-NVUE + e.viewHeight = this.finalRefresherThreshold; + // #endif + e.rate = e.viewHeight > 0 ? e.pullingDistance / e.viewHeight : 0; + this.hasTouchmove && this.oldPullingDistance !== e.pullingDistance && this.$emit('refresherTouchmove', e); + this.oldPullingDistance = e.pullingDistance; + }, + // 清除refresherCompleteTimeout + _cleanRefresherCompleteTimeout() { + this.refresherCompleteTimeout = this._cleanTimeout(this.refresherCompleteTimeout); + // #ifdef APP-NVUE + this._nRefresherEnd(false); + // #endif + }, + // 清除refresherEndTimeout + _cleanRefresherEndTimeout() { + this.refresherEndTimeout = this._cleanTimeout(this.refresherEndTimeout); + }, + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/scroller.js b/uni_modules/z-paging/components/z-paging/js/modules/scroller.js new file mode 100644 index 0000000..b67f251 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/scroller.js @@ -0,0 +1,589 @@ +// [z-paging]scroll相关模块 +import u from '.././z-paging-utils' +import Enum from '.././z-paging-enum' + +// #ifdef APP-NVUE +const weexDom = weex.requireModule('dom'); +// #endif + +export default { + props: { + // 使用页面滚动,默认为否,当设置为是时则使用页面的滚动而非此组件内部的scroll-view的滚动,使用页面滚动时z-paging无需设置确定的高度且对于长列表展示性能更高,但配置会略微繁琐 + usePageScroll: { + type: Boolean, + default: u.gc('usePageScroll', false) + }, + // 是否可以滚动,使用内置scroll-view和nvue时有效,默认为是 + scrollable: { + type: Boolean, + default: u.gc('scrollable', true) + }, + // 控制是否出现滚动条,默认为是 + showScrollbar: { + type: Boolean, + default: u.gc('showScrollbar', true) + }, + // 是否允许横向滚动,默认为否 + scrollX: { + type: Boolean, + default: u.gc('scrollX', false) + }, + // iOS设备上滚动到顶部时是否允许回弹效果,默认为否。关闭回弹效果后可使滚动到顶部与下拉刷新更连贯,但是有吸顶view时滚动到顶部时可能出现抖动。 + scrollToTopBounceEnabled: { + type: Boolean, + default: u.gc('scrollToTopBounceEnabled', false) + }, + // iOS设备上滚动到底部时是否允许回弹效果,默认为是。 + scrollToBottomBounceEnabled: { + type: Boolean, + default: u.gc('scrollToBottomBounceEnabled', true) + }, + // 在设置滚动条位置时使用动画过渡,默认为否 + scrollWithAnimation: { + type: Boolean, + default: u.gc('scrollWithAnimation', false) + }, + // 值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素 + scrollIntoView: { + type: String, + default: u.gc('scrollIntoView', '') + }, + }, + data() { + return { + scrollTop: 0, + oldScrollTop: 0, + scrollLeft: 0, + oldScrollLeft: 0, + scrollViewStyle: {}, + scrollViewContainerStyle: {}, + scrollViewInStyle: {}, + pageScrollTop: -1, + scrollEnable: true, + privateScrollWithAnimation: -1, + cacheScrollNodeHeight: -1, + superContentHeight: 0, + lastScrollHeight: 0, + lastScrollDirection: '', + setContentHeightPending: false + } + }, + watch: { + oldScrollTop(newVal) { + !this.usePageScroll && this._scrollTopChange(newVal,false); + }, + pageScrollTop(newVal) { + this.usePageScroll && this._scrollTopChange(newVal,true); + }, + usePageScroll: { + handler(newVal) { + this.loaded && this.autoHeight && this._setAutoHeight(!newVal); + // #ifdef H5 + if (newVal) { + this.$nextTick(() => { + const mainScrollRef = this.$refs['zp-scroll-view'].$refs.main; + if (mainScrollRef) { + mainScrollRef.style = {}; + } + }) + } + // #endif + }, + immediate: true + }, + finalScrollTop(newVal) { + this.renderPropScrollTop = newVal < 6 ? 0 : 10; + } + }, + computed: { + finalScrollWithAnimation() { + if (this.privateScrollWithAnimation !== -1) { + return this.privateScrollWithAnimation === 1; + } + return this.scrollWithAnimation; + }, + finalScrollViewStyle() { + if (this.superContentZIndex != 1) { + this.scrollViewStyle['z-index'] = this.superContentZIndex; + this.scrollViewStyle['position'] = 'relative'; + } + return this.scrollViewStyle; + }, + 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: { + // 滚动到顶部,animate为是否展示滚动动画,默认为是 + scrollToTop(animate, checkReverse = true) { + // 如果是聊天记录模式并且列表倒置了,则滚动到顶部实际上是滚动到底部 + if (this.useChatRecordMode && checkReverse && !this.isChatRecordModeAndNotInversion) { + this.scrollToBottom(animate, false); + return; + } + this.$nextTick(() => { + this._scrollToTop(animate, false); + // #ifdef APP-NVUE + if (this.nvueFastScroll && animate) { + u.delay(() => { + this._scrollToTop(false, false); + }); + } + // #endif + }) + }, + // 滚动到底部,animate为是否展示滚动动画,默认为是 + scrollToBottom(animate, checkReverse = true) { + // 如果是聊天记录模式并且列表倒置了,则滚动到底部实际上是滚动到顶部 + if (this.useChatRecordMode && checkReverse && !this.isChatRecordModeAndNotInversion) { + this.scrollToTop(animate, false); + return; + } + this.$nextTick(() => { + this._scrollToBottom(animate); + // #ifdef APP-NVUE + if (this.nvueFastScroll && animate) { + u.delay(() => { + this._scrollToBottom(false); + }); + } + // #endif + }) + }, + // 滚动到指定view(vue中有效)。sel为需要滚动的view的id值,不包含"#";offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 + scrollIntoViewById(sel, offset, animate) { + this._scrollIntoView(sel, offset, animate); + }, + // 滚动到指定view(vue中有效)。nodeTop为需要滚动的view的top值(通过uni.createSelectorQuery()获取);offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 + scrollIntoViewByNodeTop(nodeTop, offset, animate) { + this.scrollTop = this.oldScrollTop; + this.$nextTick(() => { + this._scrollIntoViewByNodeTop(nodeTop, offset, animate); + }) + }, + // y轴滚动到指定位置(vue中有效)。y为与顶部的距离,单位为px;offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 + scrollToY(y, offset, animate) { + this.scrollTop = this.oldScrollTop; + this.$nextTick(() => { + this._scrollToY(y, offset, animate); + }) + }, + // x轴滚动到指定位置(非页面滚动且在vue中有效)。x为与左侧的距离,单位为px;offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 + scrollToX(x, offset, animate) { + this.scrollLeft = this.oldScrollLeft; + this.$nextTick(() => { + this._scrollToX(x, offset, animate); + }) + }, + // 滚动到指定view(nvue中和虚拟列表中有效)。index为需要滚动的view的index(第几个,从0开始);offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 + scrollIntoViewByIndex(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) { + this._scrollIntoView(view, offset, animate); + }, + // 当使用页面滚动并且自定义下拉刷新时,请在页面的onPageScroll中调用此方法,告知z-paging当前的pageScrollTop,否则会导致在任意位置都可以下拉刷新 + updatePageScrollTop(value) { + this.pageScrollTop = value; + }, + // 当使用页面滚动并且设置了slot="top"时,默认初次加载会自动获取其高度,并使内部容器下移,当slot="top"的view高度动态改变时,在其高度需要更新时调用此方法 + updatePageScrollTopHeight() { + this._updatePageScrollTopOrBottomHeight('top'); + }, + // 当使用页面滚动并且设置了slot="bottom"时,默认初次加载会自动获取其高度,并使内部容器下移,当slot="bottom"的view高度动态改变时,在其高度需要更新时调用此方法 + updatePageScrollBottomHeight() { + this._updatePageScrollTopOrBottomHeight('bottom'); + }, + // 更新slot="left"和slot="right"宽度,当slot="left"或slot="right"宽度动态改变时调用 + updateLeftAndRightWidth() { + if (!this.finalIsOldWebView) return; + this.$nextTick(() => this._updateLeftAndRightWidth(this.scrollViewContainerStyle, 'zp-page')); + }, + // 更新z-paging内置scroll-view的scrollTop + updateScrollViewScrollTop(scrollTop, animate = true) { + this._updatePrivateScrollWithAnimation(animate); + this.scrollTop = this.oldScrollTop; + this.$nextTick(() => { + this.scrollTop = scrollTop; + this.oldScrollTop = this.scrollTop; + }); + }, + + // 当滚动到顶部时 + _onScrollToUpper() { + this._emitScrollEvent('scrolltoupper'); + this.$emit('scrollTopChange', 0); + this.$nextTick(() => { + this.oldScrollTop = 0; + }) + }, + // 当滚动到底部时 + _onScrollToLower(e) { + (!e.detail || !e.detail.direction || e.detail.direction === 'bottom') + && this.toBottomLoadingMoreEnabled + && this._onLoadingMore(this.useChatRecordMode ? 'click' : 'toBottom'); + }, + // 滚动到顶部 + _scrollToTop(animate = true, isPrivate = true) { + // #ifdef APP-NVUE + // 在nvue中需要通过weex.scrollToElement滚动到顶部,此时在顶部插入了一个view,使得滚动到这个view位置 + const el = this.$refs['zp-n-list-top-tag']; + if (this.usePageScroll) { + this._getNodeClientRect('zp-page-scroll-top', false).then(node => { + const nodeHeight = node ? node[0].height : 0; + weexDom.scrollToElement(el, { + offset: -nodeHeight, + animated: animate + }); + }); + } else { + if (!this.isIos && this.nvueListIs === 'scroller') { + this._getNodeClientRect('zp-n-refresh-container', false).then(node => { + const nodeHeight = node ? node[0].height : 0; + weexDom.scrollToElement(el, { + offset: -nodeHeight, + animated: animate + }); + }); + } else { + weexDom.scrollToElement(el, { + offset: 0, + animated: animate + }); + } + } + return; + // #endif + if (this.usePageScroll) { + this.$nextTick(() => { + uni.pageScrollTo({ + scrollTop: 0, + duration: animate ? 100 : 0, + }); + }); + return; + } + this._updatePrivateScrollWithAnimation(animate); + this.scrollTop = this.oldScrollTop; + this.$nextTick(() => { + this.scrollTop = 0; + this.oldScrollTop = this.scrollTop; + }); + }, + // 滚动到底部 + async _scrollToBottom(animate = true) { + // #ifdef APP-NVUE + // 在nvue中需要通过weex.scrollToElement滚动到顶部,此时在底部插入了一个view,使得滚动到这个view位置 + const el = this.$refs['zp-n-list-bottom-tag']; + if (el) { + weexDom.scrollToElement(el, { + offset: 0, + animated: animate + }); + } else { + u.consoleErr('滚动到底部失败,因为您设置了hideNvueBottomTag为true'); + } + return; + // #endif + if (this.usePageScroll) { + this.$nextTick(() => { + uni.pageScrollTo({ + scrollTop: Number.MAX_VALUE, + duration: animate ? 100 : 0, + }); + }); + return; + } + try { + 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; + const scrollViewH = scrollViewNode ? scrollViewNode[0].height : 0; + if (pagingContainerH > scrollViewH) { + this.scrollTop = this.oldScrollTop; + this.$nextTick(() => { + this.scrollTop = pagingContainerH - scrollViewH + this.virtualPlaceholderTopHeight; + this.oldScrollTop = this.scrollTop; + }); + } + } catch (e) {} + }, + // 滚动到指定view + _scrollIntoView(sel, offset = 0, animate = false, finishCallback) { + try { + this.scrollTop = this.oldScrollTop; + this.$nextTick(() => { + // #ifdef APP-NVUE + const refs = this.$parent.$refs; + if (!refs) return; + const dataType = Object.prototype.toString.call(sel); + let el = null; + if (dataType === '[object Number]') { + const els = refs[`z-paging-${sel}`]; + el = els ? els[0] : null; + } else if (dataType === '[object Array]') { + el = sel[0]; + } else { + el = sel; + } + if (el) { + weexDom.scrollToElement(el, { + offset: -offset, + animated: animate + }); + } else { + u.consoleErr('在nvue中滚动到指定位置,cell必须设置 :ref="`z-paging-${index}`"'); + } + return; + // #endif + // 获取指定view的节点信息 + this._getNodeClientRect('#' + sel.replace('#', ''), false).then((node) => { + if (node) { + // 获取zp-scroll-view-container的节点信息 + this._getNodeClientRect('.zp-scroll-view-container').then((svContainerNode) => { + if (svContainerNode) { + // 滚动的top为指定view的top减zp-scroll-view-container的top,因为指定view的top是相对于整个窗口的,需要考虑相对的位置关系 + this._scrollIntoViewByNodeTop(node[0].top - svContainerNode[0].top, offset, animate); + finishCallback && finishCallback(); + } + }); + } else { + u.consoleErr(`无法获取${sel}的节点信息,请检查!`); + } + }); + }); + } catch (e) {} + }, + // 通过nodeTop滚动到指定view + _scrollIntoViewByNodeTop(nodeTop, offset = 0, animate = false) { + // 如果是聊天记录模式并且列表倒置了,此时nodeTop需要等于scroll-view高度 - nodeTop + if (this.isChatRecordModeAndInversion) { + this._getNodeClientRect('.zp-scroll-view').then(sNode => { + if (sNode) { + this._scrollToY(sNode[0].height - nodeTop, offset, animate, true); + } + }) + } else { + this._scrollToY(nodeTop, offset, animate, true); + } + }, + // y轴滚动到指定位置 + _scrollToY(y, offset = 0, animate = false, addScrollTop = false) { + this._updatePrivateScrollWithAnimation(animate); + u.delay(() => { + if (this.usePageScroll) { + if (addScrollTop && this.pageScrollTop !== -1) { + y += this.pageScrollTop; + } + const scrollTop = y - offset; + uni.pageScrollTo({ + scrollTop, + duration: animate ? 100 : 0 + }); + } else { + if (addScrollTop) { + y += this.oldScrollTop; + } + this.scrollTop = y - offset; + } + }, 10) + }, + // x轴滚动到指定位置 + _scrollToX(x, offset = 0, animate = false) { + this._updatePrivateScrollWithAnimation(animate); + u.delay(() => { + if (!this.usePageScroll) { + this.scrollLeft = x - offset; + } else { + u.consoleErr('使用页面滚动时不支持scrollToX'); + } + }, 10) + }, + // scroll-view滚动中 + _scroll(e) { + this.$emit('scroll', e); + const { scrollTop, scrollLeft, scrollHeight } = e.detail; + + if (this.watchScrollDirectionChange) { + // 计算scroll-view滚动方向,正常情况下上次滚动的oldScrollTop大于当前scrollTop即为向上滚动,反之为向下滚动 + let direction = this.oldScrollTop > scrollTop ? 'top' : 'bottom'; + // 此处为解决在iOS中,滚动到顶部因bounce的影响回弹导致滚动方向为bottom的问题:如果滚动到顶部了并且scrollTop小于顶部滚动区域,则强制设置direction为top + // 此外发现在h5中下拉刷新时direction有概率被判断为bottom(oldScrollTop > scrollTop),因为下拉刷新时会禁止scroll-view滚动,则以此为依据强制设置direction为top + if (scrollTop <= 0 || !this.scrollEnable) { + direction = 'top'; + } + // 此处为解决在iOS中,滚动到底部因bounce的影响回弹导致滚动方向为top的问题:如果滚动到底部了并且scrollTop超过底部滚动区域,则强制设置direction为bottom + if (scrollTop > this.lastScrollHeight - this.scrollViewHeight - 1 && this.scrollEnable) { + direction = 'bottom'; + } + // emit 列表滚动方向改变事件 + if (direction !== this.lastScrollDirection) { + this.$emit('scrollDirectionChange', direction); + this.lastScrollDirection = direction; + } + // 当scrollHeight变化时,需要延迟100毫秒设置lastScrollHeight,如果直接根据scrollHeight的话,因为此时数据还未改变,会导致滚动方向从bottom变为top + if (this.lastScrollHeight !== scrollHeight && !this.setContentHeightPending) { + // 因此处会多次触发,因此加个标识确保在延时期间仅触发一次 + this.setContentHeightPending = true; + u.delay(() => { + this.lastScrollHeight = scrollHeight; + this.setContentHeightPending = false; + }) + } + } + + // #ifndef APP-NVUE + this.finalUseVirtualList && this._updateVirtualScroll(scrollTop, this.oldScrollTop - scrollTop); + // #endif + this.oldScrollTop = scrollTop; + this.oldScrollLeft = scrollLeft; + // 滚动区域内容的总高度 - 当前滚动的scrollTop = 当前滚动区域的顶部与内容底部的距离 + const scrollDiff = e.detail.scrollHeight - this.oldScrollTop; + // 在非ios平台滚动中,再次验证一下是否滚动到了底部。因为在一些安卓设备中,有概率滚动到底部不触发@scrolltolower事件,因此添加双重检测逻辑 + !this.isIos && this._checkScrolledToBottom(scrollDiff); + }, + // emit scrolltolower/scrolltoupper事件 + _emitScrollEvent(type) { + const reversedType = type === 'scrolltolower' ? 'scrolltoupper' : 'scrolltolower'; + const eventType = this.useChatRecordMode && !this.isChatRecordModeAndNotInversion ? reversedType : type; + this.$emit(eventType); + }, + // 更新内置的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) { + // #ifndef APP-NVUE + this.$nextTick(() => { + this._checkScrollViewShouldFullHeight((scrollViewNode, pagingContainerNode) => { + this._preCheckShowNoMoreInside(totalData, scrollViewNode, pagingContainerNode) + }); + }) + // #endif + // #ifdef APP-NVUE + this._preCheckShowNoMoreInside(totalData) + // #endif + } else { + this._preCheckShowNoMoreInside(totalData) + } + }, + // 检测z-paging是否要全屏覆盖(当使用页面滚动并且不满全屏时,默认z-paging需要铺满全屏,避免数据过少时内部的empty-view无法正确展示) + async _checkScrollViewShouldFullHeight(callback) { + try { + const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view'); + const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container-content'); + if (!scrollViewNode || !pagingContainerNode) return; + const scrollViewHeight = pagingContainerNode[0].height; + const scrollViewTop = scrollViewNode[0].top; + if (this.isAddedData && scrollViewHeight + scrollViewTop <= this.windowHeight) { + this._setAutoHeight(true, scrollViewNode); + callback(scrollViewNode, pagingContainerNode); + } else { + this._setAutoHeight(false); + callback(null, null); + } + } catch (e) { + 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导致下拉刷新判断异常,因此判断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) { + this.wxsPageScrollTop = scrollTop; + } else if (!isPageScrollTop && this.wxsScrollTop !== scrollTop) { + this.wxsScrollTop = scrollTop; + if (scrollTop > 6) { + this.scrollEnable = true; + } + } + }, + // 更新使用页面滚动时slot="top"或"bottom"插入view的高度 + _updatePageScrollTopOrBottomHeight(type) { + // #ifndef APP-NVUE + if (!this.usePageScroll) return; + // #endif + this._doCheckScrollViewShouldFullHeight(this.realTotalData); + const node = `.zp-page-${type}`; + const marginText = `margin${type.slice(0,1).toUpperCase() + type.slice(1)}`; + // 是否设置底部安全区域间距,仅当开启底部安全区域并且slot=bottom不存在的时候才处理,如果slot=bottom存在则直接在bottom底部插入占位view + // 如果useSafeAreaPlaceholder为true,这里也不需要额外通过marginBottom设置底部安全区域了 + const safeAreaInsetBottomAdd = this.safeAreaInsetBottom && !this.zSlots.bottom && !this.useSafeAreaPlaceholder; + this.$nextTick(() => { + let delayTime = 0; + // #ifdef MP-BAIDU || APP-NVUE + delayTime = 50; + // #endif + u.delay(() => { + this._getNodeClientRect(node).then((res) => { + if (res) { + let pageScrollNodeHeight = res[0].height; + if (type === 'bottom') { + if (safeAreaInsetBottomAdd) { + pageScrollNodeHeight += this.safeAreaBottom; + } + } else { + this.cacheTopHeight = pageScrollNodeHeight; + } + this.$set(this.scrollViewStyle, marginText, `${pageScrollNodeHeight}px`); + } else if (safeAreaInsetBottomAdd) { + this.$set(this.scrollViewStyle, marginText, `${this.safeAreaBottom}px`); + } + }); + }, delayTime) + }) + }, + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/virtual-list.js b/uni_modules/z-paging/components/z-paging/js/modules/virtual-list.js new file mode 100644 index 0000000..999a283 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/virtual-list.js @@ -0,0 +1,539 @@ +// [z-paging]虚拟列表模块 +import u from '.././z-paging-utils' +import c from '.././z-paging-constant' +import Enum from '.././z-paging-enum' + +export default { + props: { + // 是否使用虚拟列表,默认为否 + useVirtualList: { + type: Boolean, + default: u.gc('useVirtualList', false) + }, + // 在使用虚拟列表时,是否使用兼容模式,默认为否 + useCompatibilityMode: { + type: Boolean, + default: u.gc('useCompatibilityMode', false) + }, + // 使用兼容模式时传递的附加数据 + extraData: { + type: Object, + default: u.gc('extraData', {}) + }, + // 是否在z-paging内部循环渲染列表(内置列表),默认为否。若use-virtual-list为true,则此项恒为true + useInnerList: { + type: Boolean, + default: u.gc('useInnerList', false) + }, + // 强制关闭inner-list,默认为false,如果为true将强制关闭innerList,适用于开启了虚拟列表后需要强制关闭inner-list的情况 + forceCloseInnerList: { + type: Boolean, + default: u.gc('forceCloseInnerList', false) + }, + // 内置列表cell的key名称,仅nvue有效,在nvue中开启use-inner-list时必须填此项 + cellKeyName: { + type: String, + default: u.gc('cellKeyName', '') + }, + // innerList样式 + innerListStyle: { + type: Object, + default: u.gc('innerListStyle', {}) + }, + // innerCell样式 + innerCellStyle: { + type: Object, + default: u.gc('innerCellStyle', {}) + }, + // 预加载的列表可视范围(列表高度)页数,默认为12,即预加载当前页及上下各12页的cell。此数值越大,则虚拟列表中加载的dom越多,内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题 + preloadPage: { + type: [Number, String], + default: u.gc('preloadPage', 12), + validator: (value) => { + if (value <= 0) u.consoleErr('preload-page必须大于0!'); + return value > 0; + } + }, + // 虚拟列表cell高度模式,默认为fixed,也就是每个cell高度完全相同,将以第一个cell高度为准进行计算。可选值【dynamic】,即代表高度是动态非固定的,【dynamic】性能低于【fixed】。 + cellHeightMode: { + 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], + default: u.gc('virtualListCol', 1) + }, + // 虚拟列表scroll取样帧率,默认为80,过低容易出现白屏问题,过高容易出现卡顿问题 + virtualScrollFps: { + type: [Number, String], + default: u.gc('virtualScrollFps', 80) + }, + // 虚拟列表cell id的前缀,适用于一个页面有多个虚拟列表的情况,用以区分不同虚拟列表cell的id,注意:请勿传数字或以数字开头的字符串。如设置为list1,则cell的id应为:list1-zp-id-${item.zp_index} + virtualCellIdPrefix: { + type: String, + default: u.gc('virtualCellIdPrefix', '') + }, + // 虚拟列表是否使用swiper-item包裹,默认为否,此属性为了解决vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若z-paging在swiper-item内存在无法获取slot插入的cell高度进而导致虚拟列表失败的问题 + // 仅vue3+(微信小程序或QQ小程序)+非内置列表写法虚拟列表有效,其他情况此属性设置任何值都无效,所以如果您在swiper-item内使用z-paging的非内置虚拟列表写法,将此属性设置为true即可 + virtualInSwiperSlot: { + type: Boolean, + default: false + }, + }, + data() { + return { + virtualListKey: u.getInstanceId(), + virtualCellHeight: 0, + virtualScrollTimeStamp: 0, + + virtualList: [], + virtualPlaceholderTopHeight: 0, + virtualPlaceholderBottomHeight: 0, + virtualTopRangeIndex: 0, + virtualBottomRangeIndex: 0, + lastVirtualTopRangeIndex: 0, + lastVirtualBottomRangeIndex: 0, + virtualItemInsertedCount: 0, + + virtualHeightCacheList: [], + + getCellHeightRetryCount: { + fixed: 0, + dynamic: 0 + }, + updateVirtualListFromDataChange: false + } + }, + watch: { + // 监听总数据的改变,刷新虚拟列表布局 + realTotalData() { + this.updateVirtualListRender(); + }, + // 监听虚拟列表渲染数组的改变并emit + virtualList(newVal){ + this.$emit('update:virtualList', newVal); + this.$emit('virtualListChange', newVal); + }, + // 监听虚拟列表顶部占位高度改变并emit + virtualPlaceholderTopHeight(newVal) { + this.$emit('virtualTopHeightChange', newVal); + } + }, + computed: { + virtualCellIndexKey() { + return c.listCellIndexKey; + }, + finalUseVirtualList() { + if (this.useVirtualList && this.usePageScroll){ + u.consoleErr('使用页面滚动时,开启虚拟列表无效!'); + } + return this.useVirtualList && !this.usePageScroll; + }, + finalUseInnerList() { + return this.useInnerList || (this.finalUseVirtualList && !this.forceCloseInnerList); + }, + finalCellKeyName() { + // #ifdef APP-NVUE + if (this.finalUseVirtualList && !this.cellKeyName.length){ + u.consoleErr('在nvue中开启use-virtual-list必须设置cell-key-name,否则将可能导致列表渲染错误!'); + } + // #endif + return this.cellKeyName; + }, + finalVirtualPageHeight(){ + return this.scrollViewHeight > 0 ? this.scrollViewHeight : this.windowHeight; + }, + finalFixedCellHeight() { + return u.convertToPx(this.fixedCellHeight); + }, + fianlVirtualCellIdPrefix() { + const prefix = this.virtualCellIdPrefix ? this.virtualCellIdPrefix + '-' : ''; + return prefix + 'zp-id'; + }, + finalPlaceholderTopHeightStyle() { + // #ifdef VUE2 + return { transform: this.virtualPlaceholderTopHeight > 0 ? `translateY(${this.virtualPlaceholderTopHeight}px)` : 'none' }; + // #endif + return {}; + }, + virtualRangePageHeight(){ + return this.finalVirtualPageHeight * this.preloadPage; + }, + virtualScrollDisTimeStamp() { + return 1000 / this.virtualScrollFps; + } + }, + methods: { + // 在使用动态高度虚拟列表时,若在列表数组中需要插入某个item,需要调用此方法;item:需要插入的item,index:插入的cell位置,若index为2,则插入的item在原list的index=1之后,index从0开始 + doInsertVirtualListItem(item, index) { + if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return; + this.realTotalData.splice(index, 0, item); + // #ifdef VUE3 + this.realTotalData = [...this.realTotalData]; + // #endif + this.virtualItemInsertedCount ++; + if (!item || Object.prototype.toString.call(item) !== '[object Object]') { + item = { item }; + } + const cellIndexKey = this.virtualCellIndexKey; + item[cellIndexKey] = `custom-${this.virtualItemInsertedCount}`; + item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`; + this.$nextTick(async () => { + let retryCount = 0; + while (retryCount <= 10) { + await u.wait(c.delayTime); + + const cellNode = await this._getVirtualCellNodeByIndex(item[cellIndexKey]); + // 如果获取当前cell的节点信息失败,则重试(不超过10次) + if (!cellNode) { + retryCount ++; + continue; + } + + const currentHeight = cellNode ? cellNode[0].height : 0; + const lastHeightCache = this.virtualHeightCacheList[index - 1]; + const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0; + // 在缓存的cell高度数组中,插入此cell高度信息 + this.virtualHeightCacheList.splice(index, 0, { + height: currentHeight, + lastTotalHeight, + totalHeight: lastTotalHeight + currentHeight + }); + + // 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要加上当前cell的高度 + for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) { + const thisNode = this.virtualHeightCacheList[i]; + thisNode.lastTotalHeight += currentHeight; + thisNode.totalHeight += currentHeight; + } + + this._updateVirtualScroll(this.oldScrollTop); + break; + } + }) + }, + // 在使用动态高度虚拟列表时,手动更新指定cell的缓存高度(当cell高度在初始化之后再次改变后调用);index:需要更新的cell在列表中的位置,从0开始 + didUpdateVirtualListCell(index) { + if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return; + const currentNode = this.virtualHeightCacheList[index]; + this.$nextTick(() => { + this._getVirtualCellNodeByIndex(index).then(cellNode => { + // 更新当前cell的高度 + const cellNodeHeight = cellNode ? cellNode[0].height : 0; + const heightDis = cellNodeHeight - currentNode.height; + currentNode.height = cellNodeHeight; + currentNode.totalHeight = currentNode.lastTotalHeight + cellNodeHeight; + + // 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要加上当前cell变化的高度 + for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) { + const thisNode = this.virtualHeightCacheList[i]; + thisNode.totalHeight += heightDis; + thisNode.lastTotalHeight += heightDis; + } + }); + }) + }, + // 在使用动态高度虚拟列表时,若删除了列表数组中的某个item,需要调用此方法以更新高度缓存数组;index:删除的cell在列表中的位置,从0开始 + didDeleteVirtualListCell(index) { + if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return; + const currentNode = this.virtualHeightCacheList[index]; + // 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要减去当前cell的高度 + for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) { + const thisNode = this.virtualHeightCacheList[i]; + thisNode.totalHeight -= currentNode.height; + thisNode.lastTotalHeight -= currentNode.height; + } + // 将当前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 + }, + // cellHeightMode为fixed时获取第一个cell高度 + _updateFixedCellHeight() { + if (!this.finalFixedCellHeight) { + this.$nextTick(() => { + u.delay(() => { + this._getVirtualCellNodeByIndex(0).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') { + const dataFromTop = dataFrom === 'top'; + const heightCacheList = this.virtualHeightCacheList; + const currentCacheList = dataFromTop ? [] : heightCacheList; + let listTotalHeight = 0; + this.$nextTick(() => { + u.delay(async () => { + for (let i = 0; i < list.length; i++) { + const cellNode = await this._getVirtualCellNodeByIndex(list[i][this.virtualCellIndexKey]); + const currentHeight = cellNode ? cellNode[0].height : 0; + if (!cellNode) { + if (this.getCellHeightRetryCount.dynamic <= 10) { + heightCacheList.splice(heightCacheList.length - i, i); + this.getCellHeightRetryCount.dynamic ++; + // 如果获取当前cell的节点信息失败,则重试(不超过10次) + this._updateDynamicCellHeight(list, dataFrom); + } + return; + } + const lastHeightCache = currentCacheList.length ? currentCacheList.slice(-1)[0] : null; + const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0; + // 缓存当前cell的高度信息:height-当前cell高度;lastTotalHeight-前面所有cell的高度总和;totalHeight-包含当前cell的所有高度总和 + currentCacheList.push({ + height: currentHeight, + lastTotalHeight, + totalHeight: lastTotalHeight + currentHeight + }); + if (dataFromTop) { + listTotalHeight += currentHeight; + } + } + // 如果数据是从顶部拼接的 + if (dataFromTop && list.length) { + for (let i = 0; i < heightCacheList.length; i++) { + // 更新之前所有项的缓存高度,需要加上此次插入的所有cell高度之和(因为是从顶部插入的cell) + const heightCacheItem = heightCacheList[i]; + heightCacheItem.lastTotalHeight += listTotalHeight; + heightCacheItem.totalHeight += listTotalHeight; + } + this.virtualHeightCacheList = currentCacheList.concat(heightCacheList); + } + this._updateVirtualScroll(this.oldScrollTop); + }, c.delayTime, 'updateDynamicCellHeightDelay') + }) + }, + // 设置cellItem的index + _setCellIndex(list, dataFrom = 'bottom') { + let currentItemIndex = 0; + const cellIndexKey = this.virtualCellIndexKey; + dataFrom === 'bottom' && ([Enum.QueryFrom.Refresh, Enum.QueryFrom.Reload].indexOf(this.queryFrom) >= 0) && this._resetDynamicListState(); + if (this.totalData.length && this.queryFrom !== Enum.QueryFrom.Refresh) { + if (dataFrom === 'bottom') { + currentItemIndex = this.realTotalData.length; + const lastItem = this.realTotalData.length ? this.realTotalData.slice(-1)[0] : null; + if (lastItem && lastItem[cellIndexKey] !== undefined) { + currentItemIndex = lastItem[cellIndexKey] + 1; + } + } else if (dataFrom === 'top') { + const firstItem = this.realTotalData.length ? this.realTotalData[0] : null; + if (firstItem && firstItem[cellIndexKey] !== undefined) { + currentItemIndex = firstItem[cellIndexKey] - list.length; + } + } + } else { + this._resetDynamicListState(); + } + for (let i = 0; i < list.length; i++) { + let item = list[i]; + 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; + } + this.getCellHeightRetryCount.dynamic = 0; + this.cellHeightMode === Enum.CellHeightMode.Dynamic && this._updateDynamicCellHeight(list, dataFrom); + }, + // 更新scroll滚动(虚拟列表滚动时触发) + _updateVirtualScroll(scrollTop, scrollDiff = 0) { + const currentTimeStamp = u.getTime(); + scrollTop === 0 && this._resetTopRange(); + if (scrollTop !== 0 && this.virtualScrollTimeStamp && currentTimeStamp - this.virtualScrollTimeStamp <= this.virtualScrollDisTimeStamp) { + return; + } + this.virtualScrollTimeStamp = currentTimeStamp; + + let scrollIndex = 0; + const cellHeightMode = this.cellHeightMode; + if (cellHeightMode === Enum.CellHeightMode.Fixed) { + // 如果是固定高度的虚拟列表 + // 计算当前滚动到的cell的index = scrollTop / 虚拟列表cell的固定高度 + scrollIndex = parseInt(scrollTop / this.virtualCellHeight) || 0; + // 更新顶部和底部占位view的高度(为兼容考虑,顶部采用transformY的方式占位) + this._updateFixedTopRangeIndex(scrollIndex); + this._updateFixedBottomRangeIndex(scrollIndex); + } else if(cellHeightMode === Enum.CellHeightMode.Dynamic) { + // 如果是不固定高度的虚拟列表 + // 当前滚动的方向 + const scrollDirection = scrollDiff > 0 ? 'top' : 'bottom'; + // 视图区域的高度 + const rangePageHeight = this.virtualRangePageHeight; + // 顶部视图区域外的高度(顶部不需要渲染而是需要占位部分的高度) + const topRangePageOffset = scrollTop - rangePageHeight; + // 底部视图区域外的高度(底部不需要渲染而是需要占位部分的高度) + const bottomRangePageOffset = scrollTop + this.finalVirtualPageHeight + rangePageHeight; + + let virtualBottomRangeIndex = 0; + let virtualPlaceholderBottomHeight = 0; + let reachedLimitBottom = false; + const heightCacheList = this.virtualHeightCacheList; + const lastHeightCache = !!heightCacheList ? heightCacheList.slice(-1)[0] : null; + + let startTopRangeIndex = this.virtualTopRangeIndex; + // 如果是向底部滚动(顶部占位的高度不断增大,顶部的实际渲染cell数量不断减少) + if (scrollDirection === 'bottom') { + // 从顶部视图边缘的cell的位置开始向后查找 + for (let i = startTopRangeIndex; i < heightCacheList.length; i++){ + const heightCacheItem = heightCacheList[i]; + // 如果查找到某个cell对应的totalHeight大于顶部视图区域外的高度,则此cell为顶部视图边缘的cell + if (heightCacheItem && heightCacheItem.totalHeight > topRangePageOffset) { + // 记录顶部视图边缘cell的index并更新顶部占位区域的高度并停止继续查找 + this.virtualTopRangeIndex = i; + this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight; + break; + } + } + } else { + // 如果是向顶部滚动(顶部占位的高度不断减少,顶部的实际渲染cell数量不断增加) + let topRangeMatched = false; + // 从顶部视图边缘的cell的位置开始向前查找 + for (let i = startTopRangeIndex; i >= 0; i--){ + const heightCacheItem = heightCacheList[i]; + // 如果查找到某个cell对应的totalHeight小于顶部视图区域外的高度,则此cell为顶部视图边缘的cell + if (heightCacheItem && heightCacheItem.totalHeight < topRangePageOffset) { + // 记录顶部视图边缘cell的index并更新顶部占位区域的高度并停止继续查找 + this.virtualTopRangeIndex = i; + this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight; + topRangeMatched = true; + break; + } + } + // 如果查找不到,则认为顶部占位高度为0了,顶部cell不需要继续复用,重置topRangeIndex和placeholderTopHeight + !topRangeMatched && this._resetTopRange(); + } + // 从顶部视图边缘的cell的位置开始向后查找 + for (let i = this.virtualTopRangeIndex; i < heightCacheList.length; i++){ + const heightCacheItem = heightCacheList[i]; + // 如果查找到某个cell对应的totalHeight大于底部视图区域外的高度,则此cell为底部视图边缘的cell + if (heightCacheItem && heightCacheItem.totalHeight > bottomRangePageOffset) { + // 记录底部视图边缘cell的index并更新底部占位区域的高度并停止继续查找 + virtualBottomRangeIndex = i; + virtualPlaceholderBottomHeight = lastHeightCache.totalHeight - heightCacheItem.totalHeight; + reachedLimitBottom = true; + break; + } + } + if (!reachedLimitBottom || this.virtualBottomRangeIndex === 0) { + this.virtualBottomRangeIndex = this.realTotalData.length ? this.realTotalData.length - 1 : this.pageSize; + this.virtualPlaceholderBottomHeight = 0; + } else { + this.virtualBottomRangeIndex = virtualBottomRangeIndex; + this.virtualPlaceholderBottomHeight = virtualPlaceholderBottomHeight; + } + this._updateVirtualList(); + } + }, + // 更新fixedCell模式下topRangeIndex&placeholderTopHeight + _updateFixedTopRangeIndex(scrollIndex) { + let virtualTopRangeIndex = this.virtualCellHeight === 0 ? 0 : scrollIndex - (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * this.preloadPage; + virtualTopRangeIndex *= this.virtualListCol; + virtualTopRangeIndex = Math.max(0, virtualTopRangeIndex); + this.virtualTopRangeIndex = virtualTopRangeIndex; + this.virtualPlaceholderTopHeight = (virtualTopRangeIndex / this.virtualListCol) * this.virtualCellHeight; + }, + // 更新fixedCell模式下bottomRangeIndex&placeholderBottomHeight + _updateFixedBottomRangeIndex(scrollIndex) { + let virtualBottomRangeIndex = this.virtualCellHeight === 0 ? this.pageSize : scrollIndex + (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * (this.preloadPage + 1); + virtualBottomRangeIndex *= this.virtualListCol; + virtualBottomRangeIndex = Math.min(this.realTotalData.length, virtualBottomRangeIndex); + this.virtualBottomRangeIndex = virtualBottomRangeIndex; + this.virtualPlaceholderBottomHeight = (this.realTotalData.length - virtualBottomRangeIndex) * this.virtualCellHeight / this.virtualListCol; + this._updateVirtualList(); + }, + // 更新virtualList + _updateVirtualList() { + const shouldUpdateList = this.updateVirtualListFromDataChange || (this.lastVirtualTopRangeIndex !== this.virtualTopRangeIndex || this.lastVirtualBottomRangeIndex !== this.virtualBottomRangeIndex); + if (shouldUpdateList) { + this.updateVirtualListFromDataChange = false; + this.lastVirtualTopRangeIndex = this.virtualTopRangeIndex; + this.lastVirtualBottomRangeIndex = this.virtualBottomRangeIndex; + this.virtualList = this.realTotalData.slice(this.virtualTopRangeIndex, this.virtualBottomRangeIndex + 1); + } + }, + // 重置动态cell模式下的高度缓存数据、虚拟列表和滚动状态 + _resetDynamicListState(resetVirtualList = false) { + this.virtualHeightCacheList = []; + if (resetVirtualList) { + this.virtualList = []; + } + this.virtualTopRangeIndex = 0; + this.virtualPlaceholderTopHeight = 0; + }, + // 重置topRangeIndex和placeholderTopHeight + _resetTopRange() { + this.virtualTopRangeIndex = 0; + this.virtualPlaceholderTopHeight = 0; + this._updateVirtualList(); + }, + // 检测虚拟列表当前滚动位置,如发现滚动位置不正确则重新计算虚拟列表相关参数(为解决在App中可能出现的长时间进入后台后打开App白屏的问题) + _checkVirtualListScroll() { + if (this.finalUseVirtualList) { + this.$nextTick(() => { + this._getNodeClientRect('.zp-paging-touch-view').then(node => { + const currentTop = node ? node[0].top : 0; + if (!node || (currentTop === this.pagingOrgTop && this.virtualPlaceholderTopHeight !== 0)) { + this._updateVirtualScroll(0); + } + }); + }) + } + }, + // 获取对应index的虚拟列表cell节点信息 + _getVirtualCellNodeByIndex(index) { + let inDom = this.finalUseInnerList; + // 在vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若z-paging在swiper-item内存在无法获取slot插入的cell高度的问题 + // 通过uni.createSelectorQuery().in(this.$parent)来解决此问题 + // #ifdef VUE3 + // #ifdef MP-WEIXIN || MP-QQ + if (this.forceCloseInnerList && this.virtualInSwiperSlot) { + inDom = this.$parent; + } + // #endif + // #endif + return this._getNodeClientRect(`#${this.fianlVirtualCellIdPrefix}-${index}`, inDom); + }, + // 处理使用内置列表时点击了cell事件 + _innerCellClick(item, index) { + this.$emit('innerCellClick', item, index); + } + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-constant.js b/uni_modules/z-paging/components/z-paging/js/z-paging-constant.js new file mode 100644 index 0000000..4f66137 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-constant.js @@ -0,0 +1,19 @@ +// [z-paging]常量 + +export default { + // 当前版本号 + version: '2.8.7', + // 延迟操作的通用时间 + delayTime: 100, + // 请求失败时候全局emit使用的key + errorUpdateKey: 'z-paging-error-emit', + // 全局emit complete的key + completeUpdateKey: 'z-paging-complete-emit', + // z-paging缓存的前缀key + cachePrefixKey: 'z-paging-cache', + + // 虚拟列表中列表index的key + listCellIndexKey: 'zp_index', + // 虚拟列表中列表的唯一key + listCellIndexUniqueKey: 'zp_unique_index' +} diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-enum.js b/uni_modules/z-paging/components/z-paging/js/z-paging-enum.js new file mode 100644 index 0000000..d24b688 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-enum.js @@ -0,0 +1,45 @@ +// [z-paging]枚举 + +export default { + // 当前加载类型 refresher:下拉刷新 load-more:上拉加载更多 + LoadingType: { + Refresher: 'refresher', + LoadMore: 'load-more' + }, + // 下拉刷新状态 default:默认状态 release-to-refresh:松手立即刷新 loading:刷新中 complete:刷新结束 go-f2:松手进入二楼 + Refresher: { + Default: 'default', + ReleaseToRefresh: 'release-to-refresh', + Loading: 'loading', + Complete: 'complete', + GoF2: 'go-f2' + }, + // 底部加载更多状态 default:默认状态 loading:加载中 no-more:没有更多数据 fail:加载失败 + More: { + Default: 'default', + Loading: 'loading', + NoMore: 'no-more', + Fail: 'fail' + }, + // @query触发来源 user-pull-down:用户主动下拉刷新 reload:通过reload触发 refresh:通过refresh触发 load-more:通过滚动到底部加载更多或点击底部加载更多触发 + QueryFrom: { + UserPullDown: 'user-pull-down', + Reload: 'reload', + Refresh: 'refresh', + LoadMore: 'load-more' + }, + // 虚拟列表cell高度模式 + CellHeightMode: { + // 固定高度 + Fixed: 'fixed', + // 动态高度 + Dynamic: 'dynamic' + }, + // 列表缓存模式 + CacheMode: { + // 默认模式,只会缓存一次 + Default: 'default', + // 总是缓存,每次列表刷新(下拉刷新、调用reload等)都会更新缓存 + Always: 'always' + } +} \ No newline at end of file diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-interceptor.js b/uni_modules/z-paging/components/z-paging/js/z-paging-interceptor.js new file mode 100644 index 0000000..147291a --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-interceptor.js @@ -0,0 +1,97 @@ +// [z-paging]拦截器 + +const queryKey = 'Query'; +const fetchParamsKey = 'FetchParams'; +const fetchResultKey = 'FetchResult'; +const language2LocalKey = 'Language2Local'; + +// 拦截&处理@query事件 +function handleQuery(callback) { + _addHandleByKey(queryKey, callback); + return this; +} + +// 拦截&处理@query事件(私有,请勿调用) +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) { + _addHandleByKey(language2LocalKey, callback); + return this; +} + +// 拦截&处理系统language转i18n local(私有,请勿调用) +function _handleLanguage2Local(language, local) { + const callback = _getHandleByKey(language2LocalKey); + return callback ? callback(language, local) : local; +} + +// 获取当前app对象 +function _getApp(){ + // #ifndef APP-NVUE + return getApp(); + // #endif + // #ifdef APP-NVUE + return getApp({ allowDefault: true }); + // #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 +}; diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-main.js b/uni_modules/z-paging/components/z-paging/js/z-paging-main.js new file mode 100644 index 0000000..5b5e0c1 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-main.js @@ -0,0 +1,537 @@ +// [z-paging]核心js + +import zStatic from './z-paging-static' +import c from './z-paging-constant' +import u from './z-paging-utils' + +import zPagingRefresh from '../components/z-paging-refresh' +import zPagingLoadMore from '../components/z-paging-load-more' +import zPagingEmptyView from '../../z-paging-empty-view/z-paging-empty-view' + +// modules +import commonLayoutModule from './modules/common-layout' +import dataHandleModule from './modules/data-handle' +import i18nModule from './modules/i18n' +import nvueModule from './modules/nvue' +import emptyModule from './modules/empty' +import refresherModule from './modules/refresher' +import loadMoreModule from './modules/load-more' +import loadingModule from './modules/loading' +import chatRecordModerModule from './modules/chat-record-mode' +import scrollerModule from './modules/scroller' +import backToTopModule from './modules/back-to-top' +import virtualListModule from './modules/virtual-list' + +import Enum from './z-paging-enum' + +const systemInfo = u.getSystemInfoSync(); +export default { + name: "z-paging", + components: { + zPagingRefresh, + zPagingLoadMore, + zPagingEmptyView + }, + mixins: [ + commonLayoutModule, + dataHandleModule, + i18nModule, + nvueModule, + emptyModule, + refresherModule, + loadMoreModule, + loadingModule, + chatRecordModerModule, + scrollerModule, + backToTopModule, + virtualListModule + ], + data() { + return { + // --------------静态资源--------------- + base64BackToTop: zStatic.base64BackToTop, + + // -------------全局数据相关-------------- + // 当前加载类型 + loadingType: Enum.LoadingType.Refresher, + requestTimeStamp: 0, + wxsPropType: '', + renderPropScrollTop: -1, + checkScrolledToBottomTimeOut: null, + cacheTopHeight: -1, + statusBarHeight: systemInfo.statusBarHeight, + scrollViewHeight: 0, + pagingOrgTop: -1, + + // --------------状态&判断--------------- + insideOfPaging: -1, + isLoadFailed: false, + isIos: systemInfo.platform === 'ios', + disabledBounce: false, + fromCompleteEmit: false, + disabledCompleteEmit: false, + pageLaunched: false, + active: false, + + // ---------------wxs相关--------------- + wxsIsScrollTopInTopRange: true, + wxsScrollTop: 0, + wxsPageScrollTop: 0, + wxsOnPullingDown: false, + }; + }, + props: { + // 调用complete后延迟处理的时间,单位为毫秒,默认0毫秒,优先级高于minDelay + delay: { + type: [Number, String], + default: u.gc('delay', 0), + }, + // 触发@query后最小延迟处理的时间,单位为毫秒,默认0毫秒,优先级低于delay(假设设置为300毫秒,若分页请求时间小于300毫秒,则在调用complete后延迟[300毫秒-请求时长];若请求时长大于300毫秒,则不延迟),当show-refresher-when-reload为true或reload(true)时,其最小值为400 + minDelay: { + type: [Number, String], + default: u.gc('minDelay', 0), + }, + // 设置z-paging的style,部分平台(如微信小程序)无法直接修改组件的style,可使用此属性代替 + pagingStyle: { + type: Object, + default: u.gc('pagingStyle', {}), + }, + // 设置z-paging的class,优先级低于pagingStyle和height、width、maxWidth、bgColor + pagingClass: { + type: [String, Array, Object], + default: u.gc('pagingClass', ''), + }, + // z-paging的高度,优先级低于pagingStyle中设置的height;传字符串,如100px、100rpx、100% + height: { + type: String, + default: u.gc('height', '') + }, + // z-paging的宽度,优先级低于pagingStyle中设置的width;传字符串,如100px、100rpx、100% + width: { + 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, + default: u.gc('bgColor', '') + }, + // 设置z-paging的容器(插槽的父view)的style + pagingContentStyle: { + type: Object, + default: u.gc('pagingContentStyle', {}), + }, + // z-paging是否自动高度,若自动高度则会自动铺满屏幕 + autoHeight: { + type: Boolean, + default: u.gc('autoHeight', false) + }, + // z-paging是否自动高度时,附加的高度,注意添加单位px或rpx,若需要减少高度,则传负数 + autoHeightAddition: { + type: [Number, String], + default: u.gc('autoHeightAddition', '0px') + }, + // loading(下拉刷新、上拉加载更多)的主题样式,支持black,white,默认black + defaultThemeStyle: { + type: String, + default: u.gc('defaultThemeStyle', 'black') + }, + // z-paging是否使用fixed布局,若使用fixed布局,则z-paging的父view无需固定高度,z-paging高度默认为100%,默认为是(当使用内置scroll-view滚动时有效) + fixed: { + type: Boolean, + default: u.gc('fixed', true) + }, + // 是否开启底部安全区域适配 + safeAreaInsetBottom: { + type: Boolean, + default: u.gc('safeAreaInsetBottom', false) + }, + // 开启底部安全区域适配后,是否使用placeholder形式实现,默认为否。为否时滚动区域会自动避开底部安全区域,也就是所有滚动内容都不会挡住底部安全区域,若设置为是,则滚动时滚动内容会挡住底部安全区域,但是当滚动到底部时才会避开底部安全区域 + useSafeAreaPlaceholder: { + type: Boolean, + default: u.gc('useSafeAreaPlaceholder', false) + }, + // z-paging bottom的背景色,默认透明,传字符串,如"#ffffff" + bottomBgColor: { + type: String, + default: u.gc('bottomBgColor', '') + }, + // slot="top"的view的z-index,默认为99,仅使用页面滚动时有效 + topZIndex: { + type: Number, + default: u.gc('topZIndex', 99) + }, + // z-paging内容容器父view的z-index,默认为1 + superContentZIndex: { + type: Number, + default: u.gc('superContentZIndex', 1) + }, + // z-paging内容容器部分的z-index,默认为1 + contentZIndex: { + type: Number, + default: u.gc('contentZIndex', 1) + }, + // z-paging二楼的z-index,默认为100 + f2ZIndex: { + type: Number, + default: u.gc('f2ZIndex', 100) + }, + // 使用页面滚动时,是否在不满屏时自动填充满屏幕,默认为是 + autoFullHeight: { + type: Boolean, + default: u.gc('autoFullHeight', true) + }, + // 是否监听列表触摸方向改变,默认为否 + watchTouchDirectionChange: { + type: Boolean, + default: u.gc('watchTouchDirectionChange', false) + }, + // 是否监听列表滚动方向改变,默认为否 + watchScrollDirectionChange: { + type: Boolean, + default: u.gc('watchScrollDirectionChange', false) + }, + // 是否只使用基础布局,设置为true后将关闭mounted自动请求数据、关闭下拉刷新和滚动到底部加载更多,强制隐藏空数据图。默认为否 + layoutOnly: { + type: Boolean, + default: u.gc('layoutOnly', false) + }, + // z-paging中布局的单位,默认为rpx + unit: { + type: String, + default: u.gc('unit', 'rpx') + } + }, + created() { + // 组件创建时,检测是否开始加载状态 + if (this.createdReload && !this.isOnly && this.auto) { + this._startLoading(); + this.$nextTick(this._preReload); + } + }, + mounted() { + this.active = true; + this.wxsPropType = u.getTime().toString(); + this.renderJsIgnore; + if (!this.createdReload && !this.isOnly && this.auto) { + // 开始预加载 + u.delay(() => this.$nextTick(this._preReload), 0); + } + // 如果开启了列表缓存,在初始化的时候通过缓存数据填充列表数据 + this.finalUseCache && this._setListByLocalCache(); + let delay = 0; + // #ifdef H5 || MP + delay = c.delayTime; + // #endif + this.$nextTick(() => { + // 初始化systemInfo + this.systemInfo = u.getSystemInfoSync(); + // 初始化z-paging高度 + !this.usePageScroll && this.autoHeight && this._setAutoHeight(); + this.loaded = true; + u.delay(() => { + // 更新fixed模式下z-paging的布局,主要是更新windowTop、windowBottom + this.updateFixedLayout(); + // 更新缓存中z-paging整个内容容器高度 + this._updateCachedSuperContentHeight(); + // 更新z-paging中scroll-view高度 + this._updateScrollViewHeight(); + }); + }) + // 初始化页面滚动模式下slot="top"、slot="bottom"高度 + this.updatePageScrollTopHeight(); + this.updatePageScrollBottomHeight(); + // 初始化slot="left"、slot="right"宽度 + this.updateLeftAndRightWidth(); + if (this.finalRefresherEnabled && this.useCustomRefresher) { + this.$nextTick(() => { + this.isTouchmoving = true; + }) + } + if (!this.layoutOnly) { + // 监听uni.$emit中全局emit的complete error等事件 + this._onEmit(); + } + // #ifdef APP-NVUE + if (!this.isIos && !this.useChatRecordMode) { + this.nLoadingMoreFixedHeight = true; + } + // 在nvue中更新nvue下拉刷新view容器的宽度,而不是写死默认的750rpx,需要考虑列表宽度不是铺满屏幕的情况 + this._nUpdateRefresherWidth(); + // #endif + // #ifndef APP-PLUS + this.$nextTick(() => { + // 非app平台中,在通过获取css设置的底部安全区域占位view高度设置bottom距离后,更新页面滚动底部高度 + setTimeout(() => { + this._getCssSafeAreaInsetBottom(() => this.safeAreaInsetBottom && this.updatePageScrollBottomHeight()); + }, delay) + }) + // #endif + }, + destroyed() { + this._handleUnmounted(); + }, + // #ifdef VUE3 + unmounted() { + this._handleUnmounted(); + }, + // #endif + watch: { + defaultThemeStyle: { + handler(newVal) { + if (newVal.length) { + this.finalRefresherDefaultStyle = newVal; + } + }, + immediate: true + }, + autoHeight(newVal) { + this.loaded && !this.usePageScroll && this._setAutoHeight(newVal); + }, + autoHeightAddition(newVal) { + this.loaded && !this.usePageScroll && this.autoHeight && this._setAutoHeight(newVal); + }, + }, + computed: { + // 当前z-paging的内置样式 + finalPagingStyle() { + const pagingStyle = { ...this.pagingStyle }; + if (!this.systemInfo) return pagingStyle; + const { windowTop, windowBottom } = this; + if (!this.usePageScroll && this.fixed) { + if (windowTop && !pagingStyle.top) { + pagingStyle.top = windowTop + 'px'; + } + if (windowBottom && !pagingStyle.bottom) { + pagingStyle.bottom = windowBottom + 'px'; + } + } + if (this.bgColor.length && !pagingStyle['background']) { + pagingStyle['background'] = this.bgColor; + } + if (this.height.length && !pagingStyle['height']) { + pagingStyle['height'] = this.height; + } + 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内容的样式 + finalPagingContentStyle() { + if (this.contentZIndex != 1) { + this.pagingContentStyle['z-index'] = this.contentZIndex; + this.pagingContentStyle['position'] = 'relative'; + } + return this.pagingContentStyle; + }, + // 最终的当前开启安全区域适配后,是否使用placeholder形式实现。如果slot=bottom存在,则应当交由固定在底部的view处理,因此需排除此情况 + finalUseSafeAreaPlaceholder() { + return this.useSafeAreaPlaceholder && !this.zSlots.bottom; + }, + renderJsIgnore() { + if ((this.usePageScroll && this.useChatRecordMode) || (!this.refresherEnabled && this.scrollable) || !this.useCustomRefresher) { + this.$nextTick(() => { + this.renderPropScrollTop = 10; + }) + } + return 0; + }, + windowHeight() { + if (!this.systemInfo) return 0; + return this.systemInfo.windowHeight || 0; + }, + windowBottom() { + if (!this.systemInfo) return 0; + return this.systemInfo.windowBottom || 0; + }, + // 是否是ios+h5 + isIosAndH5() { + // #ifndef H5 + return false; + // #endif + return this.isIos; + }, + // 是否是只使用基础布局或者只使用下拉刷新 + isOnly() { + return this.layoutOnly || this.refresherOnly; + }, + }, + methods: { + // 当前版本号 + getVersion() { + return `z-paging v${c.version}`; + }, + // 设置nvue List的specialEffects + setSpecialEffects(args) { + this.setListSpecialEffects(args); + }, + // 与setSpecialEffects等效,兼容旧版本 + setListSpecialEffects(args) { + this.nFixFreezing = args && Object.keys(args).length; + if (this.isIos) { + this.privateRefresherEnabled = 0; + } + !this.usePageScroll && this.$refs['zp-n-list'].setSpecialEffects(args); + }, + // #ifdef APP-VUE + // 当app长时间进入后台后进入前台,因系统内存管理导致app重新加载时,进行一些适配处理 + _handlePageLaunch() { + // 首次触发不进行处理,只有进入后台后打开app重新加载时才处理 + if (this.pageLaunched) { + // 解决在vue3+ios中,app ReLaunch时顶部下拉刷新展示位置向下偏移的问题 + // #ifdef VUE3 + this.refresherThresholdUpdateTag = 1; + this.$nextTick(() => { + this.refresherThresholdUpdateTag = 0; + }) + // #endif + // 解决使用虚拟列表时,app ReLaunch时白屏问题 + this._checkVirtualListScroll(); + } + this.pageLaunched = true; + }, + // #endif + // 使手机发生较短时间的振动(15ms) + _doVibrateShort() { + // #ifndef H5 + + // #ifdef APP-PLUS + if (this.isIos) { + const UISelectionFeedbackGenerator = plus.ios.importClass('UISelectionFeedbackGenerator'); + const feedbackGenerator = new UISelectionFeedbackGenerator(); + feedbackGenerator.init(); + setTimeout(() => { + feedbackGenerator.selectionChanged(); + }, 0) + } else { + plus.device.vibrate(15); + } + // #endif + // #ifndef APP-PLUS + uni.vibrateShort(); + // #endif + + // #endif + }, + // 设置z-paging高度 + async _setAutoHeight(shouldFullHeight = true, scrollViewNode = null) { + const heightKey = 'min-height'; + try { + if (shouldFullHeight) { + // 如果需要铺满全屏,则计算当前全屏可是区域的高度 + let finalScrollViewNode = scrollViewNode || await this._getNodeClientRect('.zp-scroll-view'); + let finalScrollBottomNode = await this._getNodeClientRect('.zp-page-bottom'); + if (finalScrollViewNode) { + const scrollViewTop = finalScrollViewNode[0].top; + let scrollViewHeight = this.windowHeight - scrollViewTop; + scrollViewHeight -= finalScrollBottomNode ? finalScrollBottomNode[0].height : 0; + const additionHeight = u.convertToPx(this.autoHeightAddition); + // 在支付宝小程序中,添加!important会导致min-height失效,因此在支付宝小程序中需要去掉 + let importantSuffix = ' !important'; + // #ifdef MP-ALIPAY + importantSuffix = ''; + // #endif + const finalHeight = scrollViewHeight + additionHeight - (this.insideMore ? 1 : 0) + 'px' + importantSuffix; + this.$set(this.scrollViewStyle, heightKey, finalHeight); + this.$set(this.scrollViewInStyle, heightKey, finalHeight); + } + } else { + this.$delete(this.scrollViewStyle, heightKey); + this.$delete(this.scrollViewInStyle, heightKey); + } + } catch (e) {} + }, + // 更新scroll-view高度 + async _updateScrollViewHeight() { + const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view'); + if (scrollViewNode) { + const scrollViewNodeHeight = scrollViewNode[0].height; + this.scrollViewHeight = scrollViewNodeHeight; + this.pagingOrgTop = scrollViewNode[0].top; + // 设置scroll-view内容器的最小高度等于scroll-view的高度(为了解决在快手小程序中内容较少时scroll-view内容器高度无法铺满scroll-view的问题) + // #ifdef MP-KUAISHOU + this.$set(this.scrollViewInStyle, 'min-height', scrollViewNodeHeight + 'px'); + // #endif + } + }, + // 组件销毁后续处理 + _handleUnmounted() { + this.active = false; + if (!this.layoutOnly) { + this._offEmit(); + } + // 取消监听键盘高度变化事件(H5、百度小程序、抖音小程序、飞书小程序、QQ小程序、快手小程序不支持) + // #ifndef H5 || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-KUAISHOU + this.useChatRecordMode && uni.offKeyboardHeightChange(this._handleKeyboardHeightChange); + // #endif + }, + // 触发更新是否超出页面状态 + _updateInsideOfPaging() { + this.insideMore && this.insideOfPaging === true && setTimeout(this.doLoadMore, 200) + }, + // 清除timeout + _cleanTimeout(timeout) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + return timeout; + }, + // 添加全局emit监听 + _onEmit() { + uni.$on(c.errorUpdateKey, (errorMsg) => { + if (this.loading) { + if (!!errorMsg) { + this.customerEmptyViewErrorText = errorMsg; + } + this.complete(false).catch(() => {}); + } + }) + uni.$on(c.completeUpdateKey, (data) => { + setTimeout(() => { + if (this.loading) { + if (!this.disabledCompleteEmit) { + const type = data.type || 'normal'; + const list = data.list || data; + const rule = data.rule; + this.fromCompleteEmit = true; + switch (type){ + case 'normal': + this.complete(list); + break; + case 'total': + this.completeByTotal(list, rule); + break; + case 'nomore': + this.completeByNoMore(list, rule); + break; + case 'key': + this.completeByKey(list, rule); + break; + default: + break; + } + } else { + this.disabledCompleteEmit = false; + } + } + }, 1); + }) + }, + // 销毁全局emit和listener监听 + _offEmit(){ + uni.$off(c.errorUpdateKey); + uni.$off(c.completeUpdateKey); + }, + }, +}; diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-mixin.js b/uni_modules/z-paging/components/z-paging/js/z-paging-mixin.js new file mode 100644 index 0000000..9d09caf --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-mixin.js @@ -0,0 +1,22 @@ +// [z-paging]使用页面滚动时引入此mixin,用于监听和处理onPullDownRefresh等页面生命周期方法 + +export default { + onPullDownRefresh() { + if (this.isPagingRefNotFound()) return; + this.$refs.paging.reload().catch(() => {}); + }, + onPageScroll(e) { + if (this.isPagingRefNotFound()) return; + this.$refs.paging.updatePageScrollTop(e.scrollTop); + e.scrollTop < 10 && this.$refs.paging.doChatRecordLoadMore(); + }, + onReachBottom() { + if (this.isPagingRefNotFound()) return; + this.$refs.paging.pageReachBottom(); + }, + methods: { + isPagingRefNotFound() { + return !this.$refs.paging; + } + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-static.js b/uni_modules/z-paging/components/z-paging/js/z-paging-static.js new file mode 100644 index 0000000..dbd02bc --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-static.js @@ -0,0 +1,13 @@ +// [z-paging]公用的静态图片资源 + +export default { + base64Arrow: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAD1BMVEVHcExRUVFMTExRUVFRUVE9CdWsAAAABHRSTlMAjjrY9ZnUjwAAAQFJREFUWMPt2MsNgzAMgGEEE1B1gKJmAIRYoCH7z9RCXrabh33iYktcIv35EEg5ZBh07pvxJU6MFSPOSRnjnBUjUsaciRUjMsb4xIoRCWNiYsUInzE5sWKEyxiYWDbyefqHx1zIeiYTk7mQYziTYecxHvEJjwmIT3hMQELCYSISEg4TkZj0mYTEpM8kJCU9JiMp6TEZyUmbAUhO2gxAQNJiIAKSFgMRmNQZhMCkziAEJTUGIyipMRjBSZkhCE7KDEFIUmTeGCHJxWz0zXaE0GTCG8ZFtEaS347r/1fe11YyHYVfubxayfjoHmc0YYwmmmiiiSaaaKLJ7ckyz5ve+dw3Xw2emdwm9xSbAAAAAElFTkSuQmCC', + base64ArrowWhite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEVHcEz///////////////////+IGTx/AAAABnRSTlMA/dAkXZOhASU/AAABYElEQVRYw+2YwXLCIBCGsdAHWGbyAKZ4zxi9O017rxLf/1UaWFAgA1m8dcpedNSPf/l/Vh0Ya/Wn6hN0JcGvoCqRM4C8VBFiDwBqqNuJKV0rAnCgy3AUqZE57x0iqTL8Br4U3WBf/YWaIlTKfAcELU/h9w72CSVPa3C3OCDvhpHbRp/s2vq4fHhCeiCl2A3m4Qd71DQR257mFBlMcTlbFnFWzNtHxewYEfSiaLS4el8d8nyhmKJd1CF4eOS0keLMAuSxubLBIeIGQW8YHCFFo7EH9+YDcQt9FMZEswTheaNxTHwHT8SZorJjMrEVwo4Zo0U8HSEyZvJMOg4RjnmmRr8nDYeIz3OMkbfE/QhBo+U9RnZJxjGCRh/WKmHEMWLNkfPKsGh/CWJk1JjG0kcuJggTt34VDP8aWAFhp4nybVb5+9qQhjSkIQ1pSEMa8k+Q5U9rV3dF8MpFBK+/7miVq1/HZ2qmo9D+pAAAAABJRU5ErkJggg==', + base64Flower: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAKlBMVEVHcEzDw8Ovr6+pqamUlJTCwsKenp61tbWxsbGysrLNzc2bm5u5ubmjo6MpovhuAAAACnRSTlMA/P79/sHDhiZS0DxZowAABBBJREFUWMPtl89rE0EUx7ctTXatB3MI1SWnDbUKPUgXqh4ED8Uf7KUVSm3ooVSpSii0Fn/gD4j4o+APiEoVmos9FO2celiqZVgwgaKHPQiCCkv+F99kM7Ozm5kxq1dfD91k9pPve9/3ZjbRNHHok/mKli4eIPNgSuRObuN9SqSEzM20iGnm0yIbqCuV7NSSSIV7uyPM6JMBYdeTOanh/QihJYZsUCSby+VkMj2AvOt0rAeQAwqE3lfKMZVlQCZk1QOCKkkVPadITCfIRNKxfoJI5+0OIFtJx14CMSg1mRSDko7VAfksRQzEbGYqxOJcVTWMCH2I1/IACNW0PWU2M8cmAVHtnH5mM1VRWtwKZjOd5JbF6s1IbaYqaotjNlPHgDAnlAizubTR6ovMYn052g/U5qcmOpi0WL8xTS/3IfSet5m8MEr5ajjF5le6dq/OJpobrdY0t3i9QgefWrxW9/1BLhk0E9m8FeUMhhXal499iD0eQRfDF+ts/tttORRerfp+oV7f4xJj82iUYm1Yzod+ZQEAlS/8mMBwKebVmCVp1f0JLS6zKd17+iwRKTARVg2SHtz3iEbBH+Q+U28zW2Jiza8Tjb1YFoYZMsJyjDqp3M9XBQdSdPLFdxEpvOB37JrHcmR/y9+LgoTlCFGZEa2sc6d4PGlweEa2JSVPoVm+IfGG3ZL037iV9oH+P+Jxc4HGVflNq1M0pivao/EopO4b/ojVCP9GjmiXOeS0DOn1o/iiccT4ORnyvBGF3yUywkQajW4Ti0SGuiy/wVSg/L8w+X/8Q+hvUx8Xd90z4oV5a1i88MbFWHz0WZZ1UrTwBGPX3Rat9AFiXRMRjoMdIdJLEOt2h7jrYOzgOamKZSWSNspOS0X8SAqRYmxRL7sg4eLzYmNehcxh3uoyud/BH2Udux4ywxFTc1xC7Mgf4vMhc5S+kSH3Y7yj+qpwIWSoPTVCOOPVthGx9FbGqrwFw6wSFxJr+17zeKcztt3u+2roAEVgUjDd+AHGuxHy2rZHaa8JMkTHEeyi85ANPO9j9BVuBRD2FY5LDMo/Sz/2hReqGIs/KiFin+CsPsYO/yvM3jL2vE8EbX7/Bf8ejtr2GLN65bioAdgLd8Bis/mD5GmP2qeqyo2ZwQEOtAjRIDH7mBKpUcMoApbZJ5UIxkEwxyMZyMxW/uKFvHCFR3SSmerHyDNQ2dF4JG6zIMpBgLfjSF9x1D6smFcYnGApjmSLICO3ecCDWrQ48geba9DI3STy2i7ax6WIB62fSyIZIiO3GFQqSURp8wCo7GhJBGwuSovJBNjb7kT6FPVnIa9qJ2Ko+l9mefGIdinaMp0yC1URYiwsdfNE45EuA5Cx9EhalfvN5s+UyItm81vaB3p4joniN+SCP7Qc1hblAAAAAElFTkSuQmCC', + base64FlowerWhite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEX///9HcEz///////////////84chYNAAAABnRSTlP/AGzCOYZj5g1nAAACfklEQVRYw+2YTVPDIBCGtza9Jw25a0bvcax30o73OOr//yvma2F3YWlpPTijXNpAHrK8LLALVPFium2vNIFSbwGKTGQA2GUiHcD29yDNy3sMIdUBQl7r2H8mOEVqAHgPkYZUS6Qc2zYhQqtjyDZEximCZwWZLIBeIgYShs2NzxKpSUehYpMJhURGb+O+w5BpMCAREKPnCDHbIY20SzhM5yxziAXpOiBXydrekT9i5XDEq4NIIHHgyU5mRGqviII4mREJJA4QJzMiILwlRJzpKxJKvCBm8OsBBbLux0tsPl4RKYm5aPu6jw1U4mGxEUR9g8M1PcqBEp/WJliNgYOXueBzS4jZSIcgY5lCtevgDSgyzE+rAfuOTQMq0yzvoGH18qju27Mayzs4fPyMziCx81NJa5RNfW7vPYK9KOfDiVkBxFHG8hAj9txuoBuSWORsFfkpBf7xKFLSeaOefEojh5jz22DJEqMP8fUyaKdQx+RnG+yXMpe8Aars8ueR1pVH/bW3FyyvPRw90upLDHwpgBDtg4aUBNkxRLXMAi03IhcZtr1m+FeI/O/JNyDmmL1djLOauSlNflBpW18RQ2bPqXI22MXXEk75KRHTnkPkYbESbdKP2ZFk0r5sIwffAjy1lx+vx7NLjB6/E7Jfv5ERKhzpN0w8IDE8IGFDv5dhz10s7GFiXRZcUeLCEG5P5nDq9k4PFDcoMpE3GY4OuxuCXhmuyNB6k0RsLIAvqp9NE5r8ZCSS8gxnUp7ODdYhZTqxuiJ9uyJJtPmpqJ7wVj+XVieS903iViHziqAhchLEJAyb7jWU647EpUofQ0ziUuXXXhDddtlllSwjgSQu7r4BRWhQqfDPMVwAAAAASUVORK5CYII=', + base64Success: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEVRUVFHcExTU1NRUVFRUVFRUVFOSlSUAAAABnRSTlP/AI6+VySB3ZENAAACcElEQVRYw+2YyYKCMAyGI8hdpdxdZu7gcpdZ7jL6/s8yYheSNi0aPdqbwOffpGmaFOYPD3gj4bisN7vddv17N/JVgxn5x12IWgIaWTuO/IE3PseQbwjGPo2cgRmHFLJwdm/X643zwiqOKPPJ1nj3sjEP2iiifZWj5bhopSyGaEO2HX5fbQJzwJ+W7x/jw5ZFjsEU0PMph9xE8i5EqprKALW95eJQURkgzw98uJ/JvwGecR7bIjWWsUgVrrIfFZ2HlLy3sKETD1mmRLRMRhGVssRa0xJkdn3SpJBymBkM8+pSSDXMDNyDaToVHd2fgpNt0sjwiUZO19+jGQ+gQEg9Oq+bufmAVGihomNmjQG7UG3020vrlm7lkFnKFGU3kZ0KGAdmKe821pipQ+qEKcrZeTL2g5FsUks4cStjEZWwXg0b0n4GxmEpkWwIs5VBynjgK7xZaz1/0D7OxkVuLpsY5BQNFyLS84VBjjbg0iL2r2EQHBOxBhikuUOkdxODVF1cxHoWtPPsiyXO455Iv34hssCO8EV4ZIYTjS8SR4qYSHRiTiYQ4ZFbHi0iIhhBTi6dTCgSWRcnw4h4yGTuyTAiOGBIWGoZTgSHJQl+LcOJ4OCnW6yX2bMnJ9pidCOXtkTkTrIGpYuOynAiOF14SamMiOCk5Ke+mq8BcOrrvym8d0zKIQnWT+M1WwOQNO4fFiWb18hhERxJPx2fblbPHHyC41VyiAtKBUFBIih7JMWVoIQTFIr3lKPN80WvoLSWFPC653ioTZA0I0FrQ7qU6asaK0H7JmkSJa2ooOGVtNUsc3j9FYHkIkJy3SG6VHnfXKXGP9t4N9Q4Ye98AAAAAElFTkSuQmCC', + base64SuccessWhite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAGFBMVEVHcEz///////////////////////////8dS1W+AAAAB3RSTlMAiVYk6KvDHLfaegAAAo1JREFUWMPtWEtzmzAQNhCTq910ytXpiyvxTNOr60zrayepx9d02gnX4sTm7xcEiJX2gdnkGJ1A4tOnfWqXyeR1vMRYzrcPD9v5h5MBl3/Ldvx4cxIg/FWC8X0xjLjalM54uhhCfCrRuJURX0pi3EmIqZV7O59vrRZmguStHL9b7S7ftfLwOtiZDw7AHMtmquAQ12b5Wwbnordm8g9zLLO49qc/m2n6aKnhwPOGZ08hAiNHhheiHae1lOUPGZpQkPKa3q0mOUjaRzSRaGUjpy/mmWSwySSpllcEteBKAT52KEnSbblA51pJEPxBQoiH1FP4E3s5+FJv07h6/ylD6ui7B+9fq/ehrFB98ghec9EoVtyjK8pqCHLmCBOwMWSCeWFNN4MbPAk55NhsvoFHSSVR0k5TCTTEzlUGcqV/nVp7n9oIVkmtaqbAEqEgfdgHJPwsEAyZ9r4VAZXFjpEwyaw3+H2v42KYxKhs1XvY/gSSGv+IHyUSuHXCeZhLAgVI3EjgSGo1Fb3xO0tGGU9S2/KAIbtjxpJASG73qox6w5LUq0cEOa+iIONIWIilQSQ0pPa2jgaRQAgQP7c0mITRWGxpMAmEQFN2NAQJNCV0mI6GIIEO47hlQ0ORQLd0nL+hoUjg1m6I1TRr8uYEAriBHLcVFQ5UEMiBe3XkTBEG04WXlGKGxPnMS305XQPA1Ocn2JiuAZwE66fxnKwBnDTuXxZTMq85lwW6kt5ndLqZPefiU1yvmktcUSooChJF2aMprhQlnKJQ5FxRKkcVRa+itNYU8Io2oVkY14w0NMWYlqft91Bj9VHq+ca3b43BxjWJmla0sfKohlfTVpPN+93L/yLQ/IjQ/O5Q/VR5HdL4D7mlxmjwVdELAAAAAElFTkSuQmCC', + base64Empty: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAMAAAAL34HQAAALeGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDIgNzkuMTY0NDg4LCAyMDIwLzA3LzEwLTIyOjA2OjUzICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIiB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTAyLTIyVDIxOjIxOjQ1KzA4OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDI0LTAxLTEzVDE5OjA5OjQwKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyNC0wMS0xM1QxOTowOTo0MCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ZWQwMWYzNWQtOWRjOC00MDBiLWEyMmQtNjM5OGZiNzVhNGRiIiB4bXBNTTpEb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6ZDhlMzQ3ZmEtMDY2My1jYTRiLTgzNTctNTk4YjBkNGIzOTU2IiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6ZDA4MDI4MDItMzUyYS04NTRhLTkxYjctNmRlNmQ1MmViM2QwIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiIHRpZmY6T3JpZW50YXRpb249IjEiIHRpZmY6WFJlc29sdXRpb249IjMwMDAwMDAvMTAwMDAiIHRpZmY6WVJlc29sdXRpb249IjMwMDAwMDAvMTAwMDAiIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiIGV4aWY6Q29sb3JTcGFjZT0iMSIgZXhpZjpQaXhlbFhEaW1lbnNpb249IjMwMCIgZXhpZjpQaXhlbFlEaW1lbnNpb249IjMwMCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZDA4MDI4MDItMzUyYS04NTRhLTkxYjctNmRlNmQ1MmViM2QwIiBzdEV2dDp3aGVuPSIyMDIyLTAyLTIyVDIxOjIxOjQ1KzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjQwNjg2NzJkLWY5NDMtOTU0Mi1iMDBiLTVlMDExNmE1NmIzZSIgc3RFdnQ6d2hlbj0iMjAyNC0wMS0xM1QxMDoyNjoxNiswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDphYmJkZmUyZC0xY2Q2LTJiNDgtYjUyNS05YzlhZjdlNjA4NDMiIHN0RXZ0OndoZW49IjIwMjQtMDEtMTNUMTE6MjM6NDArMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY29udmVydGVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJmcm9tIGFwcGxpY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3AgdG8gaW1hZ2UvcG5nIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJkZXJpdmVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJjb252ZXJ0ZWQgZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YTQ5MjM5MDAtNDhiZC03YTQ1LWI4NGItYmVlZTVjOWUxYTM1IiBzdEV2dDp3aGVuPSIyMDI0LTAxLTEzVDExOjIzOjQwKzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmVkMDFmMzVkLTlkYzgtNDAwYi1hMjJkLTYzOThmYjc1YTRkYiIgc3RFdnQ6d2hlbj0iMjAyNC0wMS0xM1QxOTowOTo0MCswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDIyLjAgKE1hY2ludG9zaCkiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDx4bXBNTTpEZXJpdmVkRnJvbSBz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', + base64Error: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAMAAAAL34HQAAALeGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDIgNzkuMTY0NDg4LCAyMDIwLzA3LzEwLTIyOjA2OjUzICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIiB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTAyLTIyVDIxOjIxOjQ1KzA4OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDI0LTAxLTEzVDE5OjEwOjEwKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyNC0wMS0xM1QxOToxMDoxMCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTQ3NTExNjAtZDY5MC00ZTkzLWFhNGUtNGMwYTViNGU1ZGFjIiB4bXBNTTpEb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6YzRiNzlkYWMtZTJmYS1iNzQ0LWIxM2ItOWU1N2VjMDhhM2YwIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6ZDA4MDI4MDItMzUyYS04NTRhLTkxYjctNmRlNmQ1MmViM2QwIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiIHRpZmY6T3JpZW50YXRpb249IjEiIHRpZmY6WFJlc29sdXRpb249IjMwMDAwMDAvMTAwMDAiIHRpZmY6WVJlc29sdXRpb249IjMwMDAwMDAvMTAwMDAiIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiIGV4aWY6Q29sb3JTcGFjZT0iMSIgZXhpZjpQaXhlbFhEaW1lbnNpb249IjMwMCIgZXhpZjpQaXhlbFlEaW1lbnNpb249IjMwMCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZDA4MDI4MDItMzUyYS04NTRhLTkxYjctNmRlNmQ1MmViM2QwIiBzdEV2dDp3aGVuPSIyMDIyLTAyLTIyVDIxOjIxOjQ1KzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjQwNjg2NzJkLWY5NDMtOTU0Mi1iMDBiLTVlMDExNmE1NmIzZSIgc3RFdnQ6d2hlbj0iMjAyNC0wMS0xM1QxMDoyNjoxNiswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpjZjk1NTE1OC04MjFiLTA4NDUtYWJmNS05YTE1NGM1ZTY4NjEiIHN0RXZ0OndoZW49IjIwMjQtMDEtMTNUMTE6MDQ6MDQrMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY29udmVydGVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJmcm9tIGFwcGxpY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3AgdG8gaW1hZ2UvcG5nIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJkZXJpdmVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJjb252ZXJ0ZWQgZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZGM1Y2IyNWItZDZlNC0yZjQ2LTgyODQtZmUwOTNlY2M2ZTkxIiBzdEV2dDp3aGVuPSIyMDI0LTAxLTEzVDExOjA0OjA0KzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjE0NzUxMTYwLWQ2OTAtNGU5My1hYTRlLTRjMGE1YjRlNWRhYyIgc3RFdnQ6d2hlbj0iMjAyNC0wMS0xM1QxOToxMDoxMCswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDIyLjAgKE1hY2ludG9zaCkiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmNmOTU1MTU4LTgyMWItMDg0NS1hYmY1LTlhMTU0YzVlNjg2MSIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjM2ZGQ4NTQxLWQ0MWEtYmY0Yy1iZjA3LWNmNjZhNjZhMDg2MSIgc3RSZWY6b3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOmQwODAyODAyLTM1MmEtODU0YS05MWI3LTZkZTZkNTJlYjNkMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Ph2LDQsAAAAJcEhZcwAACxMAAAsTAQCanBgAAAA5UExURUdwTNra2s7Ozq2tre3t7dPT087OzuPj4+3t7dbW1u/v79bW1vz8/MrKytDQ0Nzc3MPDw/X19bi4uMZQDnEAAAAKdFJOUwBqEPywotz+wzqApqiTAAAHW0lEQVR42u1b25akIAwcbx2UFoj//7HLTQVBRcSZfTDnbM/uTl/KSlEkwf75eeONN95444033njjjTduR9/0/yOsbqoevObL7101tYX1HFs9QFtfZalRP+rpQVgdAFx990ZnT8L6eZItUl99jeGpf1DxdV/VP9fV1f/PFlF1bYHoVFSRC60IyVjrFRnuB8IoxpExSrstsErKHpJw1eqybNLbAQvAYkKjUrjoBgKRqAaeIjG5+qaps6hKcMWmcdSwqAJWBbAgCZZaIYbsqggqqlHNbFFa5yVR4jKvrKEErOEjNCqNSwHrfE8lpLsod/u+cOPPMPBJ+Gz5dM0cXNgclre+pSxhYI1WW5Tf9ENSMIdLCiWs6q9hwQprBVYKFqyPlx4WtoSvrT9lC/wkGt8qlkQooC3hi6sgW3Bb8gtdpSV/za/mn49pC0oYhONbfyd5hzDLFivKFpTS1gKM0we0tQCEncfgQn7Rt+DC/299i1MSRJcBC0r7VviG5KZvwV5WIUobxHyrJKy8VRjXVgFYsPu5kOtbxdhycCDuihziXVLoW7xwEiUmDgd544B46luWLW+nugMLB2BimmC3cxTNxCDg8xFtuUSNqoFsDKzY8psa+XtBNWXr74N6qxwsS5T6VL5robKl10+ZRu5S9qBvUYuJwVHzjwjrE3G33qKh+WXBgmkmCvHYquTvZ8oo7rLFA4PJgYW0MdePIRQIGUPNbSMw5lubJMKtJI6+Wk6cVFMmACO+VVryeL7ZgI8MhwS2fnNPPK0geHBRd11eJSiyL4KjrL2umm1XIpRii1MKB/mU/iCZwF+pt5z3UJ7UiF3nQqadAXC3T3xEW2IyuDBe3yDTe0+A64it2WTyYSGVHymUI/EduvSWKJ80Dtv2NbYSoQxbMkVC7yzNGIWFvDF7gRD79RYrWW/BDGti4wwLtgvO7gWKUZ8Mt94qX8vLJE70+xVNwzDm9ghNM+FX7p/jlZUId2HJD+Tf79hMe3WNrAK/30E+C8/6xOCqbqxE5JNMYrNbnaLUvJAewfCg8zF0Ba/tbviWLvPYfsGFA1PVD8ZdnjlVc/DS/o7LK4NHjOjKKbfCTSCo5XmwKbaZM4jlc9NGEYd9Ijd0QS5ZGaOR2O+DPlGyRb2nXZzgnI1GdFWF+0gh3ifyTRqvzpXI2eElk58FeHziCF5hY+hSMV9Ge/mohUTGuQ4vzHYe8bW5sNdFQ58St22Vcf5zzJbtcGT4iYQ7iz8dFuxoWRYMjAM7KCnypHOTLSqdUwYIFpndOD/6B2FBzNQxYmW/zxYE4j8yLHga1s2Rbm/O5PXtGcuNDIW1dTj5hpjGsO+7z2Kk9NP1JWDlnWKAM4H6zCUNM05KyVPHBclYzUbgjE3N3tP2JWHBmbqD4GLeCs2jhMT13lMVljwcEbetwZgtHUxVQ21ho3fE7inf2s8vzMWq0EWpfOBg5hcDSGwaF2+LaysRIzNFqRgBv2sMhi/Ix0WiW8rBKNBv4ExBI7eorx9ANazsPCb5FkSNH+Reacos+AYxaFzX76KMH65c8ytzZ40YvpFAqtgC/otn1eCmMI5K8yVRQVVwq3aVtU+jJktwjyP7x+BKv8vtoH098vXYSJcrWGJcAW11r8WVRxe5vgcuFbXqwnaEZejS6mrLwYKUg1ch2RJswTFYgMOwoau+AQsSp/FuDhVZi7J402ifgGla/GJIzGLYG5H4rnKMCUydL9wcsmZSuPikR2QmjQbWqaV2ob2RdMvaLEvFlRiXpYeTwqVOtMZF+qi0dS4uEjJKMvWuYK3S0jHZwaq7BylYp/O2uu3q04lNqudLWEJQd/3paTBz12IaLIPtzE5P1AUuW9TB8NVzaG9/TIfV+eXsWeezz6HWlptEbo4SIAeWur/Y/RZC/gmZTiLzUY2j5ct6fjKsFvxqgyQxE9sbmfYtnJMIciEKo6+FL0wziJmtkzspIcUl0PgWrL7VCKP7hl61U4WLeN+7Ieli2vZhmq0VgjDOgIyhJ62sSpDkWNZa1wiB8WoLlxzy29XpGVPgn1ut5VYcGyRLK7OCiJaDYMrAneJUkZWdw0yDgNm5nDowqLc0Kp581FO7QS4pC9S/YRW9xkVdNOj0ZHCp9anEZw3VEK/fopiDrkMObkcdJtT1g6+uzQ60bIdUPztdWZWy53m+v/zFYPOGHO4AZsalmtJNkyHrCAx1RXX7mt5g1L1pDezpkXv8wJwpVRSSaf2c26Y0rrXXxyWBptu/ovdak+VhkqjGBZUdvKygqANKA/MqZ/36kcGwFn90RnWp66ksKuHgitLFY8BU+F2ZvqpxpMY9qR3YwOUJ12fc0KUHVKdswcKXuwetErCnwvMKuXxfc/3RVJ2yFc+iosQd3X+WGSVz1UiuN2J156FyVyHbsOUp3krezaPUT/VxXqdfwvknb/Zgp+idTxTbrkLqYuKreRnhy65Gf4W0NsDoYiqf6uZsvr8V9eo6XWc5+3TVf/3N1TfeeOONN95444033njjjTfeSI1/IeOYOeO4fGAAAAAASUVORK5CYII=', + base64BackToTop: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIBAMAAABfdrOtAAAAElBMVEVRUVH+/v5HcEyZmZlRUVFRUVGm1ByOAAAABnRSTlPMzADMTZAJBBGsAAAEnElEQVR42t2cS27jMAyGf7/2U+QCQeDsbeQCgZDujaC5/1UmkzaJn+JDFGcw3LdfflKibJkkDnxrL7dbg7sNt6+L4O8OYBM+B0ys+QrGkHZG+OEEQ8g6go8Bx1GIGMdpNOQyIG6XdMgnSPtKhLQDGEZFBgYMkhKFtGBb0EIEjDgFRowoBVaMGAWpMedEfxMiZtwpUsgZCqtlkCNUdpVAWigtCCCDFtLwIWeoreZCWiRYYEKGFEjDg+yRZCUH0iLRAgNyToXUNCRZyMqWhGnUN2IPm3wSlwJ7IUspyCBkIQUZhCykIIeQuRTkEDKXAuM9srrtYbrZN7Y98giZSoFd+t1OxmMITG0dcrSFXFchZ1tIvQZpYWxhBbK3hpQrkMEa0iwh5t4a+QvZvDXyF7J5a+Qv5PPW21/I5623v5DPW29/IaO3Xv5Clrw1y1/Ikrdm+Qs5svw83yNnSJ5BQb4F/F7EIEJSnThGBAXxkFQfLOviQUE8JAUPsosHBfGQfDAtHhREQ1JxIV00KIgmrnRI84S0yAd5BAXxxJUck0f6Qnwr9qmr6xF5xLMjcwn/iudIEAdWnyjkEXlQKZiRVzoqRyLbgeUKKR8Q4alY7cSnoxzSf2ggsqehKr6YVpcXpOd7H93f60cKhOd7Re2LteUF4eLqiVS1mr0ge4io6C2+soaFkJ7MuuuQs1yITEp9hwwKISIpzR2iESKSIoT0rLNwuVHQqoSIpAQJpGce60vIUSdEIuUqgPTsJ5QFZK8UIpBS8iG94GFrDjlrhfCl8CG96Llxmle4kEr6vKWBPIVo9kqDQSRk9/3cWoikcCFPAd33v4dIChPyEvLzBA6RlEYWke4JEUnhKXkLeUEKxRHJFfKCQHGucIW8IdZSRkLeEGMpYyEjiK2UsZARxFTKRMgYYillImQMMZQyFTKB2EmZCplAuFLIHT8TMoWwpQwiIVMIUwqpZP5bp5CCvCTiQKr5f5lCQN+tPCBn2ZvVDFJwIDUP0m1BYAfZYRNSsCB7BqTbhoARePIxtZ9tgwWkoJcwCalmv3MBAemtO4R6dah2HaKQqj8Zvp9sQDjvJ21+SPCBHPJDDk6QITekEV7gqCC19CpKAym9IMfckKv4olMBCeIrWwVEfvkshzQekO9r9P1/ALk+IG1eSPCDiCJfyG+FyU+A6ZCa/piZDinpz7LpkCv5gdkAEshP5emQhv7onw6pGeULyZCSUYiRDAmMkpJkCKs4JhFSq8p8hJBSVbAkhARV6ZUQoisik0FqXTmcDHLVFfbJIEFXoiiCNMpiSxGkVJaNiiBBWQArgTTaUl4JpNQWJUsgQVteXQg+AKkLxQWFGKW+5J2+eVp4S168X3CF1CltCKdTJ8lb84YK2bUBO+wZW0Pqv9nk4tKu49N45NJC5dMM5tLW5tOg59Jq6NM06dL+abFXwr/RkuvTXJwae1abtE/Dt0/ruksTvs84AZ/BCC4jHnyGVfiM3VBQFANEXEah+Ax18RlP4zNox2dkkM/wI58xTn8yDCXGYCDV3W5RGSajtXyGhG1jbpbjzpwGt/0MJft8jqC7iUbQ/QZaxdnKqcIftwAAAABJRU5ErkJggg==', +} diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-utils.js b/uni_modules/z-paging/components/z-paging/js/z-paging-utils.js new file mode 100644 index 0000000..f30b360 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-utils.js @@ -0,0 +1,322 @@ +// [z-paging]工具类 + +import zLocalConfig from '../config/index' +import c from './z-paging-constant' + +const storageKey = 'Z-PAGING-REFRESHER-TIME-STORAGE-KEY'; +let config = null; +let configLoaded = false; +let cachedSystemInfo = null; +const timeoutMap = {}; + +// 获取默认配置信息 +function gc(key, defaultValue) { + // 这里return一个函数以解决在vue3+appvue中,props默认配置读取在main.js之前执行导致uni.$zp全局配置无效的问题。相当于props的default中传入一个带有返回值的函数 + return () => { + // 处理z-paging全局配置 + _handleDefaultConfig(); + // 如果全局配置不存在,则返回默认值 + if (!config) return defaultValue; + const value = config[key]; + // 如果全局配置存在但对应的配置项不存在,则返回默认值;反之返回配置项 + return value === undefined ? defaultValue : value; + }; +} + +// 获取最终的touch位置 +function getTouch(e) { + let touch = null; + if (e.touches && e.touches.length) { + touch = e.touches[0]; + } else if (e.changedTouches && e.changedTouches.length) { + touch = e.changedTouches[0]; + } else if (e.datail && e.datail != {}) { + touch = e.datail; + } else { + return { touchX: 0, touchY: 0 } + } + return { + touchX: touch.clientX, + touchY: touch.clientY + }; +} + +// 判断当前手势是否在z-paging内触发 +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'), + isUseChatRecordMode: classList.contains('z-paging-use-chat-record-mode') + }; + } else { + return getTouchFromZPaging(target.parentNode); + } + } else { + return { isFromZp: false }; + } +} + +// 递归获取z-paging所在的parent,如果查找不到则返回null +function getParent(parent) { + if (!parent) return null; + if (parent.$refs.paging) return parent; + return getParent(parent.$parent); +} + +// 打印错误信息 +function consoleErr(err) { + console.error(`[z-paging]${err}`); +} + +// 延时操作,如果key存在,调用时清除对应key之前的延时操作 +function delay(callback, ms = c.delayTime, key) { + const timeout = setTimeout(callback, ms);; + if (!!key) { + timeoutMap[key] && clearTimeout(timeoutMap[key]); + timeoutMap[key] = timeout; + } + return timeout; +} + +// 设置下拉刷新时间 +function setRefesrherTime(time, key) { + const datas = getRefesrherTime() || {}; + datas[key] = time; + uni.setStorageSync(storageKey, datas); +} + +// 获取下拉刷新时间 +function getRefesrherTime() { + return uni.getStorageSync(storageKey); +} + +// 通过下拉刷新标识key获取下拉刷新时间 +function getRefesrherTimeByKey(key) { + const datas = getRefesrherTime(); + return datas && datas[key] ? datas[key] : null; +} + +// 通过下拉刷新标识key获取下拉刷新时间(格式化之后) +function getRefesrherFormatTimeByKey(key, textMap) { + const time = getRefesrherTimeByKey(key); + const timeText = time ? _timeFormat(time, textMap) : textMap.none; + return `${textMap.title}${timeText}`; +} + +// 将文本的px或者rpx转为px的值 +function convertToPx(text) { + const dataType = Object.prototype.toString.call(text); + if (dataType === '[object Number]') return text; + let isRpx = false; + if (text.indexOf('rpx') !== -1 || text.indexOf('upx') !== -1) { + text = text.replace('rpx', '').replace('upx', ''); + isRpx = true; + } else if (text.indexOf('px') !== -1) { + text = text.replace('px', ''); + } + if (!isNaN(text)) { + if (isRpx) return Number(rpx2px(text)); + return Number(text); + } + return 0; +} + +// rpx => px,预留的兼容处理 +function rpx2px(rpx) { + return uni.upx2px(rpx); +} + +// 同步获取系统信息,兼容不同平台 +function getSystemInfoSync(useCache = false) { + if (useCache && cachedSystemInfo) { + return cachedSystemInfo; + } + // 目前只用到了deviceInfo、appBaseInfo和windowInfo中的信息,因此仅整合这两个信息数据 + const infoTypes = ['DeviceInfo', 'AppBaseInfo', 'WindowInfo']; + const { deviceInfo, appBaseInfo, windowInfo } = infoTypes.reduce((acc, key) => { + const method = `get${key}`; + if (uni[method] && uni.canIUse(method)) { + acc[key.charAt(0).toLowerCase() + key.slice(1)] = uni[method](); + } + return acc; + }, {}); + // 如果deviceInfo、appBaseInfo和windowInfo都可以从各自专属的api中获取,则整合它们的数据 + if (deviceInfo && appBaseInfo && windowInfo) { + cachedSystemInfo = { ...deviceInfo, ...appBaseInfo, ...windowInfo }; + } else { + // 使用uni.getSystemInfoSync兜底,确保能获取到最终的系统信息 + cachedSystemInfo = uni.getSystemInfoSync(); + } + return cachedSystemInfo; +} + +// 获取当前时间 +function getTime() { + return (new Date()).getTime(); +} + +// 获取z-paging实例id,随机生成10位数字+字母 +function getInstanceId() { + const s = []; + const hexDigits = "0123456789abcdef"; + for (let i = 0; i < 10; i++) { + s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); + } + return s.join('') + getTime(); +} + +// 等待一段时间 +function wait(ms) { + return new Promise(resolve => { + setTimeout(resolve, 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]') { + let tempValue = value; + tempValue = tempValue.replace('rpx', '').replace('upx', '').replace('px', ''); + if (value.indexOf('rpx') === -1 && value.indexOf('upx') === -1 && value.indexOf('px') !== -1) { + tempValue = parseFloat(tempValue) * 2; + } + value = tempValue; + } + 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 useBufferedInsert(fn, delay = 50) { + let buffer = []; + let timer = null; + let latestArgs = []; + return function insertBuffered(data, ...args) { + const newData = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : data; + buffer.push(...newData); + latestArgs = args; + if (!timer) { + timer = setTimeout(() => { + fn(buffer.length === 1 ? buffer[0] : buffer, ...latestArgs); + buffer = []; + timer = null; + }, buffer.length === 1 ? 10 : delay); + } + }; +} + +// ------------------ 私有方法 ------------------------ +// 处理全局配置 +function _handleDefaultConfig() { + // 确保只加载一次全局配置 + if (configLoaded) return; + // 优先从config.js中读取 + if (zLocalConfig && Object.keys(zLocalConfig).length) { + config = zLocalConfig; + } + // 如果在config.js中读取不到,则尝试到uni.$zp读取 + if (!config && uni.$zp) { + config = uni.$zp.config; + } + // 将config中的短横线写法全部转为驼峰写法,使得读取配置时可以直接通过key去匹配,而非读取每个配置时候再去转,减少不必要的性能开支 + config = config ? Object.keys(config).reduce((result, key) => { + result[_toCamelCase(key)] = config[key]; + return result; + }, {}) : null; + configLoaded = true; +} + +// 时间格式化 +function _timeFormat(time, textMap) { + const date = new Date(time); + const currentDate = new Date(); + // 设置time对应的天,去除时分秒,使得可以直接比较日期 + const dateDay = new Date(time).setHours(0, 0, 0, 0); + // 设置当前的天,去除时分秒,使得可以直接比较日期 + const currentDateDay = new Date().setHours(0, 0, 0, 0); + const disTime = dateDay - currentDateDay; + let dayStr = ''; + const timeStr = _dateTimeFormat(date); + if (disTime === 0) { + dayStr = textMap.today; + } else if (disTime === -86400000) { + dayStr = textMap.yesterday; + } else { + dayStr = _dateDayFormat(date, date.getFullYear() !== currentDate.getFullYear()); + } + return `${dayStr} ${timeStr}`; +} + +// date格式化为年月日 +function _dateDayFormat(date, showYear = true) { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + return showYear ? `${year}-${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}` : `${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}`; +} + +// data格式化为时分 +function _dateTimeFormat(date) { + const hour = date.getHours(); + const minute = date.getMinutes(); + return `${_fullZeroToTwo(hour)}:${_fullZeroToTwo(minute)}`; +} + +// 不满2位在前面填充0 +function _fullZeroToTwo(str) { + str = str.toString(); + return str.length === 1 ? '0' + str : str; +} + +// 驼峰转短横线 +function _toKebab(value) { + return value.replace(/([A-Z])/g, "-$1").toLowerCase(); +} + +// 短横线转驼峰 +function _toCamelCase(value) { + return value.replace(/-([a-z])/g, (_, group1) => group1.toUpperCase()); +} + + +export default { + gc, + setRefesrherTime, + getRefesrherFormatTimeByKey, + getTouch, + getTouchFromZPaging, + getParent, + convertToPx, + getTime, + getInstanceId, + consoleErr, + delay, + wait, + isPromise, + addUnit, + deepCopy, + rpx2px, + getSystemInfoSync, + useBufferedInsert +}; diff --git a/uni_modules/z-paging/components/z-paging/wxs/z-paging-renderjs.js b/uni_modules/z-paging/components/z-paging/wxs/z-paging-renderjs.js new file mode 100644 index 0000000..359f1ac --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/wxs/z-paging-renderjs.js @@ -0,0 +1,67 @@ +// [z-paging]使用renderjs在app-vue和h5中对touchmove事件冒泡进行处理 + +import u from '../js/z-paging-utils' +const data = { + startY: 0, + isTouchFromZPaging: false, + isUsePageScroll: false, + isReachedTop: true, + isIosAndH5: false, + useChatRecordMode: false, + appLaunched: false +} + +export default { + mounted() { + if (window) { + this._handleTouch(); + // #ifdef APP-VUE + this.$ownerInstance.callMethod('_handlePageLaunch'); + // #endif + } + }, + methods: { + // 接收逻辑层发送的数据(是否是ios+h5) + renderPropIsIosAndH5Change(newVal) { + if (newVal === -1) return; + data.isIosAndH5 = newVal; + }, + + // 拦截处理touch事件 + _handleTouch() { + if (!window.$zPagingRenderJsInited) { + window.$zPagingRenderJsInited = true; + window.addEventListener('touchstart', this._handleTouchstart, { passive: true }) + window.addEventListener('touchmove', this._handleTouchmove, { passive: false }) + } + }, + // 处理touch开始 + _handleTouchstart(e) { + const touch = u.getTouch(e); + data.startY = touch.touchY; + const touchResult = u.getTouchFromZPaging(e.target); + data.isTouchFromZPaging = touchResult.isFromZp; + data.isUsePageScroll = touchResult.isPageScroll; + data.isReachedTop = touchResult.isReachedTop; + data.useChatRecordMode = touchResult.isUseChatRecordMode; + }, + // 处理touch中 + _handleTouchmove(e) { + const touch = u.getTouch(e); + const moveY = touch.touchY - data.startY; + // 如果是在z-paging内触摸并且(是在顶部位置且是下拉的情况下(或不是聊天记录滚动模式并且在iOS+h5+scroll-view并且是往上拉的情况:避免在此平台中滚动到底部后上拉有个系统灰色遮罩导致列表被短暂锁定的问题)) + // (data.useChatRecordMode ? moveY < 0 : moveY > 0)是为了判断是否是上拉的情况,聊天记录模式列表倒置,因此moveY < 0为上拉 + if (data.isTouchFromZPaging && ((data.isReachedTop && (data.useChatRecordMode ? moveY < 0 : moveY > 0)) || (!data.useChatRecordMode && data.isIosAndH5 && !data.isUsePageScroll && moveY < 0))) { + if (e.cancelable && !e.defaultPrevented) { + // 阻止事件冒泡,以避免在一些平台中下拉刷新时整个page跟着一起下拉&在iOS+h5+scroll-view中在底部上拉有个系统灰色遮罩导致列表被短暂锁定的问题 + e.preventDefault(); + } + } + }, + // 移除touch相关事件监听 + _removeAllEventListener(){ + window.removeEventListener('touchstart'); + window.removeEventListener('touchmove'); + } + } +}; diff --git a/uni_modules/z-paging/components/z-paging/wxs/z-paging-wxs.wxs b/uni_modules/z-paging/components/z-paging/wxs/z-paging-wxs.wxs new file mode 100644 index 0000000..ed21959 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/wxs/z-paging-wxs.wxs @@ -0,0 +1,382 @@ +// [z-paging]微信小程序、QQ小程序、app-vue、h5上使用wxs实现自定义下拉刷新,降低逻辑层与视图层的通信折损,提升性能 + +var currentDis = 0; +var isPCFlag = -1; +var startY = -1; + +// 监听js层传过来的数据 +function propObserver(newVal, oldVal, ownerIns, ins) { + var state = ownerIns.getState() || {}; + state.currentIns = ins; + var dataset = ins.getDataset(); + var loading = dataset.loading == true; + // 如果是下拉刷新结束,更新transform + if (newVal && newVal.indexOf('end') != -1) { + var transition = newVal.split('end')[0]; + _setTransform('translateY(0px)', ins, false, transition); + state.moveDis = 0; + state.oldMoveDis = 0; + currentDis = 0; + } else if (newVal && newVal.indexOf('begin') != -1) { + // 如果是下拉刷新开始,更新transform + var refresherThreshold = ins.getDataset().refresherthreshold; + _setTransformValue(refresherThreshold, ins, state, false); + } +} + +// touch开始 +function touchstart(e, ownerIns) { + var ins = _getIns(ownerIns); + var state = {}; + var dataset = {}; + ownerIns.callMethod('_handleListTouchstart'); + if (ins) { + state = ins.getState(); + dataset = ins.getDataset(); + if (_touchDisabled(e, ins, 0)) return; + } + var isTouchEnded = state.isTouchEnded; + state.oldMoveDis = 0; + var touch = _getTouch(e); + var loading = _isTrue(dataset.loading); + state.startY = touch.touchY; + startY = state.startY; + state.lastTouch = touch; + if (!loading && isTouchEnded) { + state.isTouchmoving = false; + } + state.isTouchEnded = false; + // 通知js层touch开始 + ownerIns.callMethod('_handleRefresherTouchstart', touch); +} + +// touch中 +function touchmove(e, ownerIns) { + var touch = _getTouch(e); + 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); + var moveDisObj = {}; + var moveDis = 0; + var prevent = false; + // 如果需要监听touch方向的改变 + if (watchTouchDirectionChange) { + moveDisObj = _getMoveDis(e, ins); + moveDis = moveDisObj.currentDis; + prevent = moveDisObj.isDown; + var direction = prevent ? 'top' : 'bottom'; + // 确保只在touch方向改变时通知一次js层,而不是touchmove中持续通知 + if (prevent == state.oldTouchDirection && prevent != state.oldEmitedTouchDirection) { + ownerIns.callMethod('_handleTouchDirectionChange', { direction: direction }); + state.oldEmitedTouchDirection = prevent; + } + state.oldTouchDirection = prevent; + } + // 判断是否允许下拉刷新 + if (_touchDisabled(e, ins, 1)) { + _handlePullingDown(state, ownerIns, false); + return true; + } + // 判断下拉刷新的角度是否在要求范围内 + if (!_getAngleIsInRange(e, touch, state, dataset)) { + _handlePullingDown(state, ownerIns, false); + return true; + } + moveDisObj = _getMoveDis(e, ins); + moveDis = moveDisObj.currentDis; + prevent = moveDisObj.isDown; + if (moveDis < 0) { + // moveDis小于0,将transform重置为0 + _setTransformValue(0, ins, state, false); + _handlePullingDown(state, ownerIns, false); + return true; + } + if (prevent && !state.disabledBounce) { + // 如果是用户下拉并且需要触发下拉刷新,需要通知js层将列表禁止滚动,防止在下拉刷新过程中列表也可以滚动导致的下拉刷新偏移过大的问题(在下拉刷新过程中仅通知一次) + ownerIns.callMethod('_handleScrollViewBounce', { bounce: false }); + state.disabledBounce = true; + _handlePullingDown(state, ownerIns, prevent); + return !prevent; + } + // 更新transform + _setTransformValue(moveDis, ins, state, false); + var oldRefresherStatus = state.refresherStatus; + var oldIsTouchmoving = _isTrue(dataset.oldistouchmoving); + var hasTouchmove = _isTrue(dataset.hastouchmove); + var isTouchmoving = state.isTouchmoving; + state.refresherStatus = moveDis >= refresherThreshold ? (refresherF2Enabled && moveDis > refresherF2Threshold ? 'goF2' : 'releaseToRefresh') : 'default'; + if (!isTouchmoving) { + state.isTouchmoving = true; + isTouchmoving = true; + } + if (state.isTouchEnded) { + state.isTouchEnded = false; + } + // 如果需要实时监听下拉位置偏移,则需要实时通知js层,此操作会使wxs层与js层频繁通信从而导致在一些性能较差设备中下拉刷新卡顿 + if (hasTouchmove) { + ownerIns.callMethod('_handleWxsPullingDown', { moveDis: moveDis, diffDis: moveDisObj.diffDis }); + } + // 在下拉刷新状态改变时通知js层 + if (oldRefresherStatus == undefined || oldRefresherStatus != state.refresherStatus || oldIsTouchmoving != isTouchmoving) { + ownerIns.callMethod('_handleRefresherTouchmove', moveDis, touch); + } + _handlePullingDown(state, ownerIns, prevent); + return !prevent; +} + +// touch结束 +function touchend(e, ownerIns) { + var touch = _getTouch(e); + var ins = _getIns(ownerIns); + var dataset = ins.getDataset(); + var state = ins.getState(); + if (state.disabledBounce) { + // 通知js允许列表滚动 + ownerIns.callMethod('_handleScrollViewBounce', { bounce: true }); + state.disabledBounce = false; + } + if (_touchDisabled(e, ins, 2)) return; + state.reachMaxAngle = true; + state.hitReachMaxAngleCount = 0; + state.fixedIsTopHitCount = 0; + if (!state.isTouchmoving) return; + var oldRefresherStatus = state.refresherStatus; + var oldMoveDis = state.moveDis; + var refresherThreshold = ins.getDataset().refresherthreshold; + var moveDis = _getMoveDis(e, ins).currentDis; + if (!(moveDis >= refresherThreshold && oldRefresherStatus === 'releaseToRefresh')) { + state.isTouchmoving = false; + } + // 通知js层touch结束 + ownerIns.callMethod('_handleRefresherTouchend', moveDis); + state.isTouchEnded = true; + if (oldMoveDis < refresherThreshold) return; + var animate = false; + if (moveDis >= refresherThreshold) { + moveDis = refresherThreshold; + animate = true; + } + _setTransformValue(moveDis, ins, state, animate); +} + +// #ifdef H5 +// 判断是否是pc平台 +function isPC() { + if (!navigator) return false; + if (isPCFlag != -1) return isPCFlag; + var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; + isPCFlag = agents.every(function(item) { return navigator.userAgent.indexOf(item) < 0 }); + return isPCFlag; +} + +var movable = false; + +// 在pc平台监听mousedown、mousemove、mouseup等相关事件并转为对应touch事件处理,使得在pc平台也支持通过鼠标进行下拉刷新 + +function mousedown(e, ins) { + if (!isPC()) return; + touchstart(e, ins); + movable = true; +} + +function mousemove(e, ins) { + if (!isPC() || !movable) return; + touchmove(e, ins); +} + +function mouseup(e, ins) { + if (!isPC()) return; + touchend(e, ins); + movable = false; +} + +function mouseleave(e, ins) { + if (!isPC()) return; + movable = false; +} +// #endif + + +// 修改视图层transform +function _setTransformValue(value, ins, state, animate) { + value = value || 0; + if (state.moveDis == value) return; + state.moveDis = value; + _setTransform('translateY(' + value + 'px)', ins, animate, ''); +} + +// 设置视图层transform,直接在视图层操作下拉刷新,使得js层不需要频繁和视图层通信,从而大大提升下拉刷新性能 +function _setTransform(transform, ins, animate, transition) { + var dataset = ins.getDataset(); + if (_isTrue(dataset.refreshernotransform)) return; + transform = transform == 'translateY(0px)' ? 'none' : transform; + ins.requestAnimationFrame(function() { + var stl = { 'transform': transform }; + if (animate) { + stl['transition'] = 'transform .1s linear'; + } + if (transition.length) { + stl['transition'] = transition; + } + ins.setStyle(stl); + }) +} + +// 进一步处理下拉刷新的偏移数据 +function _getMoveDis(e, ins) { + var state = ins.getState(); + var refresherThreshold = parseFloat(ins.getDataset().refresherthreshold); + var refresherOutRate = parseFloat(ins.getDataset().refresheroutrate); + var refresherPullRate = parseFloat(ins.getDataset().refresherpullrate); + var touch = _getTouch(e); + var currentStartY = !state.startY || state.startY == 'NaN' ? startY : state.startY; + var moveDis = touch.touchY - currentStartY; + var oldMoveDis = state.oldMoveDis || 0; + state.oldMoveDis = moveDis; + // 获取当前下拉刷新位置与上次的偏移量 + var diffDis = moveDis - oldMoveDis; + if (diffDis > 0) { + // 对偏移量进行进一步处理,通过refresherPullRate等配置进行约束 + diffDis = diffDis * refresherPullRate; + if (currentDis > refresherThreshold) { + diffDis = diffDis * (1 - refresherOutRate); + } + } + // 控制diffDis过大的情况,比如进入页面突然猛然下拉,此时diffDis不应进行太大的偏移 + diffDis = diffDis > 100 ? diffDis / 100 : (diffDis > 20 ? diffDis / 2.2 : diffDis); + currentDis += diffDis; + currentDis = Math.max(0, currentDis); + return { + currentDis: currentDis, + diffDis: diffDis, + isDown: diffDis > 0 + }; +} + +// 获取经过统一格式包装的当前touch对象 +function _getTouch(e) { + var touch = e; + if (e.touches && e.touches.length) { + touch = e.touches[0]; + } else if (e.changedTouches && e.changedTouches.length) { + touch = e.changedTouches[0]; + } else if (e.datail && e.datail != {}) { + touch = e.datail; + } + return { + touchX: touch.clientX, + touchY: touch.clientY + }; +} + +// 获取当前currentIns +function _getIns(ownerIns) { + var ins = ownerIns.getState().currentIns; + if (!ins) { + ownerIns.callMethod('_handlePropUpdate'); + } + return ins; +} + +// 判断当前状态是否允许下拉刷新 +function _touchDisabled(e, ins, processTag) { + var dataset = ins.getDataset(); + var state = ins.getState(); + var loading = _isTrue(dataset.loading); + var useChatRecordMode = _isTrue(dataset.usechatrecordmode); + var refresherEnabled = _isTrue(dataset.refresherenabled); + var useCustomRefresher = _isTrue(dataset.usecustomrefresher); + var usePageScroll = _isTrue(dataset.usepagescroll); + var pageScrollTop = parseFloat(dataset.pagescrolltop); + var scrollTop = parseFloat(dataset.scrolltop); + var finalScrollTop = usePageScroll ? pageScrollTop : scrollTop; + var fixedIsTop = false; + // 是否要处理滚动到顶部scrollTop不为0时候的容错,为解决在安卓中scroll-view有概率滚动到顶部时scrollTop不为0导致下拉刷新判断异常,但此方案会导致某些情况(例如滚动到距离顶部10px处)下拉抖动,因此改为通过获取zp-scroll-view的节点信息中的scrollTop进行验证的方案 + var handleFaultTolerantMove = false; + if (handleFaultTolerantMove && finalScrollTop == (state.startScrollTop || 0) && finalScrollTop <= 105) { + fixedIsTop = true; + } + var fixedIsTopHitCount = state.fixedIsTopHitCount || 0; + if (fixedIsTop) { + fixedIsTopHitCount ++; + if (fixedIsTopHitCount <= 2) { + fixedIsTop = false; + } + state.fixedIsTopHitCount = fixedIsTopHitCount; + } else { + state.fixedIsTopHitCount = 0; + } + if (handleFaultTolerantMove && processTag === 0) { + state.startScrollTop = finalScrollTop || 0; + } + if (handleFaultTolerantMove && processTag === 2) { + fixedIsTop = true; + } + return loading || useChatRecordMode || !refresherEnabled || !useCustomRefresher || + ((usePageScroll && useCustomRefresher && pageScrollTop > 5) && !fixedIsTop) || + ((!usePageScroll && useCustomRefresher && scrollTop > 5) && !fixedIsTop); +} + +// 判断下拉刷新的角度是否在要求范围内 +function _getAngleIsInRange(e, touch, state, dataset) { + var maxAngle = dataset.refreshermaxangle; + var refresherAecc = _isTrue(dataset.refresheraecc); + var lastTouch = state.lastTouch; + var reachMaxAngle = state.reachMaxAngle; + var moveDis = state.oldMoveDis; + if (!lastTouch) return true; + if (maxAngle >= 0 && maxAngle <= 90 && lastTouch) { + // 考虑下拉刷新手势由水平移动转为垂直方向移动的情况,此时不应当只判断垂直方向角度是否符合要求,应当直接禁止以避免在swiper中使用下拉刷新时,横向切换swiper途中手未离开屏幕还可以下拉刷新的问题 + if ((!moveDis || moveDis < 1) && !refresherAecc && reachMaxAngle != null && !reachMaxAngle) return false; + var x = Math.abs(touch.touchX - lastTouch.touchX); + var y = Math.abs(touch.touchY - lastTouch.touchY); + var z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); + if ((x || y) && x > 1) { + // 获取下拉刷新前后两次位移的角度 + var angle = Math.asin(y / z) / Math.PI * 180; + if (angle < maxAngle) { + // 如果角度小于配置要求,则return,同时通过hitReachMaxAngleCount控制角度判断的灵敏程度以最大程度兼容各种使用场景 + var hitReachMaxAngleCount = state.hitReachMaxAngleCount || 0; + state.hitReachMaxAngleCount = ++hitReachMaxAngleCount; + if (state.hitReachMaxAngleCount > 2) { + state.lastTouch = touch; + state.reachMaxAngle = false; + } + return false; + } + } + } + state.lastTouch = touch; + return true; +} + +// 进一步处理是否在下拉刷新并通知js层 +function _handlePullingDown(state, ins, onPullingDown) { + var oldOnPullingDown = state.onPullingDown || false; + if (oldOnPullingDown != onPullingDown) { + ins.callMethod('_handleWxsPullingDownStatusChange', onPullingDown); + } + state.onPullingDown = onPullingDown; +} + +// 判断js层传过来的值是否为true +function _isTrue(value) { + value = (typeof(value) === 'string' ? JSON.parse(value) : value) || false; + return value == true || value == 'true'; +} + +module.exports = { + touchstart: touchstart, + touchmove: touchmove, + touchend: touchend, + mousedown: mousedown, + mousemove: mousemove, + mouseup: mouseup, + mouseleave: mouseleave, + propObserver: propObserver +} diff --git a/uni_modules/z-paging/components/z-paging/z-paging.vue b/uni_modules/z-paging/components/z-paging/z-paging.vue new file mode 100644 index 0000000..8534d35 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/z-paging.vue @@ -0,0 +1,656 @@ + + + + + + + + + + + + + + diff --git a/uni_modules/z-paging/package.json b/uni_modules/z-paging/package.json new file mode 100644 index 0000000..f3871a6 --- /dev/null +++ b/uni_modules/z-paging/package.json @@ -0,0 +1,89 @@ +{ + "id": "z-paging", + "name": "z-paging", + "displayName": "【z-paging下拉刷新、上拉加载】高性能,全平台兼容。支持虚拟列表,分页全自动处理", + "version": "2.8.7", + "description": "超简单、低耦合!使用wxs+renderjs实现。支持自定义下拉刷新、上拉加载更多、虚拟列表、下拉进入二楼、自动管理空数据图、无闪动聊天分页、本地分页、国际化等数百项配置", + "keywords": [ + "下拉刷新", + "上拉加载", + "分页器", + "nvue", + "虚拟列表" +], + "repository": "https://github.com/SmileZXLee/uni-z-paging", + "engines": { + "HBuilderX": "^3.0.7" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "393727164" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/z-paging", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y", + "alipay": "n" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y", + "app-harmony": "u", + "app-uvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y", + "钉钉": "y", + "快手": "y", + "飞书": "y", + "京东": "y" + }, + "快应用": { + "华为": "y", + "联盟": "y" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/z-paging/readme.md b/uni_modules/z-paging/readme.md new file mode 100644 index 0000000..f6cbb4d --- /dev/null +++ b/uni_modules/z-paging/readme.md @@ -0,0 +1,57 @@ +# z-paging + +

+ logo +

+ +[![version](https://img.shields.io/badge/version-2.8.7-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) + + +`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) + +### 文档地址:[https://z-paging.zxlee.cn](https://z-paging.zxlee.cn) + +### 更新组件前,请注意[版本差异](https://z-paging.zxlee.cn/start/upgrade-guide.html) + +*** +### 功能&特点 +* 【配置简单】仅需两步(绑定网络请求方法、绑定分页结果数组)轻松完成完整下拉刷新,上拉加载更多功能。 +* 【低耦合,低侵入】分页自动管理。在page中无需处理任何分页相关逻辑,无需在data中定义任何分页相关变量,全由z-paging内部处理。 +* 【超灵活,支持各种类型自定义】支持自定义下拉刷新,自定义上拉加载更多等各种自定义效果;支持使用内置自动分页,同时也支持通过监听下拉刷新和滚动到底部事件自行处理;支持使用自带全屏布局规范,同时也支持将z-paging自由放在任意容器中。 +* 【功能丰富】支持国际化,支持自定义且自动管理空数据图,支持主题模式切换,支持本地分页,支持无闪动聊天分页模式,支持展示最后更新时间,支持吸顶效果,支持内部scroll-view滚动与页面滚动,支持一键滚动到顶部,支持下拉进入二楼等诸多功能。 +* 【【全平台兼容】支持vue&nvue,vue2&vue3,js&ts,支持h5、app、鸿蒙Next及各家小程序。 +* 【高性能】在app-vue、h5、微信小程序、QQ小程序上使用wxs+renderjs在视图层实现下拉刷新;支持虚拟列表,轻松渲染百万级列表数据! + +*** +### 反馈qq群 +* 官方1群`已满`:[790460711](https://jq.qq.com/?_wv=1027&k=vU2fKZZH) + +* 官方2群`已满`:[371624008](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=avPmibADf2TNi4LxkIwjCE5vbfXpa-r1&authKey=dQ%2FVDAR87ONxI4b32Py%2BvmXbhnopjHN7%2FJPtdsqJdsCPFZB6zDQ17L06Uh0kITUZ&noverify=0&group_code=371624008) +* 官方3群:[343409055](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=sIaNqiCMIjxGQVksjytCw6R8DSiibHR7&authKey=pp995q8ZzFtl5F2xUwvvceP24QTcguWW%2FRVoDnMa8JZF4L2DmS%2B%2FV%2F5sYrcgPsmW&noverify=0&group_code=343409055) + +*** + +### 预览 + +*** + +| 自定义下拉刷新效果演示 | 滑动切换选项卡+吸顶演示 | 聊天记录模式演示 | +| :----------------------------------------------------------: | :----------------------------------------------------------: | ------------------------------------------------------------ | +| ![](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) | + + +### 在线demo体验地址: + +* [https://demo.z-paging.zxlee.cn](https://demo.z-paging.zxlee.cn) + +| 扫码体验 | +| ------------------------------------------------------------ | +| ![](https://z-paging.zxlee.cn/public/img/code.png) | + +### demo下载 +* 支持vue2&vue3的`选项式api`写法demo下载,请点击页面右上角的【使用HBuilderX导入示例项目】或【下载示例项目ZIP】。 +* 支持vue3的`组合式api`写法demo下载,请访问[github](https://github.com/SmileZXLee/uni-z-paging)。 \ No newline at end of file diff --git a/uni_modules/z-paging/types/comps.d.ts b/uni_modules/z-paging/types/comps.d.ts new file mode 100644 index 0000000..1b2cd45 --- /dev/null +++ b/uni_modules/z-paging/types/comps.d.ts @@ -0,0 +1,11 @@ +declare module 'vue' { + export interface GlobalComponents { + ['z-paging']: typeof import('./comps/z-paging')['ZPaging'] + ['z-paging-swiper']: typeof import('./comps/z-paging-swiper')['ZPagingSwiper'] + ['z-paging-swiper-item']: typeof import('./comps/z-paging-swiper-item')['ZPagingSwiperItem'] + ['z-paging-empty-view']: typeof import('./comps/z-paging-empty-view')['ZPagingEmptyView'] + ['z-paging-cell']: typeof import('./comps/z-paging-cell')['ZPagingCell'] + } +} + +export {} diff --git a/uni_modules/z-paging/types/comps/_common.d.ts b/uni_modules/z-paging/types/comps/_common.d.ts new file mode 100644 index 0000000..03cb105 --- /dev/null +++ b/uni_modules/z-paging/types/comps/_common.d.ts @@ -0,0 +1,9 @@ +export interface AllowedComponentProps { + class?: unknown; + style?: unknown; +} + +export interface VNodeProps { + key?: string | number | symbol; + ref?: unknown; +} diff --git a/uni_modules/z-paging/types/comps/z-paging-cell.d.ts b/uni_modules/z-paging/types/comps/z-paging-cell.d.ts new file mode 100644 index 0000000..d0113c8 --- /dev/null +++ b/uni_modules/z-paging/types/comps/z-paging-cell.d.ts @@ -0,0 +1,29 @@ +import { AllowedComponentProps, VNodeProps } from './_common' + +// ****************************** Props ****************************** +declare interface ZPagingCellProps { + /** + * z-paging-cell样式 + */ + cellStyle?: Partial +} + +// ****************************** Slots ****************************** +declare interface ZPagingCellSlots { + // ******************** 主体布局Slot ******************** + /** + * 默认插入的view + */ + ['default']?: () => any +} + +declare interface _ZPagingCell { + new (): { + $props: AllowedComponentProps & + VNodeProps & + ZPagingCellProps + $slots: ZPagingCellSlots + } +} + +export declare const ZPagingCell: _ZPagingCell diff --git a/uni_modules/z-paging/types/comps/z-paging-empty-view.d.ts b/uni_modules/z-paging/types/comps/z-paging-empty-view.d.ts new file mode 100644 index 0000000..47e489f --- /dev/null +++ b/uni_modules/z-paging/types/comps/z-paging-empty-view.d.ts @@ -0,0 +1,95 @@ +import { AllowedComponentProps, VNodeProps } from './_common' + +// ****************************** Props ****************************** +declare interface ZPagingEmptyViewProps { + /** + * 空数据图片是否铺满z-paging,默认为是。若设置为否,则为填充满z-paging的剩余部分 + * @default false + * @since 2.0.3 + */ + emptyViewFixed?: boolean; + + /** + * 空数据图描述文字 + * @default "没有数据哦~" + */ + emptyViewText?: string; + + /** + * 空数据图图片,默认使用z-paging内置的图片 + * - 建议使用绝对路径,开头不要添加"@",请以"/"开头 + */ + emptyViewImg?: string; + + /** + * 空数据图点击重新加载文字 + * @default "重新加载" + * @since 1.6.7 + */ + emptyViewReloadText?: string; + + /** + * 空数据图样式,可设置空数据view的top等 + * - 如果空数据图不是fixed布局,则此处是`margin-top` + */ + emptyViewStyle?: Partial; + + /** + * 空数据图img样式 + */ + emptyViewImgStyle?: Partial; + + /** + * 空数据图描述文字样式 + */ + emptyViewTitleStyle?: Partial; + + /** + * 空数据图重新加载按钮样式 + * @since 1.6.7 + */ + emptyViewReloadStyle?: Partial; + + /** + * 是否显示空数据图重新加载按钮(无数据时) + * @default false + * @since 1.6.7 + */ + showEmptyViewReload?: boolean; + + /** + * 是否是加载失败 + * @default false + */ + isLoadFailed?: boolean; + + /** + * 空数据图中布局的单位 + * @default 'rpx' + * @since 2.6.7 + */ + unit?: 'rpx' | 'px'; + + // ****************************** Events ****************************** + /** + * 点击了重新加载按钮 + */ + onReload?: () => void + + /** + * 点击了空数据view + * @since 2.3.3 + */ + onViewClick?: () => void +} + +declare interface _ZPagingEmptyView { + new (): { + $props: AllowedComponentProps & + VNodeProps & + ZPagingEmptyViewProps + } +} + +export declare const ZPagingEmptyView: _ZPagingEmptyView + diff --git a/uni_modules/z-paging/types/comps/z-paging-swiper-item.d.ts b/uni_modules/z-paging/types/comps/z-paging-swiper-item.d.ts new file mode 100644 index 0000000..e956a97 --- /dev/null +++ b/uni_modules/z-paging/types/comps/z-paging-swiper-item.d.ts @@ -0,0 +1,95 @@ +import { AllowedComponentProps, VNodeProps } from './_common' + +// ****************************** Props ****************************** +declare interface ZPagingSwiperItemProps { + /** + * 当前组件的index,也就是当前组件是swiper中的第几个 + * @default 0 + */ + tabIndex?: number + + /** + * 当前swiper切换到第几个index + * @default 0 + */ + currentIndex?: number + + /** + * 是否使用虚拟列表。使用页面滚动或nvue时,不支持虚拟列表。在nvue中z-paging内置了list组件,效果与虚拟列表类似,并且可以提供更好的性能。 + * @default false + */ + useVirtualList?: boolean + + /** + * 虚拟列表cell高度模式,默认为`fixed`,也就是每个cell高度完全相同,将以第一个cell高度为准进行计算。 + * @default 'fixed' + */ + cellHeightMode?: 'fixed' | 'dynamic' + + /** + * 预加载的列表可视范围(列表高度)页数。此数值越大,则虚拟列表中加载的dom越多,内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题。 + * @default 12 + */ + preloadPage?: number | string + + /** + * 虚拟列表列数,默认为1。常用于每行有多列的情况,例如每行有2列数据,需要将此值设置为2。 + * @default 1 + * @since 2.2.8 + */ + virtualListCol?: number | string + + /** + * 虚拟列表scroll取样帧率,默认为80,过低容易出现白屏问题,过高容易出现卡顿问题 + * @default 80 + */ + virtualScrollFps?: number | string + + /** + * 是否在z-paging内部循环渲染列表(使用内置列表)。 + * @default false + */ + useInnerList?: boolean + + /** + * 内置列表cell的key名称(仅nvue有效,在nvue中开启use-inner-list时必须填此项) + * @since 2.2.7 + */ + cellKeyName?: string + + /** + * innerList样式 + */ + innerListStyle?: Partial +} + +// ****************************** Methods ****************************** +declare interface _ZPagingSwiperItemRef { + /** + * 重新加载分页数据,pageNo恢复为默认值,相当于下拉刷新的效果 + * + * @param [animate=false] 是否展示下拉刷新动画 + */ + reload: (animate?: boolean) => void; + + /** + * 请求结束 + * - 当通过complete传进去的数组长度小于pageSize时,则判定为没有更多了 + * + * @param [data] 请求结果数组 + * @param [success=true] 是否请求成功 + */ + complete: (data?: any[] | false, success?: boolean) => void; +} + +declare interface _ZPagingSwiperItem { + new (): { + $props: AllowedComponentProps & + VNodeProps & + ZPagingSwiperItemProps + } +} + +export declare const ZPagingSwiperItem: _ZPagingSwiperItem + +export declare const ZPagingSwiperItemRef: _ZPagingSwiperItemRef diff --git a/uni_modules/z-paging/types/comps/z-paging-swiper.d.ts b/uni_modules/z-paging/types/comps/z-paging-swiper.d.ts new file mode 100644 index 0000000..2ab7bb6 --- /dev/null +++ b/uni_modules/z-paging/types/comps/z-paging-swiper.d.ts @@ -0,0 +1,89 @@ +import { AllowedComponentProps, VNodeProps } from './_common' + +// ****************************** Props ****************************** +declare interface ZPagingSwiperProps { + /** + * 是否使用fixed布局,若使用fixed布局,则z-paging-swiper的父view无需固定高度,z-paging高度默认铺满屏幕,页面中的view请放z-paging-swiper标签内,需要固定在顶部的view使用slot="top"包住,需要固定在底部的view使用slot="bottom"包住。 + * @default true + */ + fixed?: boolean + + /** + * 是否开启底部安全区域适配 + * @default false + */ + safeAreaInsetBottom?: boolean + + /** + * z-paging-swiper样式 + */ + swiperStyle?: Partial +} + + +// ****************************** Slots ****************************** +declare interface ZPagingSwiperSlots { + // ******************** 主体布局Slot ******************** + /** + * 默认插入的view + */ + ['default']?: () => any + + /** + * 可以将自定义导航栏、tab-view等需要固定的(不需要跟着滚动的)元素放入slot="top"的view中。 + * 注意,当有多个需要固定的view时,请用一个view包住它们,并且在这个view上设置slot="top"。需要固定在顶部的view请勿设置position: fixed; + * @since 1.5.5 + */ + ['top']?: () => any + + /** + * 可以将需要固定在底部的(不需要跟着滚动的)元素放入slot="bottom"的view中。 + * 注意,当有多个需要固定的view时,请用一个view包住它们,并且在这个view上设置slot="bottom"。需要固定在底部的view请勿设置position: fixed; + * @since 1.6.2 + */ + ['bottom']?: () => any + + /** + * 可以将需要固定在左侧的(不需要跟着滚动的)元素放入slot="left"的view中。 + * 注意,当有多个需要固定的view时,请用一个view包住它们,并且在这个view上设置slot="left"。需要固定在左侧的view请勿设置position: fixed; + * @since 2.2.3 + */ + ['left']?: () => any + + /** + * 可以将需要固定在左侧的(不需要跟着滚动的)元素放入slot="right"的view中。 + * 注意,当有多个需要固定的view时,请用一个view包住它们,并且在这个view上设置slot="right"。需要固定在右侧的view请勿设置position: fixed; + * @since 2.2.3 + */ + ['right']?: () => any +} + +// ****************************** Methods ****************************** +declare interface _ZPagingSwiperRef { + /** + * 更新slot="left"和slot="right"宽度,当slot="left"或slot="right"宽度动态改变后调用 + * + * @since 2.3.5 + */ + updateLeftAndRightWidth: () => void; + + /** + * 更新fixed模式下z-paging-swiper的布局,在onShow时候调用,以修复在iOS+h5+tabbar+fixed+底部有安全区域的设备中从tabbar页面跳转到无tabbar页面后返回,底部有一段空白区域的问题 + * + * @since 2.6.5 + */ + updateFixedLayout: () => void; +} + +declare interface _ZPagingSwiper { + new (): { + $props: AllowedComponentProps & + VNodeProps & + ZPagingSwiperProps + $slots: ZPagingSwiperSlots + } +} + +export declare const ZPagingSwiper: _ZPagingSwiper + +export declare const ZPagingSwiperRef: _ZPagingSwiperRef diff --git a/uni_modules/z-paging/types/comps/z-paging.d.ts b/uni_modules/z-paging/types/comps/z-paging.d.ts new file mode 100644 index 0000000..6f225c7 --- /dev/null +++ b/uni_modules/z-paging/types/comps/z-paging.d.ts @@ -0,0 +1,2131 @@ +import { AllowedComponentProps, VNodeProps } from './_common' + +type _Arrayable = T | T[]; + +/** + * i18n配置信息 + */ +type _I18nText = Partial>; + +declare global { + namespace ZPagingEnums { + /** + * query的触发来源:user-pull-down:用户主动下拉刷新 reload:通过reload触发 refresh:通过refresh触发 load-more:通过滚动到底部加载更多或点击底部加载更多触发 + */ + type QueryFrom = 'user-pull-down' | 'reload' | 'refresh' | 'load-more'; + + /** + * 下拉刷新状态:default:默认状态 release-to-refresh:松手立即刷新 loading:刷新中 complete:刷新结束 go-f2:松手进入二楼 + */ + type RefresherStatus = 'default' | 'release-to-refresh' | 'loading' | 'complete' | 'go-f2'; + + /** + * 下拉进入二楼状态: go:二楼开启 close:二楼关闭 + */ + type GoF2Status = 'go' | 'close'; + + /** + * 底部加载更多状态:default:默认状态 loading:加载中 no-more:没有更多数据 fail:加载失败 + */ + type LoadMoreStatus = 'default' | 'loading' | 'no-more' | 'fail'; + + /** + * 列表触摸的方向,top代表用户将列表向上移动(scrollTop不断减小),bottom代表用户将列表向下移动(scrollTop不断增大) + */ + type TouchDirection = 'top' | 'bottom'; + + /** + * 列表滚动的方向,top代表用户将列表向上移动(scrollTop不断减小),bottom代表用户将列表向下移动(scrollTop不断增大) + */ + type ScrollDirection = 'top' | 'bottom'; + } + + namespace ZPagingParams { + /** + * z-paging返回数据 + * + * @since 2.5.3 + */ + interface ReturnData { + /** + * 总列表 + */ + totalList: T[]; + /** + * 是否没有更多数据 + */ + noMore: boolean; + } + + /** + * 嵌套父容器信息 [list组件](https://uniapp.dcloud.net.cn/component/list.html) + * + * @since 2.0.4 + */ + interface SetSpecialEffectsArgs { + /** + * 和list同时滚动的组件id,应为外层的scroller + */ + id?: string; + /** + * 要吸顶的header顶部距离scroller顶部的距离 + * - Android暂不支持 + * + * @default 0 + */ + headerHeight?: number; + } + + /** + * touchmove信息 + */ + interface RefresherTouchmoveInfo { + /** 下拉的距离 */ + pullingDistance: number; + /** 前后两次回调滑动距离的差值 */ + dy: number; + /** refresh组件高度 */ + viewHeight: number; + /** pullingDistance/viewHeight的比值 */ + rate: number; + } + + /** + * 默认事件处理 + */ + type DefaultEventHandler = (value: boolean) => void; + + /** + * 使用虚拟列表或内置列表时点击了cell的信息 + */ + interface InnerCellClickInfo { + /** 当前点击的item */ + item: any; + /** 当前点击的index */ + index: number; + } + + /** + * 键盘的高度信息 + */ + interface KeyboardHeightInfo { + /** 键盘的高度 */ + height: number; + } + + /** + * 列表滚动信息(vue) + */ + interface ScrollInfo { + detail: { + scrollLeft: number; + scrollTop: number; + scrollHeight: number; + scrollWidth: number; + deltaX: number; + deltaY: number; + } + } + /** + * 列表滚动信息(nvue) + */ + interface ScrollInfoN { + contentSize: { + width: number; + height: number; + }; + contentOffset: { + x: number; + y: number; + }; + isDragging: boolean; + } + + /** + * 滚动结束时触发事件信息 + */ + interface ScrollendEvent { + contentSize: { + width: number; + height: number; + }; + contentOffset: { + x: number; + y: number; + }; + isDragging: boolean; + } + + /** + * 下拉刷新Slot的props + */ + interface RefresherSlotProps { + /** 下拉刷新状态:default:默认状态 release-to-refresh:松手立即刷新 loading:刷新中 complete:刷新结束 go-f2:松手进入二楼 */ + refresherStatus: ZPagingEnums.RefresherStatus; + } + + /** + * 空数据图Slot的props + */ + interface EmptySlotProps { + /** 是否加载失败: 加载失败,false: 加载成功) */ + isLoadFailed: boolean; + } + + /** + * 虚拟列表&内置列表中cell Slot的props + */ + interface InnerListCellSlotProps { + /** 当前item */ + item: any; + /** 当前index */ + index: number; + } + + /** + * 聊天记录加载中Slot的props + */ + interface ChatLoadingSlotProps { + /** 底部加载更多状态:default:默认状态 loading:加载中 no-more:没有更多数据 fail:加载失败 */ + loadingMoreStatus: ZPagingEnums.LoadMoreStatus; + } + } + + /** + * 虚拟列表数据项 + * + * @since 2.7.7 + */ + type ZPagingVirtualItem = T & { + /** + * 元素真实索引 + */ + zp_index: number; + }; +} + +// ****************************** Props ****************************** +declare interface ZPagingProps { + // ******************** 数据&布局配置 ******************** + /** + * 父组件v-model所绑定的list的值 + * @default [] + */ + value?: any[] + + /** + * 自定义初始的pageNo(从第几页开始) + * @default 1 + */ + defaultPageNo?: number + + /** + * 自定义pageSize(每页显示多少条) + * - 必须和后端的pageSize一致,例如后端分页的pageSize为15,此属性必须改为15 + * @default 10 + */ + defaultPageSize?: number + + /** + * z-paging是否使用fixed布局 + * - 若使用fixed布局,则z-paging的父view无需固定高度,z-paging高度默认铺满屏幕,页面中的view请放在z-paging标签内,需要固定在顶部的view使用slot="top"包住,需要固定在底部的view使用slot="bottom"包住 + * @default true + * @since 1.5.6 + */ + fixed?: boolean + + /** + * 是否开启底部安全区域适配 + * @default false + * @since 1.6.1 + */ + safeAreaInsetBottom?: boolean + + /** + * 开启底部安全区域适配后,是否使用placeholder形式实现。 + * - 为否时滚动区域会自动避开底部安全区域,也就是所有滚动内容都不会挡住底部安全区域,若设置为是,则滚动时滚动内容会挡住底部安全区域,但是当滚动到底部时才会避开底部安全区域 + * @default false + * @since 2.2.7 + */ + useSafeAreaPlaceholder?: boolean + + /** + * 使用页面滚动,默认为否。当设置为是时,则使用页面的滚动而非此组件内部的scroll-view的滚动。 + * @default false + */ + usePageScroll?: boolean + + /** + * 使用页面滚动时,是否在不满屏时自动填充满屏幕 + * @default true + * @since 2.0.6 + */ + autoFullHeight?: boolean + + /** + * loading(下拉刷新、上拉加载更多)的主题样式,支持black,white + * @default 'black' + */ + defaultThemeStyle?: 'black' | 'white' + + /** + * 设置z-paging的style,部分平台(如微信小程序)无法直接修改组件的style,可使用此属性代替。 + */ + pagingStyle?: Partial + + /** + * 设置z-paging的class,优先级低于pagingStyle和height、width、maxWidth、bgColor + * @since 2.8.7 + */ + pagingClass?: string | string[] | Record + + /** + * z-paging的高度,优先级低于paging-style中设置的height + * - 传字符串,如100px、100rpx、100% + */ + height?: string + + /** + * z-paging的宽度,优先级低于paging-style中设置的width + * - 传字符串,如100px、100rpx、100% + */ + width?: string + + /** + * z-paging的最大宽度,优先级低于paging-style中设置的max-width + * - 默认为空,也就是铺满窗口宽度,若设置了特定值则会自动添加margin: 0 auto + */ + maxWidth?: string + + /** + * z-paging的背景色(为css中的background,因此也可以设置渐变,背景图片等),优先级低于paging-style中设置的background-color。 + * - 传字符串,如"#ffffff" + */ + bgColor?: string + + /** + * 是否监听列表触摸方向改变 + * @default false + * @since 2.3.0 + */ + watchTouchDirectionChange?: boolean + + /** + * 是否监听列表滚动方向改变 + * @default false + * @since 2.8.7 + */ + watchScrollDirectionChange?: boolean + + /** + * 是否只使用基础布局 + * - 设置为true后将关闭mounted自动请求数据、关闭下拉刷新和滚动到底部加载更多,强制隐藏空数据图 + * @default false + * @since 2.8.7 + */ + layoutOnly?: boolean + + /** + * 调用complete后延迟处理的时间,单位为毫秒,优先级高于min-delay + * @default 0 + * @since 1.9.6 + */ + delay?: number | string + + /** + * 触发@query后最小延迟处理的时间,单位为毫秒,优先级低于delay + * @default 0 + * @since 2.0.9 + */ + minDelay?: number | string + + /** + * 请求失败是否触发reject + * @default true + * @since 2.6.1 + */ + callNetworkReject?: boolean + + /** + * z-paging中默认布局的单位 + * @default rpx + * @since 2.6.7 + */ + unit?: 'rpx' | 'px' + + /** + * 自动拼接complete中传过来的数组 + * - 若设置为false,则complete中传过来的数组会直接覆盖list + * @default true + * @since 1.8.8 + */ + concat?: boolean + + /** + * 为保证数据一致,设置当前tab切换时的标识key,并在complete中传递相同key,若二者不一致,则complete将不会生效 + * @since 1.6.4 + */ + dataKey?: number | string | Record + + /** + * 【极简写法】自动注入的list名,可自动修改父view(包含ref="paging")中对应name的list值 + * - z-paging标签必须设置ref="paging" + * @since 1.8.5 + */ + autowireListName?: string + + /** + * 【极简写法】自动注入的query名,可自动调用父view(包含ref="paging")中的query方法 + * - z-paging标签必须设置ref="paging" + * @since 1.8.5 + */ + autowireQueryName?: string + + /** + * 获取分页数据Function,功能与@query类似。若设置了fetch则@query将不再触发。 + * @since 2.7.8 + */ + fetch?: (...args: any[]) => any; + + /** + * fetch的附加参数,fetch配置后有效 + * @since 2.7.8 + */ + fetchParams?: Record + + // ******************** reload相关配置 ******************** + /** + * z-paging mounted 后自动调用 reload 方法 (mounted 后自动调用接口) + * @default true + * @since 2.4.3 + */ + auto?: boolean + + /** + * reload 时自动滚动到顶部 + * - 如果reload时list被清空导致占位消失,也可能会自动返回到顶部,因此如果是这种情况还需要将autoCleanListWhenReload设置为false + * @default true + */ + autoScrollToTopWhenReload?: boolean + + /** + * reload时立即自动清空原list若立即自动清空,则在reload之后、请求回调之前页面是空白的 + * @default true + */ + autoCleanListWhenReload?: boolean + + /** + * 列表刷新时自动显示下拉刷新 view + * @default false + * @since 1.7.2 + */ + showRefresherWhenReload?: boolean + + /** + * 列表刷新时自动显示加载更多view,且为加载中状态 (仅初始设置有效,不可动态修改) + * @default false + * @since 1.7.2 + */ + showLoadingMoreWhenReload?: boolean + + /** + * 组件 created 时立即触发 reload (可解决一些情况下先看到页面再看到 loading 的问题) + * - auto 为 true 时有效。为否时将在 mounted+nextTick 后触发 reload + * @default false + * @since 2.2.3 + */ + createdReload?: boolean + + // ******************** 下拉刷新配置 ******************** + /** + * 是否开启下拉刷新 + * @default true + */ + refresherEnabled?: boolean + + /** + * 设置自定义下拉刷新阈值,默认单位为px。支持传100、"100px"或"100rpx" + * - nvue无效 + * @default '80rpx' + */ + refresherThreshold?: number | string + + /** + * 是否开启下拉刷新状态栏占位,适用于隐藏导航栏时,下拉刷新需要避开状态栏高度的情况 + * @default false + * @since 2.6.1 + */ + useRefresherStatusBarPlaceholder?: boolean + + /** + * 是否只使用下拉刷新 + * - 设置为true后将关闭mounted自动请求数据、关闭滚动到底部加载更多,强制隐藏空数据图 + * @default false + */ + refresherOnly?: boolean + + /** + * 是否使用自定义的下拉刷新,默认为是,即使用z-paging的下拉刷新 + * - 设置为false即代表使用uni scroll-view自带的下拉刷新,h5、App、微信小程序以外的平台不支持uni scroll-view自带的下拉刷新 + * @default true + */ + useCustomRefresher?: boolean + + /** + * 用户下拉刷新时是否触发reload方法 + * @default true + */ + reloadWhenRefresh?: boolean + + /** + * 下拉刷新的主题样式,支持black,white + * @default 'black' + */ + refresherThemeStyle?: string + + /** + * 自定义下拉刷新中左侧图标的样式 + */ + refresherImgStyle?: Partial + + /** + * 自定义下拉刷新中右侧状态描述文字的样式 + */ + refresherTitleStyle?: Partial + + /** + * 自定义下拉刷新中右侧最后更新时间文字的样式 + * - show-refresher-update-time为true时有效 + */ + refresherUpdateTimeStyle?: Partial + + /** + * 是否实时监听下拉刷新中进度,并通过@refresherTouchmove传递给父组件 + * @default false + * @since 2.1.0 + */ + watchRefresherTouchmove?: boolean + + /** + * 是否显示最后更新时间 + * @default false + * @since 1.6.7 + */ + showRefresherUpdateTime?: boolean + + /** + * 自定义下拉刷新默认状态下的文字 + * - 支持直接传字符串或形如:{'en':'英文','zh-Hans':'简体中文','zh-Hant':'繁体中文'}的i18n配置 + * @default '继续下拉刷新' + */ + refresherDefaultText?: string | _I18nText + + /** + * 自定义下拉刷新松手立即刷新状态下的文字 + * - 支持直接传字符串或形如:{'en':'英文','zh-Hans':'简体中文','zh-Hant':'繁体中文'}的i18n配置 + * @default '松开立即刷新' + */ + refresherPullingText?: string | _I18nText + + /** + * 自定义下拉刷新刷新中状态下的文字 + * - 支持直接传字符串或形如:{'en':'英文','zh-Hans':'简体中文','zh-Hant':'繁体中文'}的i18n配置 + * @default '正在刷新...' + */ + refresherRefreshingText?: string | _I18nText + + /** + * 自定义下拉刷新刷新结束状态下的文字 + * - 支持直接传字符串或形如:{'en':'英文','zh-Hans':'简体中文','zh-Hant':'繁体中文'}的i18n配置 + * @default '刷新成功' + * @since 2.0.6 + */ + refresherCompleteText?: string | _I18nText + + /** + * 自定义下拉刷新默认状态下的图片 + */ + refresherDefaultImg?: string + + /** + * 自定义下拉刷新松手立即刷新状态下的图片 + */ + refresherPullingImg?: string + + /** + * 自定义下拉刷新刷新中状态下的图片 + */ + refresherRefreshingImg?: string + + /** + * 自定义下拉刷新刷新结束状态下的图片 + */ + refresherCompleteImg?: string + + /** + * 自定义下拉刷新刷新中状态下是否展示旋转动画 + * @default true + * @since 2.5.8 + */ + refresherRefreshingAnimated?: boolean + + /** + * 是否开启自定义下拉刷新刷新结束回弹动画效果 + * @default true + */ + refresherEndBounceEnabled?: boolean + + /** + * 设置系统下拉刷新默认样式,支持设置 black,white,none + * @default 'black' + */ + refresherDefaultStyle?: string + + /** + * 设置自定义下拉刷新区域背景颜色 + * @default '#FFFFFF00' + */ + refresherBackground?: string + + /** + * 设置固定的自定义下拉刷新区域背景颜色 + * @default '#FFFFFF00' + */ + refresherFixedBackground?: string + + /** + * 设置固定的自定义下拉刷新区域高度 + * @default 0 + */ + refresherFixedBacHeight?: number | string + + /** + * 设置自定义下拉刷新默认状态下回弹动画时间,单位为毫秒 + * @default 100 + * @since 2.3.1 + */ + refresherDefaultDuration?: number | string + + /** + * 自定义下拉刷新结束以后延迟收回的时间,单位为毫秒 + * @default 0 + * @since 2.0.6 + */ + refresherCompleteDelay?: number | string + + /** + * 自定义下拉刷新结束收回动画时间,单位为毫秒 + * @default 300 + * @since 2.0.6 + */ + refresherCompleteDuration?: number | string + + /** + * 下拉刷新时下拉到“松手立即刷新”状态时是否使手机短振动 + * @default false + * @since 2.4.7 + */ + refresherVibrate?: boolean + + /** + * 自定义下拉刷新刷新中状态是否允许列表滚动 + * @default true + */ + refresherRefreshingScrollable?: boolean + + /** + * 自定义下拉刷新结束状态下是否允许列表滚动 + * @default false + * @since 2.1.1 + */ + refresherCompleteScrollable?: boolean + + /** + * 设置自定义下拉刷新下拉超出阈值后继续下拉位移衰减的比例 + * @default 0.65 + */ + refresherOutRate?: number + + /** + * 是否开启下拉进入二楼功能 + * @default false + * @since 2.7.7 + */ + refresherF2Enabled?: boolean + + /** + * 下拉进入二楼阈值 + * @default '200rpx' + * @since 2.7.7 + */ + refresherF2Threshold?: number | string + + /** + * 下拉进入二楼动画时间,单位为毫秒 + * @default 200 + * @since 2.7.7 + */ + refresherF2Duration?: number | string + + /** + * 下拉进入二楼状态松手后是否弹出二楼 + * @default true + * @since 2.7.7 + */ + showRefresherF2?: boolean + + /** + * 设置自定义下拉刷新下拉时实际下拉位移与用户下拉距离的比值 + * @default 0.75 + * @since 2.3.7 + */ + refresherPullRate?: number + + /** + * 自定义下拉刷新下拉帧率,默认为40,过高可能会出现抖动问题 + * @default 40 + */ + refresherFps?: number | string + + /** + * 自定义下拉刷新允许触发的最大下拉角度,默认为40度 + * - 值小于0或大于90时,代表不受角度限 + * @default 40 + * @since 2.5.8 + */ + refresherMaxAngle?: number | string + + /** + * 自定义下拉刷新的角度由未达到最大角度变到达到最大角度时,是否继续下拉刷新手势 + * @default false + */ + refresherAngleEnableChangeContinued?: boolean + + /** + * 下拉刷新时是否禁止下拉刷新view跟随用户触摸竖直移动 + * @default false + * @since 2.5.8 + */ + refresherNoTransform?: boolean + + // ******************** 底部加载更多配置 ******************** + /** + * 是否启用加载更多数据(含滑动到底部加载更多数据和点击加载更多数据) + * @default true + */ + loadingMoreEnabled?: boolean + + /** + * 距底部/右边多远时,触发 scrolltolower 事件,默认单位为px + * - 支持传100、"100px"或"100rpx" + * @default '100rpx' + */ + lowerThreshold?: number | string + + /** + * 是否启用滑动到底部加载更多数据 + * @default true + */ + toBottomLoadingMoreEnabled?: boolean + + /** + * 底部加载更多的主题样式,支持black,white + * @default 'black' + */ + loadingMoreThemeStyle?: string + + /** + * 自定义底部加载更多样式;如:{'background':'red'} + * - 此属性无法修改文字样式,修改文字样式请使用loading-more-title-custom-style + */ + loadingMoreCustomStyle?: Partial + + /** + * 自定义底部加载更多文字样式;如:{'color':'red'} + * @since 2.1.7 + */ + loadingMoreTitleCustomStyle?: Partial + + /** + * 自定义底部加载更多加载中动画样式 + */ + loadingMoreLoadingIconCustomStyle?: Partial + + /** + * 自定义底部加载更多加载中动画图标类型 + * - 可选flower或circle,默认为flower (nvue不支持) + * @default 'flower' + */ + loadingMoreLoadingIconType?: 'flower' | 'circle' + + /** + * 自定义底部加载更多加载中动画图标图片 + * - 若设置则使用自定义的动画图标,loading-more-loading-icon-type将无效 (nvue无效) + */ + loadingMoreLoadingIconCustomImage?: string + + /** + * 底部加载更多加载中view是否展示旋转动画 + * - loading-more-loading-icon-custom-image有值时有效,nvue无效 + * @default true + * @since 1.9.4 + */ + loadingMoreLoadingAnimated?: boolean + + /** + * 滑动到底部"默认"文字 + * - 支持直接传字符串或形如:{'en':'英文','zh-Hans':'简体中文','zh-Hant':'繁体中文'}的i18n配置 + * @default '点击加载更多' + */ + loadingMoreDefaultText?: string | _I18nText + + /** + * 滑动到底部"加载中"文字 + * - 支持直接传字符串或形如:{'en':'英文','zh-Hans':'简体中文','zh-Hant':'繁体中文'}的i18n配置 + * @default '正在加载...' + */ + loadingMoreLoadingText?: string | _I18nText + + /** + * 滑动到底部"没有更多"文字 + * - 支持直接传字符串或形如:{'en':'英文','zh-Hans':'简体中文','zh-Hant':'繁体中文'}的i18n配置 + * @default '没有更多了' + */ + loadingMoreNoMoreText?: string | _I18nText + + /** + * 滑动到底部"加载失败"文字 + * - 支持直接传字符串或形如:{'en':'英文','zh-Hans':'简体中文','zh-Hant':'繁体中文'}的i18n配置 + * @default '加载失败,点击重新加载' + */ + loadingMoreFailText?: string | _I18nText + + /** + * 当没有更多数据且分页内容未超出z-paging时是否隐藏没有更多数据的view + * - nvue不支持,nvue中请使用hide-no-more-by-limit控制 + * @default false + * @since 2.4.3 + */ + hideNoMoreInside?: boolean + + /** + * 当没有更多数据且分页数组长度少于这个值时,隐藏没有更多数据的view + * - 默认为0,代表不限制。此属性优先级高于`hide-no-more-inside` + * @default 0 + * @since 2.4.3 + */ + hideNoMoreByLimit?: number + + /** + * 当分页未满一屏时,是否自动加载更多 (nvue无效) + * @default false + * @since 2.0.0 + */ + insideMore?: boolean + + /** + * 滑动到底部状态为默认状态时,以加载中的状态展示 + * - 若设置为是,可避免滚动到底部看到默认状态然后立刻变为加载中状态的问题,但分页数量未超过一屏时,不会显示【点击加载更多】 + * @default false + * @since 2.2.0 + */ + loadingMoreDefaultAsLoading?: boolean + + /** + * 是否显示没有更多数据的view + * @default true + */ + showLoadingMoreNoMoreView?: boolean + + /** + * 是否显示默认的加载更多text + * @default true + */ + showDefaultLoadingMoreText?: boolean + + /** + * 是否显示没有更多数据的分割线,默认为是 + * @default true + */ + showLoadingMoreNoMoreLine?: boolean + + /** + * 自定义底部没有更多数据的分割线样式 + */ + loadingMoreNoMoreLineCustomStyle?: Partial + + // ******************** 空数据与加载失败配置 ******************** + /** + * 是否强制隐藏空数据图 + * @default false + */ + hideEmptyView?: boolean + + /** + * 空数据图片是否铺满z-paging + * - 默认为否,即填充满z-paging内列表(滚动区域)部分。若设置为否,则为填铺满整个z-paging + * @default false + * @since 2.0.3 + */ + emptyViewFixed?: boolean + + /** + * 空数据图片是否垂直居中 + * - 默认为是,若设置为否即为从空数据容器顶部开始显示 (empty-view-fixed为false时有效) + * @default true + * @since 2.0.6 + */ + emptyViewCenter?: boolean + + /** + * 空数据图描述文字 + * - 支持直接传字符串或形如:{'en':'英文','zh-Hans':'简体中文','zh-Hant':'繁体中文'}的i18n配置 + * @default '没有数据哦~' + */ + emptyViewText?: string | _I18nText + + /** + * 空数据图图片,默认使用z-paging内置的图片 + * - 建议使用绝对路径,开头不要添加"@",请以"/"开头 + */ + emptyViewImg?: string + + /** + * 空数据图“加载失败”图片,默认使用z-paging内置的图片 + * - 建议使用绝对路径,开头不要添加"@",请以"/"开头 + * @since 1.6.7 + */ + emptyViewErrorImg?: string + + /** + * 空数据图点击重新加载文字 + * - 支持直接传字符串或形如:{'en':'英文','zh-Hans':'简体中文','zh-Hant':'繁体中文'}的i18n配置 + * @default '重新加载' + * @since 1.6.7 + */ + emptyViewReloadText?: string | _I18nText + + /** + * 空数据图“加载失败”描述文字 + * - 支持直接传字符串或形如:{'en':'英文','zh-Hans':'简体中文','zh-Hant':'繁体中文'}的i18n配置 + * @default '很抱歉,加载失败' + * @since 1.6.7 + */ + emptyViewErrorText?: string | _I18nText + /** + * 空数据图父view样式 + */ + emptyViewSuperStyle?: Partial + + /** + * 空数据图样式,可设置空数据view的top等,如::empty-view-style="{'top':'100rpx'}" + */ + emptyViewStyle?: Partial + + /** + * 空数据图img样式 + */ + emptyViewImgStyle?: Partial + + /** + * 空数据图描述文字样式 + */ + emptyViewTitleStyle?: Partial + + /** + * 空数据图重新加载按钮样式 + * @since 1.6.7 + */ + emptyViewReloadStyle?: Partial + + /** + * 是否显示空数据图重新加载按钮(无数据时) + * @default false + * @since 1.6.7 + */ + showEmptyViewReload?: boolean + + /** + * 加载失败时是否显示空数据图重新加载按钮 + * @default true + * @since 1.6.7 + */ + showEmptyViewReloadWhenError?: boolean + + /** + * 加载中时是否自动隐藏空数据图 + * @default true + */ + autoHideEmptyViewWhenLoading?: boolean + + /** + * 用户下拉列表触发下拉刷新加载中时是否自动隐藏空数据图 + * @default true + * @since 2.0.9 + */ + autoHideEmptyViewWhenPull?: boolean + + // ******************** 全屏Loading配置 ******************** + /** + * 第一次加载后自动隐藏loading slot + * @default true + */ + autoHideLoadingAfterFirstLoaded?: boolean + + /** + * loading slot的父view是否铺满屏幕并固定 + * - 设置为true后,插入的loading的父view会铺满全屏。注意:插入的loading需要设置height:100%(nvue为flex:1)才可铺满全屏。loading内的view从导航栏顶部开始,会被导航栏盖住,请妥善处理。 + * @default false + * @since 2.0.9 + */ + loadingFullFixed?: boolean + + /** + * 是否自动显示系统Loading:即uni.showLoading + * - 若开启则将在刷新列表时(调用reload、refresh时)显示。下拉刷新和滚动到底部加载更多不会显示。 + * @default false + * @since 2.3.7 + */ + autoShowSystemLoading?: boolean + + /** + * 显示系统Loading时显示的文字 + * - 支持直接传字符串或形如:{'en':'英文','zh-Hans':'简体中文','zh-Hant':'繁体中文'}的i18n配置 + * @default '加载中...' + * @since 2.3.7 + */ + systemLoadingText?: string | _I18nText + + /** + * 显示系统Loading时是否显示透明蒙层,防止触摸穿透。H5、App、微信小程序、百度小程序有效。 + * @default true + * @since 2.3.9 + */ + systemLoadingMask?: boolean + + // ******************** 返回顶部按钮配 ******************** + /** + * 自动显示点击返回顶部按钮 + * @default false + */ + autoShowBackToTop?: boolean + + /** + * 点击返回顶部按钮显示/隐藏的阈值(滚动距离),默认单位为px + * - 支持传100、"100px"或"100rpx" + * @default 400rpx + */ + backToTopThreshold?: number | string + + /** + * 点击返回顶部按钮的自定义图片地址 + * - 建议使用绝对路径,开头不要添加"@",请以"/"开头 + * @default 'z-paging内置的图片' + */ + backToTopImg?: string + + /** + * 点击返回顶部按钮返回到顶部时是否展示过渡动画 + * @default true + */ + backToTopWithAnimate?: boolean + + /** + * 点击返回顶部按钮与底部的距离,默认单位为px + * - 支持传100、"100px"或"100rpx" + * @default 160rpx + */ + backToTopBottom?: number | string + + /** + * 点击返回顶部按钮的自定义样式 + */ + backToTopStyle?: Partial + + // ******************** 虚拟列表&内置列表配置 ******************** + /** + * 是否使用虚拟列表 + * - 使用页面滚动或nvue时,不支持虚拟列表。在nvue中z-paging内置了list组件,效果与虚拟列表类似,并且可以提供更好的性能 + * @default false + */ + useVirtualList?: boolean + + /** + * 在使用虚拟列表时,是否使用兼容模式。兼容模式写法较繁琐,但可提供良好的兼容性。 + * @default false + * @since 2.4.0 + */ + useCompatibilityMode?: boolean + + /** + * 使用兼容模式时传递的附加数据,可选、非必须 + * @since 2.4.0 + */ + extraData?: Record + + /** + * 虚拟列表cell高度模式,默认为fixed,也就是每个cell高度完全相同,将以第一个cell高度为准进行计算。 + * @default 'fixed' + */ + cellHeightMode?: 'fixed' | 'dynamic' + + /** + * 预加载的列表可视范围(列表高度)页数。此数值越大,则虚拟列表中加载的dom越多,内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题。 + * @default 12 + */ + preloadPage?: number | string + + /** + * 固定的cell高度,`cell-height-mode=fixed`才有效,若设置了值,则不计算第一个cell高度而使用设置的cell高度。默认单位为px + * - 支持传100、"100px"或"100rpx" + * @since 2.7.8 + */ + fixedCellHeight?: number | string + + /** + * 虚拟列表列数,默认为1。常用于每行有多列的情况,例如每行有2列数据,需要将此值设置为2。 + * @default 1 + * @since 2.2.8 + */ + virtualListCol?: number | string + + /** + * 虚拟列表scroll取样帧率,默认为80,过低容易出现白屏问题,过高容易出现卡顿问题 + * @default 80 + */ + virtualScrollFps?: number | string + + /** + * 虚拟列表cell id的前缀,适用于一个页面有多个虚拟列表的情况,用以区分不同虚拟列表cell的id + * - 注意:请勿传数字或以数字开头的字符串。如设置为list1,则cell的id应为:list1-zp-id-${item.zp_index} + * @since 2.8.1 + */ + virtualCellIdPrefix?: string + + /** + * 是否在z-paging内部循环渲染列表(使用内置列表)。 + * @default false + */ + useInnerList?: boolean + + /** + * 强制关闭inner-list。适用于开启了虚拟列表后需要强制关闭inner-list的情况。 + * @default false + * @since 2.2.7 + */ + forceCloseInnerList?: boolean + + /** + * 虚拟列表是否使用swiper-item包裹,默认为否,此属性为了解决vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若z-paging在swiper-item内存在无法获取slot插入的cell高度进而导致虚拟列表失败的问题 + * - 仅vue3+(微信小程序或QQ小程序)+非内置列表写法虚拟列表有效,其他情况此属性设置任何值都无效,所以如果您在swiper-item内使用z-paging的非内置虚拟列表写法,将此属性设置为true即可 + * @default false + * @since 2.8.6 + */ + virtualInSwiperSlot?: boolean + + /** + * 内置列表cell的key名称(仅nvue有效,在nvue中开启use-inner-list时必须填此项) + * @since 2.2.7 + */ + cellKeyName?: string + + /** + * innerList样式 + */ + innerListStyle?: Partial + + /** + * innerCell样式 + * @since 2.2.8 + */ + innerCellStyle?: Partial + + // ******************** 本地分页配置 ******************** + /** + * 本地分页时上拉加载更多延迟时间,单位为毫秒 + * @default 200 + */ + localPagingLoadingTime?: number | string + + // ******************** 聊天记录模式配置 ******************** + /** + * 使用聊天记录模式,为保证良好的体验。 + * @default false + */ + useChatRecordMode?: boolean; + + /** + * 使用聊天记录模式时是否自动隐藏键盘(在用户触摸列表时候自动隐藏键盘) + * @default true + * @since 2.3.4 + */ + autoHideKeyboardWhenChat?: boolean; + + /** + * 使用聊天记录模式中键盘弹出时是否自动调整slot="bottom"高度 + * @default true + * @since 2.7.4 + */ + autoAdjustPositionWhenChat?: boolean; + + /** + * 使用聊天记录模式中键盘弹出时是否自动滚动到底部 + * @default false + * @since 2.7.4 + */ + autoToBottomWhenChat?: boolean; + + /** + * 使用聊天记录模式中键盘弹出时占位高度偏移距离。默认0rpx。单位px + * @default "0px" + * @since 2.7.6 + */ + chatAdjustPositionOffset?: string; + + /** + * 使用聊天记录模式中`reload`时是否显示`chatLoading` + * @default false + * @since 2.7.4 + */ + showChatLoadingWhenReload?: boolean; + + /** + * `bottom`的背景色,默认透明,传字符串,如"#ffffff" + * @since 2.7.4 + */ + bottomBgColor?: string; + + /** + * 在聊天记录模式中滑动到顶部状态为默认状态时,是否以加载中的状态展示 + * @default true + * @since 2.7.5 + */ + chatLoadingMoreDefaultAsLoading?: boolean; + + // ******************** scroll-view相关配置 ******************** + /** + * 控制是否出现滚动条 + * @default true + */ + showScrollbar?: boolean; + + /** + * 是否可以滚动,使用内置scroll-view和nvue时有效 + * @default true + */ + scrollable?: boolean; + + /** + * 是否允许横向滚动 + * @default false + * @since 2.0.6 + */ + scrollX?: boolean; + + /** + * iOS设备上滚动到顶部时是否允许回弹效果 + * - 关闭回弹效果后可使滚动到顶部后立即下拉可立即触发下拉刷新,但是有吸顶view时滚动到顶部时可能出现抖动 + * @default false + */ + scrollToTopBounceEnabled?: boolean; + + /** + * iOS设备上滚动到底部时是否允许回弹效果。可能会导致使用scroll-view滚动时无法顺利滚动到底部 + * @default true + */ + scrollToBottomBounceEnabled?: boolean; + + /** + * 在设置滚动条位置时使用动画过渡 + * @default false + */ + scrollWithAnimation?: boolean; + + /** + * 值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素。若在一些平台中无效,可以通过调用z-paging的scrollToxxx系列的方法实现。 + */ + scrollIntoView?: string; + + /** + * iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部。仅支持app-nvue,微信小程序。 + * @default true + */ + enableBackToTop?: boolean; + + // ******************** nvue独有配置 ******************** + /** + * nvue中修改列表类型。 + * @default "list" + */ + nvueListIs?: 'list' | 'waterfall' | 'scroller'; + + /** + * waterfall 配置,仅在 nvue 中且 nvueListIs=waterfall 时有效。示例: {'column-gap': 20}。配置参数详情参见: https://uniapp.dcloud.io/component/waterfall + */ + nvueWaterfallConfig?: Record; + + /** + * nvue 控制是否回弹效果,iOS 不支持动态修改。注意: 若禁用回弹效果,下拉刷新将失效。 + * @default true + */ + nvueBounce?: boolean; + + /** + * nvue 中通过代码滚动到顶部/底部时,是否加快动画效果(无滚动动画时无效)。 + * @default false + * @since 1.9.4 + */ + nvueFastScroll?: boolean; + + /** + * nvue 中 list 的 id。 + * @since 2.0.4 + */ + nvueListId?: string; + + /** + * 是否隐藏 nvue 列表底部的 tagView,此 view 用于标识滚动到底部位置。 + * - 若隐藏则滚动到底部功能将失效。 + * - 在 nvue 中实现吸顶+swiper 功能时需将最外层 z-paging 的此属性设置为 true。 + * @default false + * @since 2.0.4 + */ + hideNvueBottomTag?: boolean; + + /** + * 设置 nvue 中是否按分页模式(类似竖向 swiper)显示 List。 + * @default false + * @since 2.3.1 + */ + nvuePagingEnabled?: boolean; + + /** + * nvue 中控制 onscroll 事件触发的频率。表示两次 onscroll 事件之间列表至少滚动了指定的像素值。单位: px。 + * @since 2.3.5 + */ + offsetAccuracy?: number; + + // ******************** 缓存配置 ******************** + /** + * 是否使用缓存。若开启,将自动缓存第一页的数据。注意:默认仅会缓存组件首次加载时第一次请求到的数据,后续的下拉刷新操作不会更新缓存。 + * - 必须设置 `cacheKey`,否则缓存无效。 + * @default false + */ + useCache?: boolean; + + /** + * 使用缓存时缓存的 key,用于区分不同列表的缓存数据。 + * - useCache为 true 时必须设置,否则缓存无效。 + */ + cacheKey?: string; + + /** + * 缓存模式。 + * - default: 仅缓存组件首次加载时第一次请求到的数据。 + * - always: 总是缓存,每次列表刷新(如下拉刷新、调用 reload 等)都会更新缓存。 + * @default "default" + */ + cacheMode?: 'default' | 'always'; + + // ******************** z-index配置 ******************** + /** + * slot="top" 的 view 的 z-index。 + * - 仅使用页面滚动时有效。 + * @default 99 + */ + topZIndex?: number; + + /** + * z-paging 内容容器父 view 的 z-index。 + * @default 1 + */ + superContentZIndex?: number; + + /** + * z-paging 内容容器部分的 z-index。 + * @default 1 + */ + contentZIndex?: number; + + /** + * 空数据 view 的 z-index。 + * @default 9 + */ + emptyViewZIndex?: number; + + // ******************** 其他配置 ******************** + /** + * z-paging 是否自动高度。 + * - 自动高度时会自动铺满屏幕,不需要设置父 view 为 100% 等操作。 + * - 注意:自动高度可能并不准确。 + * @deprecated 建议使用 fixed 代替。 + * @default false + */ + autoHeight?: boolean; + + /** + * z-paging 自动高度时的附加高度。 + * - 添加单位 px 或 rpx,默认为 px。 + * - 若需要减少高度,请传负数,如 "-10rpx" 或 "10.5px"。 + * @deprecated 建议使用 fixed 代替。 + * @default "0px" + */ + autoHeightAddition?: number | string; + + + // ****************************** Events ****************************** + + // ******************** 数据处理相关事件 ******************** + /** + * 父组件v-model所绑定的list的值改变时触发此事件 + * @param value 列表数据 + */ + onInput?: (value: any[]) => any + + /** + * 下拉刷新或滚动到底部时会自动触发此方法。z-paging加载时也会触发(若要禁止,请设置:auto="false")。pageNo和pageSize会自动计算好,直接传给服务器即可。 + * @param pageNo 当前第几页 + * @param pageSize 每页多少条 + * @param from query的触发来源:user-pull-down:用户主动下拉刷新 reload:通过reload触发 refresh:通过refresh触发 load-more:通过滚动到底部加载更多或点击底部加载更多触发 + */ + onQuery?: (pageNo: number, pageSize: number, from: ZPagingEnums.QueryFrom) => void + + /** + * 分页渲染的数组改变时触发 + * @param list 最终的分页数据数组 + */ + onListChange?: (list: any[]) => void + + // ******************** 下拉刷新相关事件 ******************** + /** + * 自定义下拉刷新状态改变 + * - use-custom-refresher为false时无效 + * @param status 下拉刷新状态:default:默认状态 release-to-refresh:松手立即刷新 loading:刷新中 complete:刷新结束 go-f2:松手进入二楼 + */ + onRefresherStatusChange?: (status: ZPagingEnums.RefresherStatus) => void + + /** + * 自定义下拉刷新下拉开始 + * - use-custom-refresher为false时无效 + * @param y 当前触摸开始的屏幕点的y值(单位px) + */ + onRefresherTouchstart?: (y: number) => void + + /** + * 自定义下拉刷新下拉拖动中 + * - use-custom-refresher为false时无效 + * - 在使用wxs的平台上,为减少wxs与js通信折损,只有在z-paging添加@refresherTouchmove时,wxs才会实时将下拉拖动事件传给js,在微信小程序和QQ小程序中,因$listeners无效,所以必须设置:watch-refresher-touchmove="true"方可使此事件被触发 + * @param info touchmove信息 + */ + onRefresherTouchmove?: (info: ZPagingParams.RefresherTouchmoveInfo) => void + + /** + * 自定义下拉刷新下拉结束 + * - use-custom-refresher为false时无效 + * @param y 当前触摸开始的屏幕点的y值(单位px) + */ + onRefresherTouchend?: (y: number) => void + + /** + * 下拉进入二楼状态改变 + * - use-custom-refresher为false时无效 + * @param y 当前触摸开始的屏幕点的y值(单位px) + * @since 2.7.7 + */ + onRefresherF2Change?: (status: ZPagingEnums.GoF2Status) => void + + /** + * 自定义下拉刷新被触发 + */ + onRefresh?: () => void + + /** + * 自定义下拉刷新被复位 + */ + onRestore?: () => void + + // ******************** 底部加载更多相关事件 ******************** + /** + * 自定义下拉刷新状态改变 + * - use-custom-refresher为false时无效 + * @param status 底部加载更多状态:default:默认状态 loading:加载中 no-more:没有更多数据 fail:加载失败 + */ + onLoadingStatusChange?: (status: ZPagingEnums.LoadMoreStatus) => void + + // ******************** 空数据与加载失败相关事件 ******************** + /** + * 点击了空数据图中的重新加载按钮 + * @param handler 点击空数据图中重新加载后是否进行reload操作,默认为是。如果需要禁止reload事件,则调用handler(false) + * @since 1.8.0 + */ + onEmptyViewReload?: (handler: ZPagingParams.DefaultEventHandler) => void + + /** + * 点击了空数据图view + * @since 2.3.3 + */ + onEmptyViewClick?: () => void + + /** + * z-paging请求失败状态改变 + * @param isLoadFailed 当前是否是请求失败状态,为true代表是,反之为否;默认状态为否 + * @since 2.5.0 + */ + onIsLoadFailedChange?: (isLoadFailed: boolean) => void + + // ******************** 返回顶部按钮相关事件 ******************** + /** + * 点击了返回顶部按钮 + * @param handler 点击返回顶部按钮后是否滚动到顶部,默认为是。如果需要禁止滚动到顶部事件,则调用handler(false) + * @since 2.6.1 + */ + onBackToTopClick?: (handler: ZPagingParams.DefaultEventHandler) => void + + // ******************** 虚拟列表&内置列表相关事件 ******************** + /** + * 虚拟列表当前渲染的数组改变时触发,在虚拟列表中只会渲染可见区域内+预加载页面的数据 + * - nvue无效 + * @param list 虚拟列表当前渲染的数组 + * @since 2.2.7 + */ + onVirtualListChange?: (list: any[]) => void + + /** + * 使用虚拟列表或内置列表时点击了cell + * - nvue无效 + * @param list 虚拟列表当前渲染的数组 + * @since 2.4.0 + */ + onInnerCellClick?: (info: ZPagingParams.InnerCellClickInfo) => void + + /** + * 虚拟列表顶部占位高度改变 + * - nvue无效 + * @param height 虚拟列表顶部占位高度(单位:px) + * @since 2.7.12 + */ + onVirtualPlaceholderTopHeight?: (height: number) => void + + // ******************** 聊天记录模式相关事件 ******************** + /** + * 在聊天记录模式下,触摸列表隐藏了键盘 + * - nvue无效 + * @since 2.3.6 + */ + onHidedKeyboard?: () => void + + /** + * 键盘高度改变 + * @param info 键盘高度信息 + * @since 2.7.1 + */ + onKeyboardHeightChange?: (info: ZPagingParams.KeyboardHeightInfo) => void + + /** + * z-paging列表滚动时触发 + * @param event 滚动事件信息,vue使用_ScrollInfo,nvue使用_ScrollInfoN + */ + onScroll?: (event: ZPagingParams.ScrollInfo | ZPagingParams.ScrollInfoN) => void + + /** + * scrollTop改变时触发,使用点击返回顶部时需要获取scrollTop时可使用此事件 + * @param scrollTop + */ + onScrollTopChange?: (scrollTop: number) => void + + /** + * z-paging内置的scroll-view/list-view/waterfall滚动底部时触发 + */ + onScrolltolower?: () => void + + /** + * z-paging内置的scroll-view/list-view/waterfall滚动顶部时触发 + */ + onScrolltoupper?: () => void + + /** + * z-paging内置的list滚动结束时触发 + * - 仅nvue有效 + * @param event 滚动结束时触发事件信息 + * @since 2.7.3 + */ + onScrollend?: (event: ZPagingParams.ScrollendEvent) => void + + // ******************** 布局&交互相关事件 ******************** + /** + * z-paging中内容高度改变时触发 + * @param height 改变后的高度 + * @since 2.1.3 + */ + onContentHeightChanged?: (height: number) => void + + /** + * 监听列表触摸方向改变 + * - nvue无效 + * - 必须同时设置:watch-touch-direction-change="true" + * @param direction 列表触摸的方向,top代表用户将列表向上移动(scrollTop不断减小),bottom代表用户将列表向下移动(scrollTop不断增大) + * @since 2.3.0 + */ + onTouchDirectionChange?: (direction: ZPagingEnums.TouchDirection) => void + + /** + * 监听列表滚动方向改变 + * - 页面滚动无效 + * - 必须同时设置:watch-scroll-direction-change="true" + * @param direction 列表滚动的方向,top代表用户将列表向上移动(scrollTop不断减小),bottom代表用户将列表向下移动(scrollTop不断增大) + * @since 2.8.7 + */ + onScrollDirectionChange?: (direction: ZPagingEnums.ScrollDirection) => void +} + + +// ****************************** Slots ****************************** +declare interface ZPagingSlots { + // ******************** 主体布局Slot ******************** + /** + * 默认插入的列表view + */ + ['default']?: () => any + + /** + * 可以将自定义导航栏、tab-view等需要固定的(不需要跟着滚动的)元素放入slot="top"的view中。 + * 注意,当有多个需要固定的view时,请用一个view包住它们,并且在这个view上设置slot="top"。需要固定在顶部的view请勿设置position: fixed; + * @since 1.5.5 + */ + ['top']?: () => any + + /** + * 可以将需要固定在底部的(不需要跟着滚动的)元素放入slot="bottom"的view中。 + * 注意,当有多个需要固定的view时,请用一个view包住它们,并且在这个view上设置slot="bottom"。需要固定在底部的view请勿设置position: fixed; + * @since 1.6.2 + */ + ['bottom']?: () => any + + /** + * 可以将需要固定在左侧的(不需要跟着滚动的)元素放入slot="left"的view中。 + * 注意,当有多个需要固定的view时,请用一个view包住它们,并且在这个view上设置slot="left"。需要固定在左侧的view请勿设置position: fixed; + * @since 2.2.3 + */ + ['left']?: () => any + + /** + * 可以将需要固定在左侧的(不需要跟着滚动的)元素放入slot="right"的view中。 + * 注意,当有多个需要固定的view时,请用一个view包住它们,并且在这个view上设置slot="right"。需要固定在右侧的view请勿设置position: fixed; + * @since 2.2.3 + */ + ['right']?: () => any + + // ******************** 下拉刷新Slot ******************** + /** + * 自定义下拉刷新view,设置后则不使用uni自带的下拉刷新view和z-paging自定义的下拉刷新view。此view的style必须设置为height:100% + * - 在非nvue中,自定义下拉刷新view的高度受refresher-threshold控制,因此如果它的高度不为默认的80rpx,则需要设置refresher-threshold="自定义下拉刷新view的高度" + * @param refresherStatus 下拉刷新状态:default:默认状态 release-to-refresh:松手立即刷新 loading:刷新中 complete:刷新结束 go-f2:松手进入二楼 + */ + ['refresher']?: (props: ZPagingParams.RefresherSlotProps) => any + + /** + * 自定义结束状态下的下拉刷新view,若设置,当下拉刷新结束时,会替换当前状态下的下拉刷新view。 + * - 注意:默认情况下您无法看到结束状态的下拉刷新view,除非您设置了refresher-complete-delay并且值足够大,例如:500 + * @since 2.1.1 + */ + ['refresherComplete']?: () => any + + /** + * 自定义松手显示二楼状态下的view + * @since 2.7.7 + */ + ['refresherF2']?: () => any + + /** + * 自定义需要插入二楼的view + * @since 2.7.7 + */ + ['f2']?: () => any + + // ******************** 底部加载更多Slot ******************** + /** + * 自定义滑动到底部"默认"状态的view(即"点击加载更多view") + */ + ['loadingMoreDefault']?: () => any + + /** + * 自定义滑动到底部"加载中"状态的view + */ + ['loadingMoreLoading']?: () => any + + /** + * 自定义滑动到底部"没有更多数据"状态的view + */ + ['loadingMoreNoMore']?: () => any + + /** + * 自定义滑动到底部"加载失败"状态的view + */ + ['loadingMoreFail']?: () => any + + // ******************** 空数据图Slot ******************** + /** + * 自定义空数据占位view + * @param isLoadFailed 是否加载失败:true: 加载失败,false: 加载成功 + */ + ['empty']?: (props: ZPagingParams.EmptySlotProps) => any + + // ******************** 全屏Loading Slot ******************** + /** + * 自定义页面reload时的加载view + * - 注意:这个slot默认仅会在第一次加载时显示,若需要每次reload时都显示,需要将auto-hide-loading-after-first-loaded设置为false + */ + ['loading']?: () => any + + // ******************** 返回顶部按钮Slot ******************** + /** + * 自定义点击返回顶部view + * - 注意:此view受“【返回顶部按钮】配置”控制,且其父view默认宽高为76rpx + * @since 1.9.4 + */ + ['backToTop']?: () => any + + // ******************** 虚拟列表&内置列表Slot ******************** + /** + * 内置列表中的cell + * - use-virtual-list或use-inner-list为true时有效 + * @param item 当前item + * @param index 当前index + * @since 2.2.5 + */ + ['cell']?: (props: ZPagingParams.InnerListCellSlotProps) => any + + /** + * 内置列表中的header(在cell顶部且跟随列表滚动) + * - use-virtual-list或use-inner-list为true时有效 + * @since 2.2.5 + */ + ['header']?: () => any + + /** + * 内置列表中的footer(在cell顶部且跟随列表滚动) + * - use-virtual-list或use-inner-list为true时有效 + * @since 2.2.5 + */ + ['footer']?: () => any + + // ******************** 聊天记录模式Slot ******************** + /** + * 使用聊天记录模式时自定义顶部加载更多view(除没有更多数据外) + * - use-chat-record-mode为true时有效 + * @param loadingMoreStatus 底部加载更多状态:default:默认状态 loading:加载中 no-more:没有更多数据 fail:加载失败 + */ + ['chatLoading']?: (props: ZPagingParams.ChatLoadingSlotProps) => any + + /** + * 使用聊天记录模式时自定义没有更多数据view + * - use-chat-record-mode为true时有效 + * @since 2.7.5 + */ + ['chatNoMore']?: () => any +} + +// ****************************** Methods ****************************** +declare interface _ZPagingRef { + // ******************** 数据刷新&处理方法 ******************** + /** + * 重新加载分页数据,pageNo恢复为默认值,相当于下拉刷新的效果 + * + * @param [animate=false] 是否展示下拉刷新动画 + */ + reload: (animate?: boolean) => Promise< ZPagingParams.ReturnData>; + + /** + * 刷新列表数据,pageNo和pageSize不会重置,列表数据会重新从服务端获取 + * + * @since 2.0.4 + * @returns {Promise>} Promise,当前最新分页结果: + * - resolve 操作成功 + * - - `totalList` (T[]): 当前总列表 + * - - `noMore` (boolean): 是否没有更多数据 + * - reject 操作失败 + */ + refresh: () => Promise>; + + /** + * 刷新列表数据至指定页 + * + * @since 2.5.9 + * @param page 目标页数 + * @returns {Promise>} Promise,当前最新分页结果: + * - resolve 操作成功 + * - - `totalList` (T[]): 当前总列表 + * - - `noMore` (boolean): 是否没有更多数据 + * - reject 操作失败 + */ + refreshToPage: (page: number) => Promise>; + + /** + * 请求结束 + * - 当通过complete传进去的数组长度小于pageSize时,则判定为没有更多了 + * + * @param [data] 请求结果数组 + * @param [success=true] 是否请求成功 + * @returns {Promise>} Promise,当前最新分页结果: + * - resolve 操作成功 + * - - `totalList` (T[]): 当前总列表 + * - - `noMore` (boolean): 是否没有更多数据 + * - reject 操作失败 + */ + complete: (data?: T[] | false, success?: boolean) => Promise>; + + /** + * 请求结束 + * - 通过total判断是否有更多数据 + * + * @since 2.0.6 + * @param data 请求结果数组 + * @param total 列表总长度 + * @param [success=true] 是否请求成功 + * @returns {Promise>} Promise,当前最新分页结果: + * - resolve 操作成功 + * - - `totalList` (T[]): 当前总列表 + * - - `noMore` (boolean): 是否没有更多数据 + * - reject 操作失败 + */ + completeByTotal: (data: T[], total: number, success?: boolean) => Promise>; + + /** + * 请求结束 + * - 自行判断是否有更多数据 + * + * @since 1.9.2 + * @param data 请求结果数组 + * @param noMore 是否没有更多数据 + * @param [success=true] 是否请求成功 + * @returns {Promise>} Promise,当前最新分页结果: + * - resolve 操作成功 + * - - `totalList` (T[]): 当前总列表 + * - - `noMore` (boolean): 是否没有更多数据 + * - reject 操作失败 + */ + completeByNoMore: (data: T[], noMore: boolean, success?: boolean) => Promise>; + + /** + * 请求失败 + * - 通过方法传入请求失败原因,将请求失败原因传递给z-paging展示 + * + * @since 2.6.3 + * @param cause 请求失败原因 + * @returns {Promise>} Promise,当前最新分页结果: + * - resolve 操作成功 + * - - `totalList` (T[]): 当前总列表 + * - - `noMore` (boolean): 是否没有更多数据 + * - reject 操作失败 + */ + completeByError: (cause: string) => Promise>; + + /** + * 请求结束 + * - 保证数据一致 + * + * @since 1.6.4 + * @param data 请求结果数组 + * @param key dataKey,需与:data-key绑定的一致 + * @param [success=true] 是否请求成功 + * @returns {Promise>} Promise,当前最新分页结果: + * - resolve 操作成功 + * - - `totalList` (T[]): 当前总列表 + * - - `noMore` (boolean): 是否没有更多数据 + * - reject 操作失败 + */ + completeByKey: (data: T[], key: string, success?: boolean) => Promise>; + + /** + * 清空分页数据,pageNo恢复为默认值 + * + * @since 2.1.0 + */ + clear: () => void; + + /** + * 从顶部添加数据,不会影响分页的pageNo和pageSize + * + * @param data 需要添加的数据,可以是一条数据或一组数据 + * @param [scrollToTop=true] 是否滚动到顶部,不填默认为true + * @param [animate=true] 是否使用动画滚动到顶部 + */ + addDataFromTop: (data: _Arrayable, scrollToTop?: boolean, animate?: boolean) => void; + + /** + * 【不推荐】重新设置列表数据,调用此方法不会影响pageNo和pageSize,也不会触发请求 + * - 适用场景:当需要删除列表中某一项时,将删除对应项后的数组通过此方法传递给z-paging + * + * @param data 修改后的列表数组 + */ + resetTotalData: (data: T[]) => void; + + // ******************** 下拉刷新相关方法 ******************** + /** + * 终止下拉刷新状态 + * + * @since 2.1.0 + */ + endRefresh: () => void; + + /** + * 手动更新自定义下拉刷新view高度 + * - 常用于某些情况下使用slot="refresher"插入的view高度未能正确计算导致异常时手动更新其高度 + * + * @since 2.6.1 + */ + updateCustomRefresherHeight: () => void; + + /** + * 手动进入二楼 + * + * @since 2.8.7 + */ + goF2: () => void; + + /** + * 手动关闭二楼 + * + * @since 2.7.7 + */ + closeF2: () => void; + + // ******************** 底部加载更多相关方法 ******************** + /** + * 手动触发上拉加载更多 + * - 非必须,可依据具体需求使用,例如当z-paging未确定高度时,内部的scroll-view会无限增高,此时z-paging无法得知是否滚动到底部,您可以在页面的onReachBottom中手动调用此方法触发上拉加载更多 + * + * @param [source] 触发加载更多的来源类型 + */ + doLoadMore: (source?: "click" | "toBottom") => void; + + // ******************** 页面滚动&布局相关方法 ******************** + /** + * 当使用页面滚动并且自定义下拉刷新时,请在页面的onPageScroll中调用此方法,告知z-paging当前的pageScrollTop,否则会导致在任意位置都可以下拉刷新 + * - 若引入了mixins,则不需要调用此方法 + * + * @param scrollTop 从page的onPageScroll中获取的scrollTop + */ + updatePageScrollTop: (scrollTop: number) => void; + + /** + * 在使用页面滚动并且设置了slot="top"时,默认初次加载会自动获取其高度,并使内部容器下移,当slot="top"的view高度动态改变时,在其高度需要更新时调用此方法 + */ + updatePageScrollTopHeight: () => void; + + /** + * 在使用页面滚动并且设置了slot="bottom"时,默认初次加载会自动获取其高度,并使内部容器下移,当slot="bottom"的view高度动态改变时,在其高度需要更新时调用此方法 + */ + updatePageScrollBottomHeight: () => void; + + /** + * 更新slot="left"和slot="right"宽度,当slot="left"或slot="right"宽度动态改变后调用 + * + * @since 2.3.5 + */ + updateLeftAndRightWidth: () => void; + + /** + * 更新fixed模式下z-paging的布局,在onShow时候调用,以修复在iOS+h5+tabbar+fixed+底部有安全区域的设备中从tabbar页面跳转到无tabbar页面后返回,底部有一段空白区域的问题 + * + * @since 2.6.5 + */ + updateFixedLayout: () => void; + + // ******************** 虚拟列表相关方法 ******************** + /** + * 在使用动态高度虚拟列表时,若在列表数组中需要插入某个item,需要调用此方法 + * + * @since 2.5.9 + * @param item 插入的数据项 + * @param index 插入的cell位置,若为2,则插入的item在原list的index=1之后,从0开始 + */ + doInsertVirtualListItem: (item: T, index: number) => void; + + /** + * 在使用动态高度虚拟列表时,手动更新指定cell的缓存高度 + * - 当cell高度在初始化之后再次改变时调用 + * + * @since 2.4.0 + * @param index 需要更新的cell在列表中的位置,从0开始 + */ + didUpdateVirtualListCell: (index: number) => void; + + /** + * 在使用动态高度虚拟列表时,若删除了列表数组中的某个item,需要调用此方法以更新高度缓存数组 + * + * @since 2.4.0 + * @param index 需要更新的cell在列表中的位置,从0开始 + */ + didDeleteVirtualListCell: (index: number) => void; + + /** + * 手动触发虚拟列表渲染更新,可用于解决例如修改了虚拟列表数组中元素,但展示未更新的情况 + * + * @since 2.7.11 + */ + updateVirtualListRender: () => void; + + // ******************** 本地分页相关方法 ******************** + /** + * 设置本地分页,请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging作分页处理 + * - 若调用了此方法,则上拉加载更多时内部会自动分页,不会触发@query所绑定的事件 + * + * @param data 请求结果数组 + * @param [success=true] 是否请求成功 + * @returns {Promise>} Promise,当前最新分页结果: + * - resolve 操作成功 + * - - `totalList` (T[]): 当前总列表 + * - - `noMore` (boolean): 是否没有更多数据 + * - reject 操作失败 + */ + setLocalPaging: (data: T[], success?: boolean) => Promise>; + + // ******************** 聊天记录模式相关方法 ******************** + /** + * 手动触发滚动到顶部加载更多,聊天记录模式时有效 + */ + doChatRecordLoadMore: () => void; + + /** + * 添加聊天记录,use-chat-record-mode为true时有效 + * + * @param data 需要添加的聊天数据,可以是一条数据或一组数据 + * @param [scrollToBottom=true] 是否滚动到底部 + * @param [animate=true] 是否使用动画滚动到底部 + */ + addChatRecordData: (data: _Arrayable, scrollToBottom?: boolean, animate?: boolean) => void; + + /** + * 手动添加键盘高度变化监听 + * + * @since 2.8.7 + */ + addKeyboardHeightChangeListener: () => void; + + // ******************** 滚动到指定位置方法 ******************** + /** + * 滚动到顶部 + * + * @param [animate=true] 是否有动画效果 + */ + scrollToTop: (animate?: boolean) => void; + + /** + * 滚动到底部 + * + * @param [animate=true] 是否有动画效果 + */ + scrollToBottom: (animate?: boolean) => void; + + /** + * 滚动到指定view + * + * @param id 需要滚动到的view的id值,不包含"#" + * @param [offset=0] 偏移量,单位为px + * @param [animate=false] 是否有动画效果 + */ + scrollIntoViewById: (id: string, offset?: number, animate?: boolean) => void; + + /** + * 滚动到指定view + * - vue中有效 + * + * @since 1.7.4 + * @param top 需要滚动的view的top值(通过uni.createSelectorQuery()获取) + * @param [offset=0] 偏移量,单位为px + * @param [animate=false] 是否有动画效果 + */ + scrollIntoViewByNodeTop: (top: number, offset?: number, animate?: boolean) => void; + + /** + * y轴滚动到指定位置 + * - vue中有效 + * - 与scrollIntoViewByNodeTop的不同之处在于,scrollToY传入的是view相对于屏幕的top值,而scrollIntoViewByNodeTop传入的top值并非是固定的,通过uni.createSelectorQuery()获取到的top会因列表滚动而改变 + * + * @since 2.1.0 + * @param y 与顶部的距离,单位为px + * @param [offset=0] 偏移量,单位为px + * @param [animate=false] 是否有动画效果 + */ + scrollToY: (y: number, offset?: number, animate?: boolean) => void; + + /** + * x轴滚动到指定位置 + * - 非页面滚动且在vue中有效 + * + * @since 2.8.5 + * @param x 与左侧的距离,单位为px + * @param [offset=0] 偏移量,单位为px + * @param [animate=false] 是否有动画效果 + */ + scrollToX: (x: number, offset?: number, animate?: boolean) => void; + + /** + * 滚动到指定view + * - nvue或虚拟列表中有效 + * - 在nvue中的cell必须设置 :ref="`z-paging-${index}`" + * + * @param index 需要滚动到的view的index(第几个) + * @param [offset=0] 偏移量,单位为px + * @param [animate=false] 是否有动画效果 + */ + scrollIntoViewByIndex: (index: number, offset?: number, animate?: boolean) => void; + + /** + * 滚动到指定view + * - nvue中有效 + * + * @param view 需要滚动到的view(通过this.$refs.xxx获取) + * @param [offset=0] 偏移量,单位为px + * @param [animate=false] 是否有动画效果 + */ + scrollIntoViewByView: (view: any, offset?: number, animate?: boolean) => void; + + /** + * 设置nvue List的specialEffects + * + * @since 2.0.4 + * @param args 参见https://uniapp.dcloud.io/component/list?id=listsetspecialeffects + */ + setSpecialEffects: (args: ZPagingParams.SetSpecialEffectsArgs) => void; + + // ******************** nvue独有方法 ******************** + /** + * 与{@link setSpecialEffects}相同 + * + * @since 2.0.4 + */ + setListSpecialEffects: (args: ZPagingParams.SetSpecialEffectsArgs) => void; + + // ******************** 缓存相关方法 ******************** + /** + * 手动更新列表缓存数据,将自动截取v-model绑定的list中的前pageSize条覆盖缓存,请确保在list数据更新到预期结果后再调用此方法 + * + * @since 2.3.9 + */ + updateCache: () => void; + + // ******************** 获取版本号方法 ******************** + /** + * 获取当前版本号 + */ + getVersion: () => string; +} + +declare interface _ZPaging { + new (): { + $props: AllowedComponentProps & + VNodeProps & + ZPagingProps + $slots: ZPagingSlots + } +} + +export declare const ZPaging: _ZPaging + +declare global { + interface ZPagingRef extends _ZPagingRef {} + // 兼容v2.8.1之前的旧版本 + interface ZPagingInstance extends _ZPagingRef {} +} diff --git a/uni_modules/z-paging/types/index.d.ts b/uni_modules/z-paging/types/index.d.ts new file mode 100644 index 0000000..7a2092d --- /dev/null +++ b/uni_modules/z-paging/types/index.d.ts @@ -0,0 +1,24 @@ +/// +declare module 'z-paging' { + export function install() : void + /** + * z-paging全局配置 + * - uni.$zp + * + * @since 2.6.5 + */ + interface $zp { + /** + * 全局配置 + */ + config : Record; + } + global { + interface Uni { + $zp : $zp + } + } +} + +declare type ZPagingSwiperRef = typeof import('./comps/z-paging-swiper')['ZPagingSwiperRef'] +declare type ZPagingSwiperItemRef = typeof import('./comps/z-paging-swiper-item')['ZPagingSwiperItemRef'] \ No newline at end of file