List.vue 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. <template>
  2. <view class="tab-content">
  3. <!-- <view class="col-12 m-b30">
  4. <view class="card card-action action-elevate action-border-primary">
  5. <view class="row g-0">
  6. <view class="col-md-3">
  7. <view class="card-header border-0 p-0 m-2 position-relative overflow-hidden">
  8. <image src="/static/images/vu/news.jpg" alt="" class="img-fluid rounded" mode="widthFix" />
  9. </view>
  10. </view>
  11. <view class="col-md-9 py-3 d-flex flex-column">
  12. <view class="card-body px-3 py-2">
  13. <a href="-" class="badge badge-sm bg-danger mb-1">Trading</a>
  14. <h4><span class="text-2xs text-body"><i class="icon-calendar text-primary"></i> 2025-03-24 l
  15. 10:30 AM</span><br>
  16. <a href="-" class="text-dark">Dollar Index (ICE) Intraday:
  17. caution</a>
  18. </h4>
  19. <p>We are thrilled to announce that CWG Markets has been honored
  20. with the prestigious title of Leading Financial Services..</p>
  21. </view>
  22. </view>
  23. </view>
  24. </view>
  25. </view> -->
  26. <!-- 列表 -->
  27. <view v-if="list.length > 0" class="list">
  28. <view v-for="item in list" :key="item.id" class="col-12 m-b30">
  29. <view class="card card-action action-elevate action-border-primary cursor-pointer" @click="handleItemClick(item)">
  30. <view class="row g-0">
  31. <view class="col-md-3" v-if="item.coverImage">
  32. <view class="card-header border-0 p-0 m-2 position-relative overflow-hidden">
  33. <image v-if="item.coverImage" :src="imgUrl + item.coverImage" class="img-fluid rounded"
  34. mode="widthFix" />
  35. <view v-else class="placeholder-image"></view>
  36. </view>
  37. </view>
  38. <view class="col-md-9 py-3 d-flex flex-column">
  39. <view class="card-body px-3 py-2">
  40. <!-- <a href="#" class="badge badge-sm bg-danger mb-1">Trading</a> -->
  41. <h5>
  42. <span class="text-2xs text-body p-text"><i class="icon-calendar text-primary"></i> {{
  43. formatDate(item.deliveryTime) }}</span><br>
  44. <text class="text-dark h5">{{ item.title }}</text>
  45. </h5>
  46. <p class="p-text">{{ item.subTitle }}</p>
  47. </view>
  48. </view>
  49. </view>
  50. </view>
  51. </view>
  52. </view>
  53. <!-- 空状态 -->
  54. <view v-else-if="!loading && list.length === 0" class="list-empty-state empty">
  55. <cwg-empty-state />
  56. </view>
  57. <view class="table-loading-mask">
  58. <uni-loading v-if="loading" />
  59. </view>
  60. </view>
  61. </template>
  62. <script setup>
  63. import { ref, watch, computed } from 'vue'
  64. import { useI18n } from 'vue-i18n'
  65. import Config from '@/config/index'
  66. const { locale } = useI18n()
  67. const props = defineProps({
  68. fetchData: { type: Function, required: true },
  69. queryParams: { type: Object, default: () => ({}) },
  70. pageSize: { type: Number, default: 10 },
  71. type: { type: Number, default: 0 },
  72. immediate: { type: Boolean, default: true },
  73. })
  74. const imgUrl = computed(() => props.queryParams.tag !== 3 ? Config.Host80 : Config.Host05)
  75. const list = ref([])
  76. const page = ref(1)
  77. const loading = ref(false)
  78. const loadingMore = ref(false)
  79. const finished = ref(false)
  80. const total = ref(0)
  81. const formatDate = (dateStr) => {
  82. if (!dateStr) return ''
  83. return dateStr.slice(0, 10).replace('T', ' ')
  84. }
  85. const load = async () => {
  86. list.value = []
  87. if (loading.value) return
  88. loading.value = true
  89. finished.value = false
  90. page.value = 1
  91. try {
  92. const res = await props.fetchData({
  93. lang: locale.value == 'vn' ? 'vi' : locale.value,
  94. page: { current: page.value, row: props.queryParams?.pageSize || props.pageSize },
  95. ...props.queryParams
  96. })
  97. if (res.code === 200) {
  98. list.value = res.data || []
  99. total.value = res.page?.rowTotal || 0
  100. finished.value = list.value.length >= total.value
  101. } else {
  102. throw new Error(res.msg || '请求失败')
  103. }
  104. } catch (err) {
  105. console.error('加载失败', err)
  106. uni.showToast({ title: err.message || '加载失败', icon: 'none' })
  107. } finally {
  108. loading.value = false
  109. }
  110. }
  111. const loadMore = async () => {
  112. if (loadingMore.value || finished.value) return
  113. loadingMore.value = true
  114. try {
  115. const nextPage = page.value + 1
  116. const params = {
  117. lang: locale.value,
  118. page: { current: nextPage, row: props.queryParams?.pageSize || props.pageSize },
  119. ...props.queryParams
  120. }
  121. const res = await props.fetchData(params)
  122. if (res.code === 200) {
  123. const newList = res.data || []
  124. if (newList.length > 0) {
  125. list.value.push(...newList)
  126. page.value = nextPage
  127. }
  128. const totalRows = res.page?.rowTotal || total.value
  129. finished.value = list.value.length >= totalRows
  130. } else {
  131. throw new Error(res.msg || '请求失败')
  132. }
  133. } catch (err) {
  134. uni.showToast({ title: err.message || '加载更多失败', icon: 'none' })
  135. } finally {
  136. loadingMore.value = false
  137. }
  138. }
  139. const handleItemClick = (item) => {
  140. uni.navigateTo({ url: `/pages/analytics/detail?type=${props.type}&id=${item.id}` })
  141. }
  142. const lang = computed(() => uni.getLocale())
  143. watch(lang, () => {
  144. load()
  145. }, { immediate: true })
  146. defineExpose({ load, loadMore })
  147. </script>
  148. <style lang="scss" scoped>
  149. @import "@/uni.scss";
  150. .p-text{
  151. font-size: px2rpx(14);
  152. line-height: px2rpx(20);
  153. color: var(--cwg-gray-color)!important;
  154. }
  155. </style>