xydata-tools 1.0.44 → 1.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38,7 +38,7 @@
38
38
  </view>
39
39
  <view class="file-actions">
40
40
  <view class="action-btn delete-btn" @click="removeFile(index)">
41
- <text class="delete-icon">×</text>
41
+ ×
42
42
  </view>
43
43
  </view>
44
44
  </view>
@@ -57,7 +57,7 @@
57
57
  </view>
58
58
  </view>
59
59
  </view>
60
- </view>
60
+ </view>
61
61
  </view>
62
62
  </template>
63
63
 
@@ -178,8 +178,6 @@ export default {
178
178
  case 'jpeg':
179
179
  case 'jpg':
180
180
  case 'gif':
181
- case 'webp':
182
- case 'svg':
183
181
  return require('../../../assets/file-icon/image.png')
184
182
  case 'mp4':
185
183
  case 'mov':
@@ -515,6 +513,15 @@ export default {
515
513
  const ext = this.getFileExtension(file.name)
516
514
  const supportedTypes = ['doc', 'xls', 'ppt', 'pdf', 'docx', 'xlsx', 'pptx']
517
515
  const isDocument = supportedTypes.includes(ext)
516
+
517
+ const imageTypes = ['png', 'jpeg', 'jpg', 'gif']
518
+ console.log(ext)
519
+ if (imageTypes.includes(ext)) {
520
+ uni.previewImage({
521
+ urls: [file.url]
522
+ })
523
+ return
524
+ }
518
525
 
519
526
  // 显示加载提示
520
527
  uni.showLoading({
@@ -72,31 +72,26 @@
72
72
 
73
73
  .file-actions {
74
74
  margin-left: 16rpx;
75
- display: flex;
76
- align-items: center;
77
-
78
- .action-btn {
79
- width: 44rpx;
80
- height: 44rpx;
81
- border-radius: 50%;
82
- display: flex;
83
- align-items: center;
84
- justify-content: center;
85
- background-color: rgba(0, 0, 0, 0.6);
75
+ position: relative;
76
+ width: 40rpx;
77
+ height: 40rpx;
78
+ border-radius: 50%;
79
+ background-color: rgba(0, 0, 0, 0.6);
86
80
 
81
+ .delete-btn {
82
+ color: #fff;
83
+ font-size: 32rpx;
84
+ line-height: 1;
85
+ font-weight: 300;
86
+ position: absolute;
87
+ top: 50%;
88
+ left: 50%;
89
+ transform: translate(-50%, -50%);
90
+ padding-bottom: 2px;
87
91
  &:active {
88
92
  opacity: 0.8;
89
93
  }
90
94
  }
91
-
92
- .delete-btn {
93
- .delete-icon {
94
- color: #fff;
95
- font-size: 32rpx;
96
- line-height: 0.1;
97
- font-weight: 300;
98
- }
99
- }
100
95
  }
101
96
 
102
97
  .progress-bar {
@@ -8,8 +8,7 @@
8
8
  <view v-if="enableImage && showFileList" class="media-section">
9
9
  <shmily-drag-media v-if="imageFileList.length > 0" v-model="imageUrls" :number="imageLimit"
10
10
  :cols="cols" :imageWidth="itemWidth" :padding="10" :borderRadius="itemBorderRadius"
11
- :align="align" :draggable="draggable" moveType="image"
12
- @update:modelValue="handleImageDragChange">
11
+ :draggable="draggable" moveType="image" @update:modelValue="handleImageDragChange">
13
12
  </shmily-drag-media>
14
13
  </view>
15
14
 
@@ -18,8 +17,8 @@
18
17
  :style="{ marginTop: enableImage && videoFileList.length > 0 ? '20rpx' : '0' }">
19
18
  <shmily-drag-media v-if="videoFileList.length > 0" v-model="videoUrls" :number="videoLimit"
20
19
  :cols="cols" :imageWidth="itemWidth" :padding="10" :borderRadius="itemBorderRadius"
21
- :align="align" :draggable="draggable" moveType="video"
22
- @update:modelValue="handleVideoDragChange" @videoClick="handleVideoItemClick">
20
+ :draggable="draggable" moveType="video" @update:modelValue="handleVideoDragChange"
21
+ @videoClick="handleVideoItemClick">
23
22
  </shmily-drag-media>
24
23
  </view>
25
24
 
@@ -29,8 +28,8 @@
29
28
  <view v-if="enableImage" class="upload-button-wrapper">
30
29
  <slot name="image-upload-button" :select="selectAndUploadImage" :limit="imageLimit"
31
30
  :count="imageFileList.length">
32
- <view class="upload-btn-vertical" v-if="imageFileList.length < imageLimit"
33
- @click="selectAndUploadImage">
31
+ <view class="upload-btn-vertical" :style="{ borderRadius: itemBorderRadius + 'rpx' }"
32
+ v-if="imageFileList.length < imageLimit" @click="selectAndUploadImage">
34
33
  <image :src="imageUploadIcon" class="upload-icon"></image>
35
34
  <view class="upload-text">{{ imageButtonTitle }}</view>
36
35
  </view>
@@ -41,8 +40,8 @@
41
40
  <view v-if="enableVideo" class="upload-button-wrapper">
42
41
  <slot name="video-upload-button" :select="selectAndUploadVideo" :limit="videoLimit"
43
42
  :count="videoFileList.length">
44
- <view class="upload-btn-vertical" v-if="videoFileList.length < videoLimit"
45
- @click="selectAndUploadVideo">
43
+ <view class="upload-btn-vertical" :style="{ borderRadius: itemBorderRadius + 'rpx' }"
44
+ v-if="videoFileList.length < videoLimit" @click="selectAndUploadVideo">
46
45
  <image :src="videoUploadIcon" class="upload-icon"></image>
47
46
  <view class="upload-text">{{ videoButtonTitle }}</view>
48
47
  </view>
@@ -58,14 +57,14 @@
58
57
  :number="imageLimit" :cols="cols" :imageWidth="itemWidth" :padding="10"
59
58
  :borderRadius="itemBorderRadius" :align="align" :draggable="draggable" :enableAddButton="true"
60
59
  moveType="image" @update:modelValue="handleImageDragChange" @add-click="selectAndUploadImage">
61
- <template #add-button>
60
+ <template #add-button="{ childWidth }">
62
61
  <slot name="image-upload-button" :select="selectAndUploadImage" :limit="imageLimit"
63
62
  :count="imageFileList.length">
64
63
  <view class="drag-upload-btn">
65
64
  <image :src="imageUploadIcon" class="upload-icon"
66
- :style="{ width: uploadIconSize + 'rpx', height: uploadIconSize + 'rpx' }"></image>
67
- <view class="upload-text" :style="{ fontSize: uploadTextSize + 'rpx' }">{{
68
- imageButtonTitle }}</view>
65
+ :style="getUploadIconStyle(childWidth)"></image>
66
+ <view class="upload-text" :style="getUploadTextStyle(childWidth)">{{ imageButtonTitle }}
67
+ </view>
69
68
  </view>
70
69
  </slot>
71
70
  </template>
@@ -77,14 +76,14 @@
77
76
  :borderRadius="itemBorderRadius" :align="align" :draggable="draggable" :enableAddButton="true"
78
77
  moveType="video" @update:modelValue="handleVideoDragChange" @add-click="selectAndUploadVideo"
79
78
  @videoClick="handleVideoItemClick">
80
- <template #add-button>
79
+ <template #add-button="{ childWidth }">
81
80
  <slot name="video-upload-button" :select="selectAndUploadVideo" :limit="videoLimit"
82
81
  :count="videoFileList.length">
83
82
  <view class="drag-upload-btn">
84
83
  <image :src="videoUploadIcon" class="upload-icon"
85
- :style="{ width: uploadIconSize + 'rpx', height: uploadIconSize + 'rpx' }"></image>
86
- <view class="upload-text" :style="{ fontSize: uploadTextSize + 'rpx' }">{{
87
- videoButtonTitle }}</view>
84
+ :style="getUploadIconStyle(childWidth)"></image>
85
+ <view class="upload-text" :style="getUploadTextStyle(childWidth)">{{ videoButtonTitle }}
86
+ </view>
88
87
  </view>
89
88
  </slot>
90
89
  </template>
@@ -197,8 +196,8 @@ export default {
197
196
  // ==================== 通用配置 ====================
198
197
  /** 上传类型: 'image' - 仅图片, 'video' - 仅视频, 'both' - 图片+视频(必填) */
199
198
  uploadType: {
200
- type: String,
201
- required: true,
199
+ type: String,
200
+ default: 'image',
202
201
  validator: (value) => ['image', 'video', 'both'].includes(value)
203
202
  },
204
203
  /** 上传地址 */
@@ -335,6 +334,7 @@ export default {
335
334
  popupAnimated: false,
336
335
  mediaLoadingStatus: {},
337
336
  fullscreenVideoSrc: '', // 专门用于全屏播放的视频源
337
+ windowWidth: uni.getSystemInfoSync ? uni.getSystemInfoSync().windowWidth : 375,
338
338
  }
339
339
  },
340
340
  computed: {
@@ -458,11 +458,8 @@ export default {
458
458
 
459
459
  // ==================== 弹窗模式网格样式 ====================
460
460
  popupGridStyle() {
461
- // 如果指定了 itemWidth,使用固定宽度;否则使用 1fr 自动分配
462
- const columnWidth = this.itemWidth ? `${this.itemWidth}rpx` : '1fr'
463
-
464
461
  return {
465
- gridTemplateColumns: `repeat(${this.cols}, ${columnWidth})`,
462
+ gridTemplateColumns: `repeat(${this.cols}, 1fr)`,
466
463
  gap: '10rpx'
467
464
  }
468
465
  }
@@ -496,6 +493,48 @@ export default {
496
493
  hasEventListener(eventName) {
497
494
  return !!(this.$listeners && this.$listeners[eventName])
498
495
  },
496
+ parseChildWidth(childWidth) {
497
+ if (typeof childWidth === 'number') {
498
+ return childWidth
499
+ }
500
+ if (typeof childWidth === 'string') {
501
+ const parsed = parseFloat(childWidth)
502
+ return Number.isFinite(parsed) ? parsed : null
503
+ }
504
+ return null
505
+ },
506
+ pxToRpx(px) {
507
+ if (!px || !this.windowWidth) {
508
+ return 0
509
+ }
510
+ return px / this.windowWidth * 750
511
+ },
512
+ computeUploadIconSize(childWidth) {
513
+ const parsedPx = this.parseChildWidth(childWidth)
514
+ if (!parsedPx) {
515
+ return this.uploadIconSize
516
+ }
517
+ const widthRpx = this.pxToRpx(parsedPx)
518
+ const size = Math.floor(widthRpx * 0.3)
519
+ return Math.max(20, size)
520
+ },
521
+ computeUploadTextSize(childWidth) {
522
+ const iconSize = this.computeUploadIconSize(childWidth)
523
+ const size = Math.floor(iconSize * 0.4)
524
+ return Math.max(20, size)
525
+ },
526
+ getUploadIconStyle(childWidth) {
527
+ const size = this.computeUploadIconSize(childWidth)
528
+ return {
529
+ width: size + 'rpx',
530
+ height: size + 'rpx'
531
+ }
532
+ },
533
+ getUploadTextStyle(childWidth) {
534
+ return {
535
+ fontSize: this.computeUploadTextSize(childWidth) + 'rpx'
536
+ }
537
+ },
499
538
 
500
539
  // ==================== 图片相关方法 ====================
501
540
  /**
@@ -1018,7 +1057,7 @@ export default {
1018
1057
  showCount: this.maxShowCount,
1019
1058
  hiddenCount: this.allFileList.length - this.maxShowCount,
1020
1059
  allFiles: this.allFileList
1021
- })
1060
+ })
1022
1061
 
1023
1062
  if (!this.hasEventListener('moreClick')) {
1024
1063
  this.openPopup()
@@ -3,7 +3,7 @@
3
3
  <template v-if="viewWidth">
4
4
  <movable-area class="area" :style="{ height: areaHeight }" @mouseenter="mouseenter"
5
5
  @mouseleave="mouseleave">
6
- <movable-view v-for="(item, index) in imageList" :key="item.id" class="view" direction="all" :y="item.y"
6
+ <movable-view v-for="(item, index) in imageList" :key="item.id" class="view" direction="all" :y="item.y"
7
7
  :x="item.x" :damping="40" :disabled="!draggable" @change="onChange($event, item)"
8
8
  @touchstart="touchstart(item)" @mousedown="touchstart(item)" @touchend="touchend($event, item)"
9
9
  @mouseup="touchend($event, item)" :style="{
@@ -18,9 +18,14 @@
18
18
  borderRadius: borderRadius + 'rpx',
19
19
  transform: 'scale(' + item.scale + ')'
20
20
  }">
21
- <image v-if="moveType === 'image'" class="pre-image" :src="item.src" mode="aspectFill"></image>
21
+ <image v-if="moveType === 'image'" class="pre-image" :src="item.src" mode="aspectFill"
22
+ @load="handleMediaLoad(item)" @error="handleMediaError(item)"></image>
22
23
  <video v-else-if="moveType === 'video'" :id="'readonlyVideo' + index" class="myVideo"
23
- :src="item.src" :controls="false" :show-center-play-btn="true" object-fit="cover"></video>
24
+ :src="item.src" :controls="false" :show-center-play-btn="true" object-fit="cover"
25
+ @loadedmetadata="handleMediaLoad(item)" @error="handleMediaError(item)"></video>
26
+ <view class="media-loading-overlay" v-if="item.loading">
27
+ <view class="loading-spinner"></view>
28
+ </view>
24
29
  <view class="del-con" @click="delImages(item, index)" @touchstart.stop="delImageMp(item, index)"
25
30
  @touchend.stop="nothing()" @mousedown.stop="nothing()" @mouseup.stop="nothing()">
26
31
  <view class="del-wrap">
@@ -37,12 +42,7 @@
37
42
  @click="addImages">
38
43
  <view class="add-wrap"
39
44
  :style="{ width: childWidth, height: childWidth, borderRadius: borderRadius + 'rpx' }">
40
- <slot name="add-button" :childWidth="childWidth" :borderRadius="borderRadius">
41
- <!-- 默认添加按钮 -->
42
- <image style="width: 54rpx;height: 54rpx;"
43
- src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAAA2CAYAAACMRWrdAAABIUlEQVRoQ+2a2w2DMAxFeQzWrsMUbadAsEw3S1CqVgppKwLX8BEOP4iHTXx8uUgWdVXoVhdaV0VhSmf7vr/H8V3XzY6V3P9iD+nYOI5P7/01LMI596AwoZV0TIBXIUWFXhKLFBWYSFGhhxQN6SFFQ5i4ogITKSr0cEVDekjRECauqMBEigq9U7piOk2yAti27SUe5ljlTfPEQ6KZecTvwl4P3ytvOv06R2HDMNzes7+6aRrvnHvtf50L13Lp50rx88zcvNlS3JpwKQ67XyK04nq2nFbk/LqVjin0TvmBNgQ2S4UUDcliHgpMpKjQwxUN6SFFQ5i4ogITKSr0cEVDekjRECauqMAsVoph+hVPtYr5+03p9tbYQ96xrYtT4ootbAJGVxxVTapVswAAAABJRU5ErkJggg==">
44
- </image>
45
- </slot>
45
+ <slot name="add-button" :childWidth="childWidth" :borderRadius="borderRadius"></slot>
46
46
  </view>
47
47
  </view>
48
48
  </movable-area>
@@ -129,28 +129,30 @@ export default {
129
129
  type: Boolean,
130
130
  default: false
131
131
  },
132
- // 对齐方式:'left' 从左往右排列,'right' 从右往左排列
133
- align: {
134
- type: String,
135
- default: 'left',
136
- validator: (value) => ['left', 'right'].includes(value)
137
- },
138
- // 是否可以拖拽排序
139
- draggable: {
140
- type: Boolean,
141
- default: true
142
- },
143
- },
132
+ // 对齐方式:'left' 从左往右排列,'right' 从右往左排列
133
+ align: {
134
+ type: String,
135
+ default: 'left',
136
+ validator: (value) => ['left', 'right'].includes(value)
137
+ },
138
+ // 是否可以拖拽排序
139
+ draggable: {
140
+ type: Boolean,
141
+ default: true
142
+ },
143
+ },
144
144
  data() {
145
145
  return {
146
146
  imageList: [],
147
147
  width: 0,
148
+ containerWidth: 0,
148
149
  add: {
149
150
  x: 0,
150
151
  y: 0
151
152
  },
152
153
  colsValue: 0,
153
154
  viewWidth: 0,
155
+ rightAlignOffset: 0,
154
156
  tempItem: null,
155
157
  timer: null,
156
158
  changeStatus: true,
@@ -221,6 +223,14 @@ export default {
221
223
  },
222
224
  deep: true
223
225
  },
226
+ align() {
227
+ this.updateRightAlignOffset()
228
+ if (!this.viewWidth || !this.colsValue) {
229
+ return
230
+ }
231
+ this.recalculateAllPositions()
232
+ this.updateAddButtonPosition()
233
+ }
224
234
  },
225
235
  created() {
226
236
  this.width = uni.getSystemInfoSync().windowWidth
@@ -228,12 +238,17 @@ export default {
228
238
  mounted() {
229
239
  const query = uni.createSelectorQuery().in(this)
230
240
  query.select('.con').boundingClientRect(data => {
241
+ this.containerWidth = data.width
231
242
  this.colsValue = this.cols
232
243
  this.viewWidth = data.width / this.cols
233
244
  if (this.imageWidth > 0) {
234
245
  this.viewWidth = this.rpx2px(this.imageWidth)
235
246
  this.colsValue = Math.floor(data.width / this.viewWidth)
236
247
  }
248
+ if (this.colsValue < 1) {
249
+ this.colsValue = 1
250
+ }
251
+ this.updateRightAlignOffset()
237
252
  let list = this.value
238
253
  // #ifdef VUE3
239
254
  list = this.modelValue
@@ -259,7 +274,7 @@ export default {
259
274
  */
260
275
  calculatePosition(index, total) {
261
276
  const absY = Math.floor(index / this.colsValue)
262
-
277
+
263
278
  if (this.align === 'right') {
264
279
  // 靠右对齐:计算当前行的元素数量(包括按钮),然后从右往左排列
265
280
  const currentRowItemCount = Math.min(total - absY * this.colsValue, this.colsValue)
@@ -273,7 +288,25 @@ export default {
273
288
  return { absX, absY }
274
289
  }
275
290
  },
276
-
291
+ getRightOffset() {
292
+ return this.align === 'right' ? this.rightAlignOffset : 0
293
+ },
294
+ getAlignedX(absX) {
295
+ return this.getRightOffset() + absX * this.viewWidth
296
+ },
297
+ updateRightAlignOffset() {
298
+ if (!this.viewWidth || !this.colsValue || !this.containerWidth) {
299
+ this.rightAlignOffset = 0
300
+ return
301
+ }
302
+ if (this.align !== 'right') {
303
+ this.rightAlignOffset = 0
304
+ return
305
+ }
306
+ const theoreticalWidth = this.viewWidth * this.colsValue
307
+ this.rightAlignOffset = Math.max(0, this.containerWidth - theoreticalWidth)
308
+ },
309
+
277
310
  /**
278
311
  * 获取总数(如果显示添加按钮,则包括按钮)
279
312
  */
@@ -284,7 +317,7 @@ export default {
284
317
  }
285
318
  return this.imageList.length
286
319
  },
287
-
320
+
288
321
  getSrc(item) {
289
322
  if (this.keyName !== null) {
290
323
  return item[this.keyName]
@@ -297,10 +330,14 @@ export default {
297
330
  item.oldY = e.detail.y
298
331
  if (e.detail.source === 'touch') {
299
332
  if (item.moveEnd) {
300
- item.offset = Math.sqrt(Math.pow(item.oldX - item.absX * this.viewWidth, 2) + Math.pow(item.oldY - item
333
+ item.offset = Math.sqrt(Math.pow(item.oldX - this.getAlignedX(item.absX), 2) + Math.pow(item.oldY - item
301
334
  .absY * this.viewWidth, 2))
302
335
  }
303
- let x = Math.floor((e.detail.x + this.viewWidth / 2) / this.viewWidth)
336
+ const offsetX = this.getRightOffset()
337
+ let x = Math.floor((e.detail.x - offsetX + this.viewWidth / 2) / this.viewWidth)
338
+ if (x < 0) {
339
+ x = 0
340
+ }
304
341
  if (x >= this.colsValue) return
305
342
  let y = Math.floor((e.detail.y + this.viewWidth / 2) / this.viewWidth)
306
343
  let index = this.colsValue * y + x
@@ -317,8 +354,10 @@ export default {
317
354
  obj.y = obj.oldY
318
355
  setTimeout(() => {
319
356
  this.$nextTick(() => {
320
- obj.x = obj.absX * this.viewWidth
357
+ obj.x = this.getAlignedX(obj.absX)
321
358
  obj.y = obj.absY * this.viewWidth
359
+ obj.oldX = obj.x
360
+ obj.oldY = obj.y
322
361
  })
323
362
  }, 0)
324
363
  }
@@ -331,8 +370,10 @@ export default {
331
370
  if (!item.moveEnd) {
332
371
  setTimeout(() => {
333
372
  this.$nextTick(() => {
334
- item.x = item.absX * this.viewWidth
373
+ item.x = this.getAlignedX(item.absX)
335
374
  item.y = item.absY * this.viewWidth
375
+ item.oldX = item.x
376
+ item.oldY = item.y
336
377
  })
337
378
  }, 0)
338
379
  }
@@ -351,8 +392,10 @@ export default {
351
392
  obj.absY = pos.absY
352
393
  setTimeout(() => {
353
394
  this.$nextTick(() => {
354
- obj.x = obj.absX * this.viewWidth
395
+ obj.x = this.getAlignedX(obj.absX)
355
396
  obj.y = obj.absY * this.viewWidth
397
+ obj.oldX = obj.x
398
+ obj.oldY = obj.y
356
399
  })
357
400
  }, 0)
358
401
  },
@@ -361,7 +404,7 @@ export default {
361
404
  if (!this.draggable) {
362
405
  return
363
406
  }
364
-
407
+
365
408
  this.imageList.forEach(v => {
366
409
  v.zIndex = v.index + 9
367
410
  })
@@ -383,7 +426,7 @@ export default {
383
426
  this.lastTouchEndTime = Date.now()
384
427
  } else if (eventType === 'mouseup') {
385
428
  // 如果 100ms 内已经处理过 touchend,则忽略此 mouseup(避免重复)
386
- if (this.lastTouchEndTime && Date.now() - this.lastTouchEndTime < 100) {
429
+ if (this.lastTouchEndTime && Date.now() - this.lastTouchEndTime < 100) {
387
430
  if (this.timer) {
388
431
  clearTimeout(this.timer)
389
432
  this.timer = null
@@ -415,8 +458,10 @@ export default {
415
458
  item.moveEnd = false
416
459
  setTimeout(() => {
417
460
  this.$nextTick(() => {
418
- item.x = item.absX * this.viewWidth
461
+ item.x = this.getAlignedX(item.absX)
419
462
  item.y = item.absY * this.viewWidth
463
+ item.oldX = item.x
464
+ item.oldY = item.y
420
465
  this.tempItem = null
421
466
  this.changeStatus = true
422
467
  })
@@ -473,8 +518,10 @@ export default {
473
518
  v.x = v.oldX
474
519
  v.y = v.oldY
475
520
  this.$nextTick(() => {
476
- v.x = v.absX * this.viewWidth
521
+ v.x = this.getAlignedX(v.absX)
477
522
  v.y = v.absY * this.viewWidth
523
+ v.oldX = v.x
524
+ v.oldY = v.y
478
525
  this.tempItem = null
479
526
  })
480
527
  }
@@ -526,7 +573,7 @@ export default {
526
573
  const pos = this.calculatePosition(i, totalWithButton)
527
574
  obj.absX = pos.absX
528
575
  obj.absY = pos.absY
529
- const newX = obj.absX * this.viewWidth
576
+ const newX = this.getAlignedX(obj.absX)
530
577
  const newY = obj.absY * this.viewWidth
531
578
 
532
579
  // 如果位置有变化,才进行动画
@@ -584,7 +631,7 @@ export default {
584
631
  },
585
632
  addProperties(item) {
586
633
  const newIndex = this.imageList.length
587
-
634
+
588
635
  // 先添加图片到列表
589
636
  this.imageList.push({
590
637
  src: item,
@@ -601,9 +648,10 @@ export default {
601
648
  id: this.guid(16),
602
649
  disable: false,
603
650
  offset: 0,
604
- moveEnd: false
651
+ moveEnd: false,
652
+ loading: true
605
653
  })
606
-
654
+
607
655
  // 如果是右对齐且显示按钮,添加新图片后需要重新计算所有图片位置
608
656
  if (this.align === 'right' && this.showAddButton) {
609
657
  this.recalculateAllPositions()
@@ -614,15 +662,15 @@ export default {
614
662
  const obj = this.imageList[newIndex]
615
663
  obj.absX = pos.absX
616
664
  obj.absY = pos.absY
617
- obj.x = pos.absX * this.viewWidth
665
+ obj.x = this.getAlignedX(pos.absX)
618
666
  obj.y = pos.absY * this.viewWidth
619
667
  obj.oldX = obj.x
620
668
  obj.oldY = obj.y
621
669
  }
622
-
670
+
623
671
  this.updateAddButtonPosition()
624
672
  },
625
-
673
+
626
674
  /**
627
675
  * 重新计算所有图片的位置(用于右对齐时添加/删除图片)
628
676
  */
@@ -630,9 +678,9 @@ export default {
630
678
  const totalWithButton = this.getTotalCount()
631
679
  this.imageList.forEach((obj, i) => {
632
680
  const pos = this.calculatePosition(i, totalWithButton)
633
- const newX = pos.absX * this.viewWidth
681
+ const newX = this.getAlignedX(pos.absX)
634
682
  const newY = pos.absY * this.viewWidth
635
-
683
+
636
684
  // 如果位置有变化,进行动画过渡
637
685
  if (obj.absX !== pos.absX || obj.absY !== pos.absY) {
638
686
  obj.x = obj.oldX
@@ -674,19 +722,29 @@ export default {
674
722
  return
675
723
  }
676
724
  const length = this.imageList.length
677
-
725
+
678
726
  if (this.align === 'right' && this.showAddButton) {
679
727
  // 右对齐且显示按钮:按钮的位置就是"虚拟的第 length 个元素"的位置
680
728
  // 总数包括按钮本身
681
729
  const totalWithButton = length + 1
682
730
  const pos = this.calculatePosition(length, totalWithButton)
683
- this.add.x = pos.absX * this.viewWidth + 'px'
731
+ this.add.x = this.getAlignedX(pos.absX) + 'px'
684
732
  this.add.y = pos.absY * this.viewWidth + 'px'
685
733
  } else {
686
734
  // 左对齐或不显示按钮:按钮直接跟在最后一个元素后面
687
- this.add.x = (length % this.colsValue) * this.viewWidth + 'px'
688
- this.add.y = Math.floor(length / this.colsValue) * this.viewWidth + 'px'
735
+ const absX = length % this.colsValue
736
+ const absY = Math.floor(length / this.colsValue)
737
+ this.add.x = this.getAlignedX(absX) + 'px'
738
+ this.add.y = absY * this.viewWidth + 'px'
689
739
  }
740
+ },
741
+ handleMediaLoad(item) {
742
+ if (!item) return
743
+ item.loading = false
744
+ },
745
+ handleMediaError(item) {
746
+ if (!item) return
747
+ item.loading = false
690
748
  }
691
749
  }
692
750
  }
@@ -735,6 +793,29 @@ export default {
735
793
  }
736
794
  }
737
795
 
796
+ .media-loading-overlay {
797
+ position: absolute;
798
+ top: 0;
799
+ left: 0;
800
+ right: 0;
801
+ bottom: 0;
802
+ display: flex;
803
+ align-items: center;
804
+ justify-content: center;
805
+ background-color: rgba(0, 0, 0, 0.05);
806
+ border-radius: inherit;
807
+ z-index: 5;
808
+ }
809
+
810
+ .loading-spinner {
811
+ width: 40rpx;
812
+ height: 40rpx;
813
+ border: 4rpx solid rgba(0, 0, 0, 0.1);
814
+ border-top-color: #007aff;
815
+ border-radius: 50%;
816
+ animation: spinner-rotate 0.8s linear infinite;
817
+ }
818
+
738
819
  .del-con {
739
820
  position: absolute;
740
821
  top: 0rpx;
@@ -774,4 +855,14 @@ export default {
774
855
  }
775
856
  }
776
857
  }
858
+
859
+ @keyframes spinner-rotate {
860
+ 0% {
861
+ transform: rotate(0deg);
862
+ }
863
+
864
+ 100% {
865
+ transform: rotate(360deg);
866
+ }
867
+ }
777
868
  </style>