@10yun/cv-mobile-ui 0.5.57 → 0.5.58

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.
@@ -0,0 +1,186 @@
1
+ // 重构数据结构为双向映射,便于快速查找
2
+ const MIME_MAPPINGS = (() => {
3
+ const mappings = {
4
+ // 图片格式
5
+ svg: ['image/svg+xml', 'image/svg'],
6
+ jpg: ['image/jpeg', 'image/jpg'],
7
+ jpeg: ['image/jpeg'],
8
+ png: ['image/png'],
9
+ gif: ['image/gif'],
10
+ webp: ['image/webp'],
11
+ bmp: ['image/bmp', 'image/x-bmp', 'image/x-ms-bmp'],
12
+ ico: ['image/x-icon', 'image/vnd.microsoft.icon'],
13
+ tiff: ['image/tiff', 'image/tif', 'image/x-tiff'],
14
+ apng: ['image/apng'],
15
+ avif: ['image/avif'],
16
+ heic: ['image/heic'],
17
+
18
+ // 文档格式
19
+ pdf: ['application/pdf'],
20
+ txt: ['text/plain'],
21
+ csv: ['text/csv'],
22
+ json: ['application/json'],
23
+ xml: ['application/xml'],
24
+
25
+ // Office 文档
26
+ doc: ['application/msword'],
27
+ docx: ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
28
+ xls: ['application/vnd.ms-excel'],
29
+ xlsx: ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
30
+ ppt: ['application/vnd.ms-powerpoint'],
31
+ pptx: ['application/vnd.openxmlformats-officedocument.presentationml.presentation'],
32
+
33
+ // 压缩文件
34
+ zip: ['application/zip'],
35
+ rar: ['application/x-rar-compressed'],
36
+ '7z': ['application/x-7z-compressed'],
37
+
38
+ // 音频/视频
39
+ mp3: ['audio/mpeg'],
40
+ mp4: ['video/mp4'],
41
+ mov: ['video/quicktime'],
42
+ avi: ['video/x-msvideo'],
43
+ wav: ['audio/wav']
44
+ };
45
+
46
+ // 创建双向映射
47
+ const extensionToMime = {};
48
+ const mimeToExtension = {};
49
+
50
+ for (const [ext, mimes] of Object.entries(mappings)) {
51
+ extensionToMime[ext] = mimes[0]; // 每个扩展名映射到第一个MIME类型
52
+
53
+ for (const mime of mimes) {
54
+ mimeToExtension[mime] = ext;
55
+ }
56
+ }
57
+ return { extensionToMime, mimeToExtension };
58
+ })();
59
+ /**
60
+ * 根据MIME类型获取文件扩展名
61
+ * @param {string} type - MIME类型(如 'image/jpeg')
62
+ * @returns {string} 对应的文件扩展名(默认返回'jpg')
63
+ */
64
+ export function fileMimeToExtension(type) {
65
+ if (!type) return 'jpg';
66
+
67
+ const normalizedType = type.toLowerCase().trim();
68
+ return MIME_MAPPINGS.mimeToExtension[normalizedType] || 'jpg';
69
+ }
70
+ /**
71
+ * 根据文件扩展名获取MIME类型
72
+ * @param {string} ext - 文件扩展名(如 'jpg')
73
+ * @returns {string} 对应的MIME类型(默认返回空字符串)
74
+ */
75
+ export function fileExtensionToMime(ext) {
76
+ if (!ext) return '';
77
+ // 处理带点的情况(如 '.jpg')
78
+ const normalizedExt = ext.replace(/^\./, '').toLowerCase();
79
+ return MIME_MAPPINGS.extensionToMime[normalizedExt] || '';
80
+ }
81
+
82
+ export function generateUniqidName() {
83
+ // 获取当前日期时间,格式化为 YYYYMMDDHHmmss
84
+ const now = new Date();
85
+ const datePart = [
86
+ now.getFullYear(),
87
+ String(now.getMonth() + 1).padStart(2, '0'),
88
+ String(now.getDate()).padStart(2, '0'),
89
+ String(now.getHours()).padStart(2, '0'),
90
+ String(now.getMinutes()).padStart(2, '0'),
91
+ String(now.getSeconds()).padStart(2, '0')
92
+ ].join('');
93
+
94
+ // 生成唯一ID部分(类似PHP的uniqid)
95
+ const uniquePart = Math.random().toString(36).substring(2, 10);
96
+
97
+ return datePart + uniquePart;
98
+ }
99
+ export function checkIsObject(value) {
100
+ return Object.prototype.toString.call(value) === '[object Object]';
101
+ }
102
+ export function parseJsonSafe(str) {
103
+ if (typeof str !== 'string') return {};
104
+ try {
105
+ const obj = JSON.parse(str);
106
+ return typeof obj === 'object' && obj !== null ? obj : {};
107
+ } catch {
108
+ return {};
109
+ }
110
+ }
111
+
112
+ export function parseFormData(data) {
113
+ let formData = {};
114
+ // let formData = new FormData();
115
+ formData['success_action_status'] = data.success_action_status || '200';
116
+ // formData['OSSAccessKeyId']= data.accessid;
117
+ formData['policy'] = data.policy;
118
+ formData['signature'] = data.signature;
119
+ formData['x-oss-signature'] = data.signature;
120
+ formData['x-oss-signature-version'] = 'OSS4-HMAC-SHA256';
121
+ formData['x-oss-credential'] = data.x_oss_credential;
122
+ formData['x-oss-date'] = data.x_oss_date;
123
+ formData['x-oss-security-token'] = data.security_token;
124
+ let full_key = data.key;
125
+ if (!data.key) {
126
+ full_key = data.dir + '/' + file.name;
127
+ }
128
+ formData['key'] = full_key; // 文件名
129
+ if (data.callback) {
130
+ // 添加回调参数
131
+ formData['callback'] = data.callback;
132
+ }
133
+ if (data.callback_var && checkIsObject(data.callback_var)) {
134
+ // headers['x-oss-callback-var'] = data.callback_var;
135
+ // 在POST请求的Body中使用表单域来携带参数
136
+
137
+ for (const key in data.callback_var) {
138
+ if (data.callback_var.hasOwnProperty(key)) {
139
+ // 确保是对象自身的属性,而非继承的
140
+ formData[key] = data.callback_var[key];
141
+ }
142
+ }
143
+ // formData['callback-var', data.callback_var);
144
+ }
145
+ // formData['file']= file; // file 必须为最后一个表单域
146
+ return formData;
147
+ }
148
+ function getFileInfo(imgStr) {
149
+ let index = imgStr.lastIndexOf('/');
150
+ let strName = imgStr.substring(index + 1, imgStr.length);
151
+ let strType = '';
152
+ if (strName.lastIndexOf('.') > -1) {
153
+ strType = strName.slice(strName.lastIndexOf('.') + 1);
154
+ strType = 'image/' + strType;
155
+ }
156
+ return {
157
+ name: strName,
158
+ url: imgStr,
159
+ type: strType,
160
+ status: 'success'
161
+ };
162
+ }
163
+ export function content_to_array(valDef) {
164
+ let existList = [];
165
+ let value_type1 = Object.prototype.toString.call(valDef);
166
+ if (value_type1 === '[object String]') {
167
+ existList.push(getFileInfo(valDef));
168
+ } else if (value_type1 === '[object Object]') {
169
+ if (valDef.name && valDef.url) {
170
+ existList.push(valDef);
171
+ }
172
+ } else if (value_type1 === '[object Array]') {
173
+ for (let i in valDef) {
174
+ let itemDef = valDef[i];
175
+ let value_type2 = Object.prototype.toString.call(itemDef);
176
+ if (value_type2 === '[object String]') {
177
+ existList.push(getFileInfo(itemDef));
178
+ } else if (value_type2 === '[object Object]') {
179
+ if (itemDef.name && itemDef.url) {
180
+ existList.push(itemDef);
181
+ }
182
+ }
183
+ }
184
+ }
185
+ return existList;
186
+ }
@@ -0,0 +1,176 @@
1
+ import { defineMixin } from '../../libs/vue';
2
+ import defProps from '../../libs/config/props.js';
3
+ export const props = defineMixin({
4
+ props: {
5
+ // 接受的文件类型, 可选值为all media image file video
6
+ accept: {
7
+ type: String,
8
+ default: () => defProps.upload.accept
9
+ },
10
+ extension: {
11
+ type: Array,
12
+ default: () => defProps.upload.extension
13
+ },
14
+ // 图片或视频拾取模式,当accept为image类型时设置capture可选额外camera可以直接调起摄像头
15
+ capture: {
16
+ type: [String, Array],
17
+ default: () => defProps.upload.capture
18
+ },
19
+ // 当accept为video时生效,是否压缩视频,默认为true
20
+ compressed: {
21
+ type: Boolean,
22
+ default: () => defProps.upload.compressed
23
+ },
24
+ // 当accept为video时生效,可选值为back或front
25
+ camera: {
26
+ type: String,
27
+ default: () => defProps.upload.camera
28
+ },
29
+ // 当accept为video时生效,拍摄视频最长拍摄时间,单位秒
30
+ maxDuration: {
31
+ type: Number,
32
+ default: () => defProps.upload.maxDuration
33
+ },
34
+ // 上传区域的图标,只能内置图标
35
+ uploadIcon: {
36
+ type: String,
37
+ default: () => defProps.upload.uploadIcon
38
+ },
39
+ // 上传区域的图标的颜色,默认
40
+ uploadIconColor: {
41
+ type: String,
42
+ default: () => defProps.upload.uploadIconColor
43
+ },
44
+ // 是否开启文件读取前事件
45
+ useBeforeRead: {
46
+ type: Boolean,
47
+ default: () => defProps.upload.useBeforeRead
48
+ },
49
+ // 读取后的处理函数
50
+ afterRead: {
51
+ type: Function,
52
+ default: null
53
+ },
54
+ // 读取前的处理函数
55
+ beforeRead: {
56
+ type: Function,
57
+ default: null
58
+ },
59
+ // 是否显示组件自带的图片&视频预览功能
60
+ previewFullImage: {
61
+ type: Boolean,
62
+ default: () => defProps.upload.previewFullImage
63
+ },
64
+ // 最大上传数量
65
+ maxCount: {
66
+ type: [String, Number],
67
+ default: () => defProps.upload.maxCount
68
+ },
69
+ // 是否启用
70
+ disabled: {
71
+ type: Boolean,
72
+ default: () => defProps.upload.disabled
73
+ },
74
+ // 预览上传的图片时的裁剪模式,和image组件mode属性一致
75
+ imageMode: {
76
+ type: String,
77
+ default: () => defProps.upload.imageMode
78
+ },
79
+ // 标识符,可以在回调函数的第二项参数中获取
80
+ name: {
81
+ type: String,
82
+ default: () => defProps.upload.name
83
+ },
84
+ // 所选的图片的尺寸, 可选值为original compressed
85
+ sizeType: {
86
+ type: Array,
87
+ default: () => defProps.upload.sizeType
88
+ },
89
+ // 是否开启图片多选,部分安卓机型不支持
90
+ multiple: {
91
+ type: Boolean,
92
+ default: () => defProps.upload.multiple
93
+ },
94
+ // 是否展示删除按钮
95
+ deletable: {
96
+ type: Boolean,
97
+ default: () => defProps.upload.deletable
98
+ },
99
+ // 文件大小限制,单位为byte
100
+ maxSize: {
101
+ type: [String, Number],
102
+ default: () => defProps.upload.maxSize
103
+ },
104
+ // 显示已上传的文件列表
105
+ fileList: {
106
+ type: Array,
107
+ default: () => defProps.upload.fileList
108
+ },
109
+ // 上传区域的提示文字
110
+ uploadText: {
111
+ type: String,
112
+ default: () => defProps.upload.uploadText
113
+ },
114
+ // 内部预览图片区域和选择图片按钮的区域宽度
115
+ width: {
116
+ type: [String, Number],
117
+ default: () => defProps.upload.width
118
+ },
119
+ // 内部预览图片区域和选择图片按钮的区域高度
120
+ height: {
121
+ type: [String, Number],
122
+ default: () => defProps.upload.height
123
+ },
124
+ // 是否在上传完成后展示预览图
125
+ previewImage: {
126
+ type: Boolean,
127
+ default: () => defProps.upload.previewImage
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
+ },
166
+ // 自定义自动上传后处理
167
+ customAfterAutoUpload: {
168
+ type: Boolean,
169
+ default: () => defProps.upload.customAfterAutoUpload
170
+ },
171
+ videoPreviewObjectFit: {
172
+ type: String,
173
+ default: () => defProps.upload.videoPreviewObjectFit
174
+ }
175
+ }
176
+ });
@@ -2,6 +2,18 @@ import { defineMixin } from '../../libs/vue';
2
2
  import defProps from '../../libs/config/props.js';
3
3
  export const props = defineMixin({
4
4
  props: {
5
+ modelValue: {
6
+ type: [Number, String, Array],
7
+ default: ''
8
+ },
9
+ initValue: {
10
+ type: [String, Array],
11
+ default: ''
12
+ },
13
+ iconSize: {
14
+ type: [String, Number],
15
+ default: 12
16
+ },
5
17
  // 接受的文件类型, 可选值为all media image file video
6
18
  accept: {
7
19
  type: String,
@@ -82,21 +82,22 @@
82
82
  </view>
83
83
  <view
84
84
  class="u-upload__deletable"
85
+ :style="iconDelStyle"
85
86
  v-if="item.status !== 'uploading' && (deletable || item.deletable)"
86
87
  @tap.stop="deleteItem(index)"
87
88
  >
88
89
  <view class="u-upload__deletable__icon">
89
- <up-icon name="close" color="#ffffff" size="10"></up-icon>
90
+ <up-icon name="close" color="#ffffff" :size="iconSize"></up-icon>
90
91
  </view>
91
92
  </view>
92
93
  <slot name="success">
93
- <view class="u-upload__success" v-if="item.status === 'success'">
94
+ <view class="u-upload__success" :style="iconSuccStyle1" v-if="item.status === 'success'">
94
95
  <!-- #ifdef APP-NVUE -->
95
96
  <image :src="successIcon" class="u-upload__success__icon"></image>
96
97
  <!-- #endif -->
97
98
  <!-- #ifndef APP-NVUE -->
98
- <view class="u-upload__success__icon">
99
- <up-icon name="checkmark" color="#ffffff" size="12"></up-icon>
99
+ <view class="u-upload__success__icon" :style="iconSuccStyle2">
100
+ <up-icon name="checkmark" color="#ffffff" :size="iconSize"></up-icon>
100
101
  </view>
101
102
  <!-- #endif -->
102
103
  </view>
@@ -158,6 +159,14 @@ import { mixin } from '../../libs/mixin/mixin';
158
159
  import { addStyle, addUnit, toast } from '../../libs/function/index';
159
160
  import test from '../../libs/function/test';
160
161
  import { t } from '../../libs/i18n';
162
+ import {
163
+ fileMimeToExtension,
164
+ checkIsObject,
165
+ generateUniqidName,
166
+ parseJsonSafe,
167
+ parseFormData,
168
+ content_to_array
169
+ } from './file.js';
161
170
  /**
162
171
  * upload 上传
163
172
  * @description 该组件用于上传图片场景
@@ -204,10 +213,18 @@ export default {
204
213
  lists: [],
205
214
  isInCount: true,
206
215
  popupShow: false,
207
- currentItemIndex: -1
216
+ currentItemIndex: -1,
217
+ localList: []
208
218
  };
209
219
  },
210
220
  watch: {
221
+ initValue: {
222
+ handler() {
223
+ this._init_parse_list();
224
+ },
225
+ immediate: true,
226
+ deep: true
227
+ },
211
228
  // 监听文件列表的变化,重新整理内部数据
212
229
  fileList: {
213
230
  handler() {
@@ -232,12 +249,59 @@ export default {
232
249
  }
233
250
  },
234
251
  // #ifdef VUE3
235
- emits: ['error', 'beforeRead', 'oversize', 'afterRead', 'delete', 'clickPreview', 'update:fileList', 'afterAutoUpload'],
252
+ emits: [
253
+ 'error',
254
+ 'beforeRead',
255
+ 'oversize',
256
+ 'afterRead',
257
+ 'delete',
258
+ 'clickPreview',
259
+ 'update:modelValue',
260
+ 'update:fileList',
261
+ 'afterAutoUpload'
262
+ ],
236
263
  // #endif
264
+ computed: {
265
+ // 去除 单位
266
+ iconSizeNum() {
267
+ let size = this.iconSize || 0; // 空值给默认 0
268
+ // if (typeof this.iconSize === 'string' && (this.iconSize.includes('px') || this.iconSize.includes('rpx'))) {
269
+ if (typeof size === 'string') {
270
+ size = size.replace(/[^0-9.]/g, '');
271
+ }
272
+ return parseFloat(size) || 0;
273
+ },
274
+ iconDelStyle() {
275
+ return {
276
+ height: this.iconSizeNum + 4 + 'px',
277
+ width: this.iconSizeNum + 4 + 'px'
278
+ };
279
+ },
280
+ iconSuccStyle1() {
281
+ return {
282
+ 'border-width': this.iconSizeNum - 5 + 'px'
283
+ };
284
+ },
285
+ iconSuccStyle2() {
286
+ return {
287
+ bottom: -(this.iconSizeNum - 3) + 'px',
288
+ right: -(this.iconSizeNum - 3) + 'px'
289
+ };
290
+ }
291
+ },
237
292
  methods: {
238
293
  t,
239
294
  addUnit,
240
295
  addStyle,
296
+ _init_parse_list() {
297
+ if (this.initValue) {
298
+ this.localList = content_to_array(this.initValue);
299
+ } else {
300
+ this.localList = [];
301
+ }
302
+ console.log('---localList--', this.localList);
303
+ this.formatFileList();
304
+ },
241
305
  videoErrorCallback() {},
242
306
  loadedVideoMetadata(e) {
243
307
  if (this.currentItemIndex < 0) {
@@ -297,9 +361,12 @@ export default {
297
361
  })
298
362
  .exec();
299
363
  },
364
+
300
365
  formatFileList() {
301
- const { fileList = [], maxCount } = this;
302
- const lists = fileList.map((item) => {
366
+ const { fileList = [], localList = [], maxCount } = this;
367
+ const fileList2 = localList || fileList || [];
368
+ const lists = fileList2.map((item) => {
369
+ // const lists = fileList.map((item) => {
303
370
  const name = item.name || item.url || item.thumb;
304
371
  return Object.assign(Object.assign({}, item), {
305
372
  // 如果item.url为本地选择的blob文件的话,无法判断其为video还是image,此处优先通过accept做判断处理
@@ -381,6 +448,7 @@ export default {
381
448
  index: index == null ? this.fileList.length : index
382
449
  };
383
450
  },
451
+
384
452
  async onAfterRead(file) {
385
453
  const { maxSize, afterRead } = this;
386
454
  const oversize = Array.isArray(file) ? file.some((item) => item.size > maxSize) : file.size > maxSize;
@@ -426,23 +494,31 @@ export default {
426
494
  case 'upload_oss':
427
495
  // 阿里云前端直传
428
496
  // 获取签名
429
- console.log();
430
497
  let formData = {};
498
+ let save_name = generateUniqidName();
499
+ let extension = fileMimeToExtension(lists[j].type);
500
+ // console.log('---lists[j]--', lists[j]);
431
501
  let ret = await uni.request({
432
502
  url: this.autoUploadAuthUrl,
433
- method: 'get',
503
+ method: 'post',
434
504
  header: this.autoUploadHeader,
435
505
  data: {
436
- filename: lists[j].name
506
+ upload_type: 'aliyun_oss_type2',
507
+ save_sign: 'image',
508
+ filename: lists[j].name,
509
+ save_extension: extension,
510
+ save_name: save_name
437
511
  }
438
512
  });
439
513
  // console.log(ret);
440
514
  let res0 = ret.data;
515
+ let res0Param = {};
441
516
  if (res0.code == 200) {
442
517
  // 路径 + 文件名 + 扩展名
443
518
  // 不传递filename就要拼接key
444
519
  // res0.data.params.key = res0.data.params.dir + res0.data.params.uniqidName + fileExt;
445
- formData = res0.data.params;
520
+ res0Param = res0.data.params || res0.data;
521
+ formData = parseFormData(res0Param);
446
522
  } else {
447
523
  uni.showToast({
448
524
  title: res0.msg,
@@ -451,7 +527,7 @@ export default {
451
527
  return;
452
528
  }
453
529
  var uploadTask = uni.uploadFile({
454
- url: res0.data.params.host,
530
+ url: res0Param.host,
455
531
  filePath: lists[j].url,
456
532
  name: 'file',
457
533
  // fileType: 'video', // 仅支付宝小程序,且必填。
@@ -460,22 +536,31 @@ export default {
460
536
  success: (uploadFileRes) => {
461
537
  let thumb = '';
462
538
  let afterPromise = '';
539
+ let res1 = parseJsonSafe(uploadFileRes?.data || {});
463
540
  if (that.customAfterAutoUpload) {
464
541
  afterPromise = new Promise((resolve, reject) => {
542
+ let res2 = res1?.data || res1 || {};
543
+ // TODO 判断是否多图,
544
+ this.$emit('update:modelValue', res2.filelog_id || '');
465
545
  that.$emit(
466
546
  'afterAutoUpload',
467
- Object.assign(res0, {
547
+ Object.assign(res2, {
468
548
  callback: (r) => {
469
549
  r.url ? resolve(r) : reject();
470
550
  }
471
551
  })
552
+ // Object.assign(res0, res1, {
553
+ // callback: (r) => {
554
+ // r.url ? resolve(r) : reject();
555
+ // }
556
+ // })
472
557
  );
473
558
  });
474
559
  }
475
560
  if (test.promise(afterPromise)) {
476
561
  afterPromise.then((data) => that.succcessUpload(len + j, data.url, data.thumb));
477
562
  } else {
478
- result = res0.data.params.host + '/' + res0.data.params.key;
563
+ result = res0Param.host + '/' + res0Param.key;
479
564
  if (that.accept === 'video' || test.video(result)) {
480
565
  thumb = result + '?x-oss-process=video/snapshot,t_10000,m_fast';
481
566
  }