index.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. <script lang="ts" setup>
  2. import { usePagination } from 'alova/client'
  3. import { reactive, ref, watch } from 'vue'
  4. import { getCouponIssuerAccountByPageMap } from '@/api/income'
  5. import { getInviteList } from '@/api/me'
  6. import CustomNavigationBar from '@/components/CustomNavigationBar.vue'
  7. import IncomeItem from '@/components/IncomeItem.vue'
  8. import MescrollUni from '@/components/mescroll.vue'
  9. import MescrollEmpty from '@/custom-components/custom-mescroll-uni/components/mescroll-empty.vue'
  10. import { changtime, safeAreaInsets } from '@/utils'
  11. definePage({
  12. style: {
  13. navigationBarTitleText: '收益',
  14. navigationStyle: 'custom',
  15. },
  16. })
  17. const topSafeAreaHeight = safeAreaInsets?.top || 0
  18. const activeIndex = ref(0)
  19. const showLoading = ref(false)
  20. const pickerDate = ref(Date.now())
  21. const pickerShow = ref(false)
  22. const totalAmount = ref(0)
  23. let mescroll = null
  24. const inviteType = reactive([
  25. { name: '已结算', key: 0 },
  26. { name: '待结算', key: 1 },
  27. ])
  28. const { send, data, pageCount, loading, page, pageSize, isLastPage, refresh, reload, onSuccess, onError } = usePagination((page, pageSize) => getCouponIssuerAccountByPageMap({
  29. pageNo: page,
  30. pageSize,
  31. type: 1,
  32. status: activeIndex.value,
  33. year: changtime(pickerDate.value, 'YYYY'),
  34. month: changtime(pickerDate.value, 'MM'),
  35. }), {
  36. initialPage: 1,
  37. initialPageSize: 10,
  38. append: true,
  39. preloadNextPage: false,
  40. preloadPreviousPage: false,
  41. total: response => response.total,
  42. data: response => response.detailList,
  43. immediate: false,
  44. })
  45. // ============ mescroll 配置 ============
  46. const downOption = reactive({
  47. use: true,
  48. auto: false,
  49. bgColor: '#f8f8fa',
  50. textColor: '#666',
  51. textInOffset: '下拉刷新',
  52. textOutOffset: '释放刷新',
  53. textLoading: '刷新中...',
  54. offset: 80,
  55. })
  56. const upOption = reactive({
  57. use: true,
  58. auto: false,
  59. noMoreSize: 7,
  60. isBounce: true,
  61. page: {
  62. num: page.value,
  63. size: pageSize.value,
  64. },
  65. empty: {
  66. use: false,
  67. },
  68. textNoMore: '--- 已经到底了 ---',
  69. toTop: {
  70. src: '/static/images/mescroll-totop.png',
  71. offset: 1000,
  72. bottom: 120,
  73. },
  74. // 开启虚拟列表优化(大数据量时)
  75. virtual: {
  76. use: false,
  77. rowHeight: 300,
  78. }
  79. })
  80. const emptyOption = reactive({
  81. use: true,
  82. tip: '暂无数据',
  83. btnText: '刷新试试',
  84. icon: '/static/images/mescroll-empty.png',
  85. fixed: false,
  86. top: '200rpx',
  87. })
  88. function mescrollInit(ref) {
  89. mescroll = ref
  90. }
  91. async function downCallback() {
  92. console.log('下拉刷新触发')
  93. await refresh()
  94. }
  95. async function upCallback() {
  96. console.log('上拉加载触发')
  97. if (isLastPage.value) {
  98. console.log(mescroll)
  99. mescroll.endSuccess(0, false)
  100. return
  101. }
  102. page.value++
  103. }
  104. onSuccess((response) => {
  105. let isNextPage = isLastPage.value
  106. // 此处处理alova isLastPage默认值为true,首次请求出现的无法加载下一页问题
  107. if (pageCount.value > page.value && isLastPage.value) {
  108. isNextPage = false
  109. }
  110. totalAmount.value = response.data?.totalAmount || 0
  111. // mescroll.setPageNum(page.value - 1)
  112. mescroll.endSuccess(data.value.length, !isNextPage)
  113. })
  114. onError(() => {
  115. console.log('请求失败')
  116. mescroll.endErr()
  117. })
  118. onShow(async () => {
  119. await send()
  120. })
  121. async function tabChange(index) {
  122. // 切换tab时,重置上拉加载
  123. // mescroll.resetUpScroll(true)
  124. // mescroll.showDownScroll()
  125. mescroll.hideUpScroll()
  126. mescroll.scrollTo(0, 0)
  127. showLoading.value = true
  128. await reload()
  129. showLoading.value = false
  130. }
  131. async function pickerConfirm() {
  132. pickerShow.value = false
  133. mescroll.scrollTo(0, 0)
  134. console.log(mescroll)
  135. mescroll.hideUpScroll()
  136. showLoading.value = true
  137. await reload()
  138. showLoading.value = false
  139. }
  140. </script>
  141. <template>
  142. <view class="income-container">
  143. <custom-navigation-bar :show-back="true" title="收益" />
  144. <view class="income-content"
  145. :style="{ marginTop: `calc(${topSafeAreaHeight}px + 88rpx)`, height: `calc(100vh - ${topSafeAreaHeight}px - 88rpx)` }">
  146. <view class="tabs-container flex">
  147. <up-tabs v-model:current="activeIndex" :scrollable="false" class="tabs-content" :list="inviteType"
  148. line-height="0" line-width="0" line-color="transparent"
  149. :active-style="{ fontWeight: 'bolder', fontSize: '30rpx', color: '#333333' }"
  150. :inactive-style="{ fontWeight: '400', fontSize: '30rpx', color: '#888888' }"
  151. item-style="height: 88rpx;" @change="tabChange" />
  152. </view>
  153. <view class="income-content-search">
  154. <view class="income-menu-time-filter-text" @click="pickerShow = true">
  155. <text>{{ changtime(pickerDate) }}</text>
  156. <up-icon name="arrow-down" color="#666666" size="14" />
  157. <up-datetime-picker v-model="pickerDate" :show="pickerShow" mode="year-month" close-on-click-overlay
  158. @confirm="pickerConfirm" @cancel="pickerShow = false" />
  159. </view>
  160. <view class="income-menu-total-amount">
  161. 合计:¥{{ totalAmount }}
  162. </view>
  163. </view>
  164. <view class="income-content-list"
  165. :style="{ height: `calc(100vh - ${topSafeAreaHeight}px - 88rpx - 88rpx - 72rpx)` }">
  166. <mescroll-uni id="mescrollContainer" :down="downOption" :up="upOption" :fixed="false"
  167. @init="mescrollInit" @down="downCallback" @up="upCallback">
  168. <view v-for="item in data" :key="item.userId" class="income-content-item">
  169. <income-item :data="item" :type="activeIndex" />
  170. </view>
  171. <mescroll-empty v-if="data.length === 0 && !loading" icon="'none'" tip="暂无数据" :option="emptyOption"
  172. @emptyclick="reload" />
  173. <!-- 滚动区域内的loading遮罩 -->
  174. <view v-if="showLoading && loading" class="loading-mask">
  175. <view class="loading-spinner" />
  176. <text class="loading-text">加载中...</text>
  177. </view>
  178. </mescroll-uni>
  179. </view>
  180. </view>
  181. </view>
  182. </template>
  183. <style lang="scss" scoped>
  184. .income-container {
  185. // height: 100vh;
  186. .income-content {
  187. display: flex;
  188. flex-direction: column;
  189. .income-content-search {
  190. padding: 27rpx 25rpx;
  191. padding-bottom: 15rpx;
  192. background-color: #f8f8fa;
  193. height: 60rpx;
  194. display: flex;
  195. justify-content: space-between;
  196. align-items: center;
  197. .income-menu-time-filter-text {
  198. font-weight: 400;
  199. font-size: 26rpx;
  200. color: #000000;
  201. display: flex;
  202. align-items: center;
  203. gap: 12rpx;
  204. }
  205. .income-menu-total-amount {
  206. font-weight: 400;
  207. font-size: 24rpx;
  208. color: #333333;
  209. }
  210. }
  211. .tabs-container {
  212. height: 88rpx;
  213. display: flex;
  214. flex-direction: row;
  215. justify-content: space-around;
  216. align-items: center;
  217. .tabs-content {
  218. width: 100%;
  219. :deep(.u-tabs__wrapper__nav__item) {
  220. &:first-child {
  221. position: relative;
  222. &:after {
  223. content: '';
  224. position: absolute;
  225. top: 50%;
  226. right: 0;
  227. transform: translateY(-50%);
  228. width: 2rpx;
  229. height: 40rpx;
  230. background-color: #9a9b9b;
  231. }
  232. }
  233. }
  234. }
  235. }
  236. .income-content-list {
  237. background-color: #f8f8fa;
  238. padding: 0 24rpx;
  239. display: flex;
  240. flex-direction: column;
  241. position: relative;
  242. #mescrollContainer {
  243. height: 100%;
  244. }
  245. .income-content-item {
  246. margin-top: 20rpx;
  247. }
  248. // loading遮罩样式
  249. .loading-mask {
  250. position: absolute;
  251. top: 0;
  252. left: 0;
  253. right: 0;
  254. bottom: 0;
  255. background-color: rgba(255, 255, 255, 0.8);
  256. display: flex;
  257. flex-direction: column;
  258. justify-content: center;
  259. align-items: center;
  260. z-index: 999;
  261. }
  262. .loading-spinner {
  263. width: 40rpx;
  264. height: 40rpx;
  265. border: 4rpx solid #e0e0e0;
  266. border-top-color: #ed6b66;
  267. border-radius: 50%;
  268. animation: spin 1s linear infinite;
  269. }
  270. .loading-text {
  271. margin-top: 16rpx;
  272. font-size: 24rpx;
  273. color: #666;
  274. }
  275. @keyframes spin {
  276. to {
  277. transform: rotate(360deg);
  278. }
  279. }
  280. }
  281. }
  282. }
  283. </style>