index.nvue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <template>
  2. <view class="page">
  3. <view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
  4. <text class="page-title">推荐</text>
  5. <scroll-view class="scroll-content" scroll-y="true" @scrolltolower="onReachBottom">
  6. <view class="recommend-grid">
  7. <view class="recommend-card" v-for="(item, index) in recommends" :key="item.id || index" @click="onItemClick(item)">
  8. <image class="card-img" :src="item.coverImage" mode="aspectFill" v-if="item.coverImage"></image>
  9. <image class="card-img" src="/static/home/banner.png" mode="aspectFill" v-else></image>
  10. <text class="card-title">{{item.title}}</text>
  11. <view class="card-footer">
  12. <view class="badge-circle">
  13. <text class="badge-text">研</text>
  14. </view>
  15. <text class="footer-name">{{item.author || '研年健康贴士'}}</text>
  16. <view class="badge-official">
  17. <text class="official-text">官方</text>
  18. </view>
  19. </view>
  20. </view>
  21. </view>
  22. <text class="load-tip" v-if="loadMore">加载中...</text>
  23. <text class="load-tip" v-else-if="noMore && recommends.length > 0">已经到底了</text>
  24. </scroll-view>
  25. <ay-toast ref="ayToast" />
  26. </view>
  27. </template>
  28. <script>
  29. import ayToast from '@/components/ay-toast/ay-toast.nvue'
  30. import { getPublishedArticles } from '@/utils/api/article.js'
  31. import config from '@/utils/http/config.js'
  32. // 文章阅读页基础地址(去掉 /api 后缀)
  33. const serverBase = config.baseUrl.replace(/\/api\/?$/, '')
  34. export default {
  35. components: {
  36. ayToast
  37. },
  38. data() {
  39. return {
  40. statusBarHeight: 44,
  41. pageNum: 1,
  42. pageSize: 10,
  43. loadMore: false,
  44. noMore: false,
  45. recommends: []
  46. }
  47. },
  48. onShow() {
  49. const sysInfo = uni.getSystemInfoSync()
  50. this.statusBarHeight = sysInfo.statusBarHeight || 44
  51. // 首次加载数据
  52. if (this.recommends.length === 0) {
  53. this.loadArticles()
  54. }
  55. },
  56. onPullDownRefresh() {
  57. this.pageNum = 1
  58. this.noMore = false
  59. this.loadArticles(true)
  60. },
  61. methods: {
  62. /** 加载文章列表 */
  63. async loadArticles(isRefresh) {
  64. if (this.loadMore) return
  65. this.loadMore = true
  66. try {
  67. const res = await getPublishedArticles({
  68. pageNum: this.pageNum,
  69. pageSize: this.pageSize
  70. })
  71. const records = res.records || []
  72. if (isRefresh) {
  73. this.recommends = records
  74. } else {
  75. this.recommends = this.recommends.concat(records)
  76. }
  77. // 判断是否还有更多数据
  78. if (records.length < this.pageSize) {
  79. this.noMore = true
  80. }
  81. } catch (e) {
  82. console.error('加载文章列表失败', e)
  83. } finally {
  84. this.loadMore = false
  85. if (isRefresh) {
  86. uni.stopPullDownRefresh()
  87. }
  88. }
  89. },
  90. /** 触底加载更多 */
  91. onReachBottom() {
  92. if (this.loadMore || this.noMore) return
  93. this.pageNum++
  94. this.loadArticles(false)
  95. },
  96. /** 点击文章,跳转webview阅读页 */
  97. onItemClick(item) {
  98. const articleUrl = serverBase + '/article/view/' + item.id
  99. uni.navigateTo({
  100. url: '/pages/webview/webview?url=' + encodeURIComponent(articleUrl) + '&title=' + encodeURIComponent(item.title || '文章详情')
  101. })
  102. }
  103. }
  104. }
  105. </script>
  106. <style>
  107. .page {
  108. flex: 1;
  109. background-color: #F5F5F5;
  110. }
  111. .status-bar {
  112. background-color: #F5F5F5;
  113. }
  114. .page-title {
  115. font-weight: bold;
  116. font-size: 48rpx;
  117. color: #545F71;
  118. margin-left: 50rpx;
  119. margin-top: 40rpx;
  120. margin-bottom: 40rpx;
  121. }
  122. .scroll-content {
  123. flex: 1;
  124. }
  125. .recommend-grid {
  126. flex-direction: row;
  127. flex-wrap: wrap;
  128. justify-content: space-between;
  129. padding-left: 40rpx;
  130. padding-right: 40rpx;
  131. }
  132. .recommend-card {
  133. width: 324rpx;
  134. background-color: #FFFFFF;
  135. border-radius: 20rpx;
  136. margin-bottom: 28rpx;
  137. overflow: hidden;
  138. }
  139. .card-img {
  140. width: 324rpx;
  141. height: 340rpx;
  142. }
  143. .card-title {
  144. padding-left: 16rpx;
  145. padding-right: 16rpx;
  146. padding-top: 20rpx;
  147. padding-bottom: 16rpx;
  148. font-size: 32rpx;
  149. color: #545F71;
  150. lines: 2;
  151. text-overflow: ellipsis;
  152. }
  153. .card-footer {
  154. flex-direction: row;
  155. align-items: center;
  156. padding-left: 16rpx;
  157. padding-bottom: 20rpx;
  158. }
  159. .badge-circle {
  160. width: 32rpx;
  161. height: 32rpx;
  162. background-color: #389588;
  163. border-radius: 32rpx;
  164. justify-content: center;
  165. align-items: center;
  166. margin-right: 4rpx;
  167. }
  168. .badge-text {
  169. font-size: 20rpx;
  170. color: #FFFFFF;
  171. }
  172. .footer-name {
  173. font-size: 24rpx;
  174. color: #BBBDBF;
  175. }
  176. .badge-official {
  177. width: 60rpx;
  178. height: 32rpx;
  179. background-color: #000000;
  180. border-radius: 4rpx;
  181. justify-content: center;
  182. align-items: center;
  183. margin-left: 10rpx;
  184. }
  185. .official-text {
  186. font-size: 24rpx;
  187. color: #EAC384;
  188. }
  189. .load-tip {
  190. text-align: center;
  191. font-size: 24rpx;
  192. color: #999999;
  193. padding-top: 20rpx;
  194. padding-bottom: 40rpx;
  195. }
  196. </style>