webcake-ui-kit 1.2.15 → 1.2.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/package.json +2 -1
- package/src/components/file-type-icon/FileTypeIcon.vue +1 -1
- package/src/components/upload/Upload.vue +534 -0
- package/src/components/upload/UploadItem.vue +262 -0
- package/src/components/upload/UploadList.vue +51 -0
- package/src/components/upload/upload-item.css +250 -0
- package/src/components/upload/upload-list.css +17 -0
- package/src/components/upload/upload-utils.js +216 -0
- package/src/components/upload/upload.css +149 -0
- package/src/index.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-ui-kit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.17",
|
|
4
4
|
"description": "UI Kit for Vue 2 && 3 - Pure Options API",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"./WkToggleGroup": "./src/components/toggle-group/ToggleGroup.vue",
|
|
67
67
|
"./WkTooltip": "./src/components/tooltip/Tooltip.vue",
|
|
68
68
|
"./WkTypography": "./src/components/typography/Typography.vue",
|
|
69
|
+
"./WkUpload": "./src/components/upload/Upload.vue",
|
|
69
70
|
"./components/*": "./src/components/*",
|
|
70
71
|
"./icons": "./src/icons/index.js",
|
|
71
72
|
"./package.json": "./package.json"
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
<path opacity="0.3" d="M20 0L32 12H24C21.7909 12 20 10.2091 20 8V0Z" fill="white" />
|
|
78
78
|
</svg>
|
|
79
79
|
|
|
80
|
-
<div :class="['ui-file-type-icon--text-wrapper']" :style="textWrapperStyle">
|
|
80
|
+
<div v-if="fileType" :class="['ui-file-type-icon--text-wrapper']" :style="textWrapperStyle">
|
|
81
81
|
<span class="ui-file-type-icon--text" :style="textStyle">{{ fileType.toUpperCase() }}</span>
|
|
82
82
|
</div>
|
|
83
83
|
</template>
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="ui-upload"
|
|
4
|
+
:class="[
|
|
5
|
+
`ui-upload--${listType}`,
|
|
6
|
+
{
|
|
7
|
+
'ui-upload--disabled': disabled,
|
|
8
|
+
'ui-upload--drag-over': dragOver
|
|
9
|
+
}
|
|
10
|
+
]"
|
|
11
|
+
>
|
|
12
|
+
<!-- Dropzone trigger (default / picture list types) -->
|
|
13
|
+
<div
|
|
14
|
+
v-if="!isCard"
|
|
15
|
+
class="ui-upload__dropzone"
|
|
16
|
+
:class="{ 'ui-upload__dropzone--over': dragOver, 'ui-upload__dropzone--disabled': disabled }"
|
|
17
|
+
role="button"
|
|
18
|
+
:tabindex="disabled ? -1 : 0"
|
|
19
|
+
:aria-disabled="disabled ? 'true' : null"
|
|
20
|
+
@click="openFileDialog"
|
|
21
|
+
@keydown.enter.prevent="openFileDialog"
|
|
22
|
+
@keydown.space.prevent="openFileDialog"
|
|
23
|
+
@dragover.prevent="onDragOver"
|
|
24
|
+
@dragleave.prevent="onDragLeave"
|
|
25
|
+
@drop.prevent="onDrop"
|
|
26
|
+
@paste="onPaste"
|
|
27
|
+
>
|
|
28
|
+
<slot name="trigger">
|
|
29
|
+
<div class="ui-upload__dropzone-inner">
|
|
30
|
+
<span class="ui-upload__feature-icon">
|
|
31
|
+
<WkiUpload :size="20" />
|
|
32
|
+
</span>
|
|
33
|
+
<div class="ui-upload__dropzone-text">
|
|
34
|
+
<p class="ui-upload__dropzone-title">
|
|
35
|
+
{{ title }} <span class="ui-upload__link">{{ browseText }}</span>
|
|
36
|
+
</p>
|
|
37
|
+
<p class="ui-upload__dropzone-hint">{{ hint }}</p>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</slot>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<!-- File list (rows for default/picture, grid of tiles for picture-card/circle) -->
|
|
44
|
+
<UploadList
|
|
45
|
+
:items="visibleFileList"
|
|
46
|
+
:list-type="listType"
|
|
47
|
+
:show-preview-icon="showFlags.showPreviewIcon"
|
|
48
|
+
:show-remove-icon="showFlags.showRemoveIcon"
|
|
49
|
+
:show-download-icon="showFlags.showDownloadIcon"
|
|
50
|
+
:hide-list="showUploadList === false"
|
|
51
|
+
>
|
|
52
|
+
<template v-if="isCard" #trigger>
|
|
53
|
+
<div
|
|
54
|
+
v-if="showCardTrigger"
|
|
55
|
+
class="ui-upload__card-trigger"
|
|
56
|
+
:class="{ 'ui-upload__card-trigger--over': dragOver, 'ui-upload__card-trigger--disabled': disabled }"
|
|
57
|
+
role="button"
|
|
58
|
+
:tabindex="disabled ? -1 : 0"
|
|
59
|
+
:aria-disabled="disabled ? 'true' : null"
|
|
60
|
+
@click="openFileDialog"
|
|
61
|
+
@keydown.enter.prevent="openFileDialog"
|
|
62
|
+
@keydown.space.prevent="openFileDialog"
|
|
63
|
+
@dragover.prevent="onDragOver"
|
|
64
|
+
@dragleave.prevent="onDragLeave"
|
|
65
|
+
@drop.prevent="onDrop"
|
|
66
|
+
@paste="onPaste"
|
|
67
|
+
>
|
|
68
|
+
<slot name="trigger">
|
|
69
|
+
<WkiPlus :size="20" />
|
|
70
|
+
<span v-if="browseText" class="ui-upload__card-trigger-text">{{ browseText }}</span>
|
|
71
|
+
</slot>
|
|
72
|
+
</div>
|
|
73
|
+
</template>
|
|
74
|
+
</UploadList>
|
|
75
|
+
|
|
76
|
+
<input
|
|
77
|
+
ref="input"
|
|
78
|
+
type="file"
|
|
79
|
+
class="ui-upload__input"
|
|
80
|
+
:accept="accept"
|
|
81
|
+
:multiple="multiple"
|
|
82
|
+
:webkitdirectory="directory ? '' : null"
|
|
83
|
+
:directory="directory ? '' : null"
|
|
84
|
+
@change="onInputChange"
|
|
85
|
+
/>
|
|
86
|
+
|
|
87
|
+
<Dialog
|
|
88
|
+
v-if="previewVisible"
|
|
89
|
+
:open="previewVisible"
|
|
90
|
+
centered
|
|
91
|
+
:title="previewTitle"
|
|
92
|
+
:footer="false"
|
|
93
|
+
:width="520"
|
|
94
|
+
@change="onPreviewChange"
|
|
95
|
+
>
|
|
96
|
+
<div class="ui-upload__preview">
|
|
97
|
+
<img :src="previewImageSrc" :alt="previewTitle" class="ui-upload__preview-img" />
|
|
98
|
+
</div>
|
|
99
|
+
</Dialog>
|
|
100
|
+
</div>
|
|
101
|
+
</template>
|
|
102
|
+
|
|
103
|
+
<script>
|
|
104
|
+
import Dialog from '../dialog/Dialog.vue'
|
|
105
|
+
import UploadList from './UploadList.vue'
|
|
106
|
+
import WkiUpload from '../../icons/Upload.vue'
|
|
107
|
+
import WkiPlus from '../../icons/Plus.vue'
|
|
108
|
+
import {
|
|
109
|
+
LIST_IGNORE,
|
|
110
|
+
attrAccept,
|
|
111
|
+
defaultRequest,
|
|
112
|
+
file2Obj,
|
|
113
|
+
genUid,
|
|
114
|
+
getFileItem,
|
|
115
|
+
isImageFileType,
|
|
116
|
+
isImageUrl,
|
|
117
|
+
previewImage,
|
|
118
|
+
removeFileItem,
|
|
119
|
+
updateFileList
|
|
120
|
+
} from './upload-utils.js'
|
|
121
|
+
|
|
122
|
+
export default {
|
|
123
|
+
name: 'Upload',
|
|
124
|
+
|
|
125
|
+
components: { Dialog, UploadList, WkiUpload, WkiPlus },
|
|
126
|
+
|
|
127
|
+
provide() {
|
|
128
|
+
return { wkUpload: this }
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
props: {
|
|
132
|
+
// --- request config ---
|
|
133
|
+
action: { type: [String, Function], default: '' },
|
|
134
|
+
method: { type: String, default: 'post' },
|
|
135
|
+
headers: { type: Object, default: () => ({}) },
|
|
136
|
+
data: { type: [Object, Function], default: () => ({}) },
|
|
137
|
+
name: { type: String, default: 'file' },
|
|
138
|
+
withCredentials: { type: Boolean, default: false },
|
|
139
|
+
|
|
140
|
+
// --- data (controlled + uncontrolled) ---
|
|
141
|
+
fileList: { type: Array, default: null },
|
|
142
|
+
defaultFileList: { type: Array, default: () => [] },
|
|
143
|
+
|
|
144
|
+
// --- selection behavior ---
|
|
145
|
+
multiple: { type: Boolean, default: false },
|
|
146
|
+
maxCount: { type: Number, default: 0 },
|
|
147
|
+
maxSize: { type: Number, default: 0 },
|
|
148
|
+
accept: { type: String, default: '' },
|
|
149
|
+
disabled: { type: Boolean, default: false },
|
|
150
|
+
directory: { type: Boolean, default: false },
|
|
151
|
+
pastable: { type: Boolean, default: false },
|
|
152
|
+
|
|
153
|
+
// --- presentation ---
|
|
154
|
+
listType: {
|
|
155
|
+
type: String,
|
|
156
|
+
default: 'default',
|
|
157
|
+
validator: v => ['default', 'picture', 'picture-card', 'picture-circle'].includes(v)
|
|
158
|
+
},
|
|
159
|
+
showUploadList: { type: [Boolean, Object], default: true },
|
|
160
|
+
|
|
161
|
+
// --- trigger copy (dropzone / add-tile) ---
|
|
162
|
+
title: { type: String, default: 'Drop files here or' },
|
|
163
|
+
browseText: { type: String, default: 'browse files' },
|
|
164
|
+
hint: { type: String, default: 'SVG, PNG, JPG or GIF' },
|
|
165
|
+
|
|
166
|
+
// --- logic hooks (function props) ---
|
|
167
|
+
beforeUpload: { type: Function, default: null },
|
|
168
|
+
transformFile: { type: Function, default: null },
|
|
169
|
+
customRequest: { type: Function, default: null },
|
|
170
|
+
beforeRemove: { type: Function, default: null },
|
|
171
|
+
previewFile: { type: Function, default: null },
|
|
172
|
+
isImageUrl: { type: Function, default: null }
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
emits: ['change', 'preview', 'remove', 'download', 'progress', 'drop', 'reject', 'update:fileList'],
|
|
176
|
+
|
|
177
|
+
data() {
|
|
178
|
+
// NB: stateFileList is populated in created(), not here — Vue 3 does not guarantee
|
|
179
|
+
// methods (normalize/ensureUid) are bound during data(), unlike Vue 2.
|
|
180
|
+
return {
|
|
181
|
+
stateFileList: [],
|
|
182
|
+
dragOver: false,
|
|
183
|
+
previewVisible: false,
|
|
184
|
+
previewImageSrc: '',
|
|
185
|
+
previewTitle: ''
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
computed: {
|
|
190
|
+
isCard() {
|
|
191
|
+
return this.listType === 'picture-card' || this.listType === 'picture-circle'
|
|
192
|
+
},
|
|
193
|
+
visibleFileList() {
|
|
194
|
+
return this.stateFileList.filter(f => f.status !== 'removed')
|
|
195
|
+
},
|
|
196
|
+
showCardTrigger() {
|
|
197
|
+
if (this.disabled) return false
|
|
198
|
+
if (this.maxCount && this.visibleFileList.length >= this.maxCount) return false
|
|
199
|
+
// Single-file card (maxCount 1 or !multiple) hides the add-tile once a file exists —
|
|
200
|
+
// the existing tile itself is the replace surface.
|
|
201
|
+
if (!this.multiple && this.visibleFileList.length >= 1) return false
|
|
202
|
+
return true
|
|
203
|
+
},
|
|
204
|
+
showFlags() {
|
|
205
|
+
const s = this.showUploadList
|
|
206
|
+
if (s && typeof s === 'object') {
|
|
207
|
+
return {
|
|
208
|
+
showPreviewIcon: s.showPreviewIcon !== false,
|
|
209
|
+
showRemoveIcon: s.showRemoveIcon !== false,
|
|
210
|
+
showDownloadIcon: s.showDownloadIcon === true
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { showPreviewIcon: true, showRemoveIcon: true, showDownloadIcon: false }
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
watch: {
|
|
218
|
+
fileList(val) {
|
|
219
|
+
// Controlled mode: parent is the source of truth — re-sync our working copy.
|
|
220
|
+
if (val != null) this.stateFileList = this.normalize(val)
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
created() {
|
|
225
|
+
this.reqs = {}
|
|
226
|
+
this._uidCache = typeof WeakMap !== 'undefined' ? new WeakMap() : null
|
|
227
|
+
this.stateFileList = this.normalize(this.fileList != null ? this.fileList : this.defaultFileList)
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
// Declare both teardown hooks: Vue 2.7 fires beforeDestroy, Vue 3.4 fires beforeUnmount.
|
|
231
|
+
beforeUnmount() {
|
|
232
|
+
this.abortAll()
|
|
233
|
+
},
|
|
234
|
+
// eslint-disable-next-line vue/no-deprecated-destroyed-lifecycle, vue/no-dupe-keys
|
|
235
|
+
beforeDestroy() {
|
|
236
|
+
this.abortAll()
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
methods: {
|
|
240
|
+
// ---- normalization -------------------------------------------------
|
|
241
|
+
ensureUid(item) {
|
|
242
|
+
if (!this._uidCache) return genUid()
|
|
243
|
+
if (this._uidCache.has(item)) return this._uidCache.get(item)
|
|
244
|
+
const id = genUid()
|
|
245
|
+
this._uidCache.set(item, id)
|
|
246
|
+
return id
|
|
247
|
+
},
|
|
248
|
+
normalize(list) {
|
|
249
|
+
const self = this
|
|
250
|
+
return (list || []).map(item => ({
|
|
251
|
+
uid: item.uid != null ? item.uid : self.ensureUid(item),
|
|
252
|
+
name: item.name,
|
|
253
|
+
size: item.size,
|
|
254
|
+
type: item.type,
|
|
255
|
+
percent: item.percent != null ? item.percent : item.status === 'uploading' ? 0 : 100,
|
|
256
|
+
status: item.status || 'done',
|
|
257
|
+
url: item.url,
|
|
258
|
+
thumbUrl: item.thumbUrl,
|
|
259
|
+
originFileObj: item.originFileObj,
|
|
260
|
+
response: item.response,
|
|
261
|
+
error: item.error,
|
|
262
|
+
loaded: item.loaded,
|
|
263
|
+
total: item.total
|
|
264
|
+
}))
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
// ---- file dialog / drop / paste -----------------------------------
|
|
268
|
+
openFileDialog() {
|
|
269
|
+
if (this.disabled) return
|
|
270
|
+
if (this.$refs.input) this.$refs.input.click()
|
|
271
|
+
},
|
|
272
|
+
onInputChange(e) {
|
|
273
|
+
const files = e.target && e.target.files ? Array.prototype.slice.call(e.target.files) : []
|
|
274
|
+
this.uploadFiles(files)
|
|
275
|
+
// Reset so selecting the same file again re-triggers change.
|
|
276
|
+
if (e.target) e.target.value = ''
|
|
277
|
+
},
|
|
278
|
+
onDragOver() {
|
|
279
|
+
if (this.disabled) return
|
|
280
|
+
this.dragOver = true
|
|
281
|
+
},
|
|
282
|
+
onDragLeave() {
|
|
283
|
+
this.dragOver = false
|
|
284
|
+
},
|
|
285
|
+
onDrop(e) {
|
|
286
|
+
this.dragOver = false
|
|
287
|
+
if (this.disabled) return
|
|
288
|
+
this.$emit('drop', e)
|
|
289
|
+
const dt = e.dataTransfer
|
|
290
|
+
if (!dt) return
|
|
291
|
+
const files = dt.files ? Array.prototype.slice.call(dt.files) : []
|
|
292
|
+
if (files.length) this.uploadFiles(files)
|
|
293
|
+
},
|
|
294
|
+
onPaste(e) {
|
|
295
|
+
if (!this.pastable || this.disabled) return
|
|
296
|
+
const cd = e.clipboardData
|
|
297
|
+
if (!cd) return
|
|
298
|
+
const files = []
|
|
299
|
+
if (cd.items) {
|
|
300
|
+
Array.prototype.slice.call(cd.items).forEach(it => {
|
|
301
|
+
if (it.kind === 'file') {
|
|
302
|
+
const f = it.getAsFile()
|
|
303
|
+
if (f) files.push(f)
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
if (files.length) {
|
|
308
|
+
e.preventDefault()
|
|
309
|
+
this.uploadFiles(files)
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
// ---- pipeline ------------------------------------------------------
|
|
314
|
+
uploadFiles(rawFiles) {
|
|
315
|
+
let files = rawFiles.slice()
|
|
316
|
+
// maxCount === 1 → replace with the last selected.
|
|
317
|
+
if (this.maxCount === 1) {
|
|
318
|
+
files = files.slice(-1)
|
|
319
|
+
} else if (this.maxCount) {
|
|
320
|
+
const remaining = this.maxCount - this.visibleFileList.length
|
|
321
|
+
if (remaining <= 0) {
|
|
322
|
+
files.forEach(f => this.$emit('reject', { file: f, reason: 'maxCount' }))
|
|
323
|
+
return
|
|
324
|
+
}
|
|
325
|
+
if (files.length > remaining) {
|
|
326
|
+
files.slice(remaining).forEach(f => this.$emit('reject', { file: f, reason: 'maxCount' }))
|
|
327
|
+
files = files.slice(0, remaining)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const batch = files
|
|
331
|
+
files.forEach(raw => this.processFile(raw, batch))
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
async processFile(raw, batch) {
|
|
335
|
+
// Static validation first — fail fast without touching the list.
|
|
336
|
+
if (!attrAccept(raw, this.accept)) {
|
|
337
|
+
this.$emit('reject', { file: raw, reason: 'accept' })
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
if (this.maxSize && raw.size > this.maxSize) {
|
|
341
|
+
this.$emit('reject', { file: raw, reason: 'size' })
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const uid = genUid()
|
|
346
|
+
try {
|
|
347
|
+
raw.uid = uid
|
|
348
|
+
} catch (err) {
|
|
349
|
+
// Some environments expose File as non-extensible — tracking still works via file2Obj.
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
let file = raw
|
|
353
|
+
let before = true
|
|
354
|
+
if (this.beforeUpload) {
|
|
355
|
+
try {
|
|
356
|
+
before = this.beforeUpload(raw, batch)
|
|
357
|
+
if (before && typeof before.then === 'function') before = await before
|
|
358
|
+
} catch (err) {
|
|
359
|
+
before = false
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (before === LIST_IGNORE) return
|
|
364
|
+
|
|
365
|
+
// beforeUpload may return a replacement File/Blob to upload.
|
|
366
|
+
if (before && before !== true && typeof before === 'object' && (before instanceof Blob || before.name)) {
|
|
367
|
+
file = before
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (before === false) {
|
|
371
|
+
// Added to the list but not auto-uploaded (consumer will upload manually).
|
|
372
|
+
this.addFile(file2Obj(file, uid), { autoUpload: false })
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (this.transformFile) {
|
|
377
|
+
try {
|
|
378
|
+
let t = this.transformFile(file)
|
|
379
|
+
if (t && typeof t.then === 'function') t = await t
|
|
380
|
+
if (t) file = t
|
|
381
|
+
} catch (err) {
|
|
382
|
+
// keep original file on transform failure
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const item = file2Obj(file, uid)
|
|
387
|
+
this.addFile(item, { autoUpload: true, rawFile: file })
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
addFile(item, opts) {
|
|
391
|
+
const options = opts || {}
|
|
392
|
+
let nextList
|
|
393
|
+
if (this.maxCount === 1) nextList = [item]
|
|
394
|
+
else nextList = updateFileList(item, this.stateFileList)
|
|
395
|
+
this.commit(item, nextList)
|
|
396
|
+
|
|
397
|
+
// Generate a local thumbnail for image files (picture list types).
|
|
398
|
+
if (isImageFileType(item.type)) {
|
|
399
|
+
previewImage(item).then(url => {
|
|
400
|
+
if (!url) return
|
|
401
|
+
const current = getFileItem(item, this.stateFileList)
|
|
402
|
+
if (current) this.setFile({ ...current, thumbUrl: url })
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (options.autoUpload) this.post(item, options.rawFile || item.originFileObj)
|
|
407
|
+
else this.setFile({ ...item, status: 'done', percent: 100 })
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
post(item, rawFile) {
|
|
411
|
+
const actionVal = typeof this.action === 'function' ? this.action(rawFile) : this.action
|
|
412
|
+
const dataVal = typeof this.data === 'function' ? this.data(rawFile) : this.data
|
|
413
|
+
const uid = item.uid
|
|
414
|
+
|
|
415
|
+
// No endpoint and no custom uploader → treat as a manual/selected entry.
|
|
416
|
+
if (!this.customRequest && !actionVal) {
|
|
417
|
+
this.setFile({ ...item, status: 'done', percent: 100 })
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const request = this.customRequest || defaultRequest
|
|
422
|
+
const handle = request({
|
|
423
|
+
action: actionVal,
|
|
424
|
+
method: this.method,
|
|
425
|
+
headers: this.headers,
|
|
426
|
+
data: dataVal,
|
|
427
|
+
name: this.name,
|
|
428
|
+
filename: this.name,
|
|
429
|
+
withCredentials: this.withCredentials,
|
|
430
|
+
file: rawFile,
|
|
431
|
+
onProgress: e => this.onFileProgress(uid, e),
|
|
432
|
+
onSuccess: (res, xhr) => this.onFileSuccess(uid, res, xhr),
|
|
433
|
+
onError: (err, res) => this.onFileError(uid, err, res)
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
if (handle && typeof handle.abort === 'function') this.reqs[uid] = handle
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
onFileProgress(uid, e) {
|
|
440
|
+
const item = getFileItem({ uid }, this.stateFileList)
|
|
441
|
+
if (!item || item.status !== 'uploading') return
|
|
442
|
+
const percent = Math.round(e.percent || 0)
|
|
443
|
+
const updated = { ...item, percent, status: 'uploading', loaded: e.loaded, total: e.total }
|
|
444
|
+
this.setFile(updated)
|
|
445
|
+
this.$emit('progress', { percent, loaded: e.loaded, total: e.total }, updated)
|
|
446
|
+
},
|
|
447
|
+
onFileSuccess(uid, res) {
|
|
448
|
+
this.clearReq(uid)
|
|
449
|
+
const item = getFileItem({ uid }, this.stateFileList)
|
|
450
|
+
if (!item) return
|
|
451
|
+
this.setFile({ ...item, status: 'done', percent: 100, response: res })
|
|
452
|
+
},
|
|
453
|
+
onFileError(uid, err, res) {
|
|
454
|
+
this.clearReq(uid)
|
|
455
|
+
const item = getFileItem({ uid }, this.stateFileList)
|
|
456
|
+
if (!item) return
|
|
457
|
+
this.setFile({ ...item, status: 'error', error: err, response: res })
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
// ---- list mutation (immutable + emit) ------------------------------
|
|
461
|
+
setFile(file) {
|
|
462
|
+
const nextList = updateFileList(file, this.stateFileList)
|
|
463
|
+
this.commit(file, nextList)
|
|
464
|
+
},
|
|
465
|
+
commit(file, nextList) {
|
|
466
|
+
this.stateFileList = nextList
|
|
467
|
+
this.$emit('change', { file, fileList: nextList })
|
|
468
|
+
this.$emit('update:fileList', nextList)
|
|
469
|
+
},
|
|
470
|
+
|
|
471
|
+
// ---- item actions (called by UploadItem via inject) ----------------
|
|
472
|
+
async handleRemove(file) {
|
|
473
|
+
if (this.beforeRemove) {
|
|
474
|
+
try {
|
|
475
|
+
let r = this.beforeRemove(file, this.stateFileList)
|
|
476
|
+
if (r && typeof r.then === 'function') r = await r
|
|
477
|
+
if (r === false) return
|
|
478
|
+
} catch (err) {
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
this.abortReq(file.uid)
|
|
483
|
+
const removedList = removeFileItem(file, this.stateFileList)
|
|
484
|
+
if (removedList === null) return
|
|
485
|
+
const removedFile = { ...file, status: 'removed' }
|
|
486
|
+
this.stateFileList = removedList
|
|
487
|
+
this.$emit('remove', removedFile)
|
|
488
|
+
this.$emit('change', { file: removedFile, fileList: removedList })
|
|
489
|
+
this.$emit('update:fileList', removedList)
|
|
490
|
+
},
|
|
491
|
+
handleRetry(file) {
|
|
492
|
+
if (!file.originFileObj) return
|
|
493
|
+
const item = { ...file, status: 'uploading', percent: 0, error: undefined }
|
|
494
|
+
this.setFile(item)
|
|
495
|
+
this.post(item, file.originFileObj)
|
|
496
|
+
},
|
|
497
|
+
handlePreview(file) {
|
|
498
|
+
this.$emit('preview', file)
|
|
499
|
+
const src = file.url || file.thumbUrl
|
|
500
|
+
if (src && this.resolveIsImage(file)) {
|
|
501
|
+
this.previewImageSrc = src
|
|
502
|
+
this.previewTitle = file.name || 'Preview'
|
|
503
|
+
this.previewVisible = true
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
handleDownload(file) {
|
|
507
|
+
this.$emit('download', file)
|
|
508
|
+
if (file.url && typeof window !== 'undefined') window.open(file.url, '_blank')
|
|
509
|
+
},
|
|
510
|
+
|
|
511
|
+
// ---- helpers -------------------------------------------------------
|
|
512
|
+
resolveIsImage(file) {
|
|
513
|
+
if (typeof this.isImageUrl === 'function') return this.isImageUrl(file)
|
|
514
|
+
return isImageUrl(file)
|
|
515
|
+
},
|
|
516
|
+
abortReq(uid) {
|
|
517
|
+
const req = this.reqs[uid]
|
|
518
|
+
if (req && typeof req.abort === 'function') req.abort()
|
|
519
|
+
this.clearReq(uid)
|
|
520
|
+
},
|
|
521
|
+
clearReq(uid) {
|
|
522
|
+
if (this.reqs[uid]) delete this.reqs[uid]
|
|
523
|
+
},
|
|
524
|
+
abortAll() {
|
|
525
|
+
Object.keys(this.reqs || {}).forEach(uid => this.abortReq(uid))
|
|
526
|
+
},
|
|
527
|
+
onPreviewChange(v) {
|
|
528
|
+
this.previewVisible = v
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
</script>
|
|
533
|
+
|
|
534
|
+
<style src="./upload.css" scoped></style>
|