灵活用工项目

messages.vue 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. <template>
  2. <view class="fe-page fe-page--tab msg-page">
  3. <scroll-view
  4. scroll-y
  5. class="page-scroll"
  6. :show-scrollbar="false"
  7. @scrolltolower="loadMore"
  8. >
  9. <view class="page-body">
  10. <view v-if="loading && !inbox.length" class="msg-empty">
  11. <text class="msg-empty__text">加载中...</text>
  12. </view>
  13. <view v-else-if="loadError && !inbox.length" class="msg-empty">
  14. <text class="msg-empty__text">{{ loadError }}</text>
  15. <view class="fe-btn fe-btn--primary fe-btn--sm msg-empty__btn" @tap="reload">重新加载</view>
  16. </view>
  17. <view v-else-if="!inbox.length" class="msg-empty">
  18. <text class="msg-empty__text">暂无消息</text>
  19. </view>
  20. <view
  21. v-for="(m, idx) in inbox"
  22. :key="m.msgKey"
  23. class="msg-card"
  24. :class="m.itemClass"
  25. @tap="onItemByIndex(idx)"
  26. >
  27. <view class="msg-card__top">
  28. <text class="fe-msg-type" :class="'fe-msg-type--' + m.typeClass">{{ m.typeLabel }}</text>
  29. <text class="msg-card__time">{{ m.timeText }}</text>
  30. </view>
  31. <text class="msg-card__title">{{ m.title }}</text>
  32. <text class="msg-card__desc">{{ m.desc }}</text>
  33. <view v-if="m.handledFlag" class="msg-card__handled">已处理</view>
  34. </view>
  35. <view v-if="inbox.length && loadingMore" class="msg-footer-tip">
  36. <text class="msg-footer-tip__text">加载更多...</text>
  37. </view>
  38. <view v-else-if="inbox.length && !hasMore" class="msg-footer-tip">
  39. <text class="msg-footer-tip__text">没有更多了</text>
  40. </view>
  41. </view>
  42. </scroll-view>
  43. <FeBottomNav current="message" role="enterprise" />
  44. </view>
  45. </template>
  46. <script>
  47. import FeBottomNav from '@/packageA/components/FeBottomNav.vue'
  48. import store from '@/common/store.js'
  49. import { getToken } from '@/utils/request.js'
  50. import { showToast } from '@/common/chat-data.js'
  51. import { listMyNotifications } from '@/api/notification.js'
  52. import { mapNotificationPage } from '@/utils/notification.js'
  53. const PAGE_SIZE = 20
  54. export default {
  55. components: { FeBottomNav },
  56. data() {
  57. return {
  58. inbox: [],
  59. loading: false,
  60. loadingMore: false,
  61. loadError: '',
  62. page: 1,
  63. hasMore: false,
  64. pendingCount: 0,
  65. unreadCount: 0
  66. }
  67. },
  68. onShow() {
  69. if (!this.ensureEnterpriseAccess()) return
  70. this.reload()
  71. },
  72. methods: {
  73. ensureEnterpriseAccess() {
  74. if (!getToken()) {
  75. uni.reLaunch({ url: '/packageA/auth/login' })
  76. return false
  77. }
  78. const s = store.getState()
  79. if (s.role === 'worker') {
  80. uni.reLaunch({ url: '/packageA/home/index' })
  81. return false
  82. }
  83. return true
  84. },
  85. reload() {
  86. this.page = 1
  87. this.hasMore = false
  88. this.loadError = ''
  89. this.fetchPage(true)
  90. },
  91. loadMore() {
  92. if (!this.hasMore || this.loading || this.loadingMore) return
  93. this.page += 1
  94. this.fetchPage(false)
  95. },
  96. async fetchPage(reset) {
  97. if (reset) this.loading = true
  98. else this.loadingMore = true
  99. try {
  100. const raw = await listMyNotifications(
  101. { page: this.page, size: PAGE_SIZE },
  102. { showError: false }
  103. )
  104. const pageData = mapNotificationPage(raw || {})
  105. this.pendingCount = pageData.pendingCount
  106. this.unreadCount = pageData.unreadCount
  107. this.hasMore = pageData.hasMore
  108. if (reset) {
  109. this.inbox = pageData.items
  110. } else {
  111. this.inbox = this.inbox.concat(pageData.items)
  112. }
  113. this.loadError = ''
  114. } catch (e) {
  115. if (reset) {
  116. this.inbox = []
  117. this.loadError = (e && (e.msg || e.message)) || '消息加载失败'
  118. showToast(this.loadError)
  119. } else {
  120. this.page = Math.max(1, this.page - 1)
  121. showToast((e && (e.msg || e.message)) || '加载更多失败')
  122. }
  123. } finally {
  124. this.loading = false
  125. this.loadingMore = false
  126. }
  127. },
  128. onItemByIndex(idx) {
  129. const item = this.inbox[idx]
  130. if (!item) return
  131. const url = item.actionUrl
  132. if (!url || typeof url !== 'string' || url.indexOf('/packageA/') !== 0) {
  133. showToast(item.title || '消息详情')
  134. return
  135. }
  136. const path = url.split('?')[0]
  137. if (path === '/packageA/home/index') {
  138. uni.reLaunch({ url })
  139. return
  140. }
  141. uni.navigateTo({
  142. url,
  143. fail: (err) => {
  144. console.error('notification navigate fail', url, err)
  145. uni.reLaunch({ url })
  146. }
  147. })
  148. }
  149. }
  150. }
  151. </script>
  152. <style lang="scss" scoped>
  153. .msg-page {
  154. display: flex;
  155. flex-direction: column;
  156. height: 100vh;
  157. overflow: hidden;
  158. background: $fe-bg;
  159. }
  160. .page-scroll {
  161. flex: 1;
  162. height: 0;
  163. }
  164. .page-body {
  165. padding: 24rpx 32rpx calc(32rpx + 120rpx + #{$fe-h-safe-bottom});
  166. }
  167. .msg-empty {
  168. padding: 120rpx 0;
  169. text-align: center;
  170. }
  171. .msg-empty__text {
  172. display: block;
  173. font-size: 28rpx;
  174. color: $fe-muted;
  175. }
  176. .msg-empty__btn {
  177. margin: 28rpx auto 0;
  178. display: inline-flex;
  179. }
  180. .msg-card {
  181. position: relative;
  182. background: $fe-surface;
  183. border-radius: $fe-radius-lg;
  184. padding: 28rpx 32rpx;
  185. margin-bottom: 20rpx;
  186. border: 1rpx solid $fe-border-light;
  187. }
  188. .msg-card--unread {
  189. border-color: rgba(124, 58, 237, 0.28);
  190. background: #FBF9FF;
  191. }
  192. .msg-card--todo {
  193. box-shadow: inset 6rpx 0 0 $fe-warning;
  194. }
  195. .msg-card--handled {
  196. opacity: 0.72;
  197. }
  198. .msg-card__top {
  199. display: flex;
  200. justify-content: space-between;
  201. align-items: center;
  202. margin-bottom: 12rpx;
  203. }
  204. .msg-card__time {
  205. font-size: 22rpx;
  206. color: $fe-gray-400;
  207. }
  208. .msg-card__title {
  209. display: block;
  210. font-size: 28rpx;
  211. font-weight: 600;
  212. color: $fe-foreground;
  213. }
  214. .msg-card__desc {
  215. display: block;
  216. margin-top: 8rpx;
  217. font-size: 24rpx;
  218. color: $fe-muted;
  219. line-height: 1.5;
  220. }
  221. .msg-card__handled {
  222. margin-top: 12rpx;
  223. font-size: 22rpx;
  224. color: $fe-gray-400;
  225. }
  226. .msg-footer-tip {
  227. padding: 16rpx 0 8rpx;
  228. text-align: center;
  229. }
  230. .msg-footer-tip__text {
  231. font-size: 24rpx;
  232. color: $fe-gray-400;
  233. }
  234. .fe-msg-type--info {
  235. background: $fe-info-light;
  236. color: #1E40AF;
  237. }
  238. .fe-msg-type--success {
  239. background: $fe-success-light;
  240. color: #065F46;
  241. }
  242. </style>