u-paging.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. <template>
  2. <view class="u-paging">
  3. <view class="u-paging__container" :style="[containerStyle]" :class="[{ 'u-paging__fixed':fixed && !usePageScroll, 'u-paging__full': !usePageScroll }]">
  4. <view class="u-paging__top" :style="[topStyle]" v-if="$slots.top">
  5. <slot name="top"></slot>
  6. </view>
  7. <view class="u-paging__main" :style="[{marginTop: usePageScroll && topStyle.top}]">
  8. <scroll-view
  9. class="u-paging__scroll"
  10. :class="[{ 'u-paging__scroll--absolute': usePageScroll === false }]"
  11. :scroll-y="!usePageScroll"
  12. :scroll-top="scrollTop"
  13. :lower-threshold="lowerThreshold"
  14. @scrolltolower="pageReachBottom"
  15. >
  16. <view class="u-paging__touch">
  17. <view
  18. class="u-paging__body"
  19. id="refresh-container"
  20. ref="refresh-container"
  21. <!-- #ifdef MP-BAIDU || MP-TOUTIAO || MP-LARK || MP-KUAISHOU || MP-XHS -->
  22. @touchstart="touchStartHandler"
  23. @touchmove="touchMoveHandler"
  24. @touchend="touchEndHandler"
  25. <!-- #endif -->
  26. <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || MP-ALIPAY || H5 -->
  27. @touchstart="pageing.touchstart"
  28. @touchmove="pageing.touchmove"
  29. @touchend="pageing.touchend"
  30. :refreshstatus="refreshStatus"
  31. :change:refreshstatus="pageing.refreshStatusChange"
  32. :refreshshow="refreshShow"
  33. :change:refreshshow="pageing.refreshShowChange"
  34. :readyrefresh="readyRefresh"
  35. :change:readyrefresh="pageing.readyRefreshChange"
  36. :refresherthreshold="refresherThreshold"
  37. :change:refresherthreshold="pageing.refresherThresholdChange"
  38. :curreadyrefresh="curReadyRefresh"
  39. :change:curreadyrefresh="pageing.curReadyRefreshChange"
  40. :refresherenabled="refresherEnabled"
  41. :change:refresherenabled="pageing.refresherEnabledChange"
  42. <!-- #endif -->
  43. >
  44. <view v-if="refresherEnabled" class="u-paging__refresh-indicator">
  45. <u-icon name="arrow-downward" v-if="refreshStatus === 1"></u-icon>
  46. <u-icon name="arrow-upward" v-if="refreshStatus === 2"></u-icon>
  47. <u-loading-icon size="18" v-if="refreshStatus === 3"></u-loading-icon>
  48. <text class="u-paging__refresh-indicator-text">{{ refreshText }}</text>
  49. </view>
  50. <view class="u-paging__wrapper">
  51. <slot></slot>
  52. <u-loadmore
  53. v-if="pageNo > 1"
  54. :status="loadMoreStatus"
  55. :load-text="loadingMoreDefaultText"
  56. :loadmore-text="loadingMoreDefaultText"
  57. :nomore-text="loadingMoreNoMoreText"
  58. :margin-top="0"
  59. :margin-bottom="0"
  60. :height="40"
  61. />
  62. </view>
  63. </view>
  64. </view>
  65. </scroll-view>
  66. </view>
  67. </view>
  68. </view>
  69. </template>
  70. <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5-->
  71. <script lang="wxs" module="pageing" src="./pageing.wxs"></script>
  72. <!-- #endif -->
  73. <!-- #ifdef MP-ALIPAY -->
  74. <script lang="sjs" module="pageing" src="./pageing.sjs"></script>
  75. <!-- #endif -->
  76. <script>
  77. // #ifdef MP-BAIDU || MP-TOUTIAO || MP-LARK || MP-KUAISHOU || MP-XHS
  78. import pageing from './pageing.js';
  79. // #endif
  80. import props from './props.js';
  81. import mixin from '../../libs/mixin/mixin'
  82. import mpMixin from '../../libs/mixin/mpMixin';
  83. /**
  84. * u-paging 分页组件
  85. * @description 支持下拉刷新和下滑加载更多的分页组件,基于wxs技术实现
  86. * @tutorial https://uview.d3u.cn/components/paging.html
  87. *
  88. * @property {Array} value/modelValue 数据列表(v-model绑定值,Vue2使用value,Vue3使用modelValue)
  89. * @property {String} bgColor 背景颜色,支持颜色名称、十六进制、rgb等格式
  90. * @property {String|Number} height 组件高度,支持字符串或数字类型,如'100px'或100
  91. * @property {Boolean} fixed 是否固定在底部,默认true
  92. * @property {Boolean} usePageScroll 是否使用页面滚动,true时使用页面滚动,false时使用组件内部滚动
  93. * @property {Boolean} refresherEnabled 是否启用下拉刷新功能,默认true
  94. * @property {Number|String} refresherThreshold 下拉刷新触发距离,单位px,默认40
  95. * @property {String} refresherDefaultText 下拉刷新前的提示文字,默认'继续下拉刷新'
  96. * @property {String} refresherPullingText 下拉刷新释放时的提示文字,默认'松开立即刷新'
  97. * @property {String} refresherRefreshingText 下拉刷新进行中的提示文字,默认'正在刷新'
  98. * @property {String} refresherCompleteText 下拉刷新完成后的提示文字,默认'刷新成功'
  99. * @property {Boolean} loadingMoreEnabled 是否启用上拉加载更多功能,默认true
  100. * @property {Number|String} lowerThreshold 上拉加载更多的触发距离,距离底部多少px时触发,默认50
  101. * @property {String} loadingMoreDefaultText 上拉加载更多时的默认提示文字,默认'加载更多...'
  102. * @property {String} loadingMoreNoMoreText 没有更多数据时的提示文字,默认'没有更多数据了'
  103. *
  104. * @event {Function} reload 刷新事件,无参数
  105. * @event {Function} complete 完成加载事件,参数:(data, hasMore)
  106. * @event {Function} clear 重置状态事件,无参数
  107. *
  108. * @example <u-paging v-model="dataList" @reload="reload" @complete="complete"></u-paging>
  109. */
  110. export default {
  111. name: 'u-paging',
  112. mixins: [
  113. mpMixin,
  114. mixin,
  115. props,
  116. // #ifdef MP-BAIDU || MP-TOUTIAO || MP-LARK || MP-KUAISHOU || MP-XHS
  117. pageing,
  118. // #endif
  119. ],
  120. data() {
  121. return {
  122. scrollTop: 0,
  123. // 1: 下拉刷新, 2: 释放刷新, 3: 刷新中, 4: 刷新完成
  124. refreshStatus: 1,
  125. refreshShow: false,
  126. readyRefresh: false,
  127. curReadyRefresh: true,
  128. isLoadingMore: false,
  129. isReload: false,
  130. pageNo: 1,
  131. pageSize: 20,
  132. hasMore: true,
  133. innerDataList: [],
  134. windowTop: 0
  135. }
  136. },
  137. computed: {
  138. // 计算刷新状态文字
  139. refreshText() {
  140. const statusMap = {
  141. 1: this.refresherDefaultText,
  142. 2: this.refresherPullingText,
  143. 3: this.refresherRefreshingText,
  144. 4: this.refresherCompleteText
  145. }
  146. return statusMap[this.refreshStatus]
  147. },
  148. // 计算加载更多状态
  149. loadMoreStatus() {
  150. if (this.isLoadingMore) {
  151. return 'loading'
  152. } else if (!this.hasMore) {
  153. return 'nomore'
  154. } else {
  155. return 'loadmore'
  156. }
  157. },
  158. containerStyle() {
  159. let style = {}
  160. if(this.bgColor){
  161. style.backgroundColor = this.bgColor
  162. }
  163. if(this.height){
  164. style.height = uni.$u.addUnit(this.height)
  165. }
  166. if(this.fixed && !this.usePageScroll) {
  167. style.top = this.windowTop
  168. }
  169. return style
  170. },
  171. topStyle(e) {
  172. let style = {}
  173. if(this.usePageScroll) {
  174. style.position = 'fixed'
  175. style.left = 0
  176. style.right = 0
  177. style.zIndex = 999
  178. style.top = this.windowTop
  179. style.width = 'auto'
  180. }
  181. return style
  182. },
  183. },
  184. watch: {
  185. // #ifdef VUE2
  186. value: {
  187. handler(newVal) {
  188. this.innerDataList = [...newVal]
  189. },
  190. immediate: true
  191. },
  192. // #endif
  193. // #ifdef VUE3
  194. modelValue: {
  195. handler(newVal) {
  196. this.innerDataList = [...newVal]
  197. },
  198. immediate: true
  199. },
  200. // #endif
  201. },
  202. mounted() {
  203. this.$nextTick(() => {
  204. this.init()
  205. })
  206. },
  207. methods: {
  208. init() {
  209. // #ifdef H5
  210. const pageHeadNode = document.getElementsByTagName('uni-page-head');
  211. if (!pageHeadNode.length){
  212. this.windowTop = 0;
  213. return;
  214. }
  215. // #endif
  216. const { windowTop } = uni.$u.window()
  217. this.windowTop = uni.$u.addUnit(windowTop);
  218. this.reload()
  219. },
  220. // 刷新
  221. reload() {
  222. this.triggerQuery(true)
  223. },
  224. // 完成加载
  225. complete(data, hasMore = true) {
  226. if (this.refreshStatus === 3 || this.isReload) {
  227. this.innerDataList = data || []
  228. this.refreshStatus = 4
  229. this.refreshShow = false
  230. this.hasMore = hasMore
  231. this.isLoadingMore = false
  232. if(this.isReload) {
  233. this.scrollTop = 0
  234. this.isReload = false
  235. }
  236. } else {
  237. if (data && data.length > 0) {
  238. this.innerDataList = [...this.innerDataList, ...data]
  239. }
  240. this.hasMore = hasMore
  241. this.isLoadingMore = false
  242. }
  243. // #ifdef VUE2
  244. this.$emit('input', this.innerDataList)
  245. // #endif
  246. // #ifdef VUE3
  247. this.$emit('update:modelValue', this.innerDataList)
  248. // #endif
  249. },
  250. // 重置状态
  251. clear() {
  252. this.pageNo = 1
  253. this.hasMore = true
  254. this.isLoadingMore = false
  255. this.innerDataList = []
  256. this.refreshStatus = 1
  257. this.refreshShow = false
  258. this.readyRefresh = false
  259. },
  260. // 设置刷新状态
  261. setRefreshStatus(status) {
  262. this.refreshStatus = status
  263. if (status === 3) {
  264. this.triggerQuery(false)
  265. }
  266. },
  267. // 设置刷新显示状态
  268. setRefreshShow(data) {
  269. this.refreshShow = data.refreshShow
  270. },
  271. // 设置准备刷新状态
  272. setReadyRefresh(data) {
  273. this.readyRefresh = data.readyRefresh
  274. },
  275. // 触发查询
  276. triggerQuery(isReload) {
  277. this.pageNo = 1
  278. this.isReload = isReload
  279. this.$emit('query', this.pageNo, this.pageSize)
  280. },
  281. // 滚动到底部触发加载更多
  282. pageReachBottom() {
  283. if (!this.isLoadingMore && this.hasMore && this.loadingMoreEnabled && this.refreshStatus != 3) {
  284. this.isLoadingMore = true
  285. this.pageNo++
  286. this.$emit('query', this.pageNo, this.pageSize)
  287. }
  288. }
  289. }
  290. }
  291. </script>
  292. <style lang="scss" scoped>
  293. @import "../../libs/css/components.scss";
  294. .u-paging{
  295. position: relative;
  296. width: 100%;
  297. height: 100%;
  298. overflow: hidden;
  299. &__top{
  300. }
  301. &__main{
  302. flex: 1;
  303. overflow: hidden;
  304. position: relative;
  305. @include flex(row);
  306. }
  307. &__container{
  308. position: relative;
  309. @include flex(column);
  310. /* #ifndef APP-NVUE */
  311. overflow: hidden;
  312. /* #endif */
  313. }
  314. &__full {
  315. width: 100%;
  316. height: 100%;
  317. }
  318. &__fixed{
  319. position: fixed;
  320. top: 0;
  321. left: 0;
  322. bottom: 0;
  323. right: 0;
  324. height: auto;
  325. width: auto;
  326. }
  327. &__refresh-indicator{
  328. @include flex(row);
  329. align-items: center;
  330. justify-content: center;
  331. margin-top: -30px;
  332. height: 30px;
  333. &-icon{
  334. }
  335. &-text{
  336. margin-left: 5px;
  337. font-size: 14px;
  338. color: #666;
  339. text-align: center;
  340. line-height: 1.4;
  341. }
  342. }
  343. &__scroll{
  344. width: 100%;
  345. height: 100%;
  346. &--absolute{
  347. position: absolute;
  348. top: 0;
  349. left: 0;
  350. display: block;
  351. }
  352. }
  353. &__touch{
  354. position: relative;
  355. width: 100%;
  356. height: 100%;
  357. }
  358. &__body{
  359. @include flex(column);
  360. position: relative;
  361. height: 100%;
  362. }
  363. &__wrapper{
  364. display: block;
  365. }
  366. &__loadmore{
  367. position: relative;
  368. margin: 5px 0;
  369. }
  370. &__item{
  371. position: relative;
  372. width: 100%;
  373. }
  374. }
  375. </style>