im-ui-mobile 0.1.13 → 0.1.15
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-upload/im-upload.vue +225 -265
- package/components/im-upload/utils/file-adapter.js +214 -0
- package/components/im-upload/utils/file-chooser.js +67 -0
- package/components/im-upload/utils/file-validator.js +306 -0
- package/package.json +1 -1
- package/types/components/upload.d.ts +6 -6
|
@@ -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,9 @@
|
|
|
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'
|
|
156
|
+
import { isFileTypeAccepted } from './utils/file-validator'
|
|
157
|
+
import { createFileFromUniFile } from './utils/file-adapter'
|
|
215
158
|
|
|
216
159
|
// 定义文件类型
|
|
217
160
|
interface UploadFile {
|
|
@@ -226,33 +169,33 @@ interface UploadFile {
|
|
|
226
169
|
response?: any
|
|
227
170
|
error?: Error
|
|
228
171
|
rawFile?: any
|
|
229
|
-
file?:
|
|
172
|
+
file?: Object
|
|
230
173
|
}
|
|
231
174
|
|
|
232
175
|
// 定义 Props
|
|
233
176
|
interface Props {
|
|
234
177
|
// 值
|
|
235
178
|
modelValue?: UploadFile[]
|
|
236
|
-
|
|
179
|
+
|
|
237
180
|
// 上传类型
|
|
238
181
|
type?: 'button' | 'card' | 'avatar' | 'drag' | undefined
|
|
239
|
-
|
|
182
|
+
|
|
240
183
|
// 按钮配置
|
|
241
184
|
buttonText?: string
|
|
242
|
-
buttonType?:
|
|
185
|
+
buttonType?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info' // 'primary' | 'default' | 'warning' | 'error'
|
|
243
186
|
buttonSize?: 'small' | 'medium' | 'large'
|
|
244
|
-
|
|
187
|
+
|
|
245
188
|
// 卡片配置
|
|
246
189
|
cardText?: string
|
|
247
|
-
|
|
190
|
+
|
|
248
191
|
// 头像配置
|
|
249
192
|
avatarText?: string
|
|
250
193
|
avatarSize?: number | string
|
|
251
194
|
avatarRadius?: string
|
|
252
|
-
|
|
195
|
+
|
|
253
196
|
// 拖拽配置
|
|
254
197
|
dragText?: string
|
|
255
|
-
|
|
198
|
+
|
|
256
199
|
// 通用配置
|
|
257
200
|
hint?: string
|
|
258
201
|
tip?: string
|
|
@@ -260,14 +203,14 @@ interface Props {
|
|
|
260
203
|
showActions?: boolean
|
|
261
204
|
showUploadAll?: boolean
|
|
262
205
|
showClear?: boolean
|
|
263
|
-
|
|
206
|
+
|
|
264
207
|
// 文件配置
|
|
265
208
|
accept?: string // 比如:image/*,.pdf,.doc,.docx
|
|
266
209
|
multiple?: boolean
|
|
267
210
|
maxCount?: number
|
|
268
211
|
maxSize?: number // 单位:字节
|
|
269
|
-
beforeUpload?: (file:
|
|
270
|
-
|
|
212
|
+
beforeUpload?: (file: Object) => boolean | Promise<boolean> // 上传前钩子
|
|
213
|
+
|
|
271
214
|
// 上传配置
|
|
272
215
|
action?: string
|
|
273
216
|
accessToken?: string,
|
|
@@ -277,36 +220,36 @@ interface Props {
|
|
|
277
220
|
withCredentials?: boolean
|
|
278
221
|
timeout?: number
|
|
279
222
|
autoUpload?: boolean
|
|
280
|
-
|
|
223
|
+
|
|
281
224
|
// 响应格式化
|
|
282
|
-
responseFormatter?: (response: any) => { url: string;
|
|
283
|
-
|
|
225
|
+
responseFormatter?: (response: any) => { url: string;[key: string]: any }
|
|
226
|
+
|
|
284
227
|
// 列表配置
|
|
285
228
|
showList?: boolean
|
|
286
229
|
listType?: 'text' | 'picture'
|
|
287
230
|
removable?: boolean
|
|
288
231
|
previewable?: boolean
|
|
289
|
-
|
|
232
|
+
|
|
290
233
|
// 状态
|
|
291
234
|
disabled?: boolean
|
|
292
235
|
readonly?: boolean
|
|
293
|
-
|
|
236
|
+
|
|
294
237
|
// 自定义上传
|
|
295
|
-
customRequest?: (file:
|
|
238
|
+
customRequest?: (file: Object, onProgress: (percent: number) => void) => Promise<any>
|
|
296
239
|
}
|
|
297
240
|
|
|
298
241
|
// 定义 Emits
|
|
299
242
|
interface Emits {
|
|
300
243
|
(e: 'update:modelValue', files: UploadFile[]): void
|
|
301
244
|
(e: 'change', files: UploadFile[]): void
|
|
302
|
-
(e: 'select', file:
|
|
245
|
+
(e: 'select', file: Object): void
|
|
303
246
|
(e: 'upload', file: UploadFile): void
|
|
304
247
|
(e: 'success', response: any, file: UploadFile): void
|
|
305
248
|
(e: 'error', error: Error, file: UploadFile): void
|
|
306
249
|
(e: 'progress', percent: number, file: UploadFile): void
|
|
307
250
|
(e: 'remove', file: UploadFile, index: number): void
|
|
308
251
|
(e: 'preview', file: UploadFile): void
|
|
309
|
-
(e: 'exceed', files:
|
|
252
|
+
(e: 'exceed', files: Object[]): void
|
|
310
253
|
(e: 'before-upload', file: UploadFile): void
|
|
311
254
|
(e: 'after-upload', file: UploadFile): void
|
|
312
255
|
}
|
|
@@ -314,54 +257,54 @@ interface Emits {
|
|
|
314
257
|
// 定义 Props 默认值
|
|
315
258
|
const props = withDefaults(defineProps<Props>(), {
|
|
316
259
|
modelValue: () => [],
|
|
317
|
-
|
|
260
|
+
|
|
318
261
|
type: undefined, // 'button',
|
|
319
|
-
|
|
262
|
+
|
|
320
263
|
buttonText: '上传文件',
|
|
321
264
|
buttonType: 'primary',
|
|
322
265
|
buttonSize: 'medium',
|
|
323
|
-
|
|
266
|
+
|
|
324
267
|
cardText: '点击上传',
|
|
325
|
-
|
|
268
|
+
|
|
326
269
|
avatarText: '上传头像',
|
|
327
270
|
avatarSize: 120,
|
|
328
271
|
avatarRadius: '50%',
|
|
329
|
-
|
|
272
|
+
|
|
330
273
|
dragText: '将文件拖到此处,或点击上传',
|
|
331
|
-
|
|
274
|
+
|
|
332
275
|
tip: '支持上传图片、文档等文件',
|
|
333
276
|
showTip: false,
|
|
334
277
|
showActions: false,
|
|
335
278
|
showUploadAll: true,
|
|
336
279
|
showClear: true,
|
|
337
|
-
|
|
280
|
+
|
|
338
281
|
accept: '*',
|
|
339
282
|
multiple: false,
|
|
340
283
|
maxCount: 9,
|
|
341
284
|
maxSize: 10 * 1024 * 1024,
|
|
342
|
-
|
|
285
|
+
|
|
343
286
|
action: '',
|
|
344
|
-
accessToken:'',
|
|
287
|
+
accessToken: '',
|
|
345
288
|
headers: () => ({}),
|
|
346
289
|
data: () => ({}),
|
|
347
290
|
name: 'file',
|
|
348
291
|
withCredentials: false,
|
|
349
292
|
timeout: 10000,
|
|
350
293
|
autoUpload: true,
|
|
351
|
-
|
|
352
|
-
responseFormatter: (response:any) => {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
294
|
+
|
|
295
|
+
responseFormatter: (response: any) => {
|
|
296
|
+
return {
|
|
297
|
+
url: response?.data?.url || response?.result?.url || response?.url || response?.fileUrl,
|
|
298
|
+
name: response?.data?.fileName || response?.result?.fileName || response?.data?.name || response?.result?.name || response?.name,
|
|
299
|
+
...response
|
|
300
|
+
}
|
|
358
301
|
},
|
|
359
|
-
|
|
302
|
+
|
|
360
303
|
showList: false,
|
|
361
304
|
listType: 'picture',
|
|
362
305
|
removable: true,
|
|
363
306
|
previewable: true,
|
|
364
|
-
|
|
307
|
+
|
|
365
308
|
disabled: false,
|
|
366
309
|
readonly: false
|
|
367
310
|
})
|
|
@@ -396,16 +339,16 @@ const generateUid = () => {
|
|
|
396
339
|
// 格式化文件大小
|
|
397
340
|
const formatSize = (bytes?: number) => {
|
|
398
341
|
if (!bytes) return '0 B'
|
|
399
|
-
|
|
342
|
+
|
|
400
343
|
const units = ['B', 'KB', 'MB', 'GB']
|
|
401
344
|
let size = bytes
|
|
402
345
|
let unitIndex = 0
|
|
403
|
-
|
|
346
|
+
|
|
404
347
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
405
348
|
size /= 1024
|
|
406
349
|
unitIndex++
|
|
407
350
|
}
|
|
408
|
-
|
|
351
|
+
|
|
409
352
|
return `${size.toFixed(1)} ${units[unitIndex]}`
|
|
410
353
|
}
|
|
411
354
|
|
|
@@ -425,10 +368,10 @@ const handleUploadTap = async () => {
|
|
|
425
368
|
if (props.disabled || props.readonly) {
|
|
426
369
|
return
|
|
427
370
|
}
|
|
428
|
-
|
|
429
|
-
const maxSelectable = props.multiple ?
|
|
371
|
+
|
|
372
|
+
const maxSelectable = props.multiple ?
|
|
430
373
|
Math.max(0, props.maxCount! - fileList.value.length) : 1
|
|
431
|
-
|
|
374
|
+
|
|
432
375
|
if (maxSelectable <= 0) {
|
|
433
376
|
emit('exceed', [])
|
|
434
377
|
uni.showToast({
|
|
@@ -437,24 +380,41 @@ const handleUploadTap = async () => {
|
|
|
437
380
|
})
|
|
438
381
|
return
|
|
439
382
|
}
|
|
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
|
-
})
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
const res = await chooseFile({
|
|
386
|
+
count: 9,
|
|
387
|
+
type: getFileType(props.accept) //'image' // image, video, all
|
|
388
|
+
})
|
|
389
|
+
for (const tempFile of res.tempFiles as Array<any>) {
|
|
390
|
+
await processFile(tempFile)
|
|
456
391
|
}
|
|
457
|
-
})
|
|
392
|
+
} catch (error) {
|
|
393
|
+
console.error('选择文件失败:', error)
|
|
394
|
+
uni.showToast({
|
|
395
|
+
title: '选择文件失败',
|
|
396
|
+
icon: 'none'
|
|
397
|
+
})
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
// uni.chooseFile({
|
|
402
|
+
// count: maxSelectable,
|
|
403
|
+
// type: getFileType(props.accept),
|
|
404
|
+
// extension: getFileExtensions(props.accept),
|
|
405
|
+
// success: async (res) => {
|
|
406
|
+
// for (const tempFile of res.tempFiles as Array<any>) {
|
|
407
|
+
// await processFile(tempFile)
|
|
408
|
+
// }
|
|
409
|
+
// },
|
|
410
|
+
// fail: (err) => {
|
|
411
|
+
// console.error('选择文件失败:', err)
|
|
412
|
+
// uni.showToast({
|
|
413
|
+
// title: '选择文件失败',
|
|
414
|
+
// icon: 'none'
|
|
415
|
+
// })
|
|
416
|
+
// }
|
|
417
|
+
// })
|
|
458
418
|
}
|
|
459
419
|
|
|
460
420
|
// 获取文件类型
|
|
@@ -469,13 +429,13 @@ const getFileExtensions = (accept: string): string[] | undefined => {
|
|
|
469
429
|
if (accept === '*' || accept.includes('all')) {
|
|
470
430
|
return undefined
|
|
471
431
|
}
|
|
472
|
-
|
|
432
|
+
|
|
473
433
|
const extensions = accept
|
|
474
434
|
.split(',')
|
|
475
435
|
.map(ext => ext.trim())
|
|
476
436
|
.filter(ext => ext.startsWith('.'))
|
|
477
437
|
.map(ext => ext.substring(1))
|
|
478
|
-
|
|
438
|
+
|
|
479
439
|
return extensions.length > 0 ? extensions : undefined
|
|
480
440
|
}
|
|
481
441
|
|
|
@@ -489,19 +449,19 @@ const processFile = async (uniFile: any) => {
|
|
|
489
449
|
})
|
|
490
450
|
return
|
|
491
451
|
}
|
|
492
|
-
|
|
452
|
+
|
|
493
453
|
// 检查文件类型
|
|
494
|
-
if (!isFileTypeAccepted(uniFile)) {
|
|
454
|
+
if (!isFileTypeAccepted(uniFile, props.accept)) {
|
|
495
455
|
uni.showToast({
|
|
496
456
|
title: '不支持的文件类型',
|
|
497
457
|
icon: 'none'
|
|
498
458
|
})
|
|
499
459
|
return
|
|
500
460
|
}
|
|
501
|
-
|
|
461
|
+
|
|
502
462
|
// 创建文件对象
|
|
503
463
|
const file = createFileFromUniFile(uniFile)
|
|
504
|
-
|
|
464
|
+
|
|
505
465
|
// 执行上传前钩子
|
|
506
466
|
if (props.beforeUpload) {
|
|
507
467
|
try {
|
|
@@ -514,7 +474,7 @@ const processFile = async (uniFile: any) => {
|
|
|
514
474
|
return
|
|
515
475
|
}
|
|
516
476
|
}
|
|
517
|
-
|
|
477
|
+
|
|
518
478
|
// 创建上传文件对象
|
|
519
479
|
const uploadFile: UploadFile = {
|
|
520
480
|
uid: generateUid(),
|
|
@@ -528,78 +488,78 @@ const processFile = async (uniFile: any) => {
|
|
|
528
488
|
rawFile: uniFile,
|
|
529
489
|
file
|
|
530
490
|
}
|
|
531
|
-
|
|
491
|
+
|
|
532
492
|
// 添加到文件列表
|
|
533
493
|
if (!props.multiple) {
|
|
534
494
|
fileList.value = [uploadFile]
|
|
535
495
|
} else {
|
|
536
496
|
fileList.value.push(uploadFile)
|
|
537
497
|
}
|
|
538
|
-
|
|
498
|
+
|
|
539
499
|
emit('select', file)
|
|
540
500
|
updateFileList()
|
|
541
|
-
|
|
501
|
+
|
|
542
502
|
// 自动上传
|
|
543
503
|
if (props.autoUpload && (props.action || props.customRequest)) {
|
|
544
504
|
await startUpload(uploadFile)
|
|
545
505
|
}
|
|
546
506
|
}
|
|
547
507
|
|
|
548
|
-
// 检查文件类型是否被接受
|
|
549
|
-
const isFileTypeAccepted = (uniFile: any): boolean => {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
508
|
+
// // 检查文件类型是否被接受
|
|
509
|
+
// const isFileTypeAccepted = (uniFile: any): boolean => {
|
|
510
|
+
// if (props.accept === '*') return true
|
|
511
|
+
|
|
512
|
+
// const acceptTypes = props.accept.split(',').map(type => type.trim())
|
|
513
|
+
|
|
514
|
+
// for (const acceptType of acceptTypes) {
|
|
515
|
+
// if (acceptType === '*') return true
|
|
516
|
+
|
|
517
|
+
// // 检查 MIME 类型
|
|
518
|
+
// if (acceptType.endsWith('/*')) {
|
|
519
|
+
// const category = acceptType.split('/')[0]
|
|
520
|
+
// if (uniFile.type?.startsWith(category + '/')) {
|
|
521
|
+
// return true
|
|
522
|
+
// }
|
|
523
|
+
// }
|
|
524
|
+
|
|
525
|
+
// // 检查扩展名
|
|
526
|
+
// if (acceptType.startsWith('.')) {
|
|
527
|
+
// const ext = acceptType.substring(1).toLowerCase()
|
|
528
|
+
// const fileName = uniFile.name.toLowerCase()
|
|
529
|
+
// if (fileName.endsWith('.' + ext)) {
|
|
530
|
+
// return true
|
|
531
|
+
// }
|
|
532
|
+
// }
|
|
533
|
+
|
|
534
|
+
// // 检查完整 MIME 类型
|
|
535
|
+
// if (uniFile.type === acceptType) {
|
|
536
|
+
// return true
|
|
537
|
+
// }
|
|
538
|
+
// }
|
|
539
|
+
|
|
540
|
+
// return false
|
|
541
|
+
// }
|
|
582
542
|
|
|
583
543
|
// 从 uniapp 文件创建 File 对象
|
|
584
|
-
const createFileFromUniFile = (uniFile: any): File => {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
}
|
|
544
|
+
// const createFileFromUniFile = (uniFile: any): File => {
|
|
545
|
+
// const file = new File([], uniFile.name, {
|
|
546
|
+
// type: uniFile.type || 'application/octet-stream',
|
|
547
|
+
// lastModified: uniFile.lastModified || Date.now()
|
|
548
|
+
// })
|
|
549
|
+
|
|
550
|
+
// Object.defineProperties(file, {
|
|
551
|
+
// size: {
|
|
552
|
+
// value: uniFile.size,
|
|
553
|
+
// writable: false
|
|
554
|
+
// },
|
|
555
|
+
// path: {
|
|
556
|
+
// value: uniFile.path,
|
|
557
|
+
// writable: false
|
|
558
|
+
// }
|
|
559
|
+
// })
|
|
560
|
+
|
|
561
|
+
// return file
|
|
562
|
+
// }
|
|
603
563
|
|
|
604
564
|
// 开始上传
|
|
605
565
|
const startUpload = async (uploadFile: UploadFile) => {
|
|
@@ -608,10 +568,10 @@ const startUpload = async (uploadFile: UploadFile) => {
|
|
|
608
568
|
emit('upload', uploadFile)
|
|
609
569
|
emit('before-upload', uploadFile)
|
|
610
570
|
updateFileList()
|
|
611
|
-
|
|
571
|
+
|
|
612
572
|
try {
|
|
613
573
|
let response: any
|
|
614
|
-
|
|
574
|
+
|
|
615
575
|
if (props.customRequest) {
|
|
616
576
|
// 使用自定义上传函数
|
|
617
577
|
response = await props.customRequest(
|
|
@@ -628,7 +588,7 @@ const startUpload = async (uploadFile: UploadFile) => {
|
|
|
628
588
|
} else {
|
|
629
589
|
throw new Error('请设置上传地址或自定义上传函数')
|
|
630
590
|
}
|
|
631
|
-
|
|
591
|
+
|
|
632
592
|
// 格式化响应数据
|
|
633
593
|
const formattedResponse = props.responseFormatter(response)
|
|
634
594
|
|
|
@@ -636,13 +596,13 @@ const startUpload = async (uploadFile: UploadFile) => {
|
|
|
636
596
|
uploadFile.progress = 100
|
|
637
597
|
uploadFile.response = formattedResponse
|
|
638
598
|
uploadFile.url = formattedResponse.url || uploadFile.url
|
|
639
|
-
|
|
599
|
+
|
|
640
600
|
emit('success', formattedResponse, uploadFile)
|
|
641
601
|
} catch (error) {
|
|
642
602
|
uploadFile.status = 'error'
|
|
643
603
|
uploadFile.error = error as Error
|
|
644
604
|
emit('error', error as Error, uploadFile)
|
|
645
|
-
|
|
605
|
+
|
|
646
606
|
uni.showToast({
|
|
647
607
|
title: `${uploadFile.name} 上传失败`,
|
|
648
608
|
icon: 'none'
|
|
@@ -655,11 +615,11 @@ const startUpload = async (uploadFile: UploadFile) => {
|
|
|
655
615
|
|
|
656
616
|
// 默认上传实现
|
|
657
617
|
const defaultUpload = (uploadFile: UploadFile): Promise<any> => {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
618
|
+
const headers = {
|
|
619
|
+
...props.headers,
|
|
620
|
+
'Authorization': `Bearer ${props.accessToken}`,
|
|
621
|
+
'AccessToken': props.accessToken,
|
|
622
|
+
}
|
|
663
623
|
|
|
664
624
|
return new Promise((resolve, reject) => {
|
|
665
625
|
uni.uploadFile({
|
|
@@ -692,21 +652,21 @@ const defaultUpload = (uploadFile: UploadFile): Promise<any> => {
|
|
|
692
652
|
complete: () => {
|
|
693
653
|
// 上传完成
|
|
694
654
|
},
|
|
695
|
-
fail: (e: any)=>{
|
|
655
|
+
fail: (e: any) => {
|
|
696
656
|
reject(e)
|
|
697
657
|
}
|
|
698
658
|
})
|
|
699
|
-
|
|
659
|
+
|
|
700
660
|
// // 监听上传进度
|
|
701
661
|
// task.onProgressUpdate = (res:any) => {
|
|
702
662
|
// uploadFile.progress = res.progress
|
|
703
663
|
// emit('progress', res.progress, uploadFile)
|
|
704
664
|
// updateFileList()
|
|
705
665
|
// }
|
|
706
|
-
|
|
666
|
+
|
|
707
667
|
// 保存任务用于取消
|
|
708
668
|
// uploadTasks.set(uploadFile.uid, task)
|
|
709
|
-
|
|
669
|
+
|
|
710
670
|
// // 上传完成后清理
|
|
711
671
|
// task.then(() => {
|
|
712
672
|
// uploadTasks.delete(uploadFile.uid)
|
|
@@ -727,7 +687,7 @@ const handleRemove = (file: UploadFile, index: number) => {
|
|
|
727
687
|
if (props.disabled || props.readonly) {
|
|
728
688
|
return
|
|
729
689
|
}
|
|
730
|
-
|
|
690
|
+
|
|
731
691
|
// 如果正在上传,先取消上传
|
|
732
692
|
if (file.status === 'uploading') {
|
|
733
693
|
const task = uploadTasks.get(file.uid)
|
|
@@ -736,7 +696,7 @@ const handleRemove = (file: UploadFile, index: number) => {
|
|
|
736
696
|
uploadTasks.delete(file.uid)
|
|
737
697
|
}
|
|
738
698
|
}
|
|
739
|
-
|
|
699
|
+
|
|
740
700
|
fileList.value.splice(index, 1)
|
|
741
701
|
emit('remove', file, index)
|
|
742
702
|
updateFileList()
|
|
@@ -747,7 +707,7 @@ const handleRetry = async (file: UploadFile, index: number) => {
|
|
|
747
707
|
if (props.disabled || props.readonly) {
|
|
748
708
|
return
|
|
749
709
|
}
|
|
750
|
-
|
|
710
|
+
|
|
751
711
|
await startUpload(file)
|
|
752
712
|
}
|
|
753
713
|
|
|
@@ -756,9 +716,9 @@ const handlePreview = (file: UploadFile) => {
|
|
|
756
716
|
if (!props.previewable) {
|
|
757
717
|
return
|
|
758
718
|
}
|
|
759
|
-
|
|
719
|
+
|
|
760
720
|
emit('preview', file)
|
|
761
|
-
|
|
721
|
+
|
|
762
722
|
// 如果是图片,使用 uni.previewImage
|
|
763
723
|
if (file.url && file.type?.startsWith('image/')) {
|
|
764
724
|
uni.previewImage({
|
|
@@ -784,11 +744,11 @@ const handleUploadAll = async () => {
|
|
|
784
744
|
if (props.disabled || props.readonly || !props.action) {
|
|
785
745
|
return
|
|
786
746
|
}
|
|
787
|
-
|
|
788
|
-
const pendingFiles = fileList.value.filter(file =>
|
|
747
|
+
|
|
748
|
+
const pendingFiles = fileList.value.filter(file =>
|
|
789
749
|
file.status === 'pending' || file.status === 'error'
|
|
790
750
|
)
|
|
791
|
-
|
|
751
|
+
|
|
792
752
|
if (pendingFiles.length === 0) {
|
|
793
753
|
uni.showToast({
|
|
794
754
|
title: '没有需要上传的文件',
|
|
@@ -796,13 +756,13 @@ const handleUploadAll = async () => {
|
|
|
796
756
|
})
|
|
797
757
|
return
|
|
798
758
|
}
|
|
799
|
-
|
|
759
|
+
|
|
800
760
|
uploadingAll.value = true
|
|
801
|
-
|
|
761
|
+
|
|
802
762
|
for (const file of pendingFiles) {
|
|
803
763
|
await startUpload(file)
|
|
804
764
|
}
|
|
805
|
-
|
|
765
|
+
|
|
806
766
|
uploadingAll.value = false
|
|
807
767
|
}
|
|
808
768
|
|
|
@@ -811,14 +771,14 @@ const handleClearAll = () => {
|
|
|
811
771
|
if (props.disabled || props.readonly) {
|
|
812
772
|
return
|
|
813
773
|
}
|
|
814
|
-
|
|
774
|
+
|
|
815
775
|
// 取消所有进行中的上传
|
|
816
776
|
uploadTasks.forEach(task => task.abort())
|
|
817
777
|
uploadTasks.clear()
|
|
818
|
-
|
|
778
|
+
|
|
819
779
|
fileList.value = []
|
|
820
780
|
updateFileList()
|
|
821
|
-
|
|
781
|
+
|
|
822
782
|
uni.showToast({
|
|
823
783
|
title: '已清空文件列表',
|
|
824
784
|
icon: 'success'
|
|
@@ -836,16 +796,16 @@ const upload = async (file: File) => {
|
|
|
836
796
|
progress: 0,
|
|
837
797
|
file: file
|
|
838
798
|
}
|
|
839
|
-
|
|
799
|
+
|
|
840
800
|
if (!props.multiple) {
|
|
841
801
|
fileList.value = [uploadFile]
|
|
842
802
|
} else {
|
|
843
803
|
fileList.value.push(uploadFile)
|
|
844
804
|
}
|
|
845
|
-
|
|
805
|
+
|
|
846
806
|
emit('select', file)
|
|
847
807
|
updateFileList()
|
|
848
|
-
|
|
808
|
+
|
|
849
809
|
await startUpload(uploadFile)
|
|
850
810
|
}
|
|
851
811
|
|
|
@@ -860,7 +820,7 @@ const abortUpload = (uid: string) => {
|
|
|
860
820
|
if (task) {
|
|
861
821
|
task.abort()
|
|
862
822
|
uploadTasks.delete(uid)
|
|
863
|
-
|
|
823
|
+
|
|
864
824
|
const file = fileList.value.find(f => f.uid === uid)
|
|
865
825
|
if (file) {
|
|
866
826
|
file.status = 'error'
|
|
@@ -883,12 +843,12 @@ defineExpose({
|
|
|
883
843
|
<style lang="scss" scoped>
|
|
884
844
|
.im-upload__area {
|
|
885
845
|
position: relative;
|
|
886
|
-
|
|
846
|
+
|
|
887
847
|
&--disabled {
|
|
888
848
|
opacity: 0.6;
|
|
889
849
|
pointer-events: none;
|
|
890
850
|
}
|
|
891
|
-
|
|
851
|
+
|
|
892
852
|
&--drag-over {
|
|
893
853
|
border-color: #409eff !important;
|
|
894
854
|
background-color: #ecf5ff !important;
|
|
@@ -921,7 +881,7 @@ defineExpose({
|
|
|
921
881
|
border-radius: 8rpx;
|
|
922
882
|
background-color: #fafafa;
|
|
923
883
|
transition: all 0.3s;
|
|
924
|
-
|
|
884
|
+
|
|
925
885
|
&:active {
|
|
926
886
|
border-color: #409eff;
|
|
927
887
|
background-color: #ecf5ff;
|
|
@@ -972,7 +932,7 @@ defineExpose({
|
|
|
972
932
|
border-radius: 8rpx;
|
|
973
933
|
background-color: #fafafa;
|
|
974
934
|
transition: all 0.3s;
|
|
975
|
-
|
|
935
|
+
|
|
976
936
|
&:active {
|
|
977
937
|
border-color: #409eff;
|
|
978
938
|
background-color: #ecf5ff;
|
|
@@ -1003,7 +963,7 @@ defineExpose({
|
|
|
1003
963
|
&--picture {
|
|
1004
964
|
margin-bottom: 24rpx;
|
|
1005
965
|
}
|
|
1006
|
-
|
|
966
|
+
|
|
1007
967
|
&--text {
|
|
1008
968
|
display: flex;
|
|
1009
969
|
justify-content: space-between;
|
|
@@ -1132,7 +1092,7 @@ defineExpose({
|
|
|
1132
1092
|
border-radius: 50%;
|
|
1133
1093
|
font-size: 24rpx;
|
|
1134
1094
|
cursor: pointer;
|
|
1135
|
-
|
|
1095
|
+
|
|
1136
1096
|
&:active {
|
|
1137
1097
|
background-color: rgba(0, 0, 0, 0.8);
|
|
1138
1098
|
}
|
|
@@ -1165,19 +1125,19 @@ defineExpose({
|
|
|
1165
1125
|
|
|
1166
1126
|
.im-upload__item-status-text {
|
|
1167
1127
|
font-size: 24rpx;
|
|
1168
|
-
|
|
1128
|
+
|
|
1169
1129
|
&--pending {
|
|
1170
1130
|
color: #909399;
|
|
1171
1131
|
}
|
|
1172
|
-
|
|
1132
|
+
|
|
1173
1133
|
&--uploading {
|
|
1174
1134
|
color: #409eff;
|
|
1175
1135
|
}
|
|
1176
|
-
|
|
1136
|
+
|
|
1177
1137
|
&--done {
|
|
1178
1138
|
color: #52c41a;
|
|
1179
1139
|
}
|
|
1180
|
-
|
|
1140
|
+
|
|
1181
1141
|
&--error {
|
|
1182
1142
|
color: #ff4d4f;
|
|
1183
1143
|
}
|
|
@@ -1197,14 +1157,14 @@ defineExpose({
|
|
|
1197
1157
|
font-size: 26rpx;
|
|
1198
1158
|
color: #409eff;
|
|
1199
1159
|
cursor: pointer;
|
|
1200
|
-
|
|
1160
|
+
|
|
1201
1161
|
&:active {
|
|
1202
1162
|
color: #337ecc;
|
|
1203
1163
|
}
|
|
1204
|
-
|
|
1164
|
+
|
|
1205
1165
|
&--remove {
|
|
1206
1166
|
color: #ff4d4f;
|
|
1207
|
-
|
|
1167
|
+
|
|
1208
1168
|
&:active {
|
|
1209
1169
|
color: #d9363e;
|
|
1210
1170
|
}
|