uview-plus 3.4.4 → 3.4.6

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.
package/changelog.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## 3.4.6(2025-03-25)
2
+ feat: checkbox触发change时携带name参数
3
+
4
+ feat: upload组件支持服务器本机和阿里云OSS自动上传功能及上传进度条
5
+
6
+ feat: upload组件支持视频预览及oss上传时获取视频封面图
7
+
8
+ feat: 新增up-action-sheet-data快捷组件
9
+
10
+ feat: 新增up-picker-data快捷组件
11
+
12
+ ## 3.4.5(2025-03-24)
13
+ feat: tag组件新增textSize/height/padding/borderRadius属性
14
+
15
+ feat: 新增genLightColor自动计算浅色方法及tag组件支持autoBgColor自动计算背景色
16
+
1
17
  ## 3.4.4(2025-03-13)
2
18
  feat: modal增加异步操作进行中点击取消弹出提示特性防止操作被中断
3
19
 
@@ -0,0 +1,101 @@
1
+ <template>
2
+ <view class="u-action-sheet-data">
3
+ <view class="u-action-sheet-data__trigger">
4
+ <slot name="trigger"></slot>
5
+ <up-input
6
+ v-if="!$slots['trigger']"
7
+ :modelValue="current"
8
+ disabled
9
+ disabledColor="#ffffff"
10
+ :placeholder="title"
11
+ border="none"
12
+ ></up-input>
13
+ <view @click="show = true"
14
+ class="u-action-sheet-data__trigger__cover"></view>
15
+ </view>
16
+ <up-action-sheet
17
+ :show="show"
18
+ :actions="options"
19
+ :title="title"
20
+ safeAreaInsetBottom
21
+ :description="description"
22
+ @close="show = false"
23
+ @select="select"
24
+ >
25
+ </up-action-sheet>
26
+ </view>
27
+ </template>
28
+
29
+ <script>
30
+ import {mapState} from 'vuex';
31
+ export default {
32
+ props: {
33
+ modelValue: {
34
+ type: [String, Number],
35
+ default: ''
36
+ },
37
+ title: {
38
+ type: String,
39
+ default: ''
40
+ },
41
+ description: {
42
+ type: String,
43
+ default: ''
44
+ },
45
+ options: {
46
+ type: Array,
47
+ default: () => {
48
+ return []
49
+ }
50
+ },
51
+ valueKey: {
52
+ type: String,
53
+ default: 'value'
54
+ },
55
+ labelKey: {
56
+ type: String,
57
+ default: 'name'
58
+ }
59
+ },
60
+ data() {
61
+ return {
62
+ show: false,
63
+ current: '',
64
+ }
65
+ },
66
+ created() {
67
+ if (this.modelValue) {
68
+ this.options.forEach((ele) => {
69
+ if (ele[this.valueKey] == this.modelValue) {
70
+ this.current = ele[this.labelKey]
71
+ }
72
+ })
73
+ }
74
+ },
75
+ emits: ['update:modelValue'],
76
+ methods: {
77
+ hideKeyboard() {
78
+ uni.hideKeyboard()
79
+ },
80
+ select(e) {
81
+ this.$emit('update:modelValue', e[this.valueKey])
82
+ this.current = e[this.labelKey]
83
+ },
84
+ }
85
+ }
86
+ </script>
87
+
88
+ <style lang="scss" scoped>
89
+ .u-action-sheet-data {
90
+ &__trigger {
91
+ position: relative;
92
+ &__cover {
93
+ position: absolute;
94
+ top: 0;
95
+ left: 0;
96
+ right: 0;
97
+ bottom: 0;
98
+ }
99
+ }
100
+ }
101
+ </style>
@@ -248,7 +248,9 @@
248
248
  }
249
249
  },
250
250
  emitEvent() {
251
- this.$emit('change', this.isChecked)
251
+ this.$emit('change', this.isChecked, {
252
+ name: this.name
253
+ })
252
254
  // 双向绑定
253
255
  if (this.usedAlone) {
254
256
  this.$emit('update:checked', this.isChecked)
@@ -0,0 +1,113 @@
1
+ <template>
2
+ <view class="u-picker-data">
3
+ <view class="u-picker-data__trigger">
4
+ <slot name="trigger"></slot>
5
+ <up-input
6
+ v-if="!$slots['trigger']"
7
+ :modelValue="current"
8
+ disabled
9
+ disabledColor="#ffffff"
10
+ :placeholder="title"
11
+ border="none"
12
+ ></up-input>
13
+ <view @click="show = true"
14
+ class="u-picker-data__trigger__cover"></view>
15
+ </view>
16
+ <up-picker
17
+ :show="show"
18
+ :columns="optionsInner"
19
+ :keyName="labelKey"
20
+ @confirm="select"
21
+ @cancel="cancel">
22
+ </up-picker>
23
+ </view>
24
+ </template>
25
+
26
+ <script>
27
+ import {mapState} from 'vuex';
28
+ export default {
29
+ props: {
30
+ modelValue: {
31
+ type: [String, Number],
32
+ default: ''
33
+ },
34
+ title: {
35
+ type: String,
36
+ default: ''
37
+ },
38
+ description: {
39
+ type: String,
40
+ default: ''
41
+ },
42
+ options: {
43
+ type: Array,
44
+ default: () => {
45
+ return []
46
+ }
47
+ },
48
+ valueKey: {
49
+ type: String,
50
+ default: 'id'
51
+ },
52
+ labelKey: {
53
+ type: String,
54
+ default: 'name'
55
+ }
56
+ },
57
+ data() {
58
+ return {
59
+ show: false,
60
+ current: '',
61
+ }
62
+ },
63
+ created() {
64
+ if (this.modelValue) {
65
+ this.options.forEach((ele) => {
66
+ if (ele[this.valueKey] == this.modelValue) {
67
+ this.current = ele[this.labelKey]
68
+ }
69
+ })
70
+ }
71
+ },
72
+ computed: {
73
+ optionsInner() {
74
+ return [this.options];
75
+ }
76
+ },
77
+ emits: ['update:modelValue'],
78
+ methods: {
79
+ hideKeyboard() {
80
+ uni.hideKeyboard()
81
+ },
82
+ cancel() {
83
+ this.show = false;
84
+ },
85
+ select(e) {
86
+ const {
87
+ columnIndex,
88
+ index,
89
+ value,
90
+ } = e;
91
+ this.show = false;
92
+ // console.log(value);
93
+ this.$emit('update:modelValue', value[0][this.valueKey]);
94
+ this.current = value[0][this.labelKey];
95
+ },
96
+ }
97
+ }
98
+ </script>
99
+
100
+ <style lang="scss" scoped>
101
+ .u-picker-data {
102
+ &__trigger {
103
+ position: relative;
104
+ &__cover {
105
+ position: absolute;
106
+ top: 0;
107
+ left: 0;
108
+ right: 0;
109
+ bottom: 0;
110
+ }
111
+ }
112
+ }
113
+ </style>
@@ -1,5 +1,11 @@
1
1
  <template>
2
- <view class="u-popup" :class="[customClass]">
2
+ <view class="u-popup" :class="[customClass]">
3
+ <view class="u-popup__trigger">
4
+ <slot name="trigger">
5
+ </slot>
6
+ <view @click="open"
7
+ class="u-popup__trigger__cover"></view>
8
+ </view>
3
9
  <u-overlay
4
10
  :show="show"
5
11
  @click="overlayClick"
@@ -188,6 +194,9 @@
188
194
  this.$emit('close')
189
195
  }
190
196
  },
197
+ open(e) {
198
+ this.$emit('update:show', true)
199
+ },
191
200
  close(e) {
192
201
  this.$emit('update:show', false)
193
202
  this.$emit('close')
@@ -240,6 +249,17 @@
240
249
 
241
250
  .u-popup {
242
251
  flex: $u-popup-flex;
252
+
253
+ &__trigger {
254
+ position: relative;
255
+ &__cover {
256
+ position: absolute;
257
+ top: 0;
258
+ left: 0;
259
+ right: 0;
260
+ bottom: 0;
261
+ }
262
+ }
243
263
 
244
264
  &__content {
245
265
  background-color: $u-popup-content-background-color;
@@ -82,9 +82,30 @@ export const props = defineMixin({
82
82
  type: String,
83
83
  default: () => defProps.tag.icon,
84
84
  },
85
- iconColor: {
85
+ // 自定义尺寸字体大小
86
+ textSize: {
86
87
  type: String,
87
- default: () => defProps.tag.iconColor
88
- }
88
+ default: () => defProps.tag.textSize
89
+ },
90
+ // 自定义尺寸高度
91
+ height: {
92
+ type: String,
93
+ default: () => defProps.tag.height
94
+ },
95
+ // 自定义尺寸padding
96
+ padding: {
97
+ type: String,
98
+ default: () => defProps.tag.padding
99
+ },
100
+ // 自定义尺寸
101
+ borderRadius: {
102
+ type: String,
103
+ default: () => defProps.tag.borderRadius
104
+ },
105
+ // 自动计算背景色
106
+ autoBgColor: {
107
+ type: Number,
108
+ default: () => defProps.tag.autoBgColor
109
+ },
89
110
  }
90
111
  })
@@ -25,6 +25,11 @@ export default {
25
25
  closable: false,
26
26
  show: true,
27
27
  icon: '',
28
- iconColor: ''
28
+ iconColor: '',
29
+ textSize: '',
30
+ height: '',
31
+ padding: '',
32
+ borderRadius: '',
33
+ autoBgColor: 0
29
34
  }
30
35
  }
@@ -64,6 +64,7 @@
64
64
  import { mpMixin } from '../../libs/mixin/mpMixin';
65
65
  import { mixin } from '../../libs/mixin/mixin';
66
66
  import test from '../../libs/function/test';
67
+ import { addUnit, genLightColor } from '../../libs/function/index';
67
68
  /**
68
69
  * Tag 标签
69
70
  * @description tag组件一般用于标记和选择,我们提供了更加丰富的表现形式,能够较全面的涵盖您的使用场景
@@ -107,6 +108,19 @@
107
108
  if(this.borderColor) {
108
109
  style.borderColor = this.borderColor
109
110
  }
111
+ if (this.height) {
112
+ style.height = addUnit(this.height)
113
+ style.lineHeight = addUnit(this.height)
114
+ }
115
+ if (this.padding) {
116
+ style.padding = this.padding
117
+ }
118
+ if (this.borderRadius) {
119
+ style.borderRadius = addUnit(this.borderRadius)
120
+ }
121
+ if (this.autoBgColor > 0 && this.color) {
122
+ style.backgroundColor = this.getBagColor(this.color)
123
+ }
110
124
  return style
111
125
  },
112
126
  // nvue下,文本颜色无法继承父元素
@@ -115,6 +129,9 @@
115
129
  if (this.color) {
116
130
  style.color = this.color
117
131
  }
132
+ if (this.textSize) {
133
+ style.textSize = addUnit(this.textSize)
134
+ }
118
135
  return style
119
136
  },
120
137
  imgStyle() {
@@ -149,6 +166,10 @@
149
166
  // 点击标签
150
167
  clickHandler() {
151
168
  this.$emit('click', this.name)
169
+ },
170
+ // 根据颜色计算浅色作为背景
171
+ getBagColor(darkColor) {
172
+ return genLightColor(darkColor, this.autoBgColor)
152
173
  }
153
174
  }
154
175
  }
@@ -125,6 +125,43 @@ export const props = defineMixin({
125
125
  previewImage: {
126
126
  type: Boolean,
127
127
  default: () => defProps.upload.previewImage
128
- }
128
+ },
129
+ // 是否自动删除
130
+ autoDelete: {
131
+ type: Boolean,
132
+ default: () => defProps.upload.autoDelete
133
+ },
134
+ // 是否自动上传需要传递action指定地址
135
+ autoUpload: {
136
+ type: Boolean,
137
+ default: () => defProps.upload.autoUpload
138
+ },
139
+ // 自动上传接口地址
140
+ autoUploadApi: {
141
+ type: String,
142
+ default: () => defProps.upload.autoUploadApi
143
+ },
144
+ // 自动上传驱动,local/oss/cos/kodo
145
+ autoUploadDriver: {
146
+ type: String,
147
+ default: () => defProps.upload.autoUploadDriver
148
+ },
149
+ // 自动上传授权接口,比如oss的签名接口。
150
+ autoUploadAuthUrl: {
151
+ type: String,
152
+ default: () => defProps.upload.autoUploadAuthUrl
153
+ },
154
+ // 自动上传携带的header
155
+ autoUploadHeader: {
156
+ type: Object,
157
+ default: () => {
158
+ return defProps.upload.autoUploadHeader
159
+ }
160
+ },
161
+ // 本地计算视频封面
162
+ getVideoThumb: {
163
+ type: Boolean,
164
+ default: () => defProps.upload.getVideoThumb
165
+ },
129
166
  }
130
167
  })
@@ -18,6 +18,27 @@
18
18
  height: addUnit(height)
19
19
  }]"
20
20
  />
21
+ <template
22
+ v-else-if="(item.isVideo || (item.type && item.type === 'video')) && getVideoThumb">
23
+ <image
24
+ :src="item.thumb"
25
+ :mode="imageMode"
26
+ class="u-upload__wrap__preview__image"
27
+ @tap="onPreviewVideo(item, index)"
28
+ :style="[{
29
+ width: addUnit(width),
30
+ height: addUnit(height)
31
+ }]"
32
+ />
33
+ <view v-if="item.status === 'success'"
34
+ class="u-upload__wrap__play"
35
+ @tap="onPreviewVideo(item, index)">
36
+ <slot name="playIcon"></slot>
37
+ <up-icon v-if="!$slots['playIcon']"
38
+ class="u-upload__wrap__play__icon"
39
+ name="play-right" size="22px"></up-icon>
40
+ </view>
41
+ </template>
21
42
  <view
22
43
  v-else
23
44
  class="u-upload__wrap__preview__other"
@@ -54,6 +75,8 @@
54
75
  v-if="item.message"
55
76
  class="u-upload__status__message"
56
77
  >{{ item.message }}</text>
78
+ <up-gap class="u-upload__progress" height="3px"
79
+ :style="{width: item.progress + '%'}"></up-gap>
57
80
  </view>
58
81
  <view
59
82
  class="u-upload__deletable"
@@ -68,30 +91,45 @@
68
91
  ></u-icon>
69
92
  </view>
70
93
  </view>
71
- <view
72
- class="u-upload__success"
73
- v-if="item.status === 'success'"
74
- >
75
- <!-- #ifdef APP-NVUE -->
76
- <image
77
- :src="successIcon"
78
- class="u-upload__success__icon"
79
- ></image>
80
- <!-- #endif -->
81
- <!-- #ifndef APP-NVUE -->
82
- <view class="u-upload__success__icon">
83
- <u-icon
84
- name="checkmark"
85
- color="#ffffff"
86
- size="12"
87
- ></u-icon>
94
+ <slot name="success">
95
+ <view
96
+ class="u-upload__success"
97
+ v-if="item.status === 'success'"
98
+ >
99
+ <!-- #ifdef APP-NVUE -->
100
+ <image
101
+ :src="successIcon"
102
+ class="u-upload__success__icon"
103
+ ></image>
104
+ <!-- #endif -->
105
+ <!-- #ifndef APP-NVUE -->
106
+ <view class="u-upload__success__icon">
107
+ <u-icon
108
+ name="checkmark"
109
+ color="#ffffff"
110
+ size="12"
111
+ ></u-icon>
112
+ </view>
113
+ <!-- #endif -->
88
114
  </view>
89
- <!-- #endif -->
90
- </view>
115
+ </slot>
91
116
  </view>
92
-
117
+ <up-popup
118
+ mode="center"
119
+ v-model:show="popupShow">
120
+ <video id="myVideo"
121
+ :src="currentItemIndex >= 0 ? lists[currentItemIndex].url : ''"
122
+ @error="videoErrorCallback" show-center-play-btn
123
+ object-fit='cover' show-fullscreen-btn='true'
124
+ enable-play-gesture controls
125
+ :autoplay="true" auto-pause-if-open-native
126
+ @loadedmetadata="loadedVideoMetadata"
127
+ :initial-time='0.1'>
128
+ </video>
129
+ </up-popup>
93
130
  </template>
94
-
131
+ <canvas id="myCanvas" type="2d"
132
+ style="width: 100px; height: 150px;display: none;"></canvas>
95
133
  <template v-if="isInCount">
96
134
  <view
97
135
  v-if="$slots.trigger"
@@ -185,6 +223,8 @@
185
223
  // #endif
186
224
  lists: [],
187
225
  isInCount: true,
226
+ popupShow: false,
227
+ currentItemIndex: -1
188
228
  }
189
229
  },
190
230
  watch: {
@@ -204,14 +244,74 @@
204
244
  },
205
245
  accept(newVal) {
206
246
  this.formatFileList()
247
+ },
248
+ popupShow(newVal) {
249
+ if (!newVal) {
250
+ this.currentItemIndex = -1;
251
+ }
207
252
  }
208
253
  },
209
254
  // #ifdef VUE3
210
- emits: ['error', 'beforeRead', 'oversize', 'afterRead', 'delete', 'clickPreview'],
255
+ emits: ['error', 'beforeRead', 'oversize', 'afterRead', 'delete', 'clickPreview', 'update:fileList'],
211
256
  // #endif
212
257
  methods: {
213
258
  addUnit,
214
259
  addStyle,
260
+ videoErrorCallback() {},
261
+ loadedVideoMetadata(e) {
262
+ if (this.currentItemIndex < 0) {
263
+ return;
264
+ }
265
+ if (this.autoUploadDriver != 'local') {
266
+ return;
267
+ }
268
+ if (!this.getVideoThumb) {
269
+ return;
270
+ }
271
+ // 截取第一帧作为封面,oss等云存储场景直接使用拼接参数。
272
+ let w = this.lists[this.currentItemIndex].width;
273
+ let h = this.lists[this.currentItemIndex].height;
274
+ const dpr = uni.getSystemInfoSync().pixelRatio;
275
+ uni.createSelectorQuery().select('#myVideo').context(res => {
276
+ console.log('select video', res)
277
+ const myVideo = res.context
278
+ uni.createSelectorQuery()
279
+ .select('#myCanvas')
280
+ .fields({ node: true, size: true })
281
+ .exec(([res]) => {
282
+ console.log('select canvas', res)
283
+ const ctx1 = res[0].node.getContext('2d')
284
+ res[0].node.width = w * dpr
285
+ res[0].node.height = h * dpr
286
+ // Draw the first frame and export it as an image
287
+ // myVideo.onPlay(() => {
288
+ setTimeout(() => {
289
+ captureFirstFrame()
290
+ }, 500)
291
+ // })
292
+ const captureFirstFrame = () => {
293
+ ctx1.drawImage(myVideo, 0, 0, w * dpr, h * dpr)
294
+ wx.canvasToTempFilePath({
295
+ canvas: res[0].node,
296
+ success: (result) => {
297
+ console.log('First frame image path:', result
298
+ .tempFilePath)
299
+ // Now you can use the image path (result.tempFilePath)
300
+ this.fileList['currentItemIndex'].thumb = result.tempFilePath
301
+ },
302
+ fail: (err) => {
303
+ console.error('Failed to export image:', err)
304
+ }
305
+ })
306
+ }
307
+
308
+ // Capture the first frame
309
+ setInterval(() => {
310
+ ctx1.drawImage(myVideo, 0, 0, w * dpr, h * dpr);
311
+ }, 1000 / 24)
312
+ }).exec()
313
+ }).exec()
314
+ },
215
315
  formatFileList() {
216
316
  const {
217
317
  fileList = [], maxCount
@@ -304,7 +404,7 @@
304
404
  index: index == null ? this.fileList.length : index,
305
405
  };
306
406
  },
307
- onAfterRead(file) {
407
+ async onAfterRead(file) {
308
408
  const {
309
409
  maxSize,
310
410
  afterRead
@@ -313,25 +413,158 @@
313
413
  file.some((item) => item.size > maxSize) :
314
414
  file.size > maxSize;
315
415
  if (oversize) {
416
+ uni.showToast({
417
+ title: '超过大小限制'
418
+ })
316
419
  this.$emit('oversize', Object.assign({
317
420
  file
318
421
  }, this.getDetail()));
319
422
  return;
320
423
  }
321
- if (typeof afterRead === 'function') {
322
- afterRead(file, this.getDetail());
424
+ let len = this.fileList.length;
425
+ if (this.autoUpload) {
426
+ // 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
427
+ let lists = [].concat(file);
428
+ let fileListLen = this.fileList.length;
429
+ lists.map((item) => {
430
+ this.fileList.push({
431
+ ...item,
432
+ status: 'uploading',
433
+ message: '上传中',
434
+ progress: 0
435
+ });
436
+ });
437
+ let that = this;
438
+ this.$emit('update:fileList', this.fileList);
439
+ for (let i = 0; i < lists.length; i++) {
440
+ let j = i;
441
+ let result = '';
442
+ switch(this.autoUploadDriver) {
443
+ case 'cos': // 腾讯云
444
+ break;
445
+ case 'kodo': // 七牛云
446
+ break;
447
+ case 'oss':
448
+ case 'upload_oss':
449
+ // 阿里云前端直传
450
+ // 获取签名
451
+ console.log()
452
+ let formData = {};
453
+ let ret = await uni.request({
454
+ url: this.autoUploadAuthUrl,
455
+ method: 'get',
456
+ header: this.autoUploadHeader,
457
+ data: {
458
+ filename: lists[j].name
459
+ }
460
+ });
461
+ // console.log(ret);
462
+ let res0 = ret.data;
463
+ if (res0.code == 200) {
464
+ // 路径 + 文件名 + 扩展名
465
+ // 不传递filename就要拼接key
466
+ // res0.data.params.key = res0.data.params.dir + res0.data.params.uniqidName + fileExt;
467
+ formData = res0.data.params;
468
+ } else {
469
+ uni.showToast({
470
+ title: res0.msg,
471
+ duration: 1500
472
+ });
473
+ return;
474
+ }
475
+ var uploadTask = uni.uploadFile({
476
+ url: res0.data.params.host,
477
+ filePath: lists[j].url,
478
+ name: 'file',
479
+ // fileType: 'video', // 仅支付宝小程序,且必填。
480
+ // header: header,
481
+ formData: formData,
482
+ success: (uploadFileRes) => {
483
+ result = res0.data.params.host + '/' + res0.data.params.key;
484
+ let thumb = '';
485
+ if (this.accept === 'video' || test.video(result)) {
486
+ thumb = result + '?x-oss-process=video/snapshot,t_10000,m_fast';
487
+ }
488
+ that.succcessUpload(len + j, result, thumb);
489
+ }
490
+ });
491
+ uploadTask.onProgressUpdate((res) => {
492
+ that.updateUpload(len + j, {
493
+ progress: res.progress
494
+ });
495
+ // console.log('上传进度' + res.progress);
496
+ // console.log('已经上传的数据长度' + res.totalBytesSent);
497
+ // console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
498
+ });
499
+ break;
500
+ case 'local':
501
+ default:
502
+ // 服务器本机上传
503
+ var uploadTask = uni.uploadFile({
504
+ url: this.autoUploadApi,
505
+ filePath: lists[j].url,
506
+ name: 'file',
507
+ // fileType: 'video', // 仅支付宝小程序,且必填。
508
+ header: this.autoUploadHeader,
509
+ success: (r) => {
510
+ result = res0.data.params.host + '/' + res0.data.params.key;
511
+ that.succcessUpload(len + j, result);
512
+ }
513
+ });
514
+ uploadTask.onProgressUpdate((res) => {
515
+ that.updateUpload(len + j, {
516
+ progress: res.progress
517
+ });
518
+ // console.log('上传进度' + res.progress);
519
+ // console.log('已经上传的数据长度' + res.totalBytesSent);
520
+ // console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
521
+ });
522
+ break;
523
+ }
524
+ }
525
+ } else {
526
+ if (typeof afterRead === 'function') {
527
+ afterRead(file, this.getDetail());
528
+ }
529
+ this.$emit('afterRead', Object.assign({
530
+ file
531
+ }, this.getDetail()));
323
532
  }
324
- this.$emit('afterRead', Object.assign({
325
- file
326
- }, this.getDetail()));
533
+ },
534
+ updateUpload(index, param) {
535
+ let item = this.fileList[index];
536
+ this.fileList.splice(index, 1, {
537
+ ...item,
538
+ status: 'uploading',
539
+ message: '',
540
+ progress: param.progress
541
+ });
542
+ this.$emit('update:fileList', this.fileList);
543
+ },
544
+ succcessUpload(index, url, thumb = '') {
545
+ let item = this.fileList[index];
546
+ this.fileList.splice(index, 1, {
547
+ ...item,
548
+ status: 'success',
549
+ message: '',
550
+ url: url,
551
+ progress: 100,
552
+ thumb: thumb
553
+ });
554
+ this.$emit('update:fileList', this.fileList);
327
555
  },
328
556
  deleteItem(index) {
329
- this.$emit(
330
- 'delete',
331
- Object.assign(Object.assign({}, this.getDetail(index)), {
332
- file: this.fileList[index],
333
- })
334
- );
557
+ if (this.autoDelete) {
558
+ this.fileList.splice(index, 1);
559
+ this.$emit('update:fileList', this.fileList);
560
+ } else {
561
+ this.$emit(
562
+ 'delete',
563
+ Object.assign(Object.assign({}, this.getDetail(index)), {
564
+ file: this.fileList[index],
565
+ })
566
+ );
567
+ }
335
568
  },
336
569
  // 预览图片
337
570
  onPreviewImage(previewItem, index) {
@@ -360,7 +593,7 @@
360
593
  },
361
594
  });
362
595
  },
363
- onPreviewVideo(index) {
596
+ onPreviewVideo(previewItem, index) {
364
597
  if (!this.previewFullImage) return;
365
598
  let current = 0;
366
599
  const sources = [];
@@ -380,6 +613,11 @@
380
613
  if (sources.length < 1) {
381
614
  return;
382
615
  }
616
+ // #ifndef MP-WEIXIN
617
+ this.popupShow = true;
618
+ this.currentItemIndex = index;
619
+ console.log(this.lists[this.currentItemIndex])
620
+ // #endif
383
621
  // #ifdef MP-WEIXIN
384
622
  wx.previewMedia({
385
623
  sources: sources,
@@ -500,6 +738,21 @@
500
738
  }
501
739
  }
502
740
  }
741
+ &__wrap__play {
742
+ position: absolute;
743
+ top: 0px;
744
+ left: 0px;
745
+ bottom: 0px;
746
+ right: 0px;
747
+ display: flex;
748
+ justify-content: center;
749
+ align-items: center;
750
+ &__icon {
751
+ background: #fff;
752
+ border-radius: 100px;
753
+ opacity: 0.8;
754
+ };
755
+ }
503
756
 
504
757
  &__deletable {
505
758
  position: absolute;
@@ -557,6 +810,12 @@
557
810
  /* #endif */
558
811
  }
559
812
  }
813
+ &__progress {
814
+ background-color: $u-primary !important;
815
+ position: absolute;
816
+ bottom: 0;
817
+ left: 0;
818
+ }
560
819
 
561
820
  &__status {
562
821
  position: absolute;
@@ -32,6 +32,13 @@ export default {
32
32
  uploadText: '',
33
33
  width: 80,
34
34
  height: 80,
35
- previewImage: true
35
+ previewImage: true,
36
+ autoDelete: false,
37
+ autoUpload: false,
38
+ autoUploadApi: '',
39
+ autoUploadAuthUrl: '',
40
+ autoUploadDriver: '',
41
+ autoUploadHeader: {},
42
+ getVideoThumb: false
36
43
  }
37
44
  }
@@ -21,21 +21,30 @@ function formatImage(res) {
21
21
  // #ifdef H5
22
22
  name: item.name,
23
23
  file: item
24
+ // #endif
25
+ // #ifndef H5
26
+ name: res.tempFilePath.split('/').pop() + '.png',
24
27
  // #endif
25
28
  }))
26
29
  }
27
30
 
28
- function formatVideo(res) {
31
+ function formatVideo(res) {
32
+ console.log(res)
29
33
  return [
30
34
  {
31
35
  ...pickExclude(res, ['tempFilePath', 'thumbTempFilePath', 'errMsg']),
32
36
  type: 'video',
33
37
  url: res.tempFilePath,
34
38
  thumb: res.thumbTempFilePath,
35
- size: res.size,
39
+ size: res.size,
40
+ width: res.width || 0, // APP 2.1.0+、H5、微信小程序、京东小程序
41
+ height: res.height || 0, // APP 2.1.0+、H5、微信小程序、京东小程序
36
42
  // #ifdef H5
37
43
  name: res.name,
38
44
  file: res
45
+ // #endif
46
+ // #ifndef H5
47
+ name: res.tempFilePath.split('/').pop() + '.mp4',
39
48
  // #endif
40
49
  }
41
50
  ]
@@ -50,6 +59,9 @@ function formatMedia(res) {
50
59
  size: item.size,
51
60
  // #ifdef H5
52
61
  file: item
62
+ // #endif
63
+ // #ifndef H5
64
+ name: res.tempFilePath.split('/').pop() + (res.type === 'video' ? '.mp4': '.png'),
53
65
  // #endif
54
66
  }))
55
67
  }
@@ -49,12 +49,12 @@ export function sleep(value = 30) {
49
49
  * @returns {string} 返回所在平台(小写)
50
50
  * @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台
51
51
  */
52
- export function os() {
53
- // #ifdef APP || H5 || MP-WEIXIN
54
- return uni.getDeviceInfo().platform.toLowerCase()
55
- // #endif
56
- // #ifndef APP || H5 || MP-WEIXIN
57
- return uni.getSystemInfoSync().platform.toLowerCase()
52
+ export function os() {
53
+ // #ifdef APP || H5 || MP-WEIXIN
54
+ return uni.getDeviceInfo().platform.toLowerCase()
55
+ // #endif
56
+ // #ifndef APP || H5 || MP-WEIXIN
57
+ return uni.getSystemInfoSync().platform.toLowerCase()
58
58
  // #endif
59
59
  }
60
60
  /**
@@ -63,26 +63,26 @@ export function os() {
63
63
  */
64
64
  export function sys() {
65
65
  return uni.getSystemInfoSync()
66
- }
67
- export function getWindowInfo() {
68
- let ret = {}
69
- // #ifdef APP || H5 || MP-WEIXIN
70
- ret = uni.getWindowInfo()
71
- // #endif
72
- // #ifndef APP || H5 || MP-WEIXIN
73
- ret = sys()
74
- // #endif
75
- return ret
76
- }
77
- export function getDeviceInfo() {
78
- let ret = {}
79
- // #ifdef APP || H5 || MP-WEIXIN
80
- ret = uni.getDeviceInfo()
81
- // #endif
82
- // #ifndef APP || H5 || MP-WEIXIN
83
- ret = sys()
84
- // #endif
85
- return ret
66
+ }
67
+ export function getWindowInfo() {
68
+ let ret = {}
69
+ // #ifdef APP || H5 || MP-WEIXIN
70
+ ret = uni.getWindowInfo()
71
+ // #endif
72
+ // #ifndef APP || H5 || MP-WEIXIN
73
+ ret = sys()
74
+ // #endif
75
+ return ret
76
+ }
77
+ export function getDeviceInfo() {
78
+ let ret = {}
79
+ // #ifdef APP || H5 || MP-WEIXIN
80
+ ret = uni.getDeviceInfo()
81
+ // #endif
82
+ // #ifndef APP || H5 || MP-WEIXIN
83
+ ret = sys()
84
+ // #endif
85
+ return ret
86
86
  }
87
87
 
88
88
  /**
@@ -730,12 +730,99 @@ export function getValueByPath(obj, path) {
730
730
  }, obj);
731
731
  }
732
732
 
733
+ /**
734
+ * 生成同色系浅色背景色
735
+ * @param {string} textColor - 支持 #RGB、#RRGGBB、rgb()、rgba() 格式
736
+ * @param {number} [lightness=85] - 目标亮度百分比(默认85%)
737
+ * @returns {string} 十六进制颜色值
738
+ */
739
+ export function genLightColor(textColor, lightness = 95) {
740
+ // 手动解析颜色值(避免使用document)
741
+ const rgb = parseColorWithoutDOM(textColor);
742
+
743
+ // RGB转HSL色域
744
+ const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
745
+
746
+ // 生成浅色背景
747
+ const bgHsl = {
748
+ h: hsl.h,
749
+ s: hsl.s,
750
+ l: Math.min(lightness, 95)
751
+ };
752
+
753
+ return hslToHex(bgHsl.h, bgHsl.s, bgHsl.l);
754
+ }
755
+
756
+ /* 手动解析颜色字符串(兼容uni-app环境) */
757
+ function parseColorWithoutDOM(colorStr) {
758
+ // 统一转小写处理
759
+ const str = colorStr.toLowerCase().trim();
760
+
761
+ // 处理十六进制格式
762
+ if (str.startsWith('#')) {
763
+ const hex = str.replace('#', '');
764
+ const fullHex = hex.length === 3 ?
765
+ hex.split('').map(c => c + c).join('') : hex;
766
+
767
+ return {
768
+ r: parseInt(fullHex.substring(0,2), 16),
769
+ g: parseInt(fullHex.substring(2,4), 16),
770
+ b: parseInt(fullHex.substring(4,6), 16)
771
+ };
772
+ }
773
+
774
+ // 处理rgb/rgba格式
775
+ const rgbMatch = str.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
776
+ if (rgbMatch) {
777
+ return {
778
+ r: +rgbMatch[1],
779
+ g: +rgbMatch[2],
780
+ b: +rgbMatch[3]
781
+ };
782
+ }
783
+
784
+ throw new Error('Invalid color format');
785
+ }
786
+
787
+ // 辅助函数:RGB 转 HSL(色相、饱和度、亮度)
788
+ function rgbToHsl(r, g, b) {
789
+ r /= 255, g /= 255, b /= 255;
790
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
791
+ let h, s, l = (max + min) / 2;
792
+
793
+ if (max === min) {
794
+ h = s = 0; // achromatic
795
+ } else {
796
+ const d = max - min;
797
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
798
+ switch (max) {
799
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
800
+ case g: h = (b - r) / d + 2; break;
801
+ case b: h = (r - g) / d + 4; break;
802
+ }
803
+ h = (h * 60).toFixed(1);
804
+ }
805
+ return { h: +h, s: +(s * 100).toFixed(1), l: +(l * 100).toFixed(1) };
806
+ }
807
+
808
+ // 辅助函数:HSL 转十六进制
809
+ function hslToHex(h, s, l) {
810
+ l /= 100;
811
+ const a = s * Math.min(l, 1 - l) / 100;
812
+ const f = n => {
813
+ const k = (n + h / 30) % 12;
814
+ const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
815
+ return Math.round(255 * color).toString(16).padStart(2, '0');
816
+ };
817
+ return `#${f(0)}${f(8)}${f(4)}`;
818
+ }
819
+
733
820
  export default {
734
821
  range,
735
822
  getPx,
736
823
  sleep,
737
824
  os,
738
- sys,
825
+ sys,
739
826
  getWindowInfo,
740
827
  random,
741
828
  guid,
@@ -762,5 +849,5 @@ export default {
762
849
  page,
763
850
  pages,
764
851
  getValueByPath,
765
- // setConfig
852
+ genLightColor
766
853
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "id": "uview-plus",
3
3
  "name": "uview-plus",
4
4
  "displayName": "零云®uview-plus3.0重磅发布,全面的Vue3鸿蒙移动组件库。",
5
- "version": "3.4.4",
5
+ "version": "3.4.6",
6
6
  "description": "零云®uview-plus已兼容vue3,全面的组件和便捷的工具会让您信手拈来,如鱼得水",
7
7
  "keywords": [
8
8
  "uview",