gui-page.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. <template>
  2. <view
  3. :style="{opacity : pageStatus ? 1 : 0}"
  4. class="gui-sbody gui-flex gui-column"
  5. :class="[fullPage || refresh || loadmore ? 'gui-flex1' : '']">
  6. <!-- 自定义头部 -->
  7. <view
  8. class="gui-header gui-transition-all"
  9. :class="headerClass"
  10. v-if="customHeader"
  11. id="guiPageHeader"
  12. ref="guiPageHeader">
  13. <!-- 状态栏 -->
  14. <view
  15. class="gui-page-status-bar"
  16. :class="statusBarClass"
  17. :style="{height:statusBarHeight+'px'}">
  18. <slot name="gStatusBar"></slot>
  19. </view>
  20. <!-- 头部插槽 -->
  21. <view
  22. class="gui-flex gui-column gui-justify-content-center"
  23. @tap.stop.prevnet="headerTap">
  24. <slot name="gHeader"></slot>
  25. </view>
  26. </view>
  27. <!-- 自定义头部占位 -->
  28. <view
  29. v-if="customHeader && isHeaderSized"
  30. :style="{height:headerHeight+'px'}"></view>
  31. <!-- 页面主体 -->
  32. <view
  33. class="gui-flex gui-column gui-relative"
  34. v-if="!refresh && !loadmore"
  35. id="guiPageBody"
  36. :class="[fullPage ? 'gui-flex1' : '']"
  37. ref="guiPageBody">
  38. <slot name="gBody"></slot>
  39. </view>
  40. <!-- 刷新加载主体 非 nvue -->
  41. <!-- #ifndef APP-NVUE -->
  42. <view
  43. class="gui-flex1 gui-relative"
  44. v-if="refresh || loadmore"
  45. id="guiPageBody"
  46. ref="guiPageBody"
  47. :style="{marginTop:fixedTopMargin+'px'}">
  48. <scroll-view
  49. class="gui-absolute-full"
  50. :scroll-y="true"
  51. :show-scrollbar="false"
  52. @touchstart="touchstart"
  53. @touchmove="touchmove"
  54. @touchend="touchend"
  55. @scroll="scroll"
  56. :scroll-into-view="topTagID"
  57. :scroll-with-animation="false"
  58. @scrolltolower="loadmorefun">
  59. <view id="guiPageBodyTopTag">
  60. <gui-refresh
  61. ref="guiPageRefresh"
  62. @reload="reload"
  63. :refreshText="refreshText"
  64. :customClass="refreshClasses"
  65. :triggerHeight="reFreshTriggerHeight"
  66. :refreshFontSize="refreshFontSize"></gui-refresh>
  67. </view>
  68. <slot name="gBody"></slot>
  69. <view v-if="loadmore">
  70. <gui-loadmore
  71. ref="guipageloadmore"
  72. :status="loadMoreStatus"
  73. :loadMoreText="loadMoreText"
  74. :customClass="loadMoreClass"
  75. :loadMoreFontSize="loadMoreFontSize"></gui-loadmore>
  76. </view>
  77. </scroll-view>
  78. </view>
  79. <!-- #endif -->
  80. <!-- 刷新加载主体 nvue -->
  81. <!-- #ifdef APP-NVUE -->
  82. <view
  83. class="gui-flex gui-column gui-flex1"
  84. v-if="refresh || loadmore"
  85. id="guiPageBody"
  86. ref="guiPageBody"
  87. :style="{marginTop:fixedTopMargin+'px'}">
  88. <list
  89. :show-scrollbar="false"
  90. class="gui-flex1"
  91. @loadmore="loadmorefun"
  92. @scroll="scroll">
  93. <refresh
  94. v-if="refresh"
  95. :display="refreshing ? 'show' : 'hide'"
  96. @refresh="onrefresh"
  97. @pullingdown="onpullingdown"></refresh>
  98. <cell ref="guiPageBodyTopRef"></cell>
  99. <cell v-if="refresh">
  100. <gui-refresh
  101. ref="guiPageRefresh"
  102. :refreshText="refreshText"
  103. :customClass="refreshClasses"
  104. :triggerHeight="reFreshTriggerHeight"
  105. :refreshFontSize="refreshFontSize"></gui-refresh>
  106. </cell>
  107. <slot name="gBody"></slot>
  108. <cell
  109. v-if="loadmore"
  110. class="gui-page-loadmore">
  111. <gui-loadmore
  112. ref="guipageloadmore"
  113. :status="loadMoreStatus"
  114. :loadMoreText="loadMoreText"
  115. :customClass="loadMoreClass"
  116. :loadMoreFontSize="loadMoreFontSize"></gui-loadmore>
  117. </cell>
  118. </list>
  119. </view>
  120. <!-- #endif -->
  121. <!-- 页面底部 -->
  122. <!-- 底部占位 -->
  123. <view
  124. v-if="customFooter"
  125. :style="{height:footerHeight+'px'}"></view>
  126. <view
  127. v-if="customFooter"
  128. class="gui-page-footer gui-border-box"
  129. id="guiPageFooter"
  130. ref="guiPageFooter"
  131. :class="footerClass">
  132. <slot name="gFooter"></slot>
  133. <gui-iphone-bottom
  134. :need="!isSwitchPage"
  135. :customClass="footerSpaceClass"></gui-iphone-bottom>
  136. </view>
  137. <!-- 右下角悬浮挂件 -->
  138. <view
  139. class="gui-page-pendant" :class="pendantClass">
  140. <slot name="gPendant"></slot>
  141. </view>
  142. <!-- 吸顶元素 -->
  143. <view
  144. class="gui-page-fixed-top"
  145. ref="guiPageFixedTop"
  146. id="guiPageFixedTop"
  147. :style="{top:fixedTop+'px'}">
  148. <slot name="gFixedTop"></slot>
  149. </view>
  150. <!-- 全屏 loading -->
  151. <gui-page-loading ref="guipageloading"></gui-page-loading>
  152. </view>
  153. </template>
  154. <script>
  155. // #ifdef APP-NVUE
  156. const dom = weex.requireModule('dom');
  157. // #endif
  158. export default{
  159. name : 'gui-page',
  160. props : {
  161. // #ifndef APP-NVUE
  162. fullPage : {type:Boolean, default:false},
  163. // #endif
  164. // #ifdef APP-NVUE
  165. fullPage : {type:Boolean, default:true},
  166. // #endif
  167. // 自定义头部
  168. customHeader : {type:Boolean, default:false},
  169. headerClass : {type:Array , default:function(){return [];}},
  170. isHeaderSized : {type:Boolean, default:true},
  171. statusBarClass : {type:Array , default:function(){return [];}},
  172. // 自定义底部
  173. customFooter : {type:Boolean, default:false},
  174. footerClass : {type:Array , default:function(){return [];}},
  175. footerSpaceClass : {type:Array, default:function(){return ['gui-bg-gray', 'gui-dark-bg-level-2'];}},
  176. // 挂件
  177. pendantClass : {type:Array , default:function(){return [];}},
  178. // 全屏加载状态
  179. isLoading : {type:Boolean, default:false},
  180. isSwitchPage : {type:Boolean, default:false},
  181. // 吸顶插槽样式
  182. fixedTopClass : {type:Array , default:function(){return [];}},
  183. /* 刷新 */
  184. refresh : {type:Boolean, default:false},
  185. refreshText : {type:Array, default:function () {
  186. return ['继续下拉刷新','松开手指开始刷新','数据刷新中','数据已刷新'];
  187. }},
  188. refreshClasses : {type:Array, default:function () {
  189. return [
  190. ['gui-color-gray'],
  191. ['gui-color-gray'],
  192. ['gui-primary-text'],
  193. ['gui-color-green']
  194. ];
  195. }},
  196. refreshFontSize : {type:Number, default:26},
  197. defaultHeight: {type:Number, default: 0},
  198. /* 加载更多 */
  199. loadmore : {type:Boolean, default:false},
  200. loadMoreText : {type:Array, default:function () {
  201. return ['','数据加载中', '已加载全部数据', '暂无数据'];
  202. }},
  203. loadMoreClass : {type:Array, default:function () {return ['gui-color-gray'];}},
  204. loadMoreFontSize : {type:String, default:'26rpx'},
  205. loadMoreStatus : {type:Number, default: 1},
  206. apiLoadingStatus : {type:Boolean, default:false},
  207. reFreshTriggerHeight : {type:Number, default:50}
  208. },
  209. data() {
  210. return {
  211. pageStatus : false,
  212. footerHeight : 50,
  213. statusBarHeight : 44,
  214. // #ifndef H5
  215. headerHeight : 72,
  216. // #endif
  217. // #ifdef H5
  218. headerHeight : 44,
  219. // #endif
  220. headerTapNumber : 0,
  221. fixedTop : 0,
  222. loadMoreTimer : null,
  223. fixedTopMargin : 0,
  224. scrollTop : 0,
  225. srcollTimer : null,
  226. refreshing : false,
  227. pullingdownVal : 0,
  228. topTagID : 'no'
  229. }
  230. },
  231. watch:{
  232. isLoading : function (val) {
  233. if(val){
  234. this.pageLoadingOpen();
  235. }else{
  236. this.pageLoadingClose();
  237. }
  238. }
  239. },
  240. mounted:function(){
  241. // 全屏 loading
  242. if(this.isLoading){
  243. this.pageLoadingOpen();
  244. }
  245. // 计算状态栏高度
  246. try {
  247. var system = uni.getSystemInfoSync();
  248. if(system.model){
  249. this.statusBarHeight = system.statusBarHeight;
  250. }
  251. // #ifdef APP-PLUS
  252. if(plus.navigator.isFullscreen()){
  253. this.statusBarHeight = 0;
  254. }
  255. // #endif
  256. } catch(e){return null;}
  257. // 获取自定义底部高度
  258. if(this.customFooter){
  259. setTimeout(()=>{
  260. this.getDomSize('guiPageFooter', (res)=>{
  261. this.footerHeight = res.height;
  262. }, 0);
  263. }, 200);
  264. }
  265. // 获取自定义头部高度
  266. if(this.customHeader){
  267. setTimeout(()=>{
  268. this.getDomSize('guiPageHeader', (res)=>{
  269. this.headerHeight = ~~this.defaultHeight ?? 0;
  270. // this.headerHeight = res.height;
  271. this.$nextTick(()=>{
  272. this.pageStatus = true;
  273. });
  274. }, 0);
  275. }, 200);
  276. }else{
  277. this.pageStatus = true;
  278. }
  279. // 吸顶 top
  280. // #ifdef H5
  281. if(this.customHeader){
  282. setTimeout(()=>{
  283. this.getDomSize('guiPageHeader', (res)=>{
  284. this.fixedTop = res.height;
  285. }, 0);
  286. }, 200);
  287. }else{
  288. this.fixedTop = 44;
  289. }
  290. // #endif
  291. // #ifndef H5
  292. if(this.customHeader){
  293. setTimeout(()=>{
  294. this.getDomSize('guiPageHeader', (res)=>{
  295. this.fixedTop = res.height;
  296. }, 0);
  297. }, 200);
  298. }
  299. // #endif
  300. // 全屏时适配吸顶插槽
  301. setTimeout(()=>{
  302. this.getDomSize('guiPageFixedTop', (res)=>{
  303. this.fixedTopMargin = res.height;
  304. }, 0);
  305. }, 200);
  306. },
  307. methods:{
  308. onpullingdown : function(e){
  309. if(this.apiLoadingStatus){return false;}
  310. e.changedTouches = [{pageY:e.pullingDistance}];
  311. this.$refs.guiPageRefresh.touchmove(e);
  312. },
  313. onrefresh : function(){
  314. if(this.apiLoadingStatus){return false;}
  315. this.refreshing = true;
  316. this.$refs.guiPageRefresh.refreshStatus = 2;
  317. setTimeout(()=>{
  318. this.$refs.guiPageRefresh.rotate360();
  319. }, 200);
  320. this.$emit('reload');
  321. },
  322. pageLoadingOpen : function(){
  323. this.getRefs('guipageloading',0,(ref)=>{
  324. ref.open();
  325. });
  326. },
  327. pageLoadingClose : function(){
  328. this.getRefs('guipageloading',0,(ref)=>{
  329. ref.close();
  330. });
  331. },
  332. // 下拉刷新相关
  333. touchstart : function (e){
  334. if(!this.refresh){return false;}
  335. if(this.apiLoadingStatus){return false;}
  336. this.$refs.guiPageRefresh.touchstart(e);
  337. },
  338. touchmove : function(e){
  339. if(!this.refresh){return false;}
  340. if(this.apiLoadingStatus){return false;}
  341. this.$refs.guiPageRefresh.touchmove(e);
  342. },
  343. touchend : function (e) {
  344. if(!this.refresh){return false;}
  345. if(this.apiLoadingStatus){return false;}
  346. this.$refs.guiPageRefresh.touchend(e);
  347. },
  348. scroll:function(e){
  349. if(this.srcollTimer != null){
  350. clearTimeout(this.srcollTimer);
  351. }
  352. this.srcollTimer = setTimeout(()=>{
  353. // #ifndef APP-NVUE
  354. this.$refs.guiPageRefresh.scroll(e);
  355. this.$emit('scroll', e);
  356. this.scrollTop = e.detail.scrollTop;
  357. // #endif
  358. // #ifdef APP-NVUE
  359. e.detail = {scrollTop : e.contentOffset.y * -1};
  360. this.$emit('scroll', e);
  361. this.scrollTop = e.detail.scrollTop;
  362. // #endif
  363. }, 500);
  364. },
  365. toTop : function (){
  366. // #ifndef APP-NVUE
  367. this.topTagID = 'guiPageBodyTopTag';
  368. setTimeout(()=>{
  369. this.topTagID = 'no';
  370. }, 500);
  371. // #endif
  372. // #ifdef APP-NVUE
  373. const el = this.$refs.guiPageBodyTopRef;
  374. dom.scrollToElement(el, {});
  375. // #endif
  376. },
  377. endReload : function(){
  378. this.$refs.guiPageRefresh.endReload();
  379. this.refreshing = false;
  380. },
  381. reload : function(){
  382. if(this.apiLoadingStatus){return false;}
  383. this.$emit('reload');
  384. if(this.loadmore){this.$refs.guipageloadmore.stoploadmore();}
  385. },
  386. // 获取元素尺寸
  387. getDomSize : function(domIDOrRef, fun, count){
  388. if(!count){count = 1;}
  389. if(count >= 50){
  390. fun({width:0, height:0});
  391. return false;
  392. }
  393. // #ifndef APP-NVUE
  394. uni.createSelectorQuery()
  395. .in(this)
  396. .select('#'+domIDOrRef)
  397. .boundingClientRect()
  398. .exec((res)=>{
  399. if(res[0] == null){
  400. count += 1;
  401. setTimeout(()=>{this.getDomSize(domIDOrRef, fun, count);}, 50);
  402. return ;
  403. }else{
  404. if(res[0].height == undefined){
  405. count += 1;
  406. setTimeout(()=>{this.getDomSize(domIDOrRef, fun, count);}, 50);
  407. return ;
  408. }
  409. fun(res[0]);
  410. return ;
  411. }
  412. });
  413. // #endif
  414. // #ifdef APP-NVUE
  415. var el = this.$refs[domIDOrRef];
  416. dom.getComponentRect(el, (res) => {
  417. if(res.result == false){
  418. count += 1;
  419. setTimeout(()=>{this.getDomSize(domIDOrRef, fun, count);}, 50);
  420. return ;
  421. }else{
  422. if(res.size.height < 1){
  423. count += 1;
  424. setTimeout(()=>{this.getDomSize(domIDOrRef, fun, count);}, 50);
  425. return ;
  426. }
  427. fun(res.size);
  428. return ;
  429. }
  430. });
  431. // #endif
  432. },
  433. stopfun : function(e){e.stopPropagation(); return null;},
  434. headerTap : function(){
  435. this.headerTapNumber ++;
  436. if(this.headerTapNumber >= 2){
  437. this.$emit('gotoTop');
  438. this.headerTapNumber = 0;
  439. }else{
  440. setTimeout(()=>{this.headerTapNumber = 0;}, 1000);
  441. }
  442. },
  443. getRefs : function(ref, count, fun){
  444. if(count >= 50){
  445. fun(this.$refs[ref]);
  446. return false;
  447. }
  448. var refReturn = this.$refs[ref];
  449. if(refReturn){
  450. fun(refReturn);
  451. }else{
  452. count++;
  453. setTimeout(()=>{
  454. this.getRefs(ref, count, fun);
  455. }, 100);
  456. }
  457. },
  458. loadmorefun : function () {
  459. if(!this.loadmore){return false;}
  460. if(this.apiLoadingStatus){return false;}
  461. // 获取加载组件状态看一下是否还能继续加载
  462. // 保证触底只执行一次加载
  463. if(this.loadMoreTimer != null){clearTimeout(this.loadMoreTimer);}
  464. this.loadMoreTimer = setTimeout(() => {
  465. var status = this.$refs.guipageloadmore.loadMoreStatus;
  466. if(status != 0){return null;}
  467. this.$refs.guipageloadmore.loading();
  468. this.$emit('loadmorefun');
  469. }, 80);
  470. },
  471. stopLoadmore : function(){
  472. this.$refs.guipageloadmore.stoploadmore();
  473. },
  474. stoploadmore : function(){
  475. this.$refs.guipageloadmore.stoploadmore();
  476. },
  477. nomore : function () {
  478. this.$refs.guipageloadmore.nomore();
  479. },
  480. toast : function(msg){
  481. uni.showToast({
  482. title:msg,
  483. icon:"none"
  484. })
  485. },
  486. resetFooterHeight : function(){
  487. if(this.customFooter){
  488. setTimeout(()=>{
  489. this.getDomSize('guiPageFooter', (res)=>{
  490. this.footerHeight = res.height;
  491. }, 0);
  492. }, 500);
  493. }
  494. }
  495. },
  496. emits:['scroll', 'reload', 'loadmorefun', 'gotoTop']
  497. }
  498. </script>
  499. <style scoped>
  500. /* #ifdef APP-VUE */
  501. .gui-sbody{min-height:100vh;}
  502. /* #endif */
  503. /* #ifdef MP-ALIPAY */
  504. .gui-sbody{min-height:100vh;}
  505. /* #endif */
  506. </style>