im-ui-mobile 0.1.16 → 0.1.17
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/components/im-col/im-col.vue +1 -1
- package/components/im-upload/im-upload.vue +72 -198
- package/components/im-upload/utils/file-adapter.ts +214 -0
- package/components/im-upload/utils/file-chooser.ts +69 -0
- package/components/im-upload/utils/file-validator.ts +214 -0
- package/components/im-upload/utils/upload.ts +228 -0
- package/components/im-upload-/im-upload.vue +1190 -0
- package/components/im-upload-/utils/file-adapter.ts +214 -0
- package/components/im-upload-/utils/file-chooser.ts +69 -0
- package/components/im-upload-/utils/file-validator.ts +214 -0
- package/components/im-upload-/utils/upload.ts +228 -0
- package/package.json +1 -1
- package/types/index.d.ts +1 -0
- package/utils/datetime.js +8 -8
- /package/components/{im-upload → im-upload-}/utils/file-adapter.js +0 -0
- /package/components/{im-upload → im-upload-}/utils/file-chooser.js +0 -0
- /package/components/{im-upload → im-upload-}/utils/file-validator.js +0 -0
- /package/components/{im-upload → im-upload-}/utils/upload.js +0 -0
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
<!-- 按钮类型 -->
|
|
11
11
|
<template v-if="type === 'button'">
|
|
12
12
|
<im-button :type="buttonType" :size="buttonSize" :disabled="disabled" :loading="uploading">
|
|
13
|
-
<view
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
</view>
|
|
13
|
+
<!-- <view> -->
|
|
14
|
+
<text v-if="!uploading" class="im-upload__icon">+</text>
|
|
15
|
+
<text class="im-upload__text">{{ uploading ? '上传中...' : buttonText }}</text>
|
|
16
|
+
<!-- </view> -->
|
|
17
17
|
</im-button>
|
|
18
18
|
</template>
|
|
19
19
|
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
<!-- 头像类型 -->
|
|
30
30
|
<template v-else-if="type === 'avatar'">
|
|
31
31
|
<view class="im-upload__avatar">
|
|
32
|
-
<im-avatar :
|
|
32
|
+
<im-avatar :url="fileList[0]?.url" :size="avatarSize" :radius="avatarRadius">
|
|
33
33
|
<template v-if="!fileList[0]?.url">
|
|
34
|
-
<
|
|
34
|
+
<im-icon name="plus" size="40" color="#8c939d" />
|
|
35
35
|
</template>
|
|
36
36
|
</im-avatar>
|
|
37
37
|
<text class="im-upload__avatar-text">{{ avatarText }}</text>
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
<!-- 图片列表 -->
|
|
58
58
|
<template v-if="listType === 'picture'">
|
|
59
59
|
<view class="im-upload__item-preview">
|
|
60
|
-
<image v-if="file.type?.startsWith('image
|
|
60
|
+
<image v-if="file.type?.startsWith('image')" class="im-upload__item-image" :src="file.url || file.thumbUrl"
|
|
61
61
|
mode="aspectFill" @tap="handlePreview(file)" />
|
|
62
62
|
<view v-else class="im-upload__item-file">
|
|
63
63
|
<text class="im-upload__item-file-icon">📄</text>
|
|
@@ -83,10 +83,6 @@
|
|
|
83
83
|
|
|
84
84
|
<!-- 操作按钮 -->
|
|
85
85
|
<view class="im-upload__item-actions">
|
|
86
|
-
<text v-if="file.status === 'done' && previewable" class="im-upload__item-action"
|
|
87
|
-
@tap="handlePreview(file)">
|
|
88
|
-
👁️
|
|
89
|
-
</text>
|
|
90
86
|
<text v-if="!readonly && removable" class="im-upload__item-action" @tap="handleRemove(file, index)">
|
|
91
87
|
✕
|
|
92
88
|
</text>
|
|
@@ -176,7 +172,7 @@ interface UploadFile {
|
|
|
176
172
|
// 定义 Props
|
|
177
173
|
interface Props {
|
|
178
174
|
// 值
|
|
179
|
-
modelValue?: UploadFile[]
|
|
175
|
+
modelValue?: string | UploadFile[]
|
|
180
176
|
|
|
181
177
|
// 上传类型
|
|
182
178
|
type?: 'button' | 'card' | 'avatar' | 'drag' | undefined
|
|
@@ -241,18 +237,17 @@ interface Props {
|
|
|
241
237
|
|
|
242
238
|
// 定义 Emits
|
|
243
239
|
interface Emits {
|
|
244
|
-
(e: 'update:modelValue', files: UploadFile[]): void
|
|
245
|
-
(e: 'change', files: UploadFile[]): void
|
|
240
|
+
(e: 'update:modelValue', files: string | UploadFile[]): void
|
|
241
|
+
(e: 'change', files: string | UploadFile[]): void
|
|
246
242
|
(e: 'select', file: Object): void
|
|
247
|
-
(e: '
|
|
248
|
-
(e: 'success', response: any, file: UploadFile): void
|
|
243
|
+
(e: 'success', file: UploadFile): void
|
|
249
244
|
(e: 'error', error: Error, file: UploadFile): void
|
|
250
245
|
(e: 'progress', percent: number, file: UploadFile): void
|
|
251
246
|
(e: 'remove', file: UploadFile, index: number): void
|
|
252
247
|
(e: 'preview', file: UploadFile): void
|
|
253
248
|
(e: 'exceed', files: Object[]): void
|
|
254
|
-
(e: '
|
|
255
|
-
(e: '
|
|
249
|
+
(e: 'uploading', file: UploadFile): void
|
|
250
|
+
(e: 'uploaded', file: UploadFile): void
|
|
256
251
|
}
|
|
257
252
|
|
|
258
253
|
// 定义 Props 默认值
|
|
@@ -294,6 +289,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
294
289
|
autoUpload: true,
|
|
295
290
|
|
|
296
291
|
responseFormatter: (response: any) => {
|
|
292
|
+
console.log('responseFormatter', response)
|
|
297
293
|
return {
|
|
298
294
|
url: response?.data?.url || response?.result?.url || response?.url || response?.fileUrl,
|
|
299
295
|
name: response?.data?.fileName || response?.result?.fileName || response?.data?.name || response?.result?.name || response?.name,
|
|
@@ -313,7 +309,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
313
309
|
const emit = defineEmits<Emits>()
|
|
314
310
|
|
|
315
311
|
// 响应式状态
|
|
316
|
-
const fileList = ref<UploadFile[]>(props.modelValue || []
|
|
312
|
+
const fileList = ref<UploadFile[]>([]) //props.modelValue || []
|
|
317
313
|
const dragOver = ref(false)
|
|
318
314
|
const uploadingAll = ref(false)
|
|
319
315
|
|
|
@@ -322,8 +318,19 @@ const uploadTasks = new Map<string, UniApp.UploadTask>()
|
|
|
322
318
|
|
|
323
319
|
// 监听 modelValue 变化
|
|
324
320
|
watch(() => props.modelValue, (val) => {
|
|
325
|
-
if (val)
|
|
326
|
-
|
|
321
|
+
if (!val) return
|
|
322
|
+
|
|
323
|
+
if (isSingleFile()) {
|
|
324
|
+
fileList.value = [{
|
|
325
|
+
url: String(props.modelValue),
|
|
326
|
+
uid: '',
|
|
327
|
+
name: 'single file',
|
|
328
|
+
size: 0,
|
|
329
|
+
status: 'uploading'
|
|
330
|
+
}]
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
fileList.value = val as UploadFile[]
|
|
327
334
|
}
|
|
328
335
|
}, { deep: true })
|
|
329
336
|
|
|
@@ -334,7 +341,7 @@ const uploading = computed(() => {
|
|
|
334
341
|
|
|
335
342
|
// 生成唯一ID
|
|
336
343
|
const generateUid = () => {
|
|
337
|
-
return Date.now() + '-' + Math.random().toString(36).
|
|
344
|
+
return Date.now() + '-' + Math.random().toString(36).substring(2, 9)
|
|
338
345
|
}
|
|
339
346
|
|
|
340
347
|
// 格式化文件大小
|
|
@@ -370,8 +377,13 @@ const handleUploadTap = async () => {
|
|
|
370
377
|
return
|
|
371
378
|
}
|
|
372
379
|
|
|
373
|
-
|
|
374
|
-
|
|
380
|
+
let accept = getFileType(props.accept) // image, video, all
|
|
381
|
+
let maxSelectable = props.multiple ? Math.max(0, props.maxCount! - fileList.value.length) : 1
|
|
382
|
+
|
|
383
|
+
if (props.type === 'avatar') {
|
|
384
|
+
accept = 'image'
|
|
385
|
+
maxSelectable = 1
|
|
386
|
+
}
|
|
375
387
|
|
|
376
388
|
if (maxSelectable <= 0) {
|
|
377
389
|
emit('exceed', [])
|
|
@@ -384,9 +396,10 @@ const handleUploadTap = async () => {
|
|
|
384
396
|
|
|
385
397
|
try {
|
|
386
398
|
const res = await chooseFile({
|
|
387
|
-
count:
|
|
388
|
-
type:
|
|
399
|
+
count: maxSelectable,
|
|
400
|
+
type: accept
|
|
389
401
|
})
|
|
402
|
+
|
|
390
403
|
for (const tempFile of res.tempFiles as Array<any>) {
|
|
391
404
|
await processFile(tempFile)
|
|
392
405
|
}
|
|
@@ -397,25 +410,6 @@ const handleUploadTap = async () => {
|
|
|
397
410
|
icon: 'none'
|
|
398
411
|
})
|
|
399
412
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
// uni.chooseFile({
|
|
403
|
-
// count: maxSelectable,
|
|
404
|
-
// type: getFileType(props.accept),
|
|
405
|
-
// extension: getFileExtensions(props.accept),
|
|
406
|
-
// success: async (res) => {
|
|
407
|
-
// for (const tempFile of res.tempFiles as Array<any>) {
|
|
408
|
-
// await processFile(tempFile)
|
|
409
|
-
// }
|
|
410
|
-
// },
|
|
411
|
-
// fail: (err) => {
|
|
412
|
-
// console.error('选择文件失败:', err)
|
|
413
|
-
// uni.showToast({
|
|
414
|
-
// title: '选择文件失败',
|
|
415
|
-
// icon: 'none'
|
|
416
|
-
// })
|
|
417
|
-
// }
|
|
418
|
-
// })
|
|
419
413
|
}
|
|
420
414
|
|
|
421
415
|
// 获取文件类型
|
|
@@ -425,19 +419,9 @@ const getFileType = (accept: string): 'image' | 'video' | 'all' => {
|
|
|
425
419
|
return 'all'
|
|
426
420
|
}
|
|
427
421
|
|
|
428
|
-
//
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
return undefined
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const extensions = accept
|
|
435
|
-
.split(',')
|
|
436
|
-
.map(ext => ext.trim())
|
|
437
|
-
.filter(ext => ext.startsWith('.'))
|
|
438
|
-
.map(ext => ext.substring(1))
|
|
439
|
-
|
|
440
|
-
return extensions.length > 0 ? extensions : undefined
|
|
422
|
+
// 是否仅上传单个文件
|
|
423
|
+
const isSingleFile = () => {
|
|
424
|
+
return typeof props.modelValue === 'string' || props.type === 'avatar' || !props.multiple
|
|
441
425
|
}
|
|
442
426
|
|
|
443
427
|
// 处理文件
|
|
@@ -485,7 +469,7 @@ const processFile = async (uniFile: any) => {
|
|
|
485
469
|
status: 'pending',
|
|
486
470
|
progress: 0,
|
|
487
471
|
url: uniFile.path,
|
|
488
|
-
thumbUrl: uniFile.type?.startsWith('image
|
|
472
|
+
thumbUrl: uniFile.type?.startsWith('image') ? uniFile.path : undefined,
|
|
489
473
|
rawFile: uniFile,
|
|
490
474
|
file
|
|
491
475
|
}
|
|
@@ -493,6 +477,7 @@ const processFile = async (uniFile: any) => {
|
|
|
493
477
|
// 添加到文件列表
|
|
494
478
|
if (!props.multiple) {
|
|
495
479
|
fileList.value = [uploadFile]
|
|
480
|
+
|
|
496
481
|
} else {
|
|
497
482
|
fileList.value.push(uploadFile)
|
|
498
483
|
}
|
|
@@ -506,69 +491,13 @@ const processFile = async (uniFile: any) => {
|
|
|
506
491
|
}
|
|
507
492
|
}
|
|
508
493
|
|
|
509
|
-
// // 检查文件类型是否被接受
|
|
510
|
-
// const isFileTypeAccepted = (uniFile: any): boolean => {
|
|
511
|
-
// if (props.accept === '*') return true
|
|
512
|
-
|
|
513
|
-
// const acceptTypes = props.accept.split(',').map(type => type.trim())
|
|
514
|
-
|
|
515
|
-
// for (const acceptType of acceptTypes) {
|
|
516
|
-
// if (acceptType === '*') return true
|
|
517
|
-
|
|
518
|
-
// // 检查 MIME 类型
|
|
519
|
-
// if (acceptType.endsWith('/*')) {
|
|
520
|
-
// const category = acceptType.split('/')[0]
|
|
521
|
-
// if (uniFile.type?.startsWith(category + '/')) {
|
|
522
|
-
// return true
|
|
523
|
-
// }
|
|
524
|
-
// }
|
|
525
|
-
|
|
526
|
-
// // 检查扩展名
|
|
527
|
-
// if (acceptType.startsWith('.')) {
|
|
528
|
-
// const ext = acceptType.substring(1).toLowerCase()
|
|
529
|
-
// const fileName = uniFile.name.toLowerCase()
|
|
530
|
-
// if (fileName.endsWith('.' + ext)) {
|
|
531
|
-
// return true
|
|
532
|
-
// }
|
|
533
|
-
// }
|
|
534
|
-
|
|
535
|
-
// // 检查完整 MIME 类型
|
|
536
|
-
// if (uniFile.type === acceptType) {
|
|
537
|
-
// return true
|
|
538
|
-
// }
|
|
539
|
-
// }
|
|
540
|
-
|
|
541
|
-
// return false
|
|
542
|
-
// }
|
|
543
|
-
|
|
544
|
-
// 从 uniapp 文件创建 File 对象
|
|
545
|
-
// const createFileFromUniFile = (uniFile: any): File => {
|
|
546
|
-
// const file = new File([], uniFile.name, {
|
|
547
|
-
// type: uniFile.type || 'application/octet-stream',
|
|
548
|
-
// lastModified: uniFile.lastModified || Date.now()
|
|
549
|
-
// })
|
|
550
|
-
|
|
551
|
-
// Object.defineProperties(file, {
|
|
552
|
-
// size: {
|
|
553
|
-
// value: uniFile.size,
|
|
554
|
-
// writable: false
|
|
555
|
-
// },
|
|
556
|
-
// path: {
|
|
557
|
-
// value: uniFile.path,
|
|
558
|
-
// writable: false
|
|
559
|
-
// }
|
|
560
|
-
// })
|
|
561
|
-
|
|
562
|
-
// return file
|
|
563
|
-
// }
|
|
564
|
-
|
|
565
494
|
// 开始上传
|
|
566
495
|
const startUpload = async (uploadFile: UploadFile) => {
|
|
567
496
|
uploadFile.status = 'uploading'
|
|
568
497
|
uploadFile.progress = 0
|
|
569
|
-
emit('
|
|
570
|
-
|
|
571
|
-
updateFileList()
|
|
498
|
+
emit('uploading', uploadFile)
|
|
499
|
+
|
|
500
|
+
// updateFileList()
|
|
572
501
|
|
|
573
502
|
try {
|
|
574
503
|
let response: any
|
|
@@ -580,7 +509,7 @@ const startUpload = async (uploadFile: UploadFile) => {
|
|
|
580
509
|
(percent) => {
|
|
581
510
|
uploadFile.progress = percent
|
|
582
511
|
emit('progress', percent, uploadFile)
|
|
583
|
-
updateFileList()
|
|
512
|
+
updateFileList(uploadFile)
|
|
584
513
|
}
|
|
585
514
|
)
|
|
586
515
|
} else if (props.action) {
|
|
@@ -598,7 +527,7 @@ const startUpload = async (uploadFile: UploadFile) => {
|
|
|
598
527
|
uploadFile.response = formattedResponse
|
|
599
528
|
uploadFile.url = formattedResponse.url || uploadFile.url
|
|
600
529
|
|
|
601
|
-
emit('success',
|
|
530
|
+
emit('success', uploadFile)
|
|
602
531
|
} catch (error) {
|
|
603
532
|
uploadFile.status = 'error'
|
|
604
533
|
uploadFile.error = error as Error
|
|
@@ -609,17 +538,21 @@ const startUpload = async (uploadFile: UploadFile) => {
|
|
|
609
538
|
icon: 'none'
|
|
610
539
|
})
|
|
611
540
|
} finally {
|
|
612
|
-
emit('
|
|
613
|
-
updateFileList()
|
|
541
|
+
emit('uploaded', uploadFile)
|
|
542
|
+
updateFileList(uploadFile)
|
|
614
543
|
}
|
|
615
544
|
}
|
|
616
545
|
|
|
617
546
|
// 默认上传实现
|
|
618
547
|
const defaultUpload = (uploadFile: UploadFile): Promise<any> => {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
548
|
+
let headers = props.headers
|
|
549
|
+
|
|
550
|
+
if (props.accessToken) {
|
|
551
|
+
headers = {
|
|
552
|
+
...headers,
|
|
553
|
+
'Authorization': `Bearer ${props.accessToken}`,
|
|
554
|
+
'AccessToken': props.accessToken,
|
|
555
|
+
}
|
|
623
556
|
}
|
|
624
557
|
|
|
625
558
|
return universalUploadFile({
|
|
@@ -636,43 +569,17 @@ const defaultUpload = (uploadFile: UploadFile): Promise<any> => {
|
|
|
636
569
|
timeout: props.timeout,
|
|
637
570
|
withCredentials: props.withCredentials
|
|
638
571
|
})
|
|
639
|
-
|
|
640
|
-
// return new Promise((resolve, reject) => {
|
|
641
|
-
// uni.uploadFile({
|
|
642
|
-
// url: props.action!,
|
|
643
|
-
// filePath: uploadFile.rawFile.path,
|
|
644
|
-
// name: props.name,
|
|
645
|
-
// formData: {
|
|
646
|
-
// ...props.data,
|
|
647
|
-
// filename: uploadFile.name,
|
|
648
|
-
// size: uploadFile.size,
|
|
649
|
-
// type: uploadFile.type
|
|
650
|
-
// },
|
|
651
|
-
// header: headers,
|
|
652
|
-
// timeout: props.timeout,
|
|
653
|
-
// withCredentials: props.withCredentials,
|
|
654
|
-
// success: (res: any) => {
|
|
655
|
-
// try {
|
|
656
|
-
// const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
|
|
657
|
-
// resolve(data)
|
|
658
|
-
// } catch (e: any) {
|
|
659
|
-
// reject(e)
|
|
660
|
-
// }
|
|
661
|
-
// },
|
|
662
|
-
// complete: () => {
|
|
663
|
-
// // 上传完成
|
|
664
|
-
// },
|
|
665
|
-
// fail: (e: any) => {
|
|
666
|
-
// reject(e)
|
|
667
|
-
// }
|
|
668
|
-
// })
|
|
669
|
-
// })
|
|
670
572
|
}
|
|
671
573
|
|
|
672
574
|
// 更新文件列表
|
|
673
|
-
const updateFileList = () => {
|
|
575
|
+
const updateFileList = (uploadFile?: UploadFile) => {
|
|
674
576
|
emit('change', [...fileList.value])
|
|
675
|
-
|
|
577
|
+
|
|
578
|
+
if (isSingleFile()) {
|
|
579
|
+
emit('update:modelValue', String(uploadFile?.url))
|
|
580
|
+
} else {
|
|
581
|
+
emit('update:modelValue', [...fileList.value])
|
|
582
|
+
}
|
|
676
583
|
}
|
|
677
584
|
|
|
678
585
|
// 处理删除
|
|
@@ -713,7 +620,7 @@ const handlePreview = (file: UploadFile) => {
|
|
|
713
620
|
emit('preview', file)
|
|
714
621
|
|
|
715
622
|
// 如果是图片,使用 uni.previewImage
|
|
716
|
-
if (file.url && file.type?.startsWith('image
|
|
623
|
+
if (file.url && file.type?.startsWith('image')) {
|
|
717
624
|
uni.previewImage({
|
|
718
625
|
urls: [file.url],
|
|
719
626
|
current: file.url
|
|
@@ -778,30 +685,6 @@ const handleClearAll = () => {
|
|
|
778
685
|
})
|
|
779
686
|
}
|
|
780
687
|
|
|
781
|
-
// 手动上传文件
|
|
782
|
-
const upload = async (file: File) => {
|
|
783
|
-
const uploadFile: UploadFile = {
|
|
784
|
-
uid: generateUid(),
|
|
785
|
-
name: file.name,
|
|
786
|
-
size: file.size,
|
|
787
|
-
type: file.type,
|
|
788
|
-
status: 'pending',
|
|
789
|
-
progress: 0,
|
|
790
|
-
file: file
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
if (!props.multiple) {
|
|
794
|
-
fileList.value = [uploadFile]
|
|
795
|
-
} else {
|
|
796
|
-
fileList.value.push(uploadFile)
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
emit('select', file)
|
|
800
|
-
updateFileList()
|
|
801
|
-
|
|
802
|
-
await startUpload(uploadFile)
|
|
803
|
-
}
|
|
804
|
-
|
|
805
688
|
// 清空文件列表
|
|
806
689
|
const clearFiles = () => {
|
|
807
690
|
handleClearAll()
|
|
@@ -825,7 +708,6 @@ const abortUpload = (uid: string) => {
|
|
|
825
708
|
|
|
826
709
|
// 暴露方法给父组件
|
|
827
710
|
defineExpose({
|
|
828
|
-
upload,
|
|
829
711
|
clearFiles,
|
|
830
712
|
abortUpload,
|
|
831
713
|
uploadAll: handleUploadAll,
|
|
@@ -905,11 +787,6 @@ defineExpose({
|
|
|
905
787
|
gap: 20rpx;
|
|
906
788
|
}
|
|
907
789
|
|
|
908
|
-
.im-upload__avatar-icon {
|
|
909
|
-
font-size: 40rpx;
|
|
910
|
-
color: #8c939d;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
790
|
.im-upload__avatar-text {
|
|
914
791
|
font-size: 26rpx;
|
|
915
792
|
color: #606266;
|
|
@@ -949,12 +826,15 @@ defineExpose({
|
|
|
949
826
|
}
|
|
950
827
|
|
|
951
828
|
.im-upload__list {
|
|
952
|
-
margin-top:
|
|
829
|
+
// margin-top: 20rpx;
|
|
830
|
+
display: flex;
|
|
831
|
+
flex-wrap: wrap;
|
|
953
832
|
}
|
|
954
833
|
|
|
955
834
|
.im-upload__item {
|
|
956
835
|
&--picture {
|
|
957
|
-
margin-
|
|
836
|
+
margin-top: 20rpx;
|
|
837
|
+
margin-right: 20rpx;
|
|
958
838
|
}
|
|
959
839
|
|
|
960
840
|
&--text {
|
|
@@ -1066,12 +946,6 @@ defineExpose({
|
|
|
1066
946
|
right: 8rpx;
|
|
1067
947
|
display: flex;
|
|
1068
948
|
gap: 8rpx;
|
|
1069
|
-
opacity: 0;
|
|
1070
|
-
transition: opacity 0.3s;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
.im-upload__item-preview:hover .im-upload__item-actions {
|
|
1074
|
-
opacity: 1;
|
|
1075
949
|
}
|
|
1076
950
|
|
|
1077
951
|
.im-upload__item-action {
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// utils/file-adapter.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 创建统一的文件对象(多平台兼容)
|
|
5
|
+
* @param {Object} rawFile - 原始文件数据
|
|
6
|
+
* @returns {Object} 统一格式的文件对象
|
|
7
|
+
*/
|
|
8
|
+
export const createFileFromUniFile = (rawFile: any): any => {
|
|
9
|
+
// 检查环境
|
|
10
|
+
// #ifdef H5
|
|
11
|
+
if (rawFile instanceof File) {
|
|
12
|
+
return rawFile
|
|
13
|
+
}
|
|
14
|
+
// #endif
|
|
15
|
+
|
|
16
|
+
const path = rawFile.path || rawFile.tempFilePath || rawFile.apFilePaths?.[0]
|
|
17
|
+
const name = rawFile.name || getFileNameFromPath(path)
|
|
18
|
+
const size = rawFile.size || 0
|
|
19
|
+
const type = rawFile.type || getFileTypeFromPath(name)
|
|
20
|
+
|
|
21
|
+
// 创建统一的文件对象
|
|
22
|
+
const unifiedFile = {
|
|
23
|
+
// 基础属性
|
|
24
|
+
path,
|
|
25
|
+
name,
|
|
26
|
+
size,
|
|
27
|
+
type,
|
|
28
|
+
lastModified: rawFile.lastModified || Date.now(),
|
|
29
|
+
|
|
30
|
+
// 原始数据
|
|
31
|
+
raw: rawFile,
|
|
32
|
+
|
|
33
|
+
// 方法(模拟 File 对象的部分行为)
|
|
34
|
+
slice(start: any, end: any) {
|
|
35
|
+
// 小程序环境不支持实际切片,返回一个标记对象
|
|
36
|
+
return {
|
|
37
|
+
path: this.path,
|
|
38
|
+
name: this.name,
|
|
39
|
+
size: end - start,
|
|
40
|
+
type: this.type,
|
|
41
|
+
slice: () => { }
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// 转换为 base64(异步)
|
|
46
|
+
toBase64() {
|
|
47
|
+
return new Promise((resolve: any, reject: any) => {
|
|
48
|
+
// #ifdef MP-WEIXIN
|
|
49
|
+
wx.getFileSystemManager().readFile({
|
|
50
|
+
filePath: this.path,
|
|
51
|
+
encoding: 'base64',
|
|
52
|
+
success: (res: any) => resolve(res.data),
|
|
53
|
+
fail: reject
|
|
54
|
+
})
|
|
55
|
+
// #endif
|
|
56
|
+
|
|
57
|
+
// #ifdef H5
|
|
58
|
+
const reader = new FileReader()
|
|
59
|
+
reader.onload = () => resolve(reader.result.split(',')[1])
|
|
60
|
+
reader.onerror = reject
|
|
61
|
+
reader.readAsDataURL(this)
|
|
62
|
+
// #endif
|
|
63
|
+
|
|
64
|
+
// #ifdef APP-PLUS
|
|
65
|
+
plus.io.resolveLocalFileSystemURL(this.path, (entry: any) => {
|
|
66
|
+
entry.file((file: any) => {
|
|
67
|
+
const reader = new plus.io.FileReader()
|
|
68
|
+
reader.onload = (e: any) => resolve(e.target.result.split(',')[1])
|
|
69
|
+
reader.onerror = reject
|
|
70
|
+
reader.readAsDataURL(file)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
// #endif
|
|
74
|
+
})
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// 转换为 ArrayBuffer(异步)
|
|
78
|
+
toArrayBuffer() {
|
|
79
|
+
return new Promise((resolve: any, reject: any) => {
|
|
80
|
+
// #ifdef MP-WEIXIN
|
|
81
|
+
wx.getFileSystemManager().readFile({
|
|
82
|
+
filePath: this.path,
|
|
83
|
+
success: (res: any) => resolve(res.data),
|
|
84
|
+
fail: reject
|
|
85
|
+
})
|
|
86
|
+
// #endif
|
|
87
|
+
|
|
88
|
+
// #ifdef H5
|
|
89
|
+
const reader = new FileReader()
|
|
90
|
+
reader.onload = () => resolve(reader.result)
|
|
91
|
+
reader.onerror = reject
|
|
92
|
+
reader.readAsArrayBuffer(this)
|
|
93
|
+
// #endif
|
|
94
|
+
})
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
// 获取文件 URL(用于预览等)
|
|
98
|
+
getURL() {
|
|
99
|
+
// #ifdef H5
|
|
100
|
+
if (this.raw instanceof File) {
|
|
101
|
+
return URL.createObjectURL(this.raw)
|
|
102
|
+
}
|
|
103
|
+
// #endif
|
|
104
|
+
return this.path
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// 释放资源
|
|
108
|
+
revoke() {
|
|
109
|
+
// #ifdef H5
|
|
110
|
+
if (this.raw instanceof File && this._objectURL) {
|
|
111
|
+
URL.revokeObjectURL(this._objectURL)
|
|
112
|
+
}
|
|
113
|
+
// #endif
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 如果是 H5 环境,尝试包装成 File 对象
|
|
118
|
+
// #ifdef H5
|
|
119
|
+
if (typeof File !== 'undefined' && !(rawFile instanceof File)) {
|
|
120
|
+
try {
|
|
121
|
+
// 尝试创建 File 对象(需要 fetch 支持)
|
|
122
|
+
return Object.assign(new Blob(), unifiedFile)
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.warn('无法创建 File 对象,使用统一格式:', e)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// #endif
|
|
128
|
+
|
|
129
|
+
return unifiedFile
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 从文件路径提取文件名
|
|
134
|
+
*/
|
|
135
|
+
const getFileNameFromPath = (path: any): any => {
|
|
136
|
+
if (!path) return 'unnamed'
|
|
137
|
+
return path.split('/').pop().split('?')[0]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 从文件名获取文件类型
|
|
142
|
+
*/
|
|
143
|
+
const getFileTypeFromPath = (filename: any): any => {
|
|
144
|
+
if (!filename) return 'application/octet-stream'
|
|
145
|
+
|
|
146
|
+
const extension = filename.split('.').pop().toLowerCase()
|
|
147
|
+
const typeMap: any = {
|
|
148
|
+
// 图片
|
|
149
|
+
'jpg': 'image/jpeg',
|
|
150
|
+
'jpeg': 'image/jpeg',
|
|
151
|
+
'png': 'image/png',
|
|
152
|
+
'gif': 'image/gif',
|
|
153
|
+
'bmp': 'image/bmp',
|
|
154
|
+
'webp': 'image/webp',
|
|
155
|
+
'svg': 'image/svg+xml',
|
|
156
|
+
|
|
157
|
+
// 视频
|
|
158
|
+
'mp4': 'video/mp4',
|
|
159
|
+
'avi': 'video/x-msvideo',
|
|
160
|
+
'mov': 'video/quicktime',
|
|
161
|
+
'wmv': 'video/x-ms-wmv',
|
|
162
|
+
'flv': 'video/x-flv',
|
|
163
|
+
'mkv': 'video/x-matroska',
|
|
164
|
+
|
|
165
|
+
// 音频
|
|
166
|
+
'mp3': 'audio/mpeg',
|
|
167
|
+
'wav': 'audio/wav',
|
|
168
|
+
'aac': 'audio/aac',
|
|
169
|
+
'ogg': 'audio/ogg',
|
|
170
|
+
'flac': 'audio/flac',
|
|
171
|
+
|
|
172
|
+
// 文档
|
|
173
|
+
'pdf': 'application/pdf',
|
|
174
|
+
'doc': 'application/msword',
|
|
175
|
+
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
176
|
+
'xls': 'application/vnd.ms-excel',
|
|
177
|
+
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
178
|
+
'ppt': 'application/vnd.ms-powerpoint',
|
|
179
|
+
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
180
|
+
'txt': 'text/plain'
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return typeMap[extension] || 'application/octet-stream'
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 批量转换文件对象
|
|
188
|
+
*/
|
|
189
|
+
export const createFilesFromUniFiles = (rawFiles: any): any => {
|
|
190
|
+
if (!Array.isArray(rawFiles)) {
|
|
191
|
+
rawFiles = [rawFiles]
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return rawFiles.map((file: any) => createFileFromUniFile(file))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 环境检测
|
|
199
|
+
*/
|
|
200
|
+
export const isBrowserEnv = (): any => {
|
|
201
|
+
// #ifdef H5
|
|
202
|
+
return typeof File !== 'undefined'
|
|
203
|
+
// #endif
|
|
204
|
+
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const isWeChatEnv = (): any => {
|
|
209
|
+
// #ifdef MP-WEIXIN
|
|
210
|
+
return true
|
|
211
|
+
// #endif
|
|
212
|
+
|
|
213
|
+
return false
|
|
214
|
+
}
|