xydata-tools 1.0.45 → 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.
@@ -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>
@@ -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: {
@@ -457,7 +457,7 @@ export default {
457
457
  },
458
458
 
459
459
  // ==================== 弹窗模式网格样式 ====================
460
- popupGridStyle() {
460
+ popupGridStyle() {
461
461
  return {
462
462
  gridTemplateColumns: `repeat(${this.cols}, 1fr)`,
463
463
  gap: '10rpx'
@@ -493,6 +493,48 @@ export default {
493
493
  hasEventListener(eventName) {
494
494
  return !!(this.$listeners && this.$listeners[eventName])
495
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
+ },
496
538
 
497
539
  // ==================== 图片相关方法 ====================
498
540
  /**
@@ -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,18 +129,18 @@ 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: [],
@@ -274,7 +274,7 @@ export default {
274
274
  */
275
275
  calculatePosition(index, total) {
276
276
  const absY = Math.floor(index / this.colsValue)
277
-
277
+
278
278
  if (this.align === 'right') {
279
279
  // 靠右对齐:计算当前行的元素数量(包括按钮),然后从右往左排列
280
280
  const currentRowItemCount = Math.min(total - absY * this.colsValue, this.colsValue)
@@ -306,7 +306,7 @@ export default {
306
306
  const theoreticalWidth = this.viewWidth * this.colsValue
307
307
  this.rightAlignOffset = Math.max(0, this.containerWidth - theoreticalWidth)
308
308
  },
309
-
309
+
310
310
  /**
311
311
  * 获取总数(如果显示添加按钮,则包括按钮)
312
312
  */
@@ -317,7 +317,7 @@ export default {
317
317
  }
318
318
  return this.imageList.length
319
319
  },
320
-
320
+
321
321
  getSrc(item) {
322
322
  if (this.keyName !== null) {
323
323
  return item[this.keyName]
@@ -356,6 +356,8 @@ export default {
356
356
  this.$nextTick(() => {
357
357
  obj.x = this.getAlignedX(obj.absX)
358
358
  obj.y = obj.absY * this.viewWidth
359
+ obj.oldX = obj.x
360
+ obj.oldY = obj.y
359
361
  })
360
362
  }, 0)
361
363
  }
@@ -370,6 +372,8 @@ export default {
370
372
  this.$nextTick(() => {
371
373
  item.x = this.getAlignedX(item.absX)
372
374
  item.y = item.absY * this.viewWidth
375
+ item.oldX = item.x
376
+ item.oldY = item.y
373
377
  })
374
378
  }, 0)
375
379
  }
@@ -390,6 +394,8 @@ export default {
390
394
  this.$nextTick(() => {
391
395
  obj.x = this.getAlignedX(obj.absX)
392
396
  obj.y = obj.absY * this.viewWidth
397
+ obj.oldX = obj.x
398
+ obj.oldY = obj.y
393
399
  })
394
400
  }, 0)
395
401
  },
@@ -398,7 +404,7 @@ export default {
398
404
  if (!this.draggable) {
399
405
  return
400
406
  }
401
-
407
+
402
408
  this.imageList.forEach(v => {
403
409
  v.zIndex = v.index + 9
404
410
  })
@@ -420,7 +426,7 @@ export default {
420
426
  this.lastTouchEndTime = Date.now()
421
427
  } else if (eventType === 'mouseup') {
422
428
  // 如果 100ms 内已经处理过 touchend,则忽略此 mouseup(避免重复)
423
- if (this.lastTouchEndTime && Date.now() - this.lastTouchEndTime < 100) {
429
+ if (this.lastTouchEndTime && Date.now() - this.lastTouchEndTime < 100) {
424
430
  if (this.timer) {
425
431
  clearTimeout(this.timer)
426
432
  this.timer = null
@@ -454,6 +460,8 @@ export default {
454
460
  this.$nextTick(() => {
455
461
  item.x = this.getAlignedX(item.absX)
456
462
  item.y = item.absY * this.viewWidth
463
+ item.oldX = item.x
464
+ item.oldY = item.y
457
465
  this.tempItem = null
458
466
  this.changeStatus = true
459
467
  })
@@ -512,6 +520,8 @@ export default {
512
520
  this.$nextTick(() => {
513
521
  v.x = this.getAlignedX(v.absX)
514
522
  v.y = v.absY * this.viewWidth
523
+ v.oldX = v.x
524
+ v.oldY = v.y
515
525
  this.tempItem = null
516
526
  })
517
527
  }
@@ -621,7 +631,7 @@ export default {
621
631
  },
622
632
  addProperties(item) {
623
633
  const newIndex = this.imageList.length
624
-
634
+
625
635
  // 先添加图片到列表
626
636
  this.imageList.push({
627
637
  src: item,
@@ -638,9 +648,10 @@ export default {
638
648
  id: this.guid(16),
639
649
  disable: false,
640
650
  offset: 0,
641
- moveEnd: false
651
+ moveEnd: false,
652
+ loading: true
642
653
  })
643
-
654
+
644
655
  // 如果是右对齐且显示按钮,添加新图片后需要重新计算所有图片位置
645
656
  if (this.align === 'right' && this.showAddButton) {
646
657
  this.recalculateAllPositions()
@@ -656,10 +667,10 @@ export default {
656
667
  obj.oldX = obj.x
657
668
  obj.oldY = obj.y
658
669
  }
659
-
670
+
660
671
  this.updateAddButtonPosition()
661
672
  },
662
-
673
+
663
674
  /**
664
675
  * 重新计算所有图片的位置(用于右对齐时添加/删除图片)
665
676
  */
@@ -669,7 +680,7 @@ export default {
669
680
  const pos = this.calculatePosition(i, totalWithButton)
670
681
  const newX = this.getAlignedX(pos.absX)
671
682
  const newY = pos.absY * this.viewWidth
672
-
683
+
673
684
  // 如果位置有变化,进行动画过渡
674
685
  if (obj.absX !== pos.absX || obj.absY !== pos.absY) {
675
686
  obj.x = obj.oldX
@@ -711,7 +722,7 @@ export default {
711
722
  return
712
723
  }
713
724
  const length = this.imageList.length
714
-
725
+
715
726
  if (this.align === 'right' && this.showAddButton) {
716
727
  // 右对齐且显示按钮:按钮的位置就是"虚拟的第 length 个元素"的位置
717
728
  // 总数包括按钮本身
@@ -726,6 +737,14 @@ export default {
726
737
  this.add.x = this.getAlignedX(absX) + 'px'
727
738
  this.add.y = absY * this.viewWidth + 'px'
728
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
729
748
  }
730
749
  }
731
750
  }
@@ -774,6 +793,29 @@ export default {
774
793
  }
775
794
  }
776
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
+
777
819
  .del-con {
778
820
  position: absolute;
779
821
  top: 0rpx;
@@ -813,4 +855,14 @@ export default {
813
855
  }
814
856
  }
815
857
  }
858
+
859
+ @keyframes spinner-rotate {
860
+ 0% {
861
+ transform: rotate(0deg);
862
+ }
863
+
864
+ 100% {
865
+ transform: rotate(360deg);
866
+ }
867
+ }
816
868
  </style>