gui-page.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. <template>
  2. <view :class="[
  3. 'gui-flex', 'gui-columns', 'gui-sbody',
  4. fullPage ? 'gui-flex1':'' ,
  5. refresh || loadmore ? 'gui-flex1' : ''
  6. ]">
  7. <!-- 自定义头部 -->
  8. <view
  9. class="gui-header gui-transition-all"
  10. v-if="customHeader"
  11. id="guiPageHeader"
  12. ref="guiPageHeader"
  13. :style="'height:'
  14. +(headerSets.height+statusBarHeight)+'px; z-index:'
  15. +headerSets.zIndex+';'+headerStyle">
  16. <!-- 状态栏 -->
  17. <view
  18. class="gui-page-status-bar"
  19. :style="'height:'+statusBarHeight+'px;'+statusBarStyle"></view>
  20. <!-- 头部插槽 -->
  21. <view
  22. class="gui-flex gui-columns gui-justify-content-center"
  23. @tap.stop.prevnet="headerTap"
  24. :style="{height:headerSets.height+'px'}">
  25. <slot name="gHeader"></slot>
  26. </view>
  27. </view>
  28. <!-- 自定义头部占位 -->
  29. <view
  30. v-if="customHeader && isHeaderSized"
  31. :style="'height:'+(headerSets.height+statusBarHeight)+'px; '+ headerSizedStyle + ';'"></view>
  32. <!-- 页面主体 -->
  33. <view
  34. class="gui-flex gui-columns"
  35. v-if="!refresh && !loadmore"
  36. id="guiPageBody"
  37. ref="guiPageBody"
  38. :class="[fullPage?'gui-flex1':'']">
  39. <slot name="gBody"></slot>
  40. </view>
  41. <!-- 刷新加载主体 -->
  42. <view class="gui-flex gui-columns gui-flex1"
  43. v-if="refresh || loadmore"
  44. id="guiPageBody"
  45. ref="guiPageBody"
  46. :style="{
  47. marginTop:fixedTopMargin+'px',
  48. height:refreshBodyHeight+'px'
  49. }">
  50. <scroll-view
  51. class="gui-relative"
  52. :scroll-y="true"
  53. :show-scrollbar="false"
  54. :style="{
  55. height:refreshBodyHeight+'px',
  56. opacity:refreshBodyHeight < 1 ? 0 : 1
  57. }"
  58. @touchstart="touchstart"
  59. @touchmove="touchmove"
  60. @touchend="touchend"
  61. @scroll="scroll"
  62. :scroll-top="scrollTop"
  63. @scrolltolower="loadmorefun">
  64. <view>
  65. <gui-refresh
  66. ref="guiPageRefresh"
  67. @reload="reload"
  68. :refreshText="refreshText"
  69. :refreshBgColor="refreshBgColor"
  70. :refreshColor="refreshColor"
  71. :refreshFontSize="refreshFontSize"></gui-refresh>
  72. </view>
  73. <slot name="gBody"></slot>
  74. <view
  75. v-if="loadmore"
  76. class="gui-page-loadmore">
  77. <gui-loadmore
  78. ref="guipageloadmore"
  79. :status="loadMoreStatus"
  80. :loadMoreText="loadMoreText"
  81. :loadMoreColor="loadMoreColor"
  82. :loadMoreFontSize="loadMoreFontSize"></gui-loadmore>
  83. </view>
  84. </scroll-view>
  85. </view>
  86. <!-- 页面底部 -->
  87. <!-- 底部占位 -->
  88. <view v-if="customFooter"
  89. :style="{height:footerHeight}"></view>
  90. <view class="gui-page-footer gui-border-box"
  91. :class="[isSwitchPage?'gui-switch-page-footer':'']"
  92. v-if="customFooter"
  93. id="guiPageFooter"
  94. ref="guiPageFooter"
  95. :style="{
  96. height:footerHeight,
  97. 'background-image':footerSets.bg,
  98. 'z-index':footerSets.zIndex
  99. }">
  100. <view>
  101. <slot name="gFooter"></slot>
  102. </view>
  103. <view
  104. :style="'height:'+iphoneXButtomHeight+'; '+ iphoneXButtomStyle"></view>
  105. </view>
  106. <!-- 右下角悬浮挂件 -->
  107. <view
  108. class="gui-page-pendant"
  109. :style="{
  110. right:pendantSets.right, bottom:pendantSets.bottom,
  111. width:pendantSets.width, zIndex:pendantSets.zIndex}">
  112. <slot name="gPendant"></slot>
  113. </view>
  114. <!-- 吸顶元素 -->
  115. <view
  116. class="gui-page-fixed-top"
  117. ref="guiPageFixedTop"
  118. id="guiPageFixedTop"
  119. :style="{
  120. top:fixedTop+'px',
  121. zIndex:fixedTopZIndex
  122. }">
  123. <slot name="gFixedTop"></slot>
  124. </view>
  125. <!-- 全屏 loading -->
  126. <gui-page-loading ref="guipageloading"></gui-page-loading>
  127. </view>
  128. </template>
  129. <script>
  130. // #ifdef APP-NVUE
  131. const dom = weex.requireModule('dom');
  132. // #endif
  133. export default{
  134. name : 'gui-page',
  135. props : {
  136. fullPage : {type:Boolean, default:false},
  137. customHeader : {type:Boolean, default:false},
  138. headerSets : {type:Object , default:function(){return {height:44, zIndex:100}}},
  139. headerStyle : {type:String , default:'background-color:#FFFFFF;'},
  140. isHeaderSized : {type:Boolean, default:true},
  141. statusBarStyle : {type:String , default:'background-color:#FFFFFF;'},
  142. customFooter : {type:Boolean, default:false},
  143. footerSets : {type:Object , default:function(){return {height:100, zIndex:100, bg:'linear-gradient(to bottom, #FFFFFF,#FFFFFF)'}}},
  144. pendantSets : {type:Object , default:function(){return {width:'100rpx', right:'25rpx', bottom:'100rpx', zIndex:100};}},
  145. isLoading : {type:Boolean, default:false},
  146. isSwitchPage : {type:Boolean, default:false},
  147. iphoneXButtomStyle : {type:String, default:''},
  148. headerSizedStyle : {type:String, default:''},
  149. fixedTopZIndex : {type:Number, default:2},
  150. /* 刷新 */
  151. refresh : {type:Boolean, default:false},
  152. refreshText : {type:Array, default:function () {
  153. return ['继续下拉刷新','松开手指开始刷新','数据刷新中','数据已刷新'];
  154. }},
  155. refreshBgColor : {type:Array, default:function () {
  156. return ['#FFFFFF','#FFFFFF','#FFFFFF','#63D2BC'];
  157. }},
  158. refreshColor : {type:Array, default:function () {
  159. return ['rgba(69, 90, 100, 0.6)','rgba(69, 90, 100, 0.6)','#63D2BC','#FFFFFF'];
  160. }},
  161. refreshFontSize : {type:String, default:'26rpx'},
  162. /* 加载更多 */
  163. loadmore : {type:Boolean, default:false},
  164. loadMoreText : {type:Array, default:function () {
  165. return ['','数据加载中', '已加载全部数据', '暂无数据'];
  166. }},
  167. loadMoreColor : {type:Array, default:function () {
  168. return ['rgba(69, 90, 100, 0.6)', 'rgba(69, 90, 100, 0.6)', 'rgba(69, 90, 100, 0.8)', 'rgba(69, 90, 100, 0.8)'];
  169. }},
  170. loadMoreStatus : {type:Number, defalut:0},
  171. loadMoreFontSize : {type:String, default:'26rpx'},
  172. apiLoadingStatus : {type:Boolean, default:false}
  173. },
  174. data() {
  175. return {
  176. footerHeight : '100rpx',
  177. iphoneXButtomHeight : '0rpx',
  178. statusBarHeight : 0,
  179. // #ifdef APP-NVUE
  180. animateCount : 0,
  181. // #endif
  182. headerTapNumber : 0,
  183. fixedTop : 0,
  184. refreshBodyHeight : 0,
  185. loadMoreTimer : null,
  186. fixedTopMargin : 0,
  187. scrollTop : 0,
  188. srcollTimer : null
  189. }
  190. },
  191. mounted:function(){
  192. if(this.isLoading){
  193. this.pageLoadingOpen();
  194. }
  195. // 刷新相关
  196. setTimeout(()=>{
  197. if(this.refresh || this.loadmore){
  198. this.getDomSize('guiPageBody', (res)=>{
  199. this.refreshBodyHeight = res.height;
  200. this.getDomSize('guiPageFixedTop', (res)=>{
  201. if(res.height){
  202. this.refreshBodyHeight -= res.height;
  203. this.fixedTopMargin = res.height;
  204. }
  205. })
  206. });
  207. }
  208. },200);
  209. },
  210. watch:{
  211. isLoading : function (val) {
  212. if(val){
  213. this.pageLoadingOpen();
  214. }else{
  215. this.pageLoadingClose();
  216. }
  217. }
  218. },
  219. created:function(){
  220. this.footerHeight = this.footerSets.height + 'rpx';
  221. // #ifdef H5
  222. if(this.customHeader){
  223. this.fixedTop = this.headerSets.height;
  224. }else{
  225. this.fixedTop = 44;
  226. }
  227. // #endif
  228. try {
  229. var system = uni.getSystemInfoSync();
  230. if(system.model){
  231. system.model = system.model.replace(' ', '');
  232. system.model = system.model.toLowerCase();
  233. this.statusBarHeight = system.statusBarHeight;
  234. var res1 = system.model.indexOf('iphonex');
  235. if(res1 > 5){res1 = -1;}
  236. var res2 = system.model.indexOf('iphone1');
  237. if(res2 > 5){res2 = -1;}
  238. if(res1 != -1 || res2 != -1){
  239. this.iphoneXButtomHeight = '50rpx';
  240. this.footerHeight = (this.footerSets.height + 50 ) + 'rpx';
  241. }
  242. }
  243. // #ifdef MP-ALIPAY
  244. this.statusBarHeight = 0;
  245. // #endif
  246. // #ifdef APP-PLUS
  247. this.iphoneXButtomHeight = '0rpx';
  248. this.footerHeight = this.footerSets.height + 'rpx';
  249. if(plus.navigator.isFullscreen()){
  250. this.statusBarHeight = 0;
  251. }
  252. // #endif
  253. if(this.isSwitchPage){
  254. this.iphoneXButtomHeight = '0rpx';
  255. this.footerHeight = this.footerSets.height + 'rpx';
  256. }
  257. // #ifndef H5
  258. if(this.customHeader){
  259. this.fixedTop = this.headerSets.height + this.statusBarHeight;
  260. }else{
  261. this.fixedTop = 0;
  262. }
  263. // #endif
  264. } catch (e){return null;}
  265. },
  266. methods:{
  267. pageLoadingOpen : function(){
  268. this.getRefs('guipageloading',0,(ref)=>{
  269. this.$refs.guipageloading.open();
  270. });
  271. },
  272. pageLoadingClose : function(){
  273. this.getRefs('guipageloading',0,(ref)=>{
  274. ref.close();
  275. });
  276. },
  277. // 下拉刷新相关
  278. touchstart : function (e){
  279. if(!this.refresh){return false;}
  280. if(this.apiLoadingStatus){return false;}
  281. this.$refs.guiPageRefresh.touchstart(e);
  282. },
  283. touchmove : function(e){
  284. if(!this.refresh){return false;}
  285. if(this.apiLoadingStatus){return false;}
  286. this.$refs.guiPageRefresh.touchmove(e);
  287. },
  288. touchend : function (e) {
  289. if(!this.refresh){return false;}
  290. if(this.apiLoadingStatus){return false;}
  291. this.$refs.guiPageRefresh.touchend(e);
  292. },
  293. scroll:function(e){
  294. if(this.srcollTimer != null){
  295. clearTimeout(this.srcollTimer);
  296. }
  297. this.srcollTimer = setTimeout(()=>{
  298. this.$refs.guiPageRefresh.scroll(e);
  299. this.$emit('scroll', e);
  300. this.scrollTop = e.detail.scrollTop;
  301. }, 100);
  302. },
  303. setScrollTop : function (scrollTop){
  304. this.scrollTop = scrollTop;
  305. },
  306. endReload : function(){
  307. this.$refs.guiPageRefresh.endReload();
  308. },
  309. reload : function(){
  310. if(this.apiLoadingStatus){return false;}
  311. this.$emit('reload');
  312. if(this.loadmore){this.$refs.guipageloadmore.stoploadmore();}
  313. },
  314. // 获取元素尺寸
  315. getDomSize : function(domIDOrRef, fun, count){
  316. if(!count){count = 1;}
  317. if(count >= 50){
  318. fun({width:0, height:0});
  319. return false;
  320. }
  321. // #ifndef APP-NVUE
  322. uni.createSelectorQuery().in(this).select('#'+domIDOrRef).boundingClientRect().exec((res)=>{
  323. if(res[0] == null){
  324. count += 1;
  325. setTimeout(()=>{this.getDomSize(domIDOrRef, fun, count);}, 50);
  326. }else{
  327. fun(res[0]);
  328. return ;
  329. }
  330. });
  331. // #endif
  332. // #ifdef APP-NVUE
  333. var el = this.$refs[domIDOrRef];
  334. dom.getComponentRect(el, (res) => {
  335. if(res.result == false){
  336. count += 1;
  337. setTimeout(()=>{this.getDomSize(domIDOrRef, fun, count);}, 50);
  338. }else{
  339. fun(res.size);
  340. return ;
  341. }
  342. });
  343. // #endif
  344. },
  345. stopfun : function(e){e.stopPropagation(); return null;},
  346. headerTap : function(){
  347. this.headerTapNumber ++;
  348. if(this.headerTapNumber >= 2){
  349. this.$emit('gotoTop');
  350. this.headerTapNumber = 0;
  351. }else{
  352. setTimeout(()=>{this.headerTapNumber = 0;}, 1000);
  353. }
  354. },
  355. getRefs : function(ref, count, fun){
  356. if(count >= 50){
  357. fun(this.$refs[ref]);
  358. return false;
  359. }
  360. var refReturn = this.$refs[ref];
  361. if(refReturn){
  362. // #ifdef APP-NVUE
  363. fun(refReturn);
  364. return;
  365. // #endif
  366. // #ifndef APP-NVUE
  367. if(refReturn._data){
  368. fun(refReturn);
  369. return;
  370. }
  371. // #endif
  372. }else{
  373. count++;
  374. setTimeout(()=>{
  375. this.getRefs(ref, count, fun);
  376. }, 100);
  377. }
  378. },
  379. loadmorefun : function () {
  380. if(!this.loadmore){return false;}
  381. if(this.apiLoadingStatus){return false;}
  382. // 获取加载组件状态看一下是否还能继续加载
  383. // 保证触底只执行一次加载
  384. if(this.loadMoreTimer != null){clearTimeout(this.loadMoreTimer);}
  385. this.loadMoreTimer = setTimeout(() => {
  386. var status = this.$refs.guipageloadmore.loadMoreStatus;
  387. if(status != 0){return null;}
  388. this.$refs.guipageloadmore.loading();
  389. this.$emit('loadmorefun');
  390. }, 80);
  391. },
  392. stoploadmore : function(){
  393. this.$refs.guipageloadmore.stoploadmore();
  394. },
  395. nomore : function () {
  396. this.$refs.guipageloadmore.nomore();
  397. },
  398. loadEmpty : function(){
  399. this.$refs.guipageloadmore.empty();
  400. }
  401. }
  402. }
  403. </script>
  404. <style scoped>
  405. .gui-sbody{width:750rpx;}
  406. .gui-page-loading{width:750rpx; position:fixed; left:0; top:0; bottom:0; flex:1; z-index:99999;}
  407. .gui-page-loading-points{width:20rpx; height:20rpx; border-radius:50rpx; margin:10rpx;}
  408. /* #ifndef APP-NVUE */
  409. .gui-sbody{min-height:calc(100vh - var(--window-top) - var(--window-bottom));}
  410. @keyframes pageLoading1{0% {opacity:0.5; transform:scale(1);} 40% {opacity:1; transform:scale(1.5);} 60%{opacity:0.5; transform:scale(1);}}
  411. @keyframes pageLoading2{20% {opacity:0.5; transform:scale(1);} 60% {opacity:1; transform:scale(1.5);} 80% {opacity:0.5; transform:scale(1);}}
  412. @keyframes pageLoading3{40% {opacity:0.5; transform:scale(1);} 80% {opacity:1; transform:scale(1.5);} 100% {opacity:0.5; transform:scale(1);}}
  413. .animate1{animation:pageLoading1 1.2s infinite linear;}
  414. .animate2{animation:pageLoading2 1.2s infinite linear;}
  415. .animate3{animation:pageLoading3 1.2s infinite linear;}
  416. /* #endif */
  417. .gui-header{width:750rpx; position:fixed; left:0; top:0;}
  418. .gui-page-footer{width:750rpx; position:fixed; left:0; bottom:0;}
  419. /* #ifdef H5 */
  420. .gui-switch-page-footer{bottom:50px;}
  421. /* #endif */
  422. .gui-page-status-bar{width:750rpx;}
  423. .gui-page-pendant{position:fixed;}
  424. .gui-page-fixed-top{position:fixed; top:44px; left:0px; width:750rpx; z-index:99998; overflow:hidden;}
  425. .gui-page-loadmore{padding-bottom:30rpx;}
  426. </style>