xydata-tools 1.0.45 → 1.0.47

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.
@@ -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({
@@ -73,8 +73,8 @@
73
73
  .file-actions {
74
74
  margin-left: 16rpx;
75
75
  position: relative;
76
- width: 44rpx;
77
- height: 44rpx;
76
+ width: 40rpx;
77
+ height: 40rpx;
78
78
  border-radius: 50%;
79
79
  background-color: rgba(0, 0, 0, 0.6);
80
80
 
@@ -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>
@@ -57,15 +56,15 @@
57
56
  <shmily-drag-media v-if="enableImage && !enableVideo && showFileList" v-model="imageUrls"
58
57
  :number="imageLimit" :cols="cols" :imageWidth="itemWidth" :padding="10"
59
58
  :borderRadius="itemBorderRadius" :align="align" :draggable="draggable" :enableAddButton="true"
60
- moveType="image" @update:modelValue="handleImageDragChange" @add-click="selectAndUploadImage">
61
- <template #add-button>
59
+ moveType="image" @update:modelValue="handleImageDragChange">
60
+ <template #add-button="{ childWidth }">
62
61
  <slot name="image-upload-button" :select="selectAndUploadImage" :limit="imageLimit"
63
62
  :count="imageFileList.length">
64
- <view class="drag-upload-btn">
63
+ <view class="drag-upload-btn" @click="selectAndUploadImage">
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>
@@ -75,16 +74,15 @@
75
74
  <shmily-drag-media v-else-if="enableVideo && !enableImage && showFileList" v-model="videoUrls"
76
75
  :number="videoLimit" :cols="cols" :imageWidth="itemWidth" :padding="10"
77
76
  :borderRadius="itemBorderRadius" :align="align" :draggable="draggable" :enableAddButton="true"
78
- moveType="video" @update:modelValue="handleVideoDragChange" @add-click="selectAndUploadVideo"
79
- @videoClick="handleVideoItemClick">
80
- <template #add-button>
77
+ moveType="video" @update:modelValue="handleVideoDragChange" @videoClick="handleVideoItemClick">
78
+ <template #add-button="{ childWidth }">
81
79
  <slot name="video-upload-button" :select="selectAndUploadVideo" :limit="videoLimit"
82
80
  :count="videoFileList.length">
83
- <view class="drag-upload-btn">
81
+ <view class="drag-upload-btn" @click="selectAndUploadVideo">
84
82
  <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>
83
+ :style="getUploadIconStyle(childWidth)"></image>
84
+ <view class="upload-text" :style="getUploadTextStyle(childWidth)">{{ videoButtonTitle }}
85
+ </view>
88
86
  </view>
89
87
  </slot>
90
88
  </template>
@@ -198,7 +196,7 @@ export default {
198
196
  /** 上传类型: 'image' - 仅图片, 'video' - 仅视频, 'both' - 图片+视频(必填) */
199
197
  uploadType: {
200
198
  type: String,
201
- required: true,
199
+ default: 'image',
202
200
  validator: (value) => ['image', 'video', 'both'].includes(value)
203
201
  },
204
202
  /** 上传地址 */
@@ -335,6 +333,7 @@ export default {
335
333
  popupAnimated: false,
336
334
  mediaLoadingStatus: {},
337
335
  fullscreenVideoSrc: '', // 专门用于全屏播放的视频源
336
+ windowWidth: uni.getSystemInfoSync ? uni.getSystemInfoSync().windowWidth : 375,
338
337
  }
339
338
  },
340
339
  computed: {
@@ -457,7 +456,7 @@ export default {
457
456
  },
458
457
 
459
458
  // ==================== 弹窗模式网格样式 ====================
460
- popupGridStyle() {
459
+ popupGridStyle() {
461
460
  return {
462
461
  gridTemplateColumns: `repeat(${this.cols}, 1fr)`,
463
462
  gap: '10rpx'
@@ -493,6 +492,48 @@ export default {
493
492
  hasEventListener(eventName) {
494
493
  return !!(this.$listeners && this.$listeners[eventName])
495
494
  },
495
+ parseChildWidth(childWidth) {
496
+ if (typeof childWidth === 'number') {
497
+ return childWidth
498
+ }
499
+ if (typeof childWidth === 'string') {
500
+ const parsed = parseFloat(childWidth)
501
+ return Number.isFinite(parsed) ? parsed : null
502
+ }
503
+ return null
504
+ },
505
+ pxToRpx(px) {
506
+ if (!px || !this.windowWidth) {
507
+ return 0
508
+ }
509
+ return px / this.windowWidth * 750
510
+ },
511
+ computeUploadIconSize(childWidth) {
512
+ const parsedPx = this.parseChildWidth(childWidth)
513
+ if (!parsedPx) {
514
+ return this.uploadIconSize
515
+ }
516
+ const widthRpx = this.pxToRpx(parsedPx)
517
+ const size = Math.floor(widthRpx * 0.3)
518
+ return Math.max(20, size)
519
+ },
520
+ computeUploadTextSize(childWidth) {
521
+ const iconSize = this.computeUploadIconSize(childWidth)
522
+ const size = Math.floor(iconSize * 0.4)
523
+ return Math.max(20, size)
524
+ },
525
+ getUploadIconStyle(childWidth) {
526
+ const size = this.computeUploadIconSize(childWidth)
527
+ return {
528
+ width: size + 'rpx',
529
+ height: size + 'rpx'
530
+ }
531
+ },
532
+ getUploadTextStyle(childWidth) {
533
+ return {
534
+ fontSize: this.computeUploadTextSize(childWidth) + 'rpx'
535
+ }
536
+ },
496
537
 
497
538
  // ==================== 图片相关方法 ====================
498
539
  /**
@@ -3,10 +3,10 @@
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
- @mouseup="touchend($event, item)" :style="{
9
+ @click="handleClick(item)" @mouseup="touchend($event, item)" :style="{
10
10
  width: viewWidth + 'px',
11
11
  height: viewWidth + 'px',
12
12
  'z-index': item.zIndex,
@@ -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">
@@ -33,16 +38,10 @@
33
38
  </movable-view>
34
39
  <!-- 添加按钮或自定义插槽 -->
35
40
  <view class="add" v-if="imageList.length < number && showAddButton"
36
- :style="{ top: add.y, left: add.x, width: viewWidth + 'px', height: viewWidth + 'px' }"
37
- @click="addImages">
41
+ :style="{ top: add.y, left: add.x, width: viewWidth + 'px', height: viewWidth + 'px' }">
38
42
  <view class="add-wrap"
39
43
  :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>
44
+ <slot name="add-button" :childWidth="childWidth" :borderRadius="borderRadius"></slot>
46
45
  </view>
47
46
  </view>
48
47
  </movable-area>
@@ -129,18 +128,18 @@ export default {
129
128
  type: Boolean,
130
129
  default: false
131
130
  },
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
- },
131
+ // 对齐方式:'left' 从左往右排列,'right' 从右往左排列
132
+ align: {
133
+ type: String,
134
+ default: 'left',
135
+ validator: (value) => ['left', 'right'].includes(value)
136
+ },
137
+ // 是否可以拖拽排序
138
+ draggable: {
139
+ type: Boolean,
140
+ default: true
141
+ },
142
+ },
144
143
  data() {
145
144
  return {
146
145
  imageList: [],
@@ -274,7 +273,7 @@ export default {
274
273
  */
275
274
  calculatePosition(index, total) {
276
275
  const absY = Math.floor(index / this.colsValue)
277
-
276
+
278
277
  if (this.align === 'right') {
279
278
  // 靠右对齐:计算当前行的元素数量(包括按钮),然后从右往左排列
280
279
  const currentRowItemCount = Math.min(total - absY * this.colsValue, this.colsValue)
@@ -306,7 +305,7 @@ export default {
306
305
  const theoreticalWidth = this.viewWidth * this.colsValue
307
306
  this.rightAlignOffset = Math.max(0, this.containerWidth - theoreticalWidth)
308
307
  },
309
-
308
+
310
309
  /**
311
310
  * 获取总数(如果显示添加按钮,则包括按钮)
312
311
  */
@@ -317,7 +316,7 @@ export default {
317
316
  }
318
317
  return this.imageList.length
319
318
  },
320
-
319
+
321
320
  getSrc(item) {
322
321
  if (this.keyName !== null) {
323
322
  return item[this.keyName]
@@ -356,6 +355,8 @@ export default {
356
355
  this.$nextTick(() => {
357
356
  obj.x = this.getAlignedX(obj.absX)
358
357
  obj.y = obj.absY * this.viewWidth
358
+ obj.oldX = obj.x
359
+ obj.oldY = obj.y
359
360
  })
360
361
  }, 0)
361
362
  }
@@ -370,6 +371,8 @@ export default {
370
371
  this.$nextTick(() => {
371
372
  item.x = this.getAlignedX(item.absX)
372
373
  item.y = item.absY * this.viewWidth
374
+ item.oldX = item.x
375
+ item.oldY = item.y
373
376
  })
374
377
  }, 0)
375
378
  }
@@ -390,6 +393,8 @@ export default {
390
393
  this.$nextTick(() => {
391
394
  obj.x = this.getAlignedX(obj.absX)
392
395
  obj.y = obj.absY * this.viewWidth
396
+ obj.oldX = obj.x
397
+ obj.oldY = obj.y
393
398
  })
394
399
  }, 0)
395
400
  },
@@ -398,7 +403,7 @@ export default {
398
403
  if (!this.draggable) {
399
404
  return
400
405
  }
401
-
406
+
402
407
  this.imageList.forEach(v => {
403
408
  v.zIndex = v.index + 9
404
409
  })
@@ -412,6 +417,16 @@ export default {
412
417
  this.timer = null
413
418
  }, 200)
414
419
  },
420
+ handleClick(item) {
421
+ // 设为disable后无法触发touch事件
422
+ if (!this.draggable) {
423
+ if (this.moveType === 'video') {
424
+ this.playVideo(item)
425
+ } else {
426
+ this.previewImage(item)
427
+ }
428
+ }
429
+ },
415
430
  touchend(event, item) {
416
431
  const eventType = event.type // 'touchend' 或 'mouseup'
417
432
 
@@ -420,7 +435,7 @@ export default {
420
435
  this.lastTouchEndTime = Date.now()
421
436
  } else if (eventType === 'mouseup') {
422
437
  // 如果 100ms 内已经处理过 touchend,则忽略此 mouseup(避免重复)
423
- if (this.lastTouchEndTime && Date.now() - this.lastTouchEndTime < 100) {
438
+ if (this.lastTouchEndTime && Date.now() - this.lastTouchEndTime < 100) {
424
439
  if (this.timer) {
425
440
  clearTimeout(this.timer)
426
441
  this.timer = null
@@ -454,6 +469,8 @@ export default {
454
469
  this.$nextTick(() => {
455
470
  item.x = this.getAlignedX(item.absX)
456
471
  item.y = item.absY * this.viewWidth
472
+ item.oldX = item.x
473
+ item.oldY = item.y
457
474
  this.tempItem = null
458
475
  this.changeStatus = true
459
476
  })
@@ -512,6 +529,8 @@ export default {
512
529
  this.$nextTick(() => {
513
530
  v.x = this.getAlignedX(v.absX)
514
531
  v.y = v.absY * this.viewWidth
532
+ v.oldX = v.x
533
+ v.oldY = v.y
515
534
  this.tempItem = null
516
535
  })
517
536
  }
@@ -520,30 +539,6 @@ export default {
520
539
  }
521
540
  //#endif
522
541
  },
523
- addImages() {
524
- // 如果显示添加按钮,触发自定义事件让父组件处理
525
- if (this.showAddButton) {
526
- this.$emit('add-click')
527
- return
528
- }
529
-
530
- if (typeof this.addImage === 'function') {
531
- this.addImage.bind(this.$parent)()
532
- } else {
533
- let checkNumber = this.number - this.imageList.length
534
- uni.chooseImage({
535
- count: checkNumber,
536
- sourceType: ['album', 'camera'],
537
- success: res => {
538
- let count = checkNumber <= res.tempFilePaths.length ? checkNumber : res.tempFilePaths.length
539
- for (let i = 0; i < count; i++) {
540
- this.addProperties(res.tempFilePaths[i])
541
- }
542
- this.sortList()
543
- }
544
- })
545
- }
546
- },
547
542
  delImages(item, index) {
548
543
  if (typeof this.delImage === 'function') {
549
544
  this.delImage.bind(this.$parent)(() => {
@@ -621,7 +616,7 @@ export default {
621
616
  },
622
617
  addProperties(item) {
623
618
  const newIndex = this.imageList.length
624
-
619
+
625
620
  // 先添加图片到列表
626
621
  this.imageList.push({
627
622
  src: item,
@@ -638,9 +633,10 @@ export default {
638
633
  id: this.guid(16),
639
634
  disable: false,
640
635
  offset: 0,
641
- moveEnd: false
636
+ moveEnd: false,
637
+ loading: true
642
638
  })
643
-
639
+
644
640
  // 如果是右对齐且显示按钮,添加新图片后需要重新计算所有图片位置
645
641
  if (this.align === 'right' && this.showAddButton) {
646
642
  this.recalculateAllPositions()
@@ -656,10 +652,10 @@ export default {
656
652
  obj.oldX = obj.x
657
653
  obj.oldY = obj.y
658
654
  }
659
-
655
+
660
656
  this.updateAddButtonPosition()
661
657
  },
662
-
658
+
663
659
  /**
664
660
  * 重新计算所有图片的位置(用于右对齐时添加/删除图片)
665
661
  */
@@ -669,7 +665,7 @@ export default {
669
665
  const pos = this.calculatePosition(i, totalWithButton)
670
666
  const newX = this.getAlignedX(pos.absX)
671
667
  const newY = pos.absY * this.viewWidth
672
-
668
+
673
669
  // 如果位置有变化,进行动画过渡
674
670
  if (obj.absX !== pos.absX || obj.absY !== pos.absY) {
675
671
  obj.x = obj.oldX
@@ -711,7 +707,7 @@ export default {
711
707
  return
712
708
  }
713
709
  const length = this.imageList.length
714
-
710
+
715
711
  if (this.align === 'right' && this.showAddButton) {
716
712
  // 右对齐且显示按钮:按钮的位置就是"虚拟的第 length 个元素"的位置
717
713
  // 总数包括按钮本身
@@ -726,6 +722,14 @@ export default {
726
722
  this.add.x = this.getAlignedX(absX) + 'px'
727
723
  this.add.y = absY * this.viewWidth + 'px'
728
724
  }
725
+ },
726
+ handleMediaLoad(item) {
727
+ if (!item) return
728
+ item.loading = false
729
+ },
730
+ handleMediaError(item) {
731
+ if (!item) return
732
+ item.loading = false
729
733
  }
730
734
  }
731
735
  }
@@ -774,6 +778,29 @@ export default {
774
778
  }
775
779
  }
776
780
 
781
+ .media-loading-overlay {
782
+ position: absolute;
783
+ top: 0;
784
+ left: 0;
785
+ right: 0;
786
+ bottom: 0;
787
+ display: flex;
788
+ align-items: center;
789
+ justify-content: center;
790
+ background-color: rgba(0, 0, 0, 0.05);
791
+ border-radius: inherit;
792
+ z-index: 5;
793
+ }
794
+
795
+ .loading-spinner {
796
+ width: 40rpx;
797
+ height: 40rpx;
798
+ border: 4rpx solid rgba(0, 0, 0, 0.1);
799
+ border-top-color: #007aff;
800
+ border-radius: 50%;
801
+ animation: spinner-rotate 0.8s linear infinite;
802
+ }
803
+
777
804
  .del-con {
778
805
  position: absolute;
779
806
  top: 0rpx;
@@ -813,4 +840,14 @@ export default {
813
840
  }
814
841
  }
815
842
  }
843
+
844
+ @keyframes spinner-rotate {
845
+ 0% {
846
+ transform: rotate(0deg);
847
+ }
848
+
849
+ 100% {
850
+ transform: rotate(360deg);
851
+ }
852
+ }
816
853
  </style>