my-posts.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. <template>
  2. <view class="posts-container">
  3. <scroll-view scroll-y class="posts-list" @scrolltolower="onLoadMore">
  4. <view v-if="loading && posts.length === 0" class="loading-wrap">
  5. <text class="loading-text">加载中...</text>
  6. </view>
  7. <view v-else-if="posts.length === 0" class="empty-wrap">
  8. <text class="empty-icon">📝</text>
  9. <text class="empty-text">还没有发布过记录</text>
  10. <button class="write-btn" @click="goEdit">写一篇</button>
  11. </view>
  12. <view v-else class="post-list">
  13. <view v-for="item in posts" :key="item.id" class="post-card">
  14. <image
  15. v-if="item.coverImage"
  16. class="post-cover"
  17. :src="getImageUrl(item.coverImage)"
  18. mode="aspectFill"
  19. />
  20. <view class="post-body">
  21. <text class="post-title">{{ item.title }}</text>
  22. <view class="post-meta">
  23. <text class="post-date">{{ formatDate(item.publishedAt || item.createdAt) }}</text>
  24. <text
  25. :class="['post-status', getStatusClass(item)]"
  26. >
  27. {{ getStatusText(item) }}
  28. </text>
  29. </view>
  30. <text v-if="item.auditStatus === 'rejected' && item.auditReason" class="post-reason">
  31. 驳回原因:{{ item.auditReason }}
  32. </text>
  33. </view>
  34. </view>
  35. </view>
  36. <view v-if="loadingMore" class="loading-more">
  37. <text class="loading-text">加载中...</text>
  38. </view>
  39. <view v-if="noMore && posts.length > 0" class="no-more">
  40. <text class="no-more-text">— 没有更多了 —</text>
  41. </view>
  42. </scroll-view>
  43. </view>
  44. </template>
  45. <script>
  46. import { getMyPosts } from '@/utils/api.js'
  47. import config from '@/config.js'
  48. export default {
  49. data() {
  50. return {
  51. posts: [],
  52. page: 1,
  53. size: 20,
  54. total: 0,
  55. loading: false,
  56. loadingMore: false,
  57. noMore: false
  58. }
  59. },
  60. onLoad() {
  61. this.loadPosts()
  62. },
  63. methods: {
  64. async loadPosts(isLoadMore) {
  65. if (!isLoadMore) {
  66. if (this.loading) return
  67. this.loading = true
  68. } else {
  69. if (this.loadingMore || this.noMore) return
  70. this.loadingMore = true
  71. }
  72. try {
  73. var res = await getMyPosts({ page: this.page, size: this.size })
  74. if (res.code === 200 && res.data) {
  75. var list = res.data.records || []
  76. if (this.page === 1) {
  77. this.posts = list
  78. } else {
  79. this.posts = this.posts.concat(list)
  80. }
  81. this.total = res.data.total || 0
  82. this.noMore = this.posts.length >= this.total
  83. }
  84. } catch (e) {
  85. uni.showToast({ title: '加载失败', icon: 'none' })
  86. } finally {
  87. this.loading = false
  88. this.loadingMore = false
  89. }
  90. },
  91. onLoadMore() {
  92. if (this.noMore || this.loading || this.loadingMore) return
  93. this.page++
  94. this.loadPosts(true)
  95. },
  96. goEdit() {
  97. uni.navigateTo({ url: '/pages/article-center/article-edit' })
  98. },
  99. formatDate(dateStr) {
  100. if (!dateStr) return ''
  101. return dateStr.slice(0, 10)
  102. },
  103. getStatusText(item) {
  104. if (item.status === 'draft' && (!item.auditStatus || item.auditStatus === 'pending')) {
  105. return '审核中'
  106. }
  107. if (item.auditStatus === 'rejected') return '已驳回'
  108. if (item.status === 'published') return '已发布'
  109. return '草稿'
  110. },
  111. getStatusClass(item) {
  112. if (item.auditStatus === 'approved' || item.status === 'published') return 'status-approved'
  113. if (item.auditStatus === 'pending') return 'status-pending'
  114. if (item.auditStatus === 'rejected') return 'status-rejected'
  115. return 'status-draft'
  116. },
  117. getImageUrl: function(path) {
  118. return config.API_BASE_URL + path
  119. }
  120. }
  121. }
  122. </script>
  123. <style scoped>
  124. .posts-container { min-height: 100vh; background: #f5f7fa; }
  125. .posts-list { height: 100vh; }
  126. .loading-wrap, .empty-wrap { display: flex; flex-direction: column; align-items: center; padding-top: 200rpx; }
  127. .loading-text { font-size: 26rpx; color: #999; }
  128. .empty-icon { font-size: 100rpx; margin-bottom: 24rpx; }
  129. .empty-text { font-size: 28rpx; color: #999; margin-bottom: 30rpx; }
  130. .write-btn { width: 240rpx; height: 72rpx; line-height: 72rpx; background: #5B9BD5; color: #fff; font-size: 28rpx; border-radius: 36rpx; border: none; }
  131. .write-btn::after { border: none; }
  132. .post-list { padding: 20rpx 24rpx; }
  133. .post-card {
  134. display: flex;
  135. background: #fff;
  136. border-radius: 16rpx;
  137. overflow: hidden;
  138. margin-bottom: 20rpx;
  139. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
  140. }
  141. .post-cover { width: 200rpx; height: 160rpx; flex-shrink: 0; }
  142. .post-body { flex: 1; padding: 20rpx; display: flex; flex-direction: column; justify-content: center; }
  143. .post-title { font-size: 28rpx; font-weight: bold; color: #333; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 8rpx; }
  144. .post-meta { display: flex; align-items: center; font-size: 22rpx; }
  145. .post-date { color: #999; margin-right: 16rpx; }
  146. .post-status { padding: 2rpx 12rpx; border-radius: 8rpx; }
  147. .status-approved { color: #22c55e; background: rgba(34,197,94,0.1); }
  148. .status-pending { color: #f59e0b; background: rgba(245,158,11,0.1); }
  149. .status-rejected { color: #ef4444; background: rgba(239,68,68,0.1); }
  150. .status-draft { color: #999; background: rgba(153,153,153,0.1); }
  151. .post-reason { font-size: 20rpx; color: #ef4444; margin-top: 6rpx; display: block; }
  152. .loading-more, .no-more { text-align: center; padding: 30rpx; }
  153. .no-more-text { font-size: 24rpx; color: #ccc; }
  154. </style>