track-record.vue 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. <template>
  2. <view class="record-card">
  3. <view class="title">
  4. <view class="line"></view>
  5. <text>任务跟踪记录</text>
  6. </view>
  7. <view v-for="(item, idx) in records" :key="idx" class="record-item">
  8. <view class="item-header">
  9. <text class="user">{{ item.createUserName || '-' }}</text>
  10. <text class="time">{{ item.createTime || '-' }}</text>
  11. </view>
  12. <view class="item-body">
  13. <text class="content">{{ item.remark || '-' }} {{ item.newContent || '-' }}</text>
  14. </view>
  15. <view v-if="getImgs(item).length" class="item-images">
  16. <u-image
  17. v-for="(img, i) in getImgs(item)"
  18. :key="i"
  19. :src="img"
  20. width="160rpx"
  21. height="160rpx"
  22. radius="8"
  23. mode="aspectFill"
  24. @click="preview(getImgs(item), i)"
  25. ></u-image>
  26. </view>
  27. </view>
  28. </view>
  29. </template>
  30. <script setup>
  31. const props = defineProps({
  32. records: {
  33. type: Array,
  34. default: () => []
  35. }
  36. })
  37. const isImageUrl = (v) => {
  38. if (!v || typeof v !== 'string') return false
  39. const s = v.trim()
  40. if (!s) return false
  41. if (/^https?:\/\//i.test(s) || s.startsWith('/') || s.startsWith('data:image')) return true
  42. return /\.(png|jpe?g|gif|webp|bmp)(\?.*)?$/i.test(s)
  43. }
  44. const normalizeImgs = (arr) => {
  45. if (!arr) return []
  46. if (typeof arr === 'string') {
  47. return arr.split(',').map(s => s.trim()).filter(isImageUrl)
  48. }
  49. if (Array.isArray(arr)) {
  50. return arr.map(v => typeof v === 'string' ? v : (v?.url || v?.imgUrl || '')).filter(isImageUrl)
  51. }
  52. if (typeof arr === 'object') {
  53. if (Array.isArray(arr.imgUrlList)) return normalizeImgs(arr.imgUrlList)
  54. if (arr.url) return isImageUrl(arr.url) ? [arr.url] : []
  55. }
  56. return []
  57. }
  58. const getImgs = (item) => {
  59. const candidates = [
  60. item?.imgInfo,
  61. item?.images,
  62. item?.files,
  63. item?.fileList,
  64. item?.pictures,
  65. item?.imgUrlList,
  66. item?.imgUrls,
  67. ]
  68. for (const c of candidates) {
  69. const imgs = normalizeImgs(c)
  70. if (imgs.length) return imgs
  71. }
  72. // newContent 可能是纯文本,只有确认是图片 URL 才展示
  73. return normalizeImgs(item?.newContent)
  74. }
  75. const preview = (urls, index) => {
  76. uni.previewImage({ urls, current: index })
  77. }
  78. </script>
  79. <style lang="scss" scoped>
  80. .record-card {
  81. background: #ffffff;
  82. border-radius: 12rpx;
  83. padding: 20rpx;
  84. }
  85. .title {
  86. display: flex;
  87. align-items: center;
  88. gap: 12rpx;
  89. margin-bottom: 16rpx;
  90. }
  91. .line {
  92. width: 8rpx;
  93. height: 32rpx;
  94. background: #22ac38;
  95. border-radius: 4rpx;
  96. }
  97. .record-item {
  98. padding: 20rpx 0;
  99. border-top: 1rpx solid #f0f0f0;
  100. }
  101. .item-header {
  102. display: flex;
  103. align-items: center;
  104. justify-content: space-between;
  105. margin-bottom: 8rpx;
  106. }
  107. .user {
  108. font-size: 28rpx;
  109. color: #666666;
  110. }
  111. .time {
  112. font-size: 26rpx;
  113. color: #969696;
  114. }
  115. .item-body {
  116. margin-top: 6rpx;
  117. }
  118. .content {
  119. font-size: 28rpx;
  120. color: #333333;
  121. }
  122. .item-images {
  123. margin-top: 12rpx;
  124. display: flex;
  125. flex-wrap: wrap;
  126. gap: 12rpx;
  127. }
  128. </style>