xydata-tools 1.1.21 → 1.1.23

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.
@@ -6,19 +6,36 @@
6
6
  <view v-if="actualButtonPosition === 'vertical'" :class="['vertical-layout']">
7
7
  <!-- 图片区域 -->
8
8
  <view v-if="enableImage && showFileList" class="media-section">
9
- <shmily-drag-media v-if="imageFileList.length > 0" v-model="imageUrls" :number="imageLimit"
10
- :cols="cols" :imageWidth="itemWidth" :padding="10" :borderRadius="itemBorderRadius"
11
- :draggable="draggable" moveType="image" @input="handleImageDragChange">
9
+ <shmily-drag-media
10
+ v-if="imageFileList.length > 0"
11
+ v-model="imageUrls"
12
+ :number="imageLimit"
13
+ :cols="cols"
14
+ :imageWidth="itemWidth"
15
+ :padding="10"
16
+ :borderRadius="itemBorderRadius"
17
+ :draggable="draggable"
18
+ moveType="image"
19
+ @input="handleImageDragChange"
20
+ >
12
21
  </shmily-drag-media>
13
22
  </view>
14
23
 
15
24
  <!-- 视频区域 -->
16
- <view v-if="enableVideo && showFileList" class="media-section"
17
- :style="{ marginTop: enableImage && videoFileList.length > 0 ? '20rpx' : '0' }">
18
- <shmily-drag-media v-if="videoFileList.length > 0" v-model="videoUrls" :number="videoLimit"
19
- :cols="cols" :imageWidth="itemWidth" :padding="10" :borderRadius="itemBorderRadius"
20
- :draggable="draggable" moveType="video" @input="handleVideoDragChange"
21
- @videoClick="handleVideoItemClick">
25
+ <view v-if="enableVideo && showFileList" class="media-section" :style="{ marginTop: enableImage && videoFileList.length > 0 ? '20rpx' : '0' }">
26
+ <shmily-drag-media
27
+ v-if="videoFileList.length > 0"
28
+ v-model="videoUrls"
29
+ :number="videoLimit"
30
+ :cols="cols"
31
+ :imageWidth="itemWidth"
32
+ :padding="10"
33
+ :borderRadius="itemBorderRadius"
34
+ :draggable="draggable"
35
+ moveType="video"
36
+ @input="handleVideoDragChange"
37
+ @videoClick="handleVideoItemClick"
38
+ >
22
39
  </shmily-drag-media>
23
40
  </view>
24
41
 
@@ -26,10 +43,13 @@
26
43
  <view class="upload-buttons-row">
27
44
  <!-- 图片上传按钮 -->
28
45
  <view v-if="enableImage" class="upload-button-wrapper">
29
- <slot name="image-upload-button" :select="selectAndUploadImage" :limit="imageLimit"
30
- :count="imageFileList.length">
31
- <view class="upload-btn-vertical" :style="{ borderRadius: itemBorderRadius + 'rpx' }"
32
- v-if="imageFileList.length < imageLimit" @click="selectAndUploadImage">
46
+ <slot name="image-upload-button" :select="selectAndUploadImage" :limit="imageLimit" :count="imageFileList.length">
47
+ <view
48
+ class="upload-btn-vertical"
49
+ :style="{ borderRadius: itemBorderRadius + 'rpx' }"
50
+ v-if="imageFileList.length < imageLimit"
51
+ @click="selectAndUploadImage"
52
+ >
33
53
  <image :src="imageUploadIcon" class="upload-icon"></image>
34
54
  <view class="upload-text">{{ imageButtonTitle }}</view>
35
55
  </view>
@@ -38,10 +58,13 @@
38
58
 
39
59
  <!-- 视频上传按钮 -->
40
60
  <view v-if="enableVideo" class="upload-button-wrapper">
41
- <slot name="video-upload-button" :select="selectAndUploadVideo" :limit="videoLimit"
42
- :count="videoFileList.length">
43
- <view class="upload-btn-vertical" :style="{ borderRadius: itemBorderRadius + 'rpx' }"
44
- v-if="videoFileList.length < videoLimit" @click="selectAndUploadVideo">
61
+ <slot name="video-upload-button" :select="selectAndUploadVideo" :limit="videoLimit" :count="videoFileList.length">
62
+ <view
63
+ class="upload-btn-vertical"
64
+ :style="{ borderRadius: itemBorderRadius + 'rpx' }"
65
+ v-if="videoFileList.length < videoLimit"
66
+ @click="selectAndUploadVideo"
67
+ >
45
68
  <image :src="videoUploadIcon" class="upload-icon"></image>
46
69
  <view class="upload-text">{{ videoButtonTitle }}</view>
47
70
  </view>
@@ -53,36 +76,51 @@
53
76
  <!-- 水平布局:上传按钮跟随媒体(仅单一媒体类型时使用) -->
54
77
  <view v-else class="horizontal-layout">
55
78
  <!-- 图片水平布局 -->
56
- <shmily-drag-media v-if="enableImage && !enableVideo && showFileList" v-model="imageUrls"
57
- :number="imageLimit" :cols="cols" :imageWidth="itemWidth" :padding="10"
58
- :borderRadius="itemBorderRadius" :align="align" :draggable="draggable" :enableAddButton="true"
59
- moveType="image" @input="handleImageDragChange">
60
- <template #add-button="{ childWidth }">
61
- <slot name="image-upload-button" :select="selectAndUploadImage" :limit="imageLimit"
62
- :count="imageFileList.length">
79
+ <shmily-drag-media
80
+ v-if="enableImage && !enableVideo && showFileList"
81
+ v-model="imageUrls"
82
+ :number="imageLimit"
83
+ :cols="cols"
84
+ :imageWidth="itemWidth"
85
+ :padding="10"
86
+ :borderRadius="itemBorderRadius"
87
+ :align="align"
88
+ :draggable="draggable"
89
+ :enableAddButton="true"
90
+ moveType="image"
91
+ @input="handleImageDragChange"
92
+ >
93
+ <template #add-button>
94
+ <slot name="image-upload-button" :select="selectAndUploadImage" :limit="imageLimit" :count="imageFileList.length">
63
95
  <view class="drag-upload-btn" @click="selectAndUploadImage">
64
- <image :src="imageUploadIcon" class="upload-icon"
65
- :style="getUploadIconStyle(childWidth)"></image>
66
- <view class="upload-text" :style="getUploadTextStyle(childWidth)">{{ imageButtonTitle }}
67
- </view>
96
+ <image :src="imageUploadIcon" class="upload-icon" :style="dragIconStyle"></image>
97
+ <view class="upload-text" :style="dragTextStyle">{{ imageButtonTitle }} </view>
68
98
  </view>
69
99
  </slot>
70
100
  </template>
71
101
  </shmily-drag-media>
72
102
 
73
103
  <!-- 视频水平布局 -->
74
- <shmily-drag-media v-else-if="enableVideo && !enableImage && showFileList" v-model="videoUrls"
75
- :number="videoLimit" :cols="cols" :imageWidth="itemWidth" :padding="10"
76
- :borderRadius="itemBorderRadius" :align="align" :draggable="draggable" :enableAddButton="true"
77
- moveType="video" @input="handleVideoDragChange" @videoClick="handleVideoItemClick">
78
- <template #add-button="{ childWidth }">
79
- <slot name="video-upload-button" :select="selectAndUploadVideo" :limit="videoLimit"
80
- :count="videoFileList.length">
104
+ <shmily-drag-media
105
+ v-else-if="enableVideo && !enableImage && showFileList"
106
+ v-model="videoUrls"
107
+ :number="videoLimit"
108
+ :cols="cols"
109
+ :imageWidth="itemWidth"
110
+ :padding="10"
111
+ :borderRadius="itemBorderRadius"
112
+ :align="align"
113
+ :draggable="draggable"
114
+ :enableAddButton="true"
115
+ moveType="video"
116
+ @input="handleVideoDragChange"
117
+ @videoClick="handleVideoItemClick"
118
+ >
119
+ <template #add-button>
120
+ <slot name="video-upload-button" :select="selectAndUploadVideo" :limit="videoLimit" :count="videoFileList.length">
81
121
  <view class="drag-upload-btn" @click="selectAndUploadVideo">
82
- <image :src="videoUploadIcon" class="upload-icon"
83
- :style="getUploadIconStyle(childWidth)"></image>
84
- <view class="upload-text" :style="getUploadTextStyle(childWidth)">{{ videoButtonTitle }}
85
- </view>
122
+ <image :src="videoUploadIcon" class="upload-icon" :style="dragIconStyle"></image>
123
+ <view class="upload-text" :style="dragTextStyle">{{ videoButtonTitle }} </view>
86
124
  </view>
87
125
  </slot>
88
126
  </template>
@@ -92,32 +130,43 @@
92
130
 
93
131
  <!-- ==================== 只读模式(混合展示图片和视频) ==================== -->
94
132
  <block v-else-if="allFileList.length && showFileList">
95
- <view class="readonly-grid" :class="{ 'align-right': align === 'right' && itemWidth > 0 }"
96
- :style="readonlyGridStyle">
97
- <view v-for="(file, i) in readonlyDisplayList" :key="file.id || file.url || i"
98
- :style="{ borderRadius: itemBorderRadius + 'rpx' }" :class="[
99
- 'readonly-grid-item',
100
- { 'placeholder-item': file.type === 'placeholder' },
101
- { 'filter-avatar': file.type !== 'placeholder' && isLastRealItem(i) }
102
- ]" :data-count="file.type !== 'placeholder' && isLastRealItem(i) ? '+' + (allFileList.length - maxShowCount) : ''"
103
- @click="handleReadonlyItemClick(i, file)">
104
-
133
+ <view class="readonly-grid" :class="{ 'align-right': align === 'right' && itemWidth > 0 }" :style="readonlyGridStyle">
134
+ <view
135
+ v-for="(file, i) in readonlyDisplayList"
136
+ :key="file.id || file.url || i"
137
+ :style="{ borderRadius: itemBorderRadius + 'rpx' }"
138
+ :class="['readonly-grid-item', { 'placeholder-item': file.type === 'placeholder' }, { 'filter-avatar': file.type !== 'placeholder' && isLastRealItem(i) }]"
139
+ :data-count="file.type !== 'placeholder' && isLastRealItem(i) ? '+' + (allFileList.length - maxShowCount) : ''"
140
+ @click="handleReadonlyItemClick(i, file)"
141
+ >
105
142
  <!-- 占位元素(不显示任何内容) -->
106
143
  <view v-if="file.type === 'placeholder'" class="placeholder-content"></view>
107
144
 
108
145
  <!-- 图片 -->
109
146
  <template v-else>
110
- <image v-if="file.type === 'image'" :src="lazyLoadImageSrcs[i]" mode="aspectFill"
111
- :style="{ borderRadius: itemBorderRadius + 'rpx' }" :id="componentId + '-imgview-' + i"
112
- @load="onMediaLoad('readonly-' + i)" @error="onMediaError('readonly-' + i)">
147
+ <image
148
+ v-if="file.type === 'image'"
149
+ :src="lazyLoadImageSrcs[i]"
150
+ mode="aspectFill"
151
+ :style="{ borderRadius: itemBorderRadius + 'rpx' }"
152
+ :id="componentId + '-imgview-' + i"
153
+ @load="onMediaLoad('readonly-' + i)"
154
+ @error="onMediaError('readonly-' + i)"
155
+ >
113
156
  </image>
114
157
 
115
158
  <!-- 视频 -->
116
- <video v-else-if="file.type === 'video'" :id="'readonlyVideo' + i" class="readonly-video"
117
- :src="file.url" :controls="false" :show-center-play-btn="false"
159
+ <video
160
+ v-else-if="file.type === 'video'"
161
+ :id="'readonlyVideo' + i"
162
+ class="readonly-video"
163
+ :src="file.url"
164
+ :controls="false"
165
+ :show-center-play-btn="false"
118
166
  :style="{ borderRadius: itemBorderRadius + 'rpx' }"
119
- @loadedmetadata="onMediaLoad('readonly-' + i)" @error="onMediaError('readonly-' + i)">
120
- </video>
167
+ @loadedmetadata="onMediaLoad('readonly-' + i)"
168
+ @error="onMediaError('readonly-' + i)"
169
+ ></video>
121
170
 
122
171
  <!-- 视频播放图标覆盖层 -->
123
172
  <view v-if="file.type === 'video'" class="video-play-overlay">
@@ -134,8 +183,7 @@
134
183
  </block>
135
184
 
136
185
  <!-- ==================== 自定义弹窗展示所有媒体(从底部滑出) ==================== -->
137
- <view v-if="popupVisible" class="popup-overlay" @click="closePopup" @touchmove.stop.prevent
138
- :class="{ 'popup-overlay-show': popupAnimated }">
186
+ <view v-if="popupVisible" class="popup-overlay" @click="closePopup" @touchmove.stop.prevent :class="{ 'popup-overlay-show': popupAnimated }">
139
187
  <view class="popup-content" @click.stop @touchmove.stop :class="{ 'popup-content-show': popupAnimated }">
140
188
  <view class="popup-header">
141
189
  <view class="popup-title">{{ popupTitle }}</view>
@@ -143,21 +191,30 @@
143
191
  </view>
144
192
  <scroll-view class="popup-scroll" scroll-y :show-scrollbar="false">
145
193
  <view class="popup-grid" :style="popupGridStyle">
146
- <view v-for="(file, i) in allFileList" :key="i" class="popup-grid-item"
147
- @click="handlePopupItemClick(file, i)">
148
-
194
+ <view v-for="(file, i) in allFileList" :key="i" class="popup-grid-item" @click="handlePopupItemClick(file, i)">
149
195
  <!-- 图片 -->
150
- <image v-if="file.type === 'image'" :src="popupLazyLoadImageSrcs[i]" mode="aspectFill"
151
- :style="{ borderRadius: itemBorderRadius + 'rpx' }" :id="componentId + '-popup-imgview-' + i"
152
- @load="onMediaLoad('popup-' + i)" @error="onMediaError('popup-' + i)">
196
+ <image
197
+ v-if="file.type === 'image'"
198
+ :src="popupLazyLoadImageSrcs[i]"
199
+ mode="aspectFill"
200
+ :style="{ borderRadius: itemBorderRadius + 'rpx' }"
201
+ :id="componentId + '-popup-imgview-' + i"
202
+ @load="onMediaLoad('popup-' + i)"
203
+ @error="onMediaError('popup-' + i)"
204
+ >
153
205
  </image>
154
206
 
155
207
  <!-- 视频 -->
156
- <video v-else-if="file.type === 'video'" class="popup-video" :src="file.url"
157
- :controls="false" :show-center-play-btn="false"
208
+ <video
209
+ v-else-if="file.type === 'video'"
210
+ class="popup-video"
211
+ :src="file.url"
212
+ :controls="false"
213
+ :show-center-play-btn="false"
158
214
  :style="{ borderRadius: itemBorderRadius + 'rpx' }"
159
- @loadedmetadata="onMediaLoad('popup-' + i)" @error="onMediaError('popup-' + i)">
160
- </video>
215
+ @loadedmetadata="onMediaLoad('popup-' + i)"
216
+ @error="onMediaError('popup-' + i)"
217
+ ></video>
161
218
 
162
219
  <!-- 视频播放图标 -->
163
220
  <view v-if="file.type === 'video'" class="video-play-overlay">
@@ -175,10 +232,16 @@
175
232
  </view>
176
233
 
177
234
  <!-- 专门用于全屏预览的 video,隐藏在视图外 -->
178
- <video v-if="enableVideo" id="fullscreenPreviewVideo" class="fullscreen-preview-video" :src="fullscreenVideoSrc"
179
- :controls="true" :show-center-play-btn="true" object-fit="contain"
180
- style="position: fixed; left: -9999px; top: -9999px; width: 1px; height: 1px;">
181
- </video>
235
+ <video
236
+ v-if="enableVideo"
237
+ id="fullscreenPreviewVideo"
238
+ class="fullscreen-preview-video"
239
+ :src="fullscreenVideoSrc"
240
+ :controls="true"
241
+ :show-center-play-btn="true"
242
+ object-fit="contain"
243
+ style="position: fixed; left: -9999px; top: -9999px; width: 1px; height: 1px"
244
+ ></video>
182
245
  </view>
183
246
  </template>
184
247
 
@@ -189,7 +252,7 @@ import shmilyDragMedia from './shmily-drag-media.vue';
189
252
  export default {
190
253
  name: 'MediaPicker',
191
254
  components: {
192
- shmilyDragMedia,
255
+ shmilyDragMedia
193
256
  },
194
257
  props: {
195
258
  // ==================== 通用配置 ====================
@@ -338,8 +401,8 @@ export default {
338
401
  observer: null,
339
402
  componentId: null, // 组件实例的唯一ID
340
403
  popupLazyLoadImageSrcs: [], // 用于弹窗中图片懒加载,存储进入视图的图片src
341
- popupObserver: null, // 弹窗图片懒加载观察器
342
- }
404
+ popupObserver: null // 弹窗图片懒加载观察器
405
+ };
343
406
  },
344
407
  created() {
345
408
  this.componentId = 'mp-' + Date.now() + Math.random().toString(36).substr(2, 9);
@@ -348,65 +411,67 @@ export default {
348
411
  // ==================== 媒体类型判断 ====================
349
412
  /** 是否启用图片上传 */
350
413
  enableImage() {
351
- return ['image', 'both'].includes(this.uploadType)
414
+ return ['image', 'both'].includes(this.uploadType);
352
415
  },
353
416
  /** 是否启用视频上传 */
354
417
  enableVideo() {
355
- return ['video', 'both'].includes(this.uploadType)
418
+ return ['video', 'both'].includes(this.uploadType);
356
419
  },
357
420
  /** 实际的布局方式(两种类型时强制为 vertical) */
358
421
  actualButtonPosition() {
359
422
  if (this.enableImage && this.enableVideo) {
360
- return 'vertical'
423
+ return 'vertical';
361
424
  }
362
- return this.buttonPosition
425
+ return this.buttonPosition;
363
426
  },
364
427
 
365
428
  /** 只读模式:需要显示的文件列表(右对齐时包含占位元素) */
366
429
  readonlyDisplayList() {
367
- const displayList = this.allFileList.slice(0, this.maxShowCount)
430
+ const displayList = this.allFileList.slice(0, this.maxShowCount);
368
431
 
369
432
  // 如果是右对齐,需要在最后一行前面添加占位元素
370
433
  if (this.readonly && this.align === 'right' && displayList.length > 0) {
371
- const totalCount = displayList.length
372
- const lastRowCount = totalCount % this.cols
434
+ const totalCount = displayList.length;
435
+ const lastRowCount = totalCount % this.cols;
373
436
 
374
437
  // 如果最后一行不满,需要在最后一行开始位置插入占位元素
375
438
  if (lastRowCount !== 0) {
376
- const placeholderCount = this.cols - lastRowCount
377
- const rows = Math.ceil(totalCount / this.cols)
378
- const lastRowStartIndex = (rows - 1) * this.cols
439
+ const placeholderCount = this.cols - lastRowCount;
440
+ const rows = Math.ceil(totalCount / this.cols);
441
+ const lastRowStartIndex = (rows - 1) * this.cols;
379
442
 
380
443
  // 创建占位元素
381
- const placeholders = Array(placeholderCount).fill(null).map((_, i) => ({
382
- type: 'placeholder',
383
- id: `placeholder-${i}`
384
- }))
444
+ const placeholders = Array(placeholderCount)
445
+ .fill(null)
446
+ .map((_, i) => ({
447
+ type: 'placeholder',
448
+ id: `placeholder-${i}`
449
+ }));
385
450
 
386
451
  // 将列表分为:前面几行(满的) + 最后一行
387
- const beforeLastRow = displayList.slice(0, lastRowStartIndex)
388
- const lastRow = displayList.slice(lastRowStartIndex)
452
+ const beforeLastRow = displayList.slice(0, lastRowStartIndex);
453
+ const lastRow = displayList.slice(lastRowStartIndex);
389
454
 
390
455
  // 在最后一行前面插入占位元素
391
- return [...beforeLastRow, ...placeholders, ...lastRow]
456
+ return [...beforeLastRow, ...placeholders, ...lastRow];
392
457
  }
393
458
  }
394
459
 
395
- return displayList
460
+ return displayList;
396
461
  },
397
462
 
398
463
  // ==================== 图标配置 ====================
399
464
  imageUploadIcon() {
400
- return this.imageButtonIcon || this.defaultImageIcon
465
+ return this.imageButtonIcon || this.defaultImageIcon;
401
466
  },
402
467
  videoUploadIcon() {
403
- return this.videoButtonIcon || this.defaultVideoIcon
468
+ return this.videoButtonIcon || this.defaultVideoIcon;
404
469
  },
405
470
 
406
471
  // ==================== URL 列表 ====================
407
472
  imageUrls: {
408
473
  get() {
409
- return this.imageFileList.map(file => file.url)
474
+ return this.imageFileList.map((file) => file.url);
410
475
  },
411
476
  set(newUrls) {
412
477
  // setter 用于拖拽组件更新时,由 handleImageDragChange 处理
@@ -414,7 +479,7 @@ export default {
414
479
  },
415
480
  videoUrls: {
416
481
  get() {
417
- return this.videoFileList.map(file => file.url)
482
+ return this.videoFileList.map((file) => file.url);
418
483
  },
419
484
  set(newUrls) {
420
485
  // setter 用于拖拽组件更新时,由 handleVideoDragChange 处理
@@ -422,45 +487,57 @@ export default {
422
487
  },
423
488
  /** 合并的所有媒体列表(用于只读模式和弹窗) */
424
489
  allFileList() {
425
- const combined = []
490
+ const combined = [];
426
491
  // 标记每个文件的类型
427
- this.imageFileList.forEach(file => {
428
- combined.push({ ...file, type: 'image' })
429
- })
430
- this.videoFileList.forEach(file => {
431
- combined.push({ ...file, type: 'video' })
432
- })
433
- return combined
492
+ this.imageFileList.forEach((file) => {
493
+ combined.push({ ...file, type: 'image' });
494
+ });
495
+ this.videoFileList.forEach((file) => {
496
+ combined.push({ ...file, type: 'video' });
497
+ });
498
+ return combined;
434
499
  },
435
500
 
436
501
  // ==================== 上传地址 ====================
437
502
  finalUploadUrl() {
438
- return this.actualUploadUrl || this.uploadUrl || (getApp().globalData && getApp().globalData.uploadUrl) || ''
503
+ return this.actualUploadUrl || this.uploadUrl || (getApp().globalData && getApp().globalData.uploadUrl) || '';
439
504
  },
440
505
 
441
506
  // ==================== 动态尺寸计算 ====================
442
507
  uploadIconSize() {
443
508
  if (this.itemWidth > 0) {
444
- return Math.floor((this.itemWidth - 20) * 0.3)
509
+ return Math.floor((this.itemWidth - 20) * 0.3);
445
510
  }
446
- const screenWidth = 750
447
- const gap = 20
448
- const containerWidth = (screenWidth - gap * (this.cols + 1)) / this.cols
449
- return Math.floor(containerWidth * 0.3)
511
+ const screenWidth = 750;
512
+ const gap = 20;
513
+ const containerWidth = (screenWidth - gap * (this.cols + 1)) / this.cols;
514
+ return Math.floor(containerWidth * 0.3);
450
515
  },
451
516
  uploadTextSize() {
452
- return Math.max(20, Math.floor(this.uploadIconSize * 0.38))
517
+ return Math.max(20, Math.floor(this.uploadIconSize * 0.38));
518
+ },
519
+ // 拖拽模式下的图标和文字样式(预计算好,避免模板中使用方法调用报错)
520
+ dragIconStyle() {
521
+ return {
522
+ width: this.uploadIconSize + 'rpx',
523
+ height: this.uploadIconSize + 'rpx'
524
+ };
525
+ },
526
+ dragTextStyle() {
527
+ return {
528
+ fontSize: this.uploadTextSize + 'rpx'
529
+ };
453
530
  },
454
531
 
455
532
  // ==================== 只读模式网格样式 ====================
456
533
  readonlyGridStyle() {
457
534
  // 如果指定了 itemWidth,使用固定宽度;否则使用 1fr 自动分配
458
- const columnWidth = this.itemWidth ? `${this.itemWidth}rpx` : '1fr'
535
+ const columnWidth = this.itemWidth ? `${this.itemWidth}rpx` : '1fr';
459
536
 
460
537
  return {
461
538
  gridTemplateColumns: `repeat(${this.cols}, ${columnWidth})`,
462
539
  gap: '10rpx'
463
- }
540
+ };
464
541
  },
465
542
 
466
543
  // ==================== 弹窗模式网格样式 ====================
@@ -468,19 +545,19 @@ export default {
468
545
  return {
469
546
  gridTemplateColumns: `repeat(${this.cols}, 1fr)`,
470
547
  gap: '10rpx'
471
- }
548
+ };
472
549
  }
473
550
  },
474
551
  watch: {
475
552
  imageFiles: {
476
553
  handler(val) {
477
- this.imageFileList = val || []
554
+ this.imageFileList = val || [];
478
555
  },
479
556
  immediate: true
480
557
  },
481
558
  videoFiles: {
482
559
  handler(val) {
483
- this.videoFileList = val || []
560
+ this.videoFileList = val || [];
484
561
  },
485
562
  immediate: true
486
563
  },
@@ -492,7 +569,7 @@ export default {
492
569
  },
493
570
  mounted() {
494
571
  if (!this.uploadUrl) {
495
- this.actualUploadUrl = (getApp().globalData && getApp().globalData.uploadUrl) || ''
572
+ this.actualUploadUrl = (getApp().globalData && getApp().globalData.uploadUrl) || '';
496
573
  }
497
574
  this.initLazyObserver(); // 在mounted时初始化观察器
498
575
  },
@@ -513,49 +590,7 @@ export default {
513
590
  * @returns {Boolean} - 是否被监听
514
591
  */
515
592
  hasEventListener(eventName) {
516
- return !!(this.$listeners && this.$listeners[eventName])
517
- },
518
- parseChildWidth(childWidth) {
519
- if (typeof childWidth === 'number') {
520
- return childWidth
521
- }
522
- if (typeof childWidth === 'string') {
523
- const parsed = parseFloat(childWidth)
524
- return Number.isFinite(parsed) ? parsed : null
525
- }
526
- return null
527
- },
528
- pxToRpx(px) {
529
- if (!px || !this.windowWidth) {
530
- return 0
531
- }
532
- return px / this.windowWidth * 750
533
- },
534
- computeUploadIconSize(childWidth) {
535
- const parsedPx = this.parseChildWidth(childWidth)
536
- if (!parsedPx) {
537
- return this.uploadIconSize
538
- }
539
- const widthRpx = this.pxToRpx(parsedPx)
540
- const size = Math.floor(widthRpx * 0.3)
541
- return Math.max(20, size)
542
- },
543
- computeUploadTextSize(childWidth) {
544
- const iconSize = this.computeUploadIconSize(childWidth)
545
- const size = Math.floor(iconSize * 0.4)
546
- return Math.max(20, size)
547
- },
548
- getUploadIconStyle(childWidth) {
549
- const size = this.computeUploadIconSize(childWidth)
550
- return {
551
- width: size + 'rpx',
552
- height: size + 'rpx'
553
- }
554
- },
555
- getUploadTextStyle(childWidth) {
556
- return {
557
- fontSize: this.computeUploadTextSize(childWidth) + 'rpx'
558
- }
593
+ return !!(this.$listeners && this.$listeners[eventName]);
559
594
  },
560
595
 
561
596
  // ==================== 图片相关方法 ====================
@@ -566,10 +601,10 @@ export default {
566
601
  */
567
602
  checkImageSize(size) {
568
603
  if (this.imageMaxSize <= 0) {
569
- return true
604
+ return true;
570
605
  }
571
- const maxSizeInBytes = this.imageMaxSize * 1024 * 1024
572
- return size <= maxSizeInBytes
606
+ const maxSizeInBytes = this.imageMaxSize * 1024 * 1024;
607
+ return size <= maxSizeInBytes;
573
608
  },
574
609
 
575
610
  /**
@@ -579,11 +614,11 @@ export default {
579
614
  */
580
615
  formatFileSize(size) {
581
616
  if (size < 1024) {
582
- return size + 'B'
617
+ return size + 'B';
583
618
  } else if (size < 1024 * 1024) {
584
- return (size / 1024).toFixed(2) + 'KB'
619
+ return (size / 1024).toFixed(2) + 'KB';
585
620
  } else {
586
- return (size / (1024 * 1024)).toFixed(2) + 'MB'
621
+ return (size / (1024 * 1024)).toFixed(2) + 'MB';
587
622
  }
588
623
  },
589
624
 
@@ -596,31 +631,31 @@ export default {
596
631
  this.$emit('imageLimitReached', {
597
632
  current: this.imageFileList.length,
598
633
  limit: this.imageLimit
599
- })
634
+ });
600
635
 
601
636
  // 如果用户未监听事件,显示默认提示
602
637
  if (!this.hasEventListener('imageLimitReached')) {
603
638
  uni.showToast({
604
639
  icon: 'none',
605
640
  title: `上传最大限制${this.imageLimit}张`
606
- })
641
+ });
607
642
  }
608
- return
643
+ return;
609
644
  }
610
645
 
611
646
  if (!this.finalUploadUrl) {
612
647
  uni.showToast({
613
648
  title: '请先配置上传地址',
614
649
  icon: 'none'
615
- })
616
- return
650
+ });
651
+ return;
617
652
  }
618
653
 
619
- const remainCount = this.imageLimit - this.imageFileList.length
654
+ const remainCount = this.imageLimit - this.imageFileList.length;
620
655
 
621
656
  uni.chooseImage({
622
657
  count: remainCount,
623
- sizeType: ["original", "compressed"],
658
+ sizeType: ['original', 'compressed'],
624
659
  sourceType: this.imageSourceType,
625
660
  // #ifndef MP-WEIXIN
626
661
  success: (chooseImageRes) => {
@@ -630,23 +665,23 @@ export default {
630
665
  current: this.imageFileList.length,
631
666
  limit: this.imageLimit,
632
667
  attempted: chooseImageRes.tempFiles.length
633
- })
668
+ });
634
669
 
635
670
  // 如果用户未监听事件,显示默认提示
636
671
  if (!this.hasEventListener('imageLimitReached')) {
637
672
  uni.showToast({
638
673
  icon: 'none',
639
674
  title: `上传最大限制${this.imageLimit}张`
640
- })
675
+ });
641
676
  }
642
- return
677
+ return;
643
678
  }
644
679
 
645
680
  // 检查图片大小
646
681
  if (this.imageMaxSize > 0) {
647
- const oversizedFiles = chooseImageRes.tempFiles.filter(file => !this.checkImageSize(file.size))
682
+ const oversizedFiles = chooseImageRes.tempFiles.filter((file) => !this.checkImageSize(file.size));
648
683
  if (oversizedFiles.length > 0) {
649
- const firstOversized = oversizedFiles[0]
684
+ const firstOversized = oversizedFiles[0];
650
685
 
651
686
  // 触发图片大小超限事件
652
687
  this.$emit('imageSizeExceed', {
@@ -654,7 +689,7 @@ export default {
654
689
  maxSize: this.imageMaxSize * 1024 * 1024,
655
690
  file: firstOversized,
656
691
  message: `图片大小不能超过${this.imageMaxSize}MB(当前:${this.formatFileSize(firstOversized.size)})`
657
- })
692
+ });
658
693
 
659
694
  // 如果用户未监听事件,显示默认提示
660
695
  if (!this.hasEventListener('imageSizeExceed')) {
@@ -662,34 +697,20 @@ export default {
662
697
  icon: 'none',
663
698
  title: `图片大小不能超过${this.imageMaxSize}MB(当前:${this.formatFileSize(firstOversized.size)})`,
664
699
  duration: 3000
665
- })
700
+ });
666
701
  }
667
- return
702
+ return;
668
703
  }
669
704
  }
670
705
 
671
706
  uni.showLoading({
672
707
  title: '上传中',
673
708
  mask: true
674
- })
709
+ });
675
710
 
676
- chooseImageRes.tempFilePaths.forEach((tempPath, index) => {
677
- const tempFile = chooseImageRes.tempFiles[index]
678
-
679
- new Compressor(tempFile, {
680
- quality: 0.8,
681
- success: (compressedFile) => {
682
- this.uploadImageFile(this.finalUploadUrl, compressedFile)
683
- },
684
- error: (err) => {
685
- uni.hideLoading()
686
- uni.showToast({
687
- icon: 'none',
688
- title: '图片压缩失败'
689
- })
690
- },
691
- })
692
- })
711
+ chooseImageRes.tempFiles.forEach((fileItem) => {
712
+ this.uploadImageFile(this.finalUploadUrl, fileItem);
713
+ });
693
714
  },
694
715
  // #endif
695
716
 
@@ -701,23 +722,23 @@ export default {
701
722
  current: this.imageFileList.length,
702
723
  limit: this.imageLimit,
703
724
  attempted: chooseImageRes.tempFiles.length
704
- })
725
+ });
705
726
 
706
727
  // 如果用户未监听事件,显示默认提示
707
728
  if (!this.hasEventListener('imageLimitReached')) {
708
729
  uni.showToast({
709
730
  icon: 'none',
710
731
  title: `上传最大限制${this.imageLimit}张`
711
- })
732
+ });
712
733
  }
713
- return
734
+ return;
714
735
  }
715
736
 
716
737
  // 检查图片大小
717
738
  if (this.imageMaxSize > 0) {
718
- const oversizedFiles = chooseImageRes.tempFiles.filter(file => !this.checkImageSize(file.size))
739
+ const oversizedFiles = chooseImageRes.tempFiles.filter((file) => !this.checkImageSize(file.size));
719
740
  if (oversizedFiles.length > 0) {
720
- const firstOversized = oversizedFiles[0]
741
+ const firstOversized = oversizedFiles[0];
721
742
 
722
743
  // 触发图片大小超限事件
723
744
  this.$emit('imageSizeExceed', {
@@ -725,7 +746,7 @@ export default {
725
746
  maxSize: this.imageMaxSize * 1024 * 1024,
726
747
  file: firstOversized,
727
748
  message: `图片大小不能超过${this.imageMaxSize}MB(当前:${this.formatFileSize(firstOversized.size)})`
728
- })
749
+ });
729
750
 
730
751
  // 如果用户未监听事件,显示默认提示
731
752
  if (!this.hasEventListener('imageSizeExceed')) {
@@ -733,20 +754,20 @@ export default {
733
754
  icon: 'none',
734
755
  title: `图片大小不能超过${this.imageMaxSize}MB(当前:${this.formatFileSize(firstOversized.size)})`,
735
756
  duration: 3000
736
- })
757
+ });
737
758
  }
738
- return
759
+ return;
739
760
  }
740
761
  }
741
762
 
742
763
  uni.showLoading({
743
764
  title: '上传中',
744
765
  mask: true
745
- })
766
+ });
746
767
 
747
768
  chooseImageRes.tempFilePaths.forEach((tempPath, index) => {
748
- const tempFile = chooseImageRes.tempFiles[index]
749
- const lowerPath = tempPath.toLowerCase()
769
+ const tempFile = chooseImageRes.tempFiles[index];
770
+ const lowerPath = tempPath.toLowerCase();
750
771
 
751
772
  // 对 jpg/jpeg 格式的图片进行压缩
752
773
  if (lowerPath.indexOf('.jpg') !== -1 || lowerPath.indexOf('.jpeg') !== -1) {
@@ -754,16 +775,16 @@ export default {
754
775
  src: tempFile.path,
755
776
  quality: 100,
756
777
  success: (res) => {
757
- this.uploadImageFileByPath(this.finalUploadUrl, res.tempFilePath, tempPath)
778
+ this.uploadImageFileByPath(this.finalUploadUrl, res.tempFilePath, tempPath);
758
779
  }
759
- })
780
+ });
760
781
  } else {
761
- this.uploadImageFileByPath(this.finalUploadUrl, tempPath, tempPath)
782
+ this.uploadImageFileByPath(this.finalUploadUrl, tempPath, tempPath);
762
783
  }
763
- })
764
- },
784
+ });
785
+ }
765
786
  // #endif
766
- })
787
+ });
767
788
  },
768
789
 
769
790
  /**
@@ -773,41 +794,63 @@ export default {
773
794
  uni.uploadFile({
774
795
  url,
775
796
  file,
776
- name: "file",
797
+ name: 'file',
777
798
  success: (uploadFileRes) => {
778
- const result = JSON.parse(uploadFileRes.data)
779
-
780
- const fileInfo = {
781
- fileName: result.data.fileName,
782
- name: result.data.originFileName,
783
- url: result.data.pathUrl,
784
- type: 'image'
799
+ // 1. 先检查 HTTP 状态码
800
+ if (uploadFileRes.statusCode !== 200) {
801
+ console.error('HTTP状态码错误:', uploadFileRes.statusCode);
802
+ uni.showToast({ title: `服务器响应异常(${uploadFileRes.statusCode})`, icon: 'none' });
803
+ return;
785
804
  }
786
- this.imageFileList.push(fileInfo)
787
805
 
788
- // 触发图片上传成功事件
789
- this.$emit('imageUploadSuccess', {
790
- file: fileInfo,
791
- response: result
792
- })
806
+ try {
807
+ // 2. 尝试解析 JSON
808
+ const result = JSON.parse(uploadFileRes.data);
809
+
810
+ // 3. 检查后端定义的业务 Code (假设 200 为成功)
811
+ if (result.rspCode === '000000') {
812
+ const fileInfo = {
813
+ fileName: result.data.fileName,
814
+ name: result.data.originFileName,
815
+ url: result.data.pathUrl,
816
+ type: 'image'
817
+ };
818
+ this.imageFileList.push(fileInfo);
819
+
820
+ this.$emit('imageUploadSuccess', {
821
+ uploadFileRes: uploadFileRes,
822
+ file: fileInfo,
823
+ response: result
824
+ });
825
+ } else {
826
+ // 后端业务层报错(如:格式不支持、权限不足)
827
+ uni.showToast({ title: result.rspMsg || '业务处理失败', icon: 'none' });
828
+ this.$emit('imageUploadFail', { error: result, message: result.rspMsg });
829
+ }
830
+ } catch (error) {
831
+ // 解析 JSON 失败,通常是后端挂了返回了非 JSON 格式
832
+ console.error('返回数据解析异常:', uploadFileRes.data);
833
+ uni.showToast({ title: '服务器数据解析失败', icon: 'none' });
834
+ this.$emit('imageUploadFail', { error: error, message: '解析失败' });
835
+ }
793
836
  },
794
837
  fail: (error) => {
795
838
  uni.showToast({
796
839
  icon: 'none',
797
840
  title: '上传失败'
798
- })
841
+ });
799
842
 
800
843
  // 触发图片上传失败事件
801
844
  this.$emit('imageUploadFail', {
802
845
  error: error,
803
846
  message: '图片上传失败'
804
- })
847
+ });
805
848
  },
806
849
  complete: () => {
807
- uni.hideLoading()
808
- this.emitImageChange()
850
+ uni.hideLoading();
851
+ this.emitImageChange();
809
852
  }
810
- })
853
+ });
811
854
  },
812
855
 
813
856
  /**
@@ -817,53 +860,55 @@ export default {
817
860
  uni.uploadFile({
818
861
  url,
819
862
  filePath,
820
- name: "file",
863
+ name: 'file',
821
864
  success: (uploadFileRes) => {
822
- const result = JSON.parse(uploadFileRes.data)
865
+ const result = JSON.parse(uploadFileRes.data);
823
866
 
824
867
  const fileInfo = {
825
868
  fileName: result.data.fileName,
826
869
  name: result.data.originFileName,
827
870
  url: result.data.pathUrl,
828
871
  type: 'image'
829
- }
830
- this.imageFileList.push(fileInfo)
872
+ };
873
+ this.imageFileList.push(fileInfo);
831
874
 
832
875
  // 触发图片上传成功事件
833
876
  this.$emit('imageUploadSuccess', {
834
877
  file: fileInfo,
835
878
  response: result
836
- })
879
+ });
837
880
  },
838
881
  fail: (error) => {
839
882
  uni.showToast({
840
883
  icon: 'none',
841
884
  title: '上传失败'
842
- })
885
+ });
843
886
 
844
887
  // 触发图片上传失败事件
845
888
  this.$emit('imageUploadFail', {
846
889
  error: error,
847
890
  message: '图片上传失败'
848
- })
891
+ });
849
892
  },
850
893
  complete: () => {
851
- uni.hideLoading()
852
- this.emitImageChange()
894
+ uni.hideLoading();
895
+ this.emitImageChange();
853
896
  }
854
- })
897
+ });
855
898
  },
856
899
 
857
900
  /**
858
901
  * 处理图片拖拽排序变化
859
902
  */
860
- handleImageDragChange(newUrls) {
861
- const newFileList = newUrls.map(url => {
862
- return this.imageFileList.find(file => file.url === url)
863
- }).filter(Boolean)
903
+ handleImageDragChange(newUrls) {
904
+ const newFileList = newUrls
905
+ .map((url) => {
906
+ return this.imageFileList.find((file) => file.url === url);
907
+ })
908
+ .filter(Boolean);
864
909
 
865
- this.imageFileList = newFileList
866
- this.emitImageChange()
910
+ this.imageFileList = newFileList;
911
+ this.emitImageChange();
867
912
  },
868
913
 
869
914
  /**
@@ -873,15 +918,15 @@ export default {
873
918
  uni.previewImage({
874
919
  current,
875
920
  urls,
876
- indicator: "number"
877
- })
921
+ indicator: 'number'
922
+ });
878
923
  },
879
924
 
880
925
  /**
881
926
  * 触发图片变更事件
882
927
  */
883
928
  emitImageChange() {
884
- this.$emit('imageChange', this.imageFileList)
929
+ this.$emit('imageChange', this.imageFileList);
885
930
  },
886
931
 
887
932
  // ==================== 视频相关方法 ====================
@@ -893,8 +938,8 @@ export default {
893
938
  uni.showToast({
894
939
  title: '请先配置上传地址',
895
940
  icon: 'none'
896
- })
897
- return
941
+ });
942
+ return;
898
943
  }
899
944
 
900
945
  if (this.videoFileList.length >= this.videoLimit) {
@@ -902,16 +947,16 @@ export default {
902
947
  this.$emit('videoLimitReached', {
903
948
  current: this.videoFileList.length,
904
949
  limit: this.videoLimit
905
- })
950
+ });
906
951
 
907
952
  // 如果用户未监听事件,显示默认提示
908
953
  if (!this.hasEventListener('videoLimitReached')) {
909
954
  uni.showToast({
910
955
  title: `最多上传${this.videoLimit}个视频`,
911
956
  icon: 'none'
912
- })
957
+ });
913
958
  }
914
- return
959
+ return;
915
960
  }
916
961
 
917
962
  uni.chooseVideo({
@@ -926,20 +971,20 @@ export default {
926
971
  maxDuration: this.videoMaxDuration,
927
972
  file: res,
928
973
  message: `视频时长不能超过${this.videoMaxDuration}秒`
929
- })
974
+ });
930
975
 
931
976
  // 如果用户未监听事件,显示默认提示
932
977
  if (!this.hasEventListener('videoDurationExceed')) {
933
978
  uni.showToast({
934
979
  title: `视频时长不能超过${this.videoMaxDuration}秒`,
935
980
  icon: 'none'
936
- })
981
+ });
937
982
  }
938
- return
983
+ return;
939
984
  }
940
- this.uploadVideo(res.tempFilePath)
985
+ this.uploadVideo(res.tempFilePath);
941
986
  }
942
- })
987
+ });
943
988
  },
944
989
 
945
990
  /**
@@ -949,57 +994,57 @@ export default {
949
994
  const uploadTask = uni.uploadFile({
950
995
  url: this.finalUploadUrl,
951
996
  filePath,
952
- name: "file",
997
+ name: 'file',
953
998
  success: (uploadFileRes) => {
954
- const result = JSON.parse(uploadFileRes.data)
999
+ const result = JSON.parse(uploadFileRes.data);
955
1000
 
956
1001
  const fileInfo = {
957
1002
  fileName: result.data.fileName,
958
1003
  name: result.data.originFileName || 'video',
959
1004
  url: result.data.pathUrl,
960
1005
  type: 'video'
961
- }
1006
+ };
962
1007
 
963
- this.videoFileList.push(fileInfo)
964
- this.emitVideoChange()
1008
+ this.videoFileList.push(fileInfo);
1009
+ this.emitVideoChange();
965
1010
 
966
1011
  // 触发视频上传成功事件
967
1012
  this.$emit('videoUploadSuccess', {
968
1013
  file: fileInfo,
969
1014
  response: result
970
- })
1015
+ });
971
1016
  },
972
1017
  fail: (err) => {
973
1018
  uni.showToast({
974
1019
  icon: 'none',
975
1020
  title: '视频上传失败'
976
- })
1021
+ });
977
1022
 
978
1023
  // 触发视频上传失败事件
979
1024
  this.$emit('videoUploadFail', {
980
1025
  error: err,
981
1026
  message: '视频上传失败'
982
- })
1027
+ });
983
1028
  },
984
1029
  complete: () => {
985
- uni.hideLoading()
1030
+ uni.hideLoading();
986
1031
  }
987
- })
1032
+ });
988
1033
 
989
1034
  uploadTask.onProgressUpdate((res) => {
990
1035
  uni.showLoading({
991
- title: '已上传' + res.progress + "%",
992
- mask: true,
1036
+ title: '已上传' + res.progress + '%',
1037
+ mask: true
993
1038
  });
994
- console.log("上传进度" + res.progress);
995
- console.log("已经上传的数据长度" + res.totalBytesSent);
996
- console.log("预期需要上传的数据总长度" + res.totalBytesExpectedToSend);
1039
+ console.log('上传进度' + res.progress);
1040
+ console.log('已经上传的数据长度' + res.totalBytesSent);
1041
+ console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
997
1042
  // // 测试条件,取消上传任务。
998
1043
  if (res.progress === 100) {
999
1044
  uni.hideLoading();
1000
1045
  uni.showLoading({
1001
- title: "确认中...",
1002
- mask: true,
1046
+ title: '确认中...',
1047
+ mask: true
1003
1048
  });
1004
1049
  }
1005
1050
  });
@@ -1009,42 +1054,44 @@ export default {
1009
1054
  * 处理视频拖拽排序变化
1010
1055
  */
1011
1056
  handleVideoDragChange(newUrls) {
1012
- const newFileList = newUrls.map(url => {
1013
- return this.videoFileList.find(file => file.url === url)
1014
- }).filter(Boolean)
1057
+ const newFileList = newUrls
1058
+ .map((url) => {
1059
+ return this.videoFileList.find((file) => file.url === url);
1060
+ })
1061
+ .filter(Boolean);
1015
1062
 
1016
- this.videoFileList = newFileList
1017
- this.emitVideoChange()
1063
+ this.videoFileList = newFileList;
1064
+ this.emitVideoChange();
1018
1065
  },
1019
1066
 
1020
1067
  /**
1021
1068
  * 触发视频变更事件
1022
1069
  */
1023
1070
  emitVideoChange() {
1024
- this.$emit('videoChange', this.videoFileList)
1071
+ this.$emit('videoChange', this.videoFileList);
1025
1072
  },
1026
1073
 
1027
1074
  /**
1028
1075
  * 处理拖拽组件中的视频点击
1029
1076
  */
1030
1077
  handleVideoItemClick(item) {
1031
- console.log('点击视频:', item)
1078
+ console.log('点击视频:', item);
1032
1079
  // shmily-drag-media 传递的是 item.src
1033
- this.playVideo(item.src)
1080
+ this.playVideo(item.src);
1034
1081
  },
1035
1082
 
1036
1083
  /**
1037
- * 播放视频(统一播放方法)
1084
+ * 播放视频(统一播放方法)
1038
1085
  */
1039
1086
  playVideo(url) {
1040
1087
  // 设置视频源
1041
- this.fullscreenVideoSrc = url
1088
+ this.fullscreenVideoSrc = url;
1042
1089
  // 等待视频组件更新
1043
1090
  this.$nextTick(() => {
1044
- const videoContext = uni.createVideoContext('fullscreenPreviewVideo', this)
1045
- videoContext.requestFullScreen()
1046
- videoContext.play()
1047
- })
1091
+ const videoContext = uni.createVideoContext('fullscreenPreviewVideo', this);
1092
+ videoContext.requestFullScreen();
1093
+ videoContext.play();
1094
+ });
1048
1095
  },
1049
1096
 
1050
1097
  // ==================== 只读模式交互 ====================
@@ -1052,15 +1099,15 @@ export default {
1052
1099
  * 判断是否是最后一个真实元素(用于显示 +N 遮罩)
1053
1100
  */
1054
1101
  isLastRealItem(displayIndex) {
1055
- const file = this.readonlyDisplayList[displayIndex]
1056
- if (!file || file.type === 'placeholder') return false
1102
+ const file = this.readonlyDisplayList[displayIndex];
1103
+ if (!file || file.type === 'placeholder') return false;
1057
1104
 
1058
1105
  // 计算占位元素数量
1059
- const placeholderCount = this.readonlyDisplayList.filter(f => f.type === 'placeholder').length
1060
- const realIndex = displayIndex - placeholderCount
1106
+ const placeholderCount = this.readonlyDisplayList.filter((f) => f.type === 'placeholder').length;
1107
+ const realIndex = displayIndex - placeholderCount;
1061
1108
 
1062
1109
  // 判断是否是最后一个真实元素,且总数超过限制
1063
- return this.allFileList.length > this.maxShowCount && realIndex === this.maxShowCount - 1
1110
+ return this.allFileList.length > this.maxShowCount && realIndex === this.maxShowCount - 1;
1064
1111
  },
1065
1112
 
1066
1113
  /**
@@ -1069,7 +1116,7 @@ export default {
1069
1116
  handleReadonlyItemClick(index, file) {
1070
1117
  // 如果点击的是占位元素,不做任何处理
1071
1118
  if (!file || file.type === 'placeholder') {
1072
- return
1119
+ return;
1073
1120
  }
1074
1121
 
1075
1122
  // 判断是否点击的是遮罩层(最后一项且超出限制)
@@ -1079,19 +1126,19 @@ export default {
1079
1126
  showCount: this.maxShowCount,
1080
1127
  hiddenCount: this.allFileList.length - this.maxShowCount,
1081
1128
  allFiles: this.allFileList
1082
- })
1129
+ });
1083
1130
 
1084
1131
  if (!this.hasEventListener('moreClick')) {
1085
- this.openPopup()
1132
+ this.openPopup();
1086
1133
  }
1087
1134
  } else {
1088
1135
  if (file.type === 'image') {
1089
1136
  // 预览图片(只提取图片列表)
1090
- const imageUrls = this.allFileList.filter(f => f.type === 'image').map(f => f.url)
1091
- const imageIndex = this.imageFileList.findIndex(f => f.url === file.url)
1092
- this.previewImage(imageUrls, imageIndex)
1137
+ const imageUrls = this.allFileList.filter((f) => f.type === 'image').map((f) => f.url);
1138
+ const imageIndex = this.imageFileList.findIndex((f) => f.url === file.url);
1139
+ this.previewImage(imageUrls, imageIndex);
1093
1140
  } else if (file.type === 'video') {
1094
- this.playVideo(file.url)
1141
+ this.playVideo(file.url);
1095
1142
  }
1096
1143
  }
1097
1144
  },
@@ -1101,43 +1148,43 @@ export default {
1101
1148
  */
1102
1149
  handlePopupItemClick(file, index) {
1103
1150
  if (file.type === 'image') {
1104
- const imageUrls = this.allFileList.filter(f => f.type === 'image').map(f => f.url)
1105
- const imageIndex = this.imageFileList.findIndex(f => f.url === file.url)
1106
- this.previewImage(imageUrls, imageIndex)
1151
+ const imageUrls = this.allFileList.filter((f) => f.type === 'image').map((f) => f.url);
1152
+ const imageIndex = this.imageFileList.findIndex((f) => f.url === file.url);
1153
+ this.previewImage(imageUrls, imageIndex);
1107
1154
  } else if (file.type === 'video') {
1108
- this.playVideo(file.url)
1155
+ this.playVideo(file.url);
1109
1156
  }
1110
1157
  },
1111
1158
 
1112
1159
  // ==================== 弹窗控制 ====================
1113
1160
  openPopup() {
1114
- this.popupVisible = true
1161
+ this.popupVisible = true;
1115
1162
  this.$nextTick(() => {
1116
1163
  setTimeout(() => {
1117
- this.popupAnimated = true
1164
+ this.popupAnimated = true;
1118
1165
  this.initPopupLazyObserver(); // 弹窗打开时初始化懒加载观察器
1119
- }, 50)
1120
- })
1166
+ }, 50);
1167
+ });
1121
1168
  },
1122
1169
 
1123
1170
  closePopup() {
1124
- this.popupAnimated = false
1171
+ this.popupAnimated = false;
1125
1172
  setTimeout(() => {
1126
- this.popupVisible = false
1173
+ this.popupVisible = false;
1127
1174
  if (this.popupObserver) {
1128
1175
  this.popupObserver.disconnect(); // 弹窗关闭时断开观察器
1129
1176
  this.popupObserver = null;
1130
1177
  }
1131
- }, 300)
1178
+ }, 300);
1132
1179
  },
1133
1180
 
1134
1181
  // ==================== 加载状态管理 ====================
1135
1182
  onMediaLoad(key) {
1136
- this.$set(this.mediaLoadingStatus, key, true)
1183
+ this.$set(this.mediaLoadingStatus, key, true);
1137
1184
  },
1138
1185
 
1139
1186
  onMediaError(key) {
1140
- this.$set(this.mediaLoadingStatus, key, true)
1187
+ this.$set(this.mediaLoadingStatus, key, true);
1141
1188
  },
1142
1189
 
1143
1190
  // ==================== 初始化图片懒加载观察器 ====================
@@ -1152,18 +1199,18 @@ export default {
1152
1199
  this.observer = null;
1153
1200
  }
1154
1201
 
1155
- this.lazyLoadImageSrcs = Array(this.allFileList.length).fill(null);
1202
+ this.lazyLoadImageSrcs = Array(this.allFileList.length).fill(null);
1156
1203
 
1157
1204
  this.$nextTick(() => {
1158
1205
  const currentObserver = uni.createIntersectionObserver(this, { observeAll: true });
1159
- this.observer = currentObserver;
1206
+ this.observer = currentObserver;
1160
1207
 
1161
1208
  if (!this.observer) {
1162
- console.warn("IntersectionObserver failed to create.");
1209
+ console.warn('IntersectionObserver failed to create.');
1163
1210
  return;
1164
1211
  }
1165
1212
 
1166
- this.observer.relativeToViewport({ top: 0, bottom: 50 });
1213
+ this.observer.relativeToViewport({ top: 0, bottom: 50 });
1167
1214
 
1168
1215
  this.allFileList.forEach((file, index) => {
1169
1216
  if (file.type === 'image') {
@@ -1171,7 +1218,7 @@ export default {
1171
1218
  currentObserver.observe(sel, (res) => {
1172
1219
  const isVisible = res.intersectionRatio > 0;
1173
1220
  if (isVisible && !this.lazyLoadImageSrcs[index]) {
1174
- this.$set(this.lazyLoadImageSrcs, index, file.url);
1221
+ this.$set(this.lazyLoadImageSrcs, index, file.url);
1175
1222
  }
1176
1223
  });
1177
1224
  }
@@ -1200,7 +1247,7 @@ export default {
1200
1247
  this.popupObserver = currentPopupObserver;
1201
1248
 
1202
1249
  if (!this.popupObserver) {
1203
- console.warn("Popup IntersectionObserver failed to create.");
1250
+ console.warn('Popup IntersectionObserver failed to create.');
1204
1251
  return;
1205
1252
  }
1206
1253
 
@@ -1234,12 +1281,12 @@ export default {
1234
1281
  if (this.uploadType === 'both') {
1235
1282
  return {
1236
1283
  images: this.imageFileList,
1237
- videos: this.videoFileList,
1238
- }
1284
+ videos: this.videoFileList
1285
+ };
1239
1286
  } else if (this.uploadType === 'image') {
1240
- return this.imageFileList
1287
+ return this.imageFileList;
1241
1288
  } else if (this.uploadType === 'video') {
1242
- return this.videoFileList
1289
+ return this.videoFileList;
1243
1290
  }
1244
1291
  },
1245
1292
 
@@ -1247,31 +1294,31 @@ export default {
1247
1294
  * 获取参数(不包含 url 字段)
1248
1295
  */
1249
1296
  getParams() {
1250
- const imageParams = this.imageFileList.map(file => {
1251
- const { url, type, ...rest } = file
1252
- return rest
1253
- })
1254
- const videoParams = this.videoFileList.map(file => {
1255
- const { url, type, ...rest } = file
1256
- return rest
1257
- })
1297
+ const imageParams = this.imageFileList.map((file) => {
1298
+ const { url, type, ...rest } = file;
1299
+ return rest;
1300
+ });
1301
+ const videoParams = this.videoFileList.map((file) => {
1302
+ const { url, type, ...rest } = file;
1303
+ return rest;
1304
+ });
1258
1305
  if (this.uploadType === 'both') {
1259
1306
  return {
1260
1307
  images: JSON.stringify(imageParams),
1261
1308
  videos: JSON.stringify(videoParams)
1262
- }
1309
+ };
1263
1310
  } else if (this.uploadType === 'image') {
1264
1311
  return {
1265
1312
  images: JSON.stringify(imageParams)
1266
- }
1313
+ };
1267
1314
  } else if (this.uploadType === 'video') {
1268
1315
  return {
1269
1316
  videos: JSON.stringify(videoParams)
1270
- }
1317
+ };
1271
1318
  }
1272
1319
  }
1273
1320
  }
1274
- }
1321
+ };
1275
1322
  </script>
1276
1323
 
1277
1324
  <style lang="scss">