index.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <template>
  2. <view class="imgs-row-scroll" :style="{ maxWidth: containerWidth + 'rpx' }">
  3. <u-scroll-list :indicator="true" :indicator-width="indicatorWidth" :indicator-bar-width="indicatorBarWidth" class="scroll-list">
  4. <view class="img-list">
  5. <view
  6. class="img-item"
  7. :class="{ 'is-active': highlightActive && index === localActiveIndex }"
  8. v-for="(item, index) in imageUrls"
  9. :key="index"
  10. @click.stop="handleImageClick(item, index)"
  11. :style="{
  12. marginRight: itemMargin + 'rpx',
  13. width: imageWidth + 'rpx',
  14. height: imageHeight + 'rpx'
  15. }"
  16. >
  17. <image
  18. :src="item"
  19. :mode="imgMode"
  20. :lazy-load="true"
  21. class="img-content"
  22. @error="handleImageError"
  23. />
  24. <view
  25. v-if="isShowDeleteIcon"
  26. class="delete-icon"
  27. @click.stop="handleDelete(index, item)"
  28. >
  29. <u-icon name="close" color="#fff" size="16"></u-icon>
  30. </view>
  31. </view>
  32. </view>
  33. </u-scroll-list>
  34. </view>
  35. </template>
  36. <script>
  37. export default {
  38. name: 'ImgsRowScroll',
  39. props: {
  40. images: {
  41. type: Array,
  42. default: () => []
  43. },
  44. // 从数组对象中读取图片地址的属性名
  45. keyName: {
  46. type: String,
  47. default: ''
  48. },
  49. // 是否启用预览功能
  50. previewEnabled: {
  51. type: Boolean,
  52. default: true
  53. },
  54. // 图片裁剪模式
  55. imgMode: {
  56. type: String,
  57. default: 'aspectFill'
  58. // - aspectFit :完整显示(保持比例,不裁剪)
  59. // - aspectFill :填充显示(裁剪图片填充容器)
  60. // - scaleToFill :拉伸显示(可能变形)
  61. },
  62. // 图片间距
  63. itemMargin: {
  64. type: Number,
  65. default: 16
  66. },
  67. // 图片宽度
  68. imageWidth: {
  69. type: Number,
  70. default: 200
  71. },
  72. // 图片高度
  73. imageHeight: {
  74. type: Number,
  75. default: 200
  76. },
  77. // 容器总宽度
  78. totalWidth: {
  79. type: Number,
  80. default: 0,
  81. // 说明:不传或传0时,默认100%宽度;传入正数时使用指定的rpx宽度
  82. // 例如:传600表示容器宽度为600rpx
  83. },
  84. // 是否显示指示器
  85. showIndicator: {
  86. type: Boolean,
  87. default: true
  88. },
  89. // 指示器宽度
  90. indicatorWidth: {
  91. type: Number,
  92. default: 50
  93. },
  94. // 指示器滑块宽度
  95. indicatorBarWidth: {
  96. type: Number,
  97. default: 30
  98. },
  99. // 是否显示删除图标
  100. isShowDeleteIcon: {
  101. type: Boolean,
  102. default: false
  103. },
  104. // 是否开启高亮当前选中项
  105. highlightActive: {
  106. type: Boolean,
  107. default: false
  108. },
  109. // 当前选中的索引 (外部控制)
  110. activeIndex: {
  111. type: Number,
  112. default: -1
  113. }
  114. },
  115. data() {
  116. return {
  117. localActiveIndex: -1
  118. }
  119. },
  120. watch: {
  121. activeIndex: {
  122. handler(val) {
  123. this.localActiveIndex = val;
  124. },
  125. immediate: true
  126. }
  127. },
  128. computed: {
  129. // 处理图片地址数组
  130. imageUrls() {
  131. if (!this.images || this.images.length === 0) {
  132. return [];
  133. }
  134. // 如果没有指定keyName,直接返回数组内容
  135. if (!this.keyName) {
  136. return this.images;
  137. }
  138. // 如果指定了keyName,从对象中提取指定属性
  139. return this.images.map(item => {
  140. if (typeof item === 'object' && item !== null) {
  141. return item[this.keyName] || '';
  142. }
  143. return item;
  144. }).filter(url => url); // 过滤掉空值
  145. },
  146. // 容器宽度
  147. containerWidth() {
  148. if (this.totalWidth > 0) {
  149. return this.totalWidth + 'rpx';
  150. }
  151. return '100%';
  152. }
  153. },
  154. methods: {
  155. handleImageClick(item, index) {
  156. if (this.highlightActive) {
  157. this.localActiveIndex = index;
  158. }
  159. this.$emit('clickImg', { item, index });
  160. if (!this.previewEnabled) {
  161. return;
  162. }
  163. this.previewImage(item, index);
  164. },
  165. // 预览图片
  166. previewImage(current, currentIndex) {
  167. // 过滤掉无效的图片地址
  168. const validUrls = this.imageUrls.filter(url => url && url.trim());
  169. if (validUrls.length === 0) {
  170. uni.showToast({
  171. title: '没有可预览的图片',
  172. icon: 'none'
  173. });
  174. return;
  175. }
  176. uni.previewImage({
  177. current: current, // 当前显示图片的http链接
  178. urls: validUrls, // 需要预览的图片http链接列表
  179. success: () => {
  180. console.log('图片预览成功');
  181. },
  182. fail: (err) => {
  183. console.error('图片预览失败:', err);
  184. uni.showToast({
  185. title: '预览失败',
  186. icon: 'none'
  187. });
  188. }
  189. });
  190. },
  191. // 处理图片加载错误
  192. handleImageError(e) {
  193. console.error('图片加载失败:', e.detail.errMsg);
  194. },
  195. // 处理删除图片
  196. handleDelete(index, item) {
  197. const newImages = this.images.slice();
  198. newImages.splice(index, 1);
  199. this.$emit('deleteImgInfo', {
  200. delIndex: index,
  201. delUrl: item,
  202. newImages
  203. });
  204. }
  205. }
  206. };
  207. </script>
  208. <style lang="scss" scoped>
  209. @import './index.scss'
  210. </style>