巴青农资商城

ImagePreview.vue 2.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <template>
  2. <view class="image-preview" :style="wrapStyle" @click="onTap">
  3. <image
  4. v-if="showImage"
  5. class="image-preview__img"
  6. :src="realSrc"
  7. :mode="mode"
  8. @error="onError"
  9. @load="onLoad"
  10. />
  11. <view v-else class="image-preview__slot">
  12. <image
  13. v-if="placeholder"
  14. class="image-preview__img"
  15. :src="placeholder"
  16. :mode="mode"
  17. />
  18. <u-icon v-else name="photo" color="#c0c4cc" :size="iconSize" />
  19. </view>
  20. </view>
  21. </template>
  22. <script setup>
  23. import { ref, computed, watch } from 'vue'
  24. import { resolveFileUrl, resolveFileUrlList } from '@/utils/image'
  25. const props = defineProps({
  26. /** 后端路径或逗号分隔多图(展示首张) */
  27. src: {
  28. type: String,
  29. default: ''
  30. },
  31. width: {
  32. type: [String, Number],
  33. default: ''
  34. },
  35. height: {
  36. type: [String, Number],
  37. default: ''
  38. },
  39. mode: {
  40. type: String,
  41. default: 'aspectFill'
  42. },
  43. /** 无图或加载失败时的占位图;默认空则显示灰色图标(避免误显示 logo) */
  44. placeholder: {
  45. type: String,
  46. default: ''
  47. },
  48. /** 点击预览大图 */
  49. preview: {
  50. type: Boolean,
  51. default: false
  52. },
  53. /** 预览列表;不传则从 src 逗号拆分 */
  54. previewList: {
  55. type: Array,
  56. default: () => []
  57. },
  58. previewIndex: {
  59. type: Number,
  60. default: 0
  61. },
  62. iconSize: {
  63. type: [String, Number],
  64. default: 28
  65. }
  66. })
  67. const loadFailed = ref(false)
  68. const realSrc = computed(() => {
  69. if (!props.src) return ''
  70. return resolveFileUrl(props.src)
  71. })
  72. const resolvedPreviewList = computed(() => {
  73. if (props.previewList && props.previewList.length) {
  74. return resolveFileUrlList(props.previewList)
  75. }
  76. return resolveFileUrlList(props.src)
  77. })
  78. const showImage = computed(() => !!(realSrc.value && !loadFailed.value))
  79. const wrapStyle = computed(() => {
  80. const style = {}
  81. if (props.width !== '' && props.width != null) {
  82. style.width = formatSize(props.width)
  83. }
  84. if (props.height !== '' && props.height != null) {
  85. style.height = formatSize(props.height)
  86. }
  87. return style
  88. })
  89. watch(
  90. () => props.src,
  91. () => {
  92. loadFailed.value = false
  93. }
  94. )
  95. function formatSize(val) {
  96. if (typeof val === 'number') {
  97. return `${val}rpx`
  98. }
  99. const s = String(val)
  100. if (/^\d+$/.test(s)) {
  101. return `${s}rpx`
  102. }
  103. return s
  104. }
  105. function onError() {
  106. loadFailed.value = true
  107. }
  108. function onLoad() {
  109. loadFailed.value = false
  110. }
  111. function onTap() {
  112. if (!props.preview) return
  113. const urls = resolvedPreviewList.value
  114. if (!urls.length) return
  115. const idx = Math.min(Math.max(props.previewIndex, 0), urls.length - 1)
  116. uni.previewImage({
  117. urls,
  118. current: urls[idx]
  119. })
  120. }
  121. </script>
  122. <style lang="scss" scoped>
  123. .image-preview {
  124. display: block;
  125. width: 100%;
  126. height: 100%;
  127. overflow: hidden;
  128. background: #ebeef5;
  129. }
  130. .image-preview__img {
  131. width: 100%;
  132. height: 100%;
  133. display: block;
  134. }
  135. .image-preview__slot {
  136. width: 100%;
  137. height: 100%;
  138. display: flex;
  139. align-items: center;
  140. justify-content: center;
  141. background: #ebeef5;
  142. }
  143. </style>