im-ui-mobile 0.1.13 → 0.1.14
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.
|
@@ -1,33 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<view class="im-upload">
|
|
3
3
|
<!-- 上传区域 -->
|
|
4
|
-
<view
|
|
5
|
-
|
|
6
|
-
:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
{ 'im-upload__area--drag-over': dragOver }
|
|
11
|
-
]"
|
|
12
|
-
@tap="handleUploadTap"
|
|
13
|
-
@touchmove="handleDragOver"
|
|
14
|
-
@touchend="handleDragLeave"
|
|
15
|
-
>
|
|
4
|
+
<view class="im-upload__area" :class="[
|
|
5
|
+
`im-upload__area--${type}`,
|
|
6
|
+
{ 'im-upload__area--disabled': disabled },
|
|
7
|
+
{ 'im-upload__area--readonly': readonly },
|
|
8
|
+
{ 'im-upload__area--drag-over': dragOver }
|
|
9
|
+
]" @tap="handleUploadTap" @touchmove="handleDragOver" @touchend="handleDragLeave">
|
|
16
10
|
<!-- 按钮类型 -->
|
|
17
11
|
<template v-if="type === 'button'">
|
|
18
|
-
<im-button
|
|
19
|
-
:type="buttonType"
|
|
20
|
-
:size="buttonSize"
|
|
21
|
-
:disabled="disabled"
|
|
22
|
-
:loading="uploading"
|
|
23
|
-
>
|
|
12
|
+
<im-button :type="buttonType" :size="buttonSize" :disabled="disabled" :loading="uploading">
|
|
24
13
|
<view class="im-upload__button-content">
|
|
25
14
|
<text v-if="!uploading" class="im-upload__icon">+</text>
|
|
26
15
|
<text class="im-upload__text">{{ uploading ? '上传中...' : buttonText }}</text>
|
|
27
16
|
</view>
|
|
28
17
|
</im-button>
|
|
29
18
|
</template>
|
|
30
|
-
|
|
19
|
+
|
|
31
20
|
<!-- 卡片类型 -->
|
|
32
21
|
<template v-else-if="type === 'card'">
|
|
33
22
|
<view class="im-upload__card">
|
|
@@ -36,15 +25,11 @@
|
|
|
36
25
|
<text v-if="hint" class="im-upload__card-hint">{{ hint }}</text>
|
|
37
26
|
</view>
|
|
38
27
|
</template>
|
|
39
|
-
|
|
28
|
+
|
|
40
29
|
<!-- 头像类型 -->
|
|
41
30
|
<template v-else-if="type === 'avatar'">
|
|
42
31
|
<view class="im-upload__avatar">
|
|
43
|
-
<im-avatar
|
|
44
|
-
:src="fileList[0]?.url"
|
|
45
|
-
:size="avatarSize"
|
|
46
|
-
:radius="avatarRadius"
|
|
47
|
-
>
|
|
32
|
+
<im-avatar :src="fileList[0]?.url" :size="avatarSize" :radius="avatarRadius">
|
|
48
33
|
<template v-if="!fileList[0]?.url">
|
|
49
34
|
<text class="im-upload__avatar-icon">+</text>
|
|
50
35
|
</template>
|
|
@@ -52,7 +37,7 @@
|
|
|
52
37
|
<text class="im-upload__avatar-text">{{ avatarText }}</text>
|
|
53
38
|
</view>
|
|
54
39
|
</template>
|
|
55
|
-
|
|
40
|
+
|
|
56
41
|
<!-- 拖拽区域类型 -->
|
|
57
42
|
<template v-else-if="type === 'drag'">
|
|
58
43
|
<view class="im-upload__drag">
|
|
@@ -64,74 +49,51 @@
|
|
|
64
49
|
|
|
65
50
|
<slot v-else></slot>
|
|
66
51
|
</view>
|
|
67
|
-
|
|
52
|
+
|
|
68
53
|
<!-- 文件列表 -->
|
|
69
54
|
<view v-if="showList && fileList.length > 0" class="im-upload__list">
|
|
70
|
-
<view
|
|
71
|
-
|
|
72
|
-
:key="file.uid"
|
|
73
|
-
class="im-upload__item"
|
|
74
|
-
:class="`im-upload__item--${listType}`"
|
|
75
|
-
>
|
|
55
|
+
<view v-for="(file, index) in fileList" :key="file.uid" class="im-upload__item"
|
|
56
|
+
:class="`im-upload__item--${listType}`">
|
|
76
57
|
<!-- 图片列表 -->
|
|
77
58
|
<template v-if="listType === 'picture'">
|
|
78
59
|
<view class="im-upload__item-preview">
|
|
79
|
-
<image
|
|
80
|
-
|
|
81
|
-
class="im-upload__item-image"
|
|
82
|
-
:src="file.url || file.thumbUrl"
|
|
83
|
-
mode="aspectFill"
|
|
84
|
-
@tap="handlePreview(file)"
|
|
85
|
-
/>
|
|
60
|
+
<image v-if="file.type?.startsWith('image/')" class="im-upload__item-image" :src="file.url || file.thumbUrl"
|
|
61
|
+
mode="aspectFill" @tap="handlePreview(file)" />
|
|
86
62
|
<view v-else class="im-upload__item-file">
|
|
87
63
|
<text class="im-upload__item-file-icon">📄</text>
|
|
88
64
|
<text class="im-upload__item-file-name" @tap="handlePreview(file)">
|
|
89
65
|
{{ file.name }}
|
|
90
66
|
</text>
|
|
91
67
|
</view>
|
|
92
|
-
|
|
68
|
+
|
|
93
69
|
<!-- 上传状态 -->
|
|
94
70
|
<view v-if="file.status === 'uploading'" class="im-upload__item-status">
|
|
95
71
|
<view class="im-upload__item-progress">
|
|
96
|
-
<view
|
|
97
|
-
class="im-upload__item-progress-bar"
|
|
98
|
-
:style="{ width: `${file.progress || 0}%` }"
|
|
99
|
-
></view>
|
|
72
|
+
<view class="im-upload__item-progress-bar" :style="{ width: `${file.progress || 0}%` }"></view>
|
|
100
73
|
</view>
|
|
101
74
|
<text class="im-upload__item-percent">{{ file.progress || 0 }}%</text>
|
|
102
75
|
</view>
|
|
103
|
-
|
|
76
|
+
|
|
104
77
|
<view v-else-if="file.status === 'error'" class="im-upload__item-error">
|
|
105
78
|
<text class="im-upload__item-error-text">上传失败</text>
|
|
106
|
-
<text
|
|
107
|
-
v-if="!readonly"
|
|
108
|
-
class="im-upload__item-retry"
|
|
109
|
-
@tap="handleRetry(file, index)"
|
|
110
|
-
>
|
|
79
|
+
<text v-if="!readonly" class="im-upload__item-retry" @tap="handleRetry(file, index)">
|
|
111
80
|
重试
|
|
112
81
|
</text>
|
|
113
82
|
</view>
|
|
114
|
-
|
|
83
|
+
|
|
115
84
|
<!-- 操作按钮 -->
|
|
116
85
|
<view class="im-upload__item-actions">
|
|
117
|
-
<text
|
|
118
|
-
|
|
119
|
-
class="im-upload__item-action"
|
|
120
|
-
@tap="handlePreview(file)"
|
|
121
|
-
>
|
|
86
|
+
<text v-if="file.status === 'done' && previewable" class="im-upload__item-action"
|
|
87
|
+
@tap="handlePreview(file)">
|
|
122
88
|
👁️
|
|
123
89
|
</text>
|
|
124
|
-
<text
|
|
125
|
-
v-if="!readonly && removable"
|
|
126
|
-
class="im-upload__item-action"
|
|
127
|
-
@tap="handleRemove(file, index)"
|
|
128
|
-
>
|
|
90
|
+
<text v-if="!readonly && removable" class="im-upload__item-action" @tap="handleRemove(file, index)">
|
|
129
91
|
✕
|
|
130
92
|
</text>
|
|
131
93
|
</view>
|
|
132
94
|
</view>
|
|
133
95
|
</template>
|
|
134
|
-
|
|
96
|
+
|
|
135
97
|
<!-- 文本列表 -->
|
|
136
98
|
<template v-else>
|
|
137
99
|
<view class="im-upload__item-text">
|
|
@@ -146,35 +108,25 @@
|
|
|
146
108
|
</text>
|
|
147
109
|
</view>
|
|
148
110
|
</view>
|
|
149
|
-
|
|
111
|
+
|
|
150
112
|
<!-- 操作按钮 -->
|
|
151
113
|
<view v-if="!readonly" class="im-upload__item-text-actions">
|
|
152
|
-
<text
|
|
153
|
-
v-if="file.status === 'error'"
|
|
154
|
-
class="im-upload__item-text-action"
|
|
155
|
-
@tap="handleRetry(file, index)"
|
|
156
|
-
>
|
|
114
|
+
<text v-if="file.status === 'error'" class="im-upload__item-text-action" @tap="handleRetry(file, index)">
|
|
157
115
|
重试
|
|
158
116
|
</text>
|
|
159
|
-
<text
|
|
160
|
-
|
|
161
|
-
class="im-upload__item-text-action"
|
|
162
|
-
@tap="handlePreview(file)"
|
|
163
|
-
>
|
|
117
|
+
<text v-if="file.status === 'done' && previewable" class="im-upload__item-text-action"
|
|
118
|
+
@tap="handlePreview(file)">
|
|
164
119
|
预览
|
|
165
120
|
</text>
|
|
166
|
-
<text
|
|
167
|
-
|
|
168
|
-
class="im-upload__item-text-action im-upload__item-text-action--remove"
|
|
169
|
-
@tap="handleRemove(file, index)"
|
|
170
|
-
>
|
|
121
|
+
<text v-if="removable" class="im-upload__item-text-action im-upload__item-text-action--remove"
|
|
122
|
+
@tap="handleRemove(file, index)">
|
|
171
123
|
删除
|
|
172
124
|
</text>
|
|
173
125
|
</view>
|
|
174
126
|
</template>
|
|
175
127
|
</view>
|
|
176
128
|
</view>
|
|
177
|
-
|
|
129
|
+
|
|
178
130
|
<!-- 上传提示 -->
|
|
179
131
|
<view v-if="showTip" class="im-upload__tip">
|
|
180
132
|
<text class="im-upload__tip-text">{{ tip }}</text>
|
|
@@ -182,27 +134,15 @@
|
|
|
182
134
|
已选择 {{ fileList.length }} 个文件
|
|
183
135
|
</text>
|
|
184
136
|
</view>
|
|
185
|
-
|
|
137
|
+
|
|
186
138
|
<!-- 操作按钮 -->
|
|
187
139
|
<view v-if="showActions && fileList.length > 0 && !readonly" class="im-upload__actions">
|
|
188
|
-
<im-button
|
|
189
|
-
|
|
190
|
-
type="primary"
|
|
191
|
-
size="small"
|
|
192
|
-
:loading="uploadingAll"
|
|
193
|
-
:disabled="uploadingAll || disabled"
|
|
194
|
-
@click="handleUploadAll"
|
|
195
|
-
>
|
|
140
|
+
<im-button v-if="showUploadAll" type="primary" size="small" :loading="uploadingAll"
|
|
141
|
+
:disabled="uploadingAll || disabled" @click="handleUploadAll">
|
|
196
142
|
{{ uploadingAll ? '上传中...' : '全部上传' }}
|
|
197
143
|
</im-button>
|
|
198
|
-
|
|
199
|
-
<im-button
|
|
200
|
-
v-if="showClear"
|
|
201
|
-
type="default"
|
|
202
|
-
size="small"
|
|
203
|
-
:disabled="disabled"
|
|
204
|
-
@click="handleClearAll"
|
|
205
|
-
>
|
|
144
|
+
|
|
145
|
+
<im-button v-if="showClear" type="default" size="small" :disabled="disabled" @click="handleClearAll">
|
|
206
146
|
清空列表
|
|
207
147
|
</im-button>
|
|
208
148
|
</view>
|
|
@@ -212,6 +152,7 @@
|
|
|
212
152
|
<script setup lang="ts">
|
|
213
153
|
import { ref, computed, watch } from 'vue'
|
|
214
154
|
import ImButton from '../im-button/im-button.vue'
|
|
155
|
+
import { chooseFile } from './utils/file-chooser'
|
|
215
156
|
|
|
216
157
|
// 定义文件类型
|
|
217
158
|
interface UploadFile {
|
|
@@ -233,26 +174,26 @@ interface UploadFile {
|
|
|
233
174
|
interface Props {
|
|
234
175
|
// 值
|
|
235
176
|
modelValue?: UploadFile[]
|
|
236
|
-
|
|
177
|
+
|
|
237
178
|
// 上传类型
|
|
238
179
|
type?: 'button' | 'card' | 'avatar' | 'drag' | undefined
|
|
239
|
-
|
|
180
|
+
|
|
240
181
|
// 按钮配置
|
|
241
182
|
buttonText?: string
|
|
242
|
-
buttonType?:
|
|
183
|
+
buttonType?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info' // 'primary' | 'default' | 'warning' | 'error'
|
|
243
184
|
buttonSize?: 'small' | 'medium' | 'large'
|
|
244
|
-
|
|
185
|
+
|
|
245
186
|
// 卡片配置
|
|
246
187
|
cardText?: string
|
|
247
|
-
|
|
188
|
+
|
|
248
189
|
// 头像配置
|
|
249
190
|
avatarText?: string
|
|
250
191
|
avatarSize?: number | string
|
|
251
192
|
avatarRadius?: string
|
|
252
|
-
|
|
193
|
+
|
|
253
194
|
// 拖拽配置
|
|
254
195
|
dragText?: string
|
|
255
|
-
|
|
196
|
+
|
|
256
197
|
// 通用配置
|
|
257
198
|
hint?: string
|
|
258
199
|
tip?: string
|
|
@@ -260,14 +201,14 @@ interface Props {
|
|
|
260
201
|
showActions?: boolean
|
|
261
202
|
showUploadAll?: boolean
|
|
262
203
|
showClear?: boolean
|
|
263
|
-
|
|
204
|
+
|
|
264
205
|
// 文件配置
|
|
265
206
|
accept?: string // 比如:image/*,.pdf,.doc,.docx
|
|
266
207
|
multiple?: boolean
|
|
267
208
|
maxCount?: number
|
|
268
209
|
maxSize?: number // 单位:字节
|
|
269
210
|
beforeUpload?: (file: File) => boolean | Promise<boolean> // 上传前钩子
|
|
270
|
-
|
|
211
|
+
|
|
271
212
|
// 上传配置
|
|
272
213
|
action?: string
|
|
273
214
|
accessToken?: string,
|
|
@@ -277,20 +218,20 @@ interface Props {
|
|
|
277
218
|
withCredentials?: boolean
|
|
278
219
|
timeout?: number
|
|
279
220
|
autoUpload?: boolean
|
|
280
|
-
|
|
221
|
+
|
|
281
222
|
// 响应格式化
|
|
282
|
-
responseFormatter?: (response: any) => { url: string;
|
|
283
|
-
|
|
223
|
+
responseFormatter?: (response: any) => { url: string;[key: string]: any }
|
|
224
|
+
|
|
284
225
|
// 列表配置
|
|
285
226
|
showList?: boolean
|
|
286
227
|
listType?: 'text' | 'picture'
|
|
287
228
|
removable?: boolean
|
|
288
229
|
previewable?: boolean
|
|
289
|
-
|
|
230
|
+
|
|
290
231
|
// 状态
|
|
291
232
|
disabled?: boolean
|
|
292
233
|
readonly?: boolean
|
|
293
|
-
|
|
234
|
+
|
|
294
235
|
// 自定义上传
|
|
295
236
|
customRequest?: (file: File, onProgress: (percent: number) => void) => Promise<any>
|
|
296
237
|
}
|
|
@@ -314,54 +255,54 @@ interface Emits {
|
|
|
314
255
|
// 定义 Props 默认值
|
|
315
256
|
const props = withDefaults(defineProps<Props>(), {
|
|
316
257
|
modelValue: () => [],
|
|
317
|
-
|
|
258
|
+
|
|
318
259
|
type: undefined, // 'button',
|
|
319
|
-
|
|
260
|
+
|
|
320
261
|
buttonText: '上传文件',
|
|
321
262
|
buttonType: 'primary',
|
|
322
263
|
buttonSize: 'medium',
|
|
323
|
-
|
|
264
|
+
|
|
324
265
|
cardText: '点击上传',
|
|
325
|
-
|
|
266
|
+
|
|
326
267
|
avatarText: '上传头像',
|
|
327
268
|
avatarSize: 120,
|
|
328
269
|
avatarRadius: '50%',
|
|
329
|
-
|
|
270
|
+
|
|
330
271
|
dragText: '将文件拖到此处,或点击上传',
|
|
331
|
-
|
|
272
|
+
|
|
332
273
|
tip: '支持上传图片、文档等文件',
|
|
333
274
|
showTip: false,
|
|
334
275
|
showActions: false,
|
|
335
276
|
showUploadAll: true,
|
|
336
277
|
showClear: true,
|
|
337
|
-
|
|
278
|
+
|
|
338
279
|
accept: '*',
|
|
339
280
|
multiple: false,
|
|
340
281
|
maxCount: 9,
|
|
341
282
|
maxSize: 10 * 1024 * 1024,
|
|
342
|
-
|
|
283
|
+
|
|
343
284
|
action: '',
|
|
344
|
-
accessToken:'',
|
|
285
|
+
accessToken: '',
|
|
345
286
|
headers: () => ({}),
|
|
346
287
|
data: () => ({}),
|
|
347
288
|
name: 'file',
|
|
348
289
|
withCredentials: false,
|
|
349
290
|
timeout: 10000,
|
|
350
291
|
autoUpload: true,
|
|
351
|
-
|
|
352
|
-
responseFormatter: (response:any) => {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
292
|
+
|
|
293
|
+
responseFormatter: (response: any) => {
|
|
294
|
+
return {
|
|
295
|
+
url: response?.data?.url || response?.result?.url || response?.url || response?.fileUrl,
|
|
296
|
+
name: response?.data?.fileName || response?.result?.fileName || response?.data?.name || response?.result?.name || response?.name,
|
|
297
|
+
...response
|
|
298
|
+
}
|
|
358
299
|
},
|
|
359
|
-
|
|
300
|
+
|
|
360
301
|
showList: false,
|
|
361
302
|
listType: 'picture',
|
|
362
303
|
removable: true,
|
|
363
304
|
previewable: true,
|
|
364
|
-
|
|
305
|
+
|
|
365
306
|
disabled: false,
|
|
366
307
|
readonly: false
|
|
367
308
|
})
|
|
@@ -396,16 +337,16 @@ const generateUid = () => {
|
|
|
396
337
|
// 格式化文件大小
|
|
397
338
|
const formatSize = (bytes?: number) => {
|
|
398
339
|
if (!bytes) return '0 B'
|
|
399
|
-
|
|
340
|
+
|
|
400
341
|
const units = ['B', 'KB', 'MB', 'GB']
|
|
401
342
|
let size = bytes
|
|
402
343
|
let unitIndex = 0
|
|
403
|
-
|
|
344
|
+
|
|
404
345
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
405
346
|
size /= 1024
|
|
406
347
|
unitIndex++
|
|
407
348
|
}
|
|
408
|
-
|
|
349
|
+
|
|
409
350
|
return `${size.toFixed(1)} ${units[unitIndex]}`
|
|
410
351
|
}
|
|
411
352
|
|
|
@@ -425,10 +366,10 @@ const handleUploadTap = async () => {
|
|
|
425
366
|
if (props.disabled || props.readonly) {
|
|
426
367
|
return
|
|
427
368
|
}
|
|
428
|
-
|
|
429
|
-
const maxSelectable = props.multiple ?
|
|
369
|
+
|
|
370
|
+
const maxSelectable = props.multiple ?
|
|
430
371
|
Math.max(0, props.maxCount! - fileList.value.length) : 1
|
|
431
|
-
|
|
372
|
+
|
|
432
373
|
if (maxSelectable <= 0) {
|
|
433
374
|
emit('exceed', [])
|
|
434
375
|
uni.showToast({
|
|
@@ -437,24 +378,41 @@ const handleUploadTap = async () => {
|
|
|
437
378
|
})
|
|
438
379
|
return
|
|
439
380
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
},
|
|
450
|
-
fail: (err) => {
|
|
451
|
-
console.error('选择文件失败:', err)
|
|
452
|
-
uni.showToast({
|
|
453
|
-
title: '选择文件失败',
|
|
454
|
-
icon: 'none'
|
|
455
|
-
})
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
const res = await chooseFile({
|
|
384
|
+
count: 9,
|
|
385
|
+
type: getFileType(props.accept) //'image' // image, video, all
|
|
386
|
+
})
|
|
387
|
+
for (const tempFile of res.tempFiles as Array<any>) {
|
|
388
|
+
await processFile(tempFile)
|
|
456
389
|
}
|
|
457
|
-
})
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.error('选择文件失败:', error)
|
|
392
|
+
uni.showToast({
|
|
393
|
+
title: '选择文件失败',
|
|
394
|
+
icon: 'none'
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
// uni.chooseFile({
|
|
400
|
+
// count: maxSelectable,
|
|
401
|
+
// type: getFileType(props.accept),
|
|
402
|
+
// extension: getFileExtensions(props.accept),
|
|
403
|
+
// success: async (res) => {
|
|
404
|
+
// for (const tempFile of res.tempFiles as Array<any>) {
|
|
405
|
+
// await processFile(tempFile)
|
|
406
|
+
// }
|
|
407
|
+
// },
|
|
408
|
+
// fail: (err) => {
|
|
409
|
+
// console.error('选择文件失败:', err)
|
|
410
|
+
// uni.showToast({
|
|
411
|
+
// title: '选择文件失败',
|
|
412
|
+
// icon: 'none'
|
|
413
|
+
// })
|
|
414
|
+
// }
|
|
415
|
+
// })
|
|
458
416
|
}
|
|
459
417
|
|
|
460
418
|
// 获取文件类型
|
|
@@ -469,13 +427,13 @@ const getFileExtensions = (accept: string): string[] | undefined => {
|
|
|
469
427
|
if (accept === '*' || accept.includes('all')) {
|
|
470
428
|
return undefined
|
|
471
429
|
}
|
|
472
|
-
|
|
430
|
+
|
|
473
431
|
const extensions = accept
|
|
474
432
|
.split(',')
|
|
475
433
|
.map(ext => ext.trim())
|
|
476
434
|
.filter(ext => ext.startsWith('.'))
|
|
477
435
|
.map(ext => ext.substring(1))
|
|
478
|
-
|
|
436
|
+
|
|
479
437
|
return extensions.length > 0 ? extensions : undefined
|
|
480
438
|
}
|
|
481
439
|
|
|
@@ -489,7 +447,7 @@ const processFile = async (uniFile: any) => {
|
|
|
489
447
|
})
|
|
490
448
|
return
|
|
491
449
|
}
|
|
492
|
-
|
|
450
|
+
|
|
493
451
|
// 检查文件类型
|
|
494
452
|
if (!isFileTypeAccepted(uniFile)) {
|
|
495
453
|
uni.showToast({
|
|
@@ -498,10 +456,10 @@ const processFile = async (uniFile: any) => {
|
|
|
498
456
|
})
|
|
499
457
|
return
|
|
500
458
|
}
|
|
501
|
-
|
|
459
|
+
|
|
502
460
|
// 创建文件对象
|
|
503
461
|
const file = createFileFromUniFile(uniFile)
|
|
504
|
-
|
|
462
|
+
|
|
505
463
|
// 执行上传前钩子
|
|
506
464
|
if (props.beforeUpload) {
|
|
507
465
|
try {
|
|
@@ -514,7 +472,7 @@ const processFile = async (uniFile: any) => {
|
|
|
514
472
|
return
|
|
515
473
|
}
|
|
516
474
|
}
|
|
517
|
-
|
|
475
|
+
|
|
518
476
|
// 创建上传文件对象
|
|
519
477
|
const uploadFile: UploadFile = {
|
|
520
478
|
uid: generateUid(),
|
|
@@ -528,17 +486,17 @@ const processFile = async (uniFile: any) => {
|
|
|
528
486
|
rawFile: uniFile,
|
|
529
487
|
file
|
|
530
488
|
}
|
|
531
|
-
|
|
489
|
+
|
|
532
490
|
// 添加到文件列表
|
|
533
491
|
if (!props.multiple) {
|
|
534
492
|
fileList.value = [uploadFile]
|
|
535
493
|
} else {
|
|
536
494
|
fileList.value.push(uploadFile)
|
|
537
495
|
}
|
|
538
|
-
|
|
496
|
+
|
|
539
497
|
emit('select', file)
|
|
540
498
|
updateFileList()
|
|
541
|
-
|
|
499
|
+
|
|
542
500
|
// 自动上传
|
|
543
501
|
if (props.autoUpload && (props.action || props.customRequest)) {
|
|
544
502
|
await startUpload(uploadFile)
|
|
@@ -548,12 +506,12 @@ const processFile = async (uniFile: any) => {
|
|
|
548
506
|
// 检查文件类型是否被接受
|
|
549
507
|
const isFileTypeAccepted = (uniFile: any): boolean => {
|
|
550
508
|
if (props.accept === '*') return true
|
|
551
|
-
|
|
509
|
+
|
|
552
510
|
const acceptTypes = props.accept.split(',').map(type => type.trim())
|
|
553
|
-
|
|
511
|
+
|
|
554
512
|
for (const acceptType of acceptTypes) {
|
|
555
513
|
if (acceptType === '*') return true
|
|
556
|
-
|
|
514
|
+
|
|
557
515
|
// 检查 MIME 类型
|
|
558
516
|
if (acceptType.endsWith('/*')) {
|
|
559
517
|
const category = acceptType.split('/')[0]
|
|
@@ -561,7 +519,7 @@ const isFileTypeAccepted = (uniFile: any): boolean => {
|
|
|
561
519
|
return true
|
|
562
520
|
}
|
|
563
521
|
}
|
|
564
|
-
|
|
522
|
+
|
|
565
523
|
// 检查扩展名
|
|
566
524
|
if (acceptType.startsWith('.')) {
|
|
567
525
|
const ext = acceptType.substring(1).toLowerCase()
|
|
@@ -570,13 +528,13 @@ const isFileTypeAccepted = (uniFile: any): boolean => {
|
|
|
570
528
|
return true
|
|
571
529
|
}
|
|
572
530
|
}
|
|
573
|
-
|
|
531
|
+
|
|
574
532
|
// 检查完整 MIME 类型
|
|
575
533
|
if (uniFile.type === acceptType) {
|
|
576
534
|
return true
|
|
577
535
|
}
|
|
578
536
|
}
|
|
579
|
-
|
|
537
|
+
|
|
580
538
|
return false
|
|
581
539
|
}
|
|
582
540
|
|
|
@@ -586,7 +544,7 @@ const createFileFromUniFile = (uniFile: any): File => {
|
|
|
586
544
|
type: uniFile.type || 'application/octet-stream',
|
|
587
545
|
lastModified: uniFile.lastModified || Date.now()
|
|
588
546
|
})
|
|
589
|
-
|
|
547
|
+
|
|
590
548
|
Object.defineProperties(file, {
|
|
591
549
|
size: {
|
|
592
550
|
value: uniFile.size,
|
|
@@ -597,7 +555,7 @@ const createFileFromUniFile = (uniFile: any): File => {
|
|
|
597
555
|
writable: false
|
|
598
556
|
}
|
|
599
557
|
})
|
|
600
|
-
|
|
558
|
+
|
|
601
559
|
return file
|
|
602
560
|
}
|
|
603
561
|
|
|
@@ -608,10 +566,10 @@ const startUpload = async (uploadFile: UploadFile) => {
|
|
|
608
566
|
emit('upload', uploadFile)
|
|
609
567
|
emit('before-upload', uploadFile)
|
|
610
568
|
updateFileList()
|
|
611
|
-
|
|
569
|
+
|
|
612
570
|
try {
|
|
613
571
|
let response: any
|
|
614
|
-
|
|
572
|
+
|
|
615
573
|
if (props.customRequest) {
|
|
616
574
|
// 使用自定义上传函数
|
|
617
575
|
response = await props.customRequest(
|
|
@@ -628,7 +586,7 @@ const startUpload = async (uploadFile: UploadFile) => {
|
|
|
628
586
|
} else {
|
|
629
587
|
throw new Error('请设置上传地址或自定义上传函数')
|
|
630
588
|
}
|
|
631
|
-
|
|
589
|
+
|
|
632
590
|
// 格式化响应数据
|
|
633
591
|
const formattedResponse = props.responseFormatter(response)
|
|
634
592
|
|
|
@@ -636,13 +594,13 @@ const startUpload = async (uploadFile: UploadFile) => {
|
|
|
636
594
|
uploadFile.progress = 100
|
|
637
595
|
uploadFile.response = formattedResponse
|
|
638
596
|
uploadFile.url = formattedResponse.url || uploadFile.url
|
|
639
|
-
|
|
597
|
+
|
|
640
598
|
emit('success', formattedResponse, uploadFile)
|
|
641
599
|
} catch (error) {
|
|
642
600
|
uploadFile.status = 'error'
|
|
643
601
|
uploadFile.error = error as Error
|
|
644
602
|
emit('error', error as Error, uploadFile)
|
|
645
|
-
|
|
603
|
+
|
|
646
604
|
uni.showToast({
|
|
647
605
|
title: `${uploadFile.name} 上传失败`,
|
|
648
606
|
icon: 'none'
|
|
@@ -655,11 +613,11 @@ const startUpload = async (uploadFile: UploadFile) => {
|
|
|
655
613
|
|
|
656
614
|
// 默认上传实现
|
|
657
615
|
const defaultUpload = (uploadFile: UploadFile): Promise<any> => {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
616
|
+
const headers = {
|
|
617
|
+
...props.headers,
|
|
618
|
+
'Authorization': `Bearer ${props.accessToken}`,
|
|
619
|
+
'AccessToken': props.accessToken,
|
|
620
|
+
}
|
|
663
621
|
|
|
664
622
|
return new Promise((resolve, reject) => {
|
|
665
623
|
uni.uploadFile({
|
|
@@ -692,21 +650,21 @@ const defaultUpload = (uploadFile: UploadFile): Promise<any> => {
|
|
|
692
650
|
complete: () => {
|
|
693
651
|
// 上传完成
|
|
694
652
|
},
|
|
695
|
-
fail: (e: any)=>{
|
|
653
|
+
fail: (e: any) => {
|
|
696
654
|
reject(e)
|
|
697
655
|
}
|
|
698
656
|
})
|
|
699
|
-
|
|
657
|
+
|
|
700
658
|
// // 监听上传进度
|
|
701
659
|
// task.onProgressUpdate = (res:any) => {
|
|
702
660
|
// uploadFile.progress = res.progress
|
|
703
661
|
// emit('progress', res.progress, uploadFile)
|
|
704
662
|
// updateFileList()
|
|
705
663
|
// }
|
|
706
|
-
|
|
664
|
+
|
|
707
665
|
// 保存任务用于取消
|
|
708
666
|
// uploadTasks.set(uploadFile.uid, task)
|
|
709
|
-
|
|
667
|
+
|
|
710
668
|
// // 上传完成后清理
|
|
711
669
|
// task.then(() => {
|
|
712
670
|
// uploadTasks.delete(uploadFile.uid)
|
|
@@ -727,7 +685,7 @@ const handleRemove = (file: UploadFile, index: number) => {
|
|
|
727
685
|
if (props.disabled || props.readonly) {
|
|
728
686
|
return
|
|
729
687
|
}
|
|
730
|
-
|
|
688
|
+
|
|
731
689
|
// 如果正在上传,先取消上传
|
|
732
690
|
if (file.status === 'uploading') {
|
|
733
691
|
const task = uploadTasks.get(file.uid)
|
|
@@ -736,7 +694,7 @@ const handleRemove = (file: UploadFile, index: number) => {
|
|
|
736
694
|
uploadTasks.delete(file.uid)
|
|
737
695
|
}
|
|
738
696
|
}
|
|
739
|
-
|
|
697
|
+
|
|
740
698
|
fileList.value.splice(index, 1)
|
|
741
699
|
emit('remove', file, index)
|
|
742
700
|
updateFileList()
|
|
@@ -747,7 +705,7 @@ const handleRetry = async (file: UploadFile, index: number) => {
|
|
|
747
705
|
if (props.disabled || props.readonly) {
|
|
748
706
|
return
|
|
749
707
|
}
|
|
750
|
-
|
|
708
|
+
|
|
751
709
|
await startUpload(file)
|
|
752
710
|
}
|
|
753
711
|
|
|
@@ -756,9 +714,9 @@ const handlePreview = (file: UploadFile) => {
|
|
|
756
714
|
if (!props.previewable) {
|
|
757
715
|
return
|
|
758
716
|
}
|
|
759
|
-
|
|
717
|
+
|
|
760
718
|
emit('preview', file)
|
|
761
|
-
|
|
719
|
+
|
|
762
720
|
// 如果是图片,使用 uni.previewImage
|
|
763
721
|
if (file.url && file.type?.startsWith('image/')) {
|
|
764
722
|
uni.previewImage({
|
|
@@ -784,11 +742,11 @@ const handleUploadAll = async () => {
|
|
|
784
742
|
if (props.disabled || props.readonly || !props.action) {
|
|
785
743
|
return
|
|
786
744
|
}
|
|
787
|
-
|
|
788
|
-
const pendingFiles = fileList.value.filter(file =>
|
|
745
|
+
|
|
746
|
+
const pendingFiles = fileList.value.filter(file =>
|
|
789
747
|
file.status === 'pending' || file.status === 'error'
|
|
790
748
|
)
|
|
791
|
-
|
|
749
|
+
|
|
792
750
|
if (pendingFiles.length === 0) {
|
|
793
751
|
uni.showToast({
|
|
794
752
|
title: '没有需要上传的文件',
|
|
@@ -796,13 +754,13 @@ const handleUploadAll = async () => {
|
|
|
796
754
|
})
|
|
797
755
|
return
|
|
798
756
|
}
|
|
799
|
-
|
|
757
|
+
|
|
800
758
|
uploadingAll.value = true
|
|
801
|
-
|
|
759
|
+
|
|
802
760
|
for (const file of pendingFiles) {
|
|
803
761
|
await startUpload(file)
|
|
804
762
|
}
|
|
805
|
-
|
|
763
|
+
|
|
806
764
|
uploadingAll.value = false
|
|
807
765
|
}
|
|
808
766
|
|
|
@@ -811,14 +769,14 @@ const handleClearAll = () => {
|
|
|
811
769
|
if (props.disabled || props.readonly) {
|
|
812
770
|
return
|
|
813
771
|
}
|
|
814
|
-
|
|
772
|
+
|
|
815
773
|
// 取消所有进行中的上传
|
|
816
774
|
uploadTasks.forEach(task => task.abort())
|
|
817
775
|
uploadTasks.clear()
|
|
818
|
-
|
|
776
|
+
|
|
819
777
|
fileList.value = []
|
|
820
778
|
updateFileList()
|
|
821
|
-
|
|
779
|
+
|
|
822
780
|
uni.showToast({
|
|
823
781
|
title: '已清空文件列表',
|
|
824
782
|
icon: 'success'
|
|
@@ -836,16 +794,16 @@ const upload = async (file: File) => {
|
|
|
836
794
|
progress: 0,
|
|
837
795
|
file: file
|
|
838
796
|
}
|
|
839
|
-
|
|
797
|
+
|
|
840
798
|
if (!props.multiple) {
|
|
841
799
|
fileList.value = [uploadFile]
|
|
842
800
|
} else {
|
|
843
801
|
fileList.value.push(uploadFile)
|
|
844
802
|
}
|
|
845
|
-
|
|
803
|
+
|
|
846
804
|
emit('select', file)
|
|
847
805
|
updateFileList()
|
|
848
|
-
|
|
806
|
+
|
|
849
807
|
await startUpload(uploadFile)
|
|
850
808
|
}
|
|
851
809
|
|
|
@@ -860,7 +818,7 @@ const abortUpload = (uid: string) => {
|
|
|
860
818
|
if (task) {
|
|
861
819
|
task.abort()
|
|
862
820
|
uploadTasks.delete(uid)
|
|
863
|
-
|
|
821
|
+
|
|
864
822
|
const file = fileList.value.find(f => f.uid === uid)
|
|
865
823
|
if (file) {
|
|
866
824
|
file.status = 'error'
|
|
@@ -883,12 +841,12 @@ defineExpose({
|
|
|
883
841
|
<style lang="scss" scoped>
|
|
884
842
|
.im-upload__area {
|
|
885
843
|
position: relative;
|
|
886
|
-
|
|
844
|
+
|
|
887
845
|
&--disabled {
|
|
888
846
|
opacity: 0.6;
|
|
889
847
|
pointer-events: none;
|
|
890
848
|
}
|
|
891
|
-
|
|
849
|
+
|
|
892
850
|
&--drag-over {
|
|
893
851
|
border-color: #409eff !important;
|
|
894
852
|
background-color: #ecf5ff !important;
|
|
@@ -921,7 +879,7 @@ defineExpose({
|
|
|
921
879
|
border-radius: 8rpx;
|
|
922
880
|
background-color: #fafafa;
|
|
923
881
|
transition: all 0.3s;
|
|
924
|
-
|
|
882
|
+
|
|
925
883
|
&:active {
|
|
926
884
|
border-color: #409eff;
|
|
927
885
|
background-color: #ecf5ff;
|
|
@@ -972,7 +930,7 @@ defineExpose({
|
|
|
972
930
|
border-radius: 8rpx;
|
|
973
931
|
background-color: #fafafa;
|
|
974
932
|
transition: all 0.3s;
|
|
975
|
-
|
|
933
|
+
|
|
976
934
|
&:active {
|
|
977
935
|
border-color: #409eff;
|
|
978
936
|
background-color: #ecf5ff;
|
|
@@ -1003,7 +961,7 @@ defineExpose({
|
|
|
1003
961
|
&--picture {
|
|
1004
962
|
margin-bottom: 24rpx;
|
|
1005
963
|
}
|
|
1006
|
-
|
|
964
|
+
|
|
1007
965
|
&--text {
|
|
1008
966
|
display: flex;
|
|
1009
967
|
justify-content: space-between;
|
|
@@ -1132,7 +1090,7 @@ defineExpose({
|
|
|
1132
1090
|
border-radius: 50%;
|
|
1133
1091
|
font-size: 24rpx;
|
|
1134
1092
|
cursor: pointer;
|
|
1135
|
-
|
|
1093
|
+
|
|
1136
1094
|
&:active {
|
|
1137
1095
|
background-color: rgba(0, 0, 0, 0.8);
|
|
1138
1096
|
}
|
|
@@ -1165,19 +1123,19 @@ defineExpose({
|
|
|
1165
1123
|
|
|
1166
1124
|
.im-upload__item-status-text {
|
|
1167
1125
|
font-size: 24rpx;
|
|
1168
|
-
|
|
1126
|
+
|
|
1169
1127
|
&--pending {
|
|
1170
1128
|
color: #909399;
|
|
1171
1129
|
}
|
|
1172
|
-
|
|
1130
|
+
|
|
1173
1131
|
&--uploading {
|
|
1174
1132
|
color: #409eff;
|
|
1175
1133
|
}
|
|
1176
|
-
|
|
1134
|
+
|
|
1177
1135
|
&--done {
|
|
1178
1136
|
color: #52c41a;
|
|
1179
1137
|
}
|
|
1180
|
-
|
|
1138
|
+
|
|
1181
1139
|
&--error {
|
|
1182
1140
|
color: #ff4d4f;
|
|
1183
1141
|
}
|
|
@@ -1197,14 +1155,14 @@ defineExpose({
|
|
|
1197
1155
|
font-size: 26rpx;
|
|
1198
1156
|
color: #409eff;
|
|
1199
1157
|
cursor: pointer;
|
|
1200
|
-
|
|
1158
|
+
|
|
1201
1159
|
&:active {
|
|
1202
1160
|
color: #337ecc;
|
|
1203
1161
|
}
|
|
1204
|
-
|
|
1162
|
+
|
|
1205
1163
|
&--remove {
|
|
1206
1164
|
color: #ff4d4f;
|
|
1207
|
-
|
|
1165
|
+
|
|
1208
1166
|
&:active {
|
|
1209
1167
|
color: #d9363e;
|
|
1210
1168
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// utils/file-chooser.js
|
|
2
|
+
export const chooseFile = (options = {}) => {
|
|
3
|
+
const defaultOptions = {
|
|
4
|
+
count: 1,
|
|
5
|
+
type: 'image'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const config = { ...defaultOptions, ...options }
|
|
9
|
+
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
// #ifdef H5 || APP-PLUS
|
|
12
|
+
uni.chooseFile({
|
|
13
|
+
...config,
|
|
14
|
+
success: (res) => resolve(res),
|
|
15
|
+
fail: (err) => reject(err)
|
|
16
|
+
})
|
|
17
|
+
// #endif
|
|
18
|
+
|
|
19
|
+
// #ifdef MP-WEIXIN
|
|
20
|
+
// 微信小程序需要根据type映射到不同的API
|
|
21
|
+
if (config.type === 'image' || config.type === 'all') {
|
|
22
|
+
wx.chooseImage({
|
|
23
|
+
count: config.count,
|
|
24
|
+
success: (res) => {
|
|
25
|
+
// 统一返回格式
|
|
26
|
+
const result = {
|
|
27
|
+
tempFilePaths: res.tempFilePaths,
|
|
28
|
+
tempFiles: res.tempFiles.map(file => ({
|
|
29
|
+
path: file.path,
|
|
30
|
+
size: file.size,
|
|
31
|
+
type: file.type || 'image'
|
|
32
|
+
}))
|
|
33
|
+
}
|
|
34
|
+
resolve(result)
|
|
35
|
+
},
|
|
36
|
+
fail: reject
|
|
37
|
+
})
|
|
38
|
+
} else {
|
|
39
|
+
wx.chooseMessageFile({
|
|
40
|
+
count: config.count,
|
|
41
|
+
type: config.type === 'video' ? 'video' : 'file',
|
|
42
|
+
success: (res) => {
|
|
43
|
+
resolve({
|
|
44
|
+
tempFilePaths: res.tempFiles.map(file => file.path),
|
|
45
|
+
tempFiles: res.tempFiles
|
|
46
|
+
})
|
|
47
|
+
},
|
|
48
|
+
fail: reject
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
// #endif
|
|
52
|
+
|
|
53
|
+
// #ifdef MP-ALIPAY
|
|
54
|
+
// 支付宝小程序处理
|
|
55
|
+
my.chooseImage({
|
|
56
|
+
count: config.count,
|
|
57
|
+
success: (res) => {
|
|
58
|
+
resolve({
|
|
59
|
+
tempFilePaths: res.apFilePaths,
|
|
60
|
+
tempFiles: res.apFilePaths.map(path => ({ path }))
|
|
61
|
+
})
|
|
62
|
+
},
|
|
63
|
+
fail: reject
|
|
64
|
+
})
|
|
65
|
+
// #endif
|
|
66
|
+
})
|
|
67
|
+
}
|