@idooel/components 0.0.2-beta.4 → 0.0.2-beta.6
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/dist/@idooel/components.esm.js +1449 -663
- package/dist/@idooel/components.umd.js +1449 -663
- package/package.json +10 -2
- package/packages/upload/src/index.vue +537 -205
- package/vitest.config.js +17 -0
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
:multiple="multiple"
|
|
15
15
|
:headers="headers"
|
|
16
16
|
:maximum="getMaximum"
|
|
17
|
+
:data="uploadParams"
|
|
17
18
|
@input-file="onWatchInputFiles"
|
|
18
19
|
@input="onWatchFiles"
|
|
19
20
|
style="width: 100%;">
|
|
@@ -35,17 +36,34 @@
|
|
|
35
36
|
</section>
|
|
36
37
|
</FileUpload>
|
|
37
38
|
<section class="ele-files__wrapper">
|
|
38
|
-
|
|
39
|
+
<!-- 显示正在上传的文件(有进度条) -->
|
|
40
|
+
<div class="ele-file__item" v-for="(file, idx) in uploadingFiles" :key="`uploading-${idx}`">
|
|
39
41
|
<div class="ele-file__suffix--icon">
|
|
40
42
|
<ele-icon :type="fileSuffixIcon[file.suffix] ? fileSuffixIcon[file.suffix].name : 'icon-file'"></ele-icon>
|
|
41
43
|
</div>
|
|
42
44
|
<div class="ele-file__name">
|
|
43
|
-
<div class="ele-file__inner"
|
|
44
|
-
<div v-if="
|
|
45
|
+
<div class="ele-file__inner">{{ file.name }}</div>
|
|
46
|
+
<div v-if="file.progress !== undefined" class="ele-uplpad__progress">
|
|
45
47
|
<a-progress :strokeWidth="2" :percent="Number(file.progress)" size="small" />
|
|
46
48
|
</div>
|
|
47
49
|
</div>
|
|
48
|
-
<div class="ele-file__delete" v-if="file.success || file.error
|
|
50
|
+
<div class="ele-file__delete" v-if="file.success || file.error">
|
|
51
|
+
<span class="ele-file__size">{{ (file.size / byteConversion).toFixed(2) }}M</span>
|
|
52
|
+
<span class="ele-file__delete--icon" @click="handleClickDelete(file)">
|
|
53
|
+
<ele-icon type="delete"></ele-icon>
|
|
54
|
+
</span>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<!-- 显示已上传完成的文件 -->
|
|
59
|
+
<div class="ele-file__item" v-for="(file, idx) in completedFiles" :key="`completed-${idx}`">
|
|
60
|
+
<div class="ele-file__suffix--icon">
|
|
61
|
+
<ele-icon :type="fileSuffixIcon[file.suffix] ? fileSuffixIcon[file.suffix].name : 'icon-file'"></ele-icon>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="ele-file__name">
|
|
64
|
+
<div class="ele-file__inner" @click="handleClickDownload(file)">{{ file.name }}</div>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="ele-file__delete">
|
|
49
67
|
<span class="ele-file__size">{{ (file.size / byteConversion).toFixed(2) }}M</span>
|
|
50
68
|
<span class="ele-file__delete--icon" @click="handleClickDelete(file)">
|
|
51
69
|
<ele-icon type="delete"></ele-icon>
|
|
@@ -60,7 +78,39 @@
|
|
|
60
78
|
import FileUpload from 'vue-upload-component'
|
|
61
79
|
import { v4 as uuidv4 } from 'uuid'
|
|
62
80
|
import { route, net, type } from '@idooel/shared'
|
|
63
|
-
|
|
81
|
+
|
|
82
|
+
// 常量定义
|
|
83
|
+
const CONSTANTS = {
|
|
84
|
+
DEFAULT_URL: 'zuul/api-file/workbench/file',
|
|
85
|
+
DEFAULT_ICON: '上传',
|
|
86
|
+
DEFAULT_SIZE: 100,
|
|
87
|
+
DEFAULT_MESSAGE: '单击或拖动文件到该区域以上传',
|
|
88
|
+
DEFAULT_MAXIMUM: 20,
|
|
89
|
+
BYTE_CONVERSION: 1024 * 1024,
|
|
90
|
+
CHUNK_MIN_SIZE: 3 * 1024 * 1024,
|
|
91
|
+
CHUNK_MAX_ACTIVE: 3,
|
|
92
|
+
CHUNK_MAX_RETRIES: 5,
|
|
93
|
+
SAVE_INTERVAL: 2000
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 文件后缀图标映射
|
|
97
|
+
const FILE_SUFFIX_ICONS = {
|
|
98
|
+
'doc': { name: 'icon-doc' },
|
|
99
|
+
'html': { name: 'icon-html' },
|
|
100
|
+
'mp4': { name: 'icon-mp' },
|
|
101
|
+
'pdf': { name: 'icon-pdf' },
|
|
102
|
+
'ppt': { name: 'icon-ppt' },
|
|
103
|
+
'psd': { name: 'icon-psd' },
|
|
104
|
+
'rtf': { name: 'icon-rtf' },
|
|
105
|
+
'txt': { name: 'icon-txt' },
|
|
106
|
+
'vis': { name: 'icon-vis' },
|
|
107
|
+
'xls': { name: 'icon-xls' },
|
|
108
|
+
'xml': { name: 'icon-xml' },
|
|
109
|
+
'zip': { name: 'icon-zip' },
|
|
110
|
+
'jpg': { name: 'icon-img' },
|
|
111
|
+
'mp3': { name: 'icon-mp1' }
|
|
112
|
+
}
|
|
113
|
+
|
|
64
114
|
export default {
|
|
65
115
|
name: 'ele-upload',
|
|
66
116
|
components: {
|
|
@@ -73,20 +123,19 @@ export default {
|
|
|
73
123
|
props: {
|
|
74
124
|
url: {
|
|
75
125
|
type: String,
|
|
76
|
-
|
|
77
|
-
default: `zuul/api-file/workbench/file`
|
|
126
|
+
default: CONSTANTS.DEFAULT_URL
|
|
78
127
|
},
|
|
79
128
|
icon: {
|
|
80
129
|
type: String,
|
|
81
|
-
default:
|
|
130
|
+
default: CONSTANTS.DEFAULT_ICON
|
|
82
131
|
},
|
|
83
132
|
size: {
|
|
84
133
|
type: Number,
|
|
85
|
-
default:
|
|
134
|
+
default: CONSTANTS.DEFAULT_SIZE
|
|
86
135
|
},
|
|
87
136
|
message: {
|
|
88
137
|
type: String,
|
|
89
|
-
default:
|
|
138
|
+
default: CONSTANTS.DEFAULT_MESSAGE
|
|
90
139
|
},
|
|
91
140
|
ext: {
|
|
92
141
|
type: String
|
|
@@ -99,7 +148,7 @@ export default {
|
|
|
99
148
|
},
|
|
100
149
|
maximum: {
|
|
101
150
|
type: Number,
|
|
102
|
-
default:
|
|
151
|
+
default: CONSTANTS.DEFAULT_MAXIMUM
|
|
103
152
|
},
|
|
104
153
|
multiple: {
|
|
105
154
|
type: Boolean,
|
|
@@ -127,7 +176,7 @@ export default {
|
|
|
127
176
|
},
|
|
128
177
|
byteConversion: {
|
|
129
178
|
type: Number,
|
|
130
|
-
default:
|
|
179
|
+
default: CONSTANTS.BYTE_CONVERSION
|
|
131
180
|
},
|
|
132
181
|
chunkEnabled: {
|
|
133
182
|
type: Boolean,
|
|
@@ -136,211 +185,365 @@ export default {
|
|
|
136
185
|
},
|
|
137
186
|
data() {
|
|
138
187
|
return {
|
|
139
|
-
|
|
140
|
-
|
|
188
|
+
// 文件状态管理
|
|
189
|
+
files: [], // vue-upload-component 管理的文件
|
|
190
|
+
buildedFiles: [], // 已构建完成的文件列表
|
|
191
|
+
|
|
192
|
+
// 上传状态管理
|
|
141
193
|
saveToServerAsyncPageTimer: null,
|
|
142
|
-
uploadRefId: null
|
|
194
|
+
uploadRefId: null,
|
|
195
|
+
// 多选且外部无值时,预先生成 groupId,确保首次上传参数完整
|
|
196
|
+
groupId: (this.multiple && !this.value) ? uuidv4() : null
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
created() {
|
|
200
|
+
// 多文件模式下,如果没有外部 groupId,则生成一个
|
|
201
|
+
if (this.multiple && !this.value) {
|
|
202
|
+
this.groupId = uuidv4()
|
|
203
|
+
console.log('Created with new groupId:', this.groupId)
|
|
143
204
|
}
|
|
144
205
|
},
|
|
145
206
|
watch: {
|
|
146
207
|
value: {
|
|
147
|
-
async handler
|
|
148
|
-
if (type.
|
|
149
|
-
|
|
150
|
-
} else if (
|
|
151
|
-
this.
|
|
152
|
-
this.buildedFiles = []
|
|
208
|
+
async handler(value) {
|
|
209
|
+
if (type.isEmpty(value)) {
|
|
210
|
+
this.resetFiles()
|
|
211
|
+
} else if (this.multiple) {
|
|
212
|
+
this.handleMultipleFileValue(value)
|
|
153
213
|
} else {
|
|
154
|
-
|
|
155
|
-
this.fetchFileWithFileId()
|
|
214
|
+
this.handleSingleFileValue()
|
|
156
215
|
}
|
|
157
216
|
},
|
|
158
217
|
immediate: true
|
|
159
218
|
}
|
|
160
219
|
},
|
|
161
220
|
computed: {
|
|
162
|
-
|
|
221
|
+
// ==================== 基础配置 ====================
|
|
222
|
+
prefixPath() {
|
|
163
223
|
return window.prefixPath
|
|
164
224
|
},
|
|
165
|
-
|
|
225
|
+
|
|
226
|
+
uploadRef() {
|
|
227
|
+
if (!this.uploadRefId) {
|
|
228
|
+
this.uploadRefId = `uploadRef_${uuidv4()}`
|
|
229
|
+
}
|
|
230
|
+
return this.uploadRefId
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
iconIsZhWord() {
|
|
166
234
|
return type.isZhWord(this.icon)
|
|
167
235
|
},
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
236
|
+
|
|
237
|
+
// ==================== 上传配置 ====================
|
|
238
|
+
uploadParams() {
|
|
239
|
+
return this.multiple ? { groupID: this.groupId } : {}
|
|
172
240
|
},
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
headers: {
|
|
177
|
-
...this.headers
|
|
178
|
-
},
|
|
179
|
-
minSize: 3 * this.byteConversion,
|
|
180
|
-
maxActive: 3,
|
|
181
|
-
maxRetries: 5,
|
|
182
|
-
startBody: {
|
|
183
|
-
override: true,
|
|
184
|
-
path: '/cw'
|
|
185
|
-
},
|
|
186
|
-
uploadBody: {
|
|
187
|
-
override: true,
|
|
188
|
-
path: '/cw'
|
|
189
|
-
},
|
|
190
|
-
finishBody: {
|
|
191
|
-
override: true,
|
|
192
|
-
path: '/cw'
|
|
193
|
-
}
|
|
194
|
-
}
|
|
241
|
+
|
|
242
|
+
fileSizeLimit() {
|
|
243
|
+
return this.size * this.byteConversion
|
|
195
244
|
},
|
|
196
|
-
|
|
197
|
-
|
|
245
|
+
|
|
246
|
+
postAction() {
|
|
247
|
+
const queryString = route.toQueryString(this.querys)
|
|
248
|
+
return `${this.prefixPath}${this.url}?${queryString}`
|
|
198
249
|
},
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
if ((this.isFileUploadSuccessed && this.buildedFiles.length >= 1) || !file.response) {
|
|
212
|
-
return false
|
|
213
|
-
} else {
|
|
214
|
-
return true
|
|
215
|
-
}
|
|
250
|
+
|
|
251
|
+
chunkConfig() {
|
|
252
|
+
return {
|
|
253
|
+
action: `${this.prefixPath}zuul/api-file/workbench/file/temp/chunk/vue`,
|
|
254
|
+
headers: { ...this.headers },
|
|
255
|
+
minSize: CONSTANTS.CHUNK_MIN_SIZE,
|
|
256
|
+
maxActive: CONSTANTS.CHUNK_MAX_ACTIVE,
|
|
257
|
+
maxRetries: CONSTANTS.CHUNK_MAX_RETRIES,
|
|
258
|
+
startBody: { override: true, path: '/cw' },
|
|
259
|
+
uploadBody: { override: true, path: '/cw' },
|
|
260
|
+
finishBody: { override: true, path: '/cw' }
|
|
216
261
|
}
|
|
217
262
|
},
|
|
218
|
-
|
|
263
|
+
|
|
264
|
+
getMaximum() {
|
|
219
265
|
return this.multiple ? this.maximum : 1
|
|
220
266
|
},
|
|
221
|
-
|
|
222
|
-
|
|
267
|
+
|
|
268
|
+
// ==================== 文件状态 ====================
|
|
269
|
+
uploadingFiles() {
|
|
270
|
+
return this.files.filter(file => file.progress !== undefined && !file.success)
|
|
223
271
|
},
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return
|
|
272
|
+
|
|
273
|
+
completedFiles() {
|
|
274
|
+
return this.buildedFiles.filter(file => file.fileID)
|
|
227
275
|
},
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
276
|
+
|
|
277
|
+
totalFiles() {
|
|
278
|
+
return this.uploadingFiles.length + this.completedFiles.length
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
isFileUploadSuccessed() {
|
|
282
|
+
const currentUploadingFiles = this.files.filter(file => file.response !== undefined)
|
|
283
|
+
if (currentUploadingFiles.length === 0) {
|
|
284
|
+
return this.buildedFiles.length > 0
|
|
231
285
|
}
|
|
232
|
-
return
|
|
286
|
+
return currentUploadingFiles.every(file => file.success)
|
|
233
287
|
},
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
'txt': { name: 'icon-txt' },
|
|
244
|
-
'vis': { name: 'icon-vis' },
|
|
245
|
-
'xls': { name: 'icon-xls' },
|
|
246
|
-
'xml': { name: 'icon-xml' },
|
|
247
|
-
'zip': { name: 'icon-zip' },
|
|
248
|
-
'jpg': { name: 'icon-img' },
|
|
249
|
-
'mp3': { name: 'icon-mp1' },
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
|
-
fileIds () {
|
|
253
|
-
const fileIds = this.buildedFiles.map(file => {
|
|
254
|
-
return file.fileID
|
|
255
|
-
})
|
|
256
|
-
return this.multiple ? fileIds : fileIds[0]
|
|
288
|
+
|
|
289
|
+
isShowUploadContainer() {
|
|
290
|
+
const maxFiles = this.multiple ? this.maximum : 1
|
|
291
|
+
return this.totalFiles < maxFiles
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
// ==================== 文件信息 ====================
|
|
295
|
+
fileSuffixIcon() {
|
|
296
|
+
return FILE_SUFFIX_ICONS
|
|
257
297
|
},
|
|
258
|
-
|
|
298
|
+
|
|
299
|
+
fileIds() {
|
|
300
|
+
if (this.multiple) {
|
|
301
|
+
return this.groupId
|
|
302
|
+
} else {
|
|
303
|
+
const fileIds = this.buildedFiles.map(file => file.fileID)
|
|
304
|
+
return fileIds[0]
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
fileResponseData() {
|
|
259
309
|
return this.multiple ? this.buildedFiles : this.buildedFiles[0]
|
|
260
310
|
}
|
|
261
311
|
},
|
|
262
312
|
methods: {
|
|
263
|
-
|
|
313
|
+
// ==================== 文件管理 ====================
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* 从多个数组中移除文件
|
|
317
|
+
*/
|
|
318
|
+
removeFromArrays(file, arrays, key = 'fileID') {
|
|
319
|
+
return arrays.map(arr =>
|
|
320
|
+
arr.filter(item => item[key] !== file[key] && item.id !== file.id)
|
|
321
|
+
)
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* 检查文件是否为新文件
|
|
326
|
+
*/
|
|
327
|
+
isNewFile(newFile, existingFiles, key = 'fileID') {
|
|
328
|
+
return newFile[key] && !existingFiles.some(existing => existing[key] === newFile[key])
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 合并文件数据
|
|
333
|
+
*/
|
|
334
|
+
mergeFileData(uploadFile) {
|
|
335
|
+
return {
|
|
336
|
+
...uploadFile.response.data,
|
|
337
|
+
...uploadFile
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 初始化文件列表
|
|
343
|
+
*/
|
|
344
|
+
async initializeFiles() {
|
|
264
345
|
if (!this.value) return
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
346
|
+
|
|
347
|
+
if (this.multiple) {
|
|
348
|
+
await this.fetchFilesWithGroupId()
|
|
349
|
+
} else {
|
|
350
|
+
await this.fetchFileWithFileId()
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 获取多文件组
|
|
356
|
+
*/
|
|
357
|
+
async fetchFilesWithGroupId() {
|
|
358
|
+
try {
|
|
359
|
+
const response = await net.get(`/api-file/workbench/file/group/${this.value}`)
|
|
360
|
+
const data = response.data || []
|
|
361
|
+
|
|
362
|
+
// 只有在没有现有文件时才设置初始文件列表
|
|
363
|
+
if (this.buildedFiles.length === 0) {
|
|
364
|
+
this.buildedFiles = data
|
|
365
|
+
console.log('Initial files loaded:', this.buildedFiles.length)
|
|
366
|
+
} else {
|
|
367
|
+
console.log('Keep existing files, skip initial load')
|
|
368
|
+
}
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.log('fetchFilesWithGroupId error:', error)
|
|
371
|
+
console.log('Keep current files, do not clear due to API error')
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 获取单文件
|
|
377
|
+
*/
|
|
378
|
+
async fetchFileWithFileId() {
|
|
379
|
+
try {
|
|
380
|
+
const response = await net.get(`/api-file/file/${this.value}`)
|
|
381
|
+
const data = response.data
|
|
269
382
|
this.buildedFiles = [data]
|
|
270
383
|
this.files = [data]
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const { fileID: fileId } = file
|
|
275
|
-
window.open(`/api-file/workbench/file/stream/${fileId}?origin=true`)
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.log('fetchFileWithFileId error:', error)
|
|
386
|
+
}
|
|
276
387
|
},
|
|
277
|
-
|
|
278
|
-
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* 处理文件删除
|
|
391
|
+
*/
|
|
392
|
+
handleClickDelete(file) {
|
|
279
393
|
const { fileID } = file
|
|
280
|
-
|
|
281
|
-
|
|
394
|
+
console.log('Deleting file:', { name: file.name, fileID })
|
|
395
|
+
|
|
396
|
+
// 从上传组件中移除文件
|
|
397
|
+
if (this.$refs[this.uploadRef]) {
|
|
398
|
+
this.$refs[this.uploadRef].remove(file)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 从所有数组中移除文件
|
|
402
|
+
[this.files, this.buildedFiles] = this.removeFromArrays(file, [this.files, this.buildedFiles])
|
|
403
|
+
|
|
404
|
+
console.log('After deletion - files:', this.files.length, 'buildedFiles:', this.buildedFiles.length)
|
|
405
|
+
|
|
406
|
+
// 多文件模式下,如果删除最后一个文件,重置 groupId
|
|
407
|
+
if (this.multiple && this.buildedFiles.length === 0) {
|
|
408
|
+
this.groupId = null
|
|
409
|
+
console.log('Reset groupId after deleting last file')
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// 触发 change 事件
|
|
282
413
|
this.$emit('change', this.fileIds)
|
|
283
414
|
},
|
|
284
|
-
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* 处理文件下载
|
|
418
|
+
*/
|
|
419
|
+
handleClickDownload(file) {
|
|
420
|
+
const { fileID: fileId } = file
|
|
421
|
+
window.open(`/api-file/workbench/file/stream/${fileId}?origin=true`)
|
|
422
|
+
},
|
|
423
|
+
// ==================== 上传处理 ====================
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* 处理文件上传状态变化
|
|
427
|
+
*/
|
|
428
|
+
onWatchFiles(files) {
|
|
429
|
+
console.log('onWatchFiles called with files:', files.length)
|
|
430
|
+
console.log('Current buildedFiles:', this.buildedFiles.length)
|
|
431
|
+
|
|
432
|
+
// 更新文件状态
|
|
285
433
|
this.files = files
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
})
|
|
434
|
+
|
|
435
|
+
// 处理已上传成功的文件
|
|
436
|
+
this.processUploadedFiles(files)
|
|
437
|
+
|
|
438
|
+
// 检查上传是否完成
|
|
292
439
|
if (this.isFileUploadSuccessed) {
|
|
293
440
|
this.$emit('change', this.fileIds)
|
|
294
441
|
this.$emit('on-success', this.fileResponseData)
|
|
295
442
|
}
|
|
296
443
|
},
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* 处理已上传成功的文件
|
|
447
|
+
*/
|
|
448
|
+
processUploadedFiles(files) {
|
|
449
|
+
// 处理所有有响应的文件(包括正在上传和已完成的)
|
|
450
|
+
const uploadedFiles = files.filter(file => file.response)
|
|
451
|
+
const newBuildedFiles = uploadedFiles.map(file => this.mergeFileData(file))
|
|
452
|
+
|
|
453
|
+
if (this.multiple) {
|
|
454
|
+
this.processMultipleFiles(newBuildedFiles)
|
|
455
|
+
} else {
|
|
456
|
+
// 单文件模式:只保留最新的文件
|
|
457
|
+
this.buildedFiles = newBuildedFiles
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
this.logFileStatus()
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* 处理多文件模式
|
|
465
|
+
*/
|
|
466
|
+
processMultipleFiles(newBuildedFiles) {
|
|
467
|
+
// 获取已存在的文件ID集合
|
|
468
|
+
const existingFileIds = new Set(this.buildedFiles.map(f => f.fileID).filter(id => id))
|
|
469
|
+
|
|
470
|
+
// 过滤出真正的新文件
|
|
471
|
+
const trulyNewFiles = newBuildedFiles.filter(newFile =>
|
|
472
|
+
this.isNewFile(newFile, this.buildedFiles)
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
console.log('Existing fileIDs:', Array.from(existingFileIds))
|
|
476
|
+
console.log('New uploaded files:', newBuildedFiles.map(f => ({ name: f.name, fileID: f.fileID })))
|
|
477
|
+
console.log('Truly new files:', trulyNewFiles.map(f => ({ name: f.name, fileID: f.fileID })))
|
|
478
|
+
|
|
479
|
+
// 将新文件追加到现有文件列表
|
|
480
|
+
if (trulyNewFiles.length > 0) {
|
|
481
|
+
this.buildedFiles = [...this.buildedFiles, ...trulyNewFiles]
|
|
482
|
+
console.log('Added new files, total buildedFiles:', this.buildedFiles.length)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// 更新现有文件的状态(包括新添加的文件)
|
|
486
|
+
this.updateExistingFiles(newBuildedFiles)
|
|
487
|
+
},
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* 更新现有文件状态
|
|
491
|
+
*/
|
|
492
|
+
updateExistingFiles(newBuildedFiles) {
|
|
493
|
+
this.buildedFiles = this.buildedFiles.map(existingFile => {
|
|
494
|
+
const updatedFile = newBuildedFiles.find(newFile => newFile.fileID === existingFile.fileID)
|
|
495
|
+
return updatedFile ? { ...existingFile, ...updatedFile } : existingFile
|
|
496
|
+
})
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* 记录文件状态日志
|
|
501
|
+
*/
|
|
502
|
+
logFileStatus() {
|
|
503
|
+
console.log('Final buildedFiles:', this.buildedFiles.length)
|
|
504
|
+
console.log('buildedFiles details:', this.buildedFiles.map(f => ({ name: f.name, fileID: f.fileID, success: f.success })))
|
|
505
|
+
console.log('Uploading files:', this.uploadingFiles.length)
|
|
506
|
+
console.log('Completed files:', this.completedFiles.length)
|
|
507
|
+
},
|
|
508
|
+
// ==================== 异步处理 ====================
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* 异步保存文件到服务器
|
|
512
|
+
*/
|
|
513
|
+
async saveToServerAsyncPage(payloads = {}) {
|
|
514
|
+
try {
|
|
515
|
+
const response = await net.post('zuul/api-file/workbench/file/temp/saveToServerAsyncPage', payloads, {
|
|
516
|
+
headers: {
|
|
517
|
+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
|
518
|
+
}
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
const { data } = response
|
|
304
522
|
if (data !== 'saveToServerAsyncPage') {
|
|
305
523
|
clearInterval(this.saveToServerAsyncPageTimer)
|
|
306
524
|
}
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
// if (data !== 'saveToServerAsyncPage') {
|
|
319
|
-
// clearInterval(timer)
|
|
320
|
-
// const { fileID, size } = data
|
|
321
|
-
// this.$emit('on-success', { ...data, fileId: fileID })
|
|
322
|
-
// this.$Message.success('同步成功')
|
|
323
|
-
// return { fileId: fileID, size }
|
|
324
|
-
// }
|
|
325
|
-
// })
|
|
326
|
-
// return ret
|
|
327
|
-
},
|
|
328
|
-
// 验证文件类型
|
|
525
|
+
} catch (error) {
|
|
526
|
+
console.error('saveToServerAsyncPage error:', error)
|
|
527
|
+
clearInterval(this.saveToServerAsyncPageTimer)
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
|
|
531
|
+
// ==================== 文件验证 ====================
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* 验证文件类型
|
|
535
|
+
*/
|
|
329
536
|
validateFileType(file) {
|
|
330
537
|
if (!file || !file.name) {
|
|
331
538
|
console.log('文件或文件名不存在')
|
|
332
539
|
return false
|
|
333
540
|
}
|
|
334
541
|
|
|
335
|
-
const
|
|
336
|
-
const fileExt = fileName.substring(fileName.lastIndexOf('.'))
|
|
542
|
+
const fileExt = this.getFileExtension(file.name)
|
|
337
543
|
console.log('文件扩展名:', fileExt)
|
|
338
544
|
|
|
339
|
-
// 只检查 extensions,accept 由 vue-upload-component 处理
|
|
340
545
|
if (this.extensions) {
|
|
341
|
-
const allowedExts = this.
|
|
342
|
-
.split(',')
|
|
343
|
-
.map(ext => ext.trim().startsWith('.') ? ext.trim() : `.${ext.trim()}`)
|
|
546
|
+
const allowedExts = this.getAllowedExtensions()
|
|
344
547
|
console.log('允许的扩展名:', allowedExts)
|
|
345
548
|
|
|
346
549
|
if (!allowedExts.includes(fileExt)) {
|
|
@@ -353,58 +556,181 @@ export default {
|
|
|
353
556
|
console.log('文件类型验证通过')
|
|
354
557
|
return true
|
|
355
558
|
},
|
|
356
|
-
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* 获取文件扩展名
|
|
562
|
+
*/
|
|
563
|
+
getFileExtension(fileName) {
|
|
564
|
+
return fileName.toLowerCase().substring(fileName.lastIndexOf('.'))
|
|
565
|
+
},
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* 获取允许的扩展名列表
|
|
569
|
+
*/
|
|
570
|
+
getAllowedExtensions() {
|
|
571
|
+
return this.extensions.toLowerCase()
|
|
572
|
+
.split(',')
|
|
573
|
+
.map(ext => ext.trim().startsWith('.') ? ext.trim() : `.${ext.trim()}`)
|
|
574
|
+
},
|
|
575
|
+
// ==================== 事件处理 ====================
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* 处理文件输入事件
|
|
579
|
+
*/
|
|
580
|
+
onWatchInputFiles(newFile, oldFile) {
|
|
357
581
|
if (newFile && !oldFile) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
console.log('chunk end')
|
|
385
|
-
const { data: { file, type } } = response
|
|
386
|
-
const payloads = {
|
|
387
|
-
filePath: file.match(/\/cw(.*)/) ? file.match(/\/cw(.*)/)[0] : void 0,
|
|
388
|
-
asyncID: uuidv4(),
|
|
389
|
-
isDeleteOrigin: false,
|
|
390
|
-
toImage: type === 'pdf' ? true : false,
|
|
391
|
-
unzip: type === 'zip' ? true : false,
|
|
392
|
-
_csrf: localStorage.getItem('token')
|
|
393
|
-
}
|
|
394
|
-
this.saveToServerAsyncPageTimer = setInterval(() => {
|
|
395
|
-
this.saveToServerAsyncPage(payloads)
|
|
396
|
-
}, 2000)
|
|
397
|
-
}
|
|
582
|
+
this.handleFileAdd(newFile)
|
|
583
|
+
} else if (newFile && oldFile) {
|
|
584
|
+
this.handleFileUpdate(newFile)
|
|
585
|
+
} else if (!newFile && oldFile) {
|
|
586
|
+
this.handleFileDelete()
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// 激活上传组件
|
|
590
|
+
this.activateUploadComponent(newFile, oldFile)
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* 处理文件添加
|
|
595
|
+
*/
|
|
596
|
+
handleFileAdd(newFile) {
|
|
597
|
+
console.log('add file:', newFile)
|
|
598
|
+
console.log('extensions:', this.extensions)
|
|
599
|
+
console.log('accept:', this.accept)
|
|
600
|
+
|
|
601
|
+
// 生成或使用 groupId
|
|
602
|
+
this.ensureGroupId()
|
|
603
|
+
|
|
604
|
+
// 验证文件类型
|
|
605
|
+
if (!this.validateFileType(newFile)) {
|
|
606
|
+
this.removeInvalidFile(newFile)
|
|
607
|
+
return
|
|
398
608
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
609
|
+
|
|
610
|
+
console.log('文件类型验证通过,继续上传')
|
|
611
|
+
},
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* 处理文件更新
|
|
615
|
+
*/
|
|
616
|
+
handleFileUpdate(newFile) {
|
|
617
|
+
console.log('update', newFile)
|
|
618
|
+
const { success, active, chunk, response } = newFile
|
|
619
|
+
|
|
620
|
+
if (chunk && success && !active) {
|
|
621
|
+
console.log('chunk end')
|
|
622
|
+
this.handleChunkComplete(response)
|
|
402
623
|
}
|
|
624
|
+
},
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* 处理文件删除
|
|
628
|
+
*/
|
|
629
|
+
handleFileDelete() {
|
|
630
|
+
console.log('delete')
|
|
631
|
+
},
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* 确保 groupId 存在
|
|
635
|
+
*/
|
|
636
|
+
ensureGroupId() {
|
|
637
|
+
console.log('onWatchInputFiles - multiple:', this.multiple, 'current groupId:', this.groupId)
|
|
638
|
+
|
|
639
|
+
if (this.multiple && !this.groupId) {
|
|
640
|
+
this.groupId = uuidv4()
|
|
641
|
+
console.log('Generated new groupId:', this.groupId)
|
|
642
|
+
} else if (this.multiple && this.groupId) {
|
|
643
|
+
console.log('Using existing groupId:', this.groupId)
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* 移除无效文件
|
|
649
|
+
*/
|
|
650
|
+
removeInvalidFile(file) {
|
|
651
|
+
console.log('文件类型验证失败,尝试移除文件')
|
|
652
|
+
console.log('uploadRef:', this.uploadRef)
|
|
653
|
+
console.log('$refs:', this.$refs)
|
|
654
|
+
|
|
655
|
+
if (this.$refs[this.uploadRef]) {
|
|
656
|
+
this.$refs[this.uploadRef].remove(file)
|
|
657
|
+
} else {
|
|
658
|
+
console.error('无法找到 uploadRef 引用')
|
|
659
|
+
}
|
|
660
|
+
},
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* 处理分片上传完成
|
|
664
|
+
*/
|
|
665
|
+
handleChunkComplete(response) {
|
|
666
|
+
const { data: { file, type } } = response
|
|
667
|
+
const payloads = {
|
|
668
|
+
filePath: file.match(/\/cw(.*)/) ? file.match(/\/cw(.*)/)[0] : void 0,
|
|
669
|
+
asyncID: uuidv4(),
|
|
670
|
+
isDeleteOrigin: false,
|
|
671
|
+
toImage: type === 'pdf',
|
|
672
|
+
unzip: type === 'zip',
|
|
673
|
+
_csrf: localStorage.getItem('token')
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
this.saveToServerAsyncPageTimer = setInterval(() => {
|
|
677
|
+
this.saveToServerAsyncPage(payloads)
|
|
678
|
+
}, CONSTANTS.SAVE_INTERVAL)
|
|
679
|
+
},
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* 激活上传组件
|
|
683
|
+
*/
|
|
684
|
+
activateUploadComponent(newFile, oldFile) {
|
|
403
685
|
if (Boolean(newFile) !== Boolean(oldFile) || oldFile.error !== newFile.error) {
|
|
404
686
|
if (!this.$refs[this.uploadRef].active) {
|
|
405
687
|
this.$refs[this.uploadRef].active = true
|
|
406
688
|
}
|
|
407
689
|
}
|
|
690
|
+
},
|
|
691
|
+
|
|
692
|
+
// ==================== 值变化处理 ====================
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* 重置文件状态
|
|
696
|
+
*/
|
|
697
|
+
resetFiles() {
|
|
698
|
+
this.files = []
|
|
699
|
+
this.buildedFiles = []
|
|
700
|
+
// 多选模式下保留或生成 groupId,避免首次上传为空
|
|
701
|
+
if (this.multiple) {
|
|
702
|
+
if (!this.groupId) {
|
|
703
|
+
this.groupId = uuidv4()
|
|
704
|
+
console.log('Generated groupId in resetFiles:', this.groupId)
|
|
705
|
+
} else {
|
|
706
|
+
console.log('Preserve existing groupId in resetFiles:', this.groupId)
|
|
707
|
+
}
|
|
708
|
+
} else {
|
|
709
|
+
this.groupId = null
|
|
710
|
+
console.log('Reset groupId to null (single mode)')
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* 处理多文件模式的值变化
|
|
716
|
+
*/
|
|
717
|
+
async handleMultipleFileValue(value) {
|
|
718
|
+
// multiple - value 就是 groupId
|
|
719
|
+
// 只有当 groupId 发生变化时才重新获取文件列表(初始化回显)
|
|
720
|
+
if (this.groupId !== value) {
|
|
721
|
+
this.groupId = value
|
|
722
|
+
console.log('Set groupId from external value:', this.groupId)
|
|
723
|
+
await this.fetchFilesWithGroupId()
|
|
724
|
+
} else {
|
|
725
|
+
console.log('GroupId unchanged, skip fetchFilesWithGroupId')
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* 处理单文件模式的值变化
|
|
731
|
+
*/
|
|
732
|
+
async handleSingleFileValue() {
|
|
733
|
+
await this.fetchFileWithFileId()
|
|
408
734
|
}
|
|
409
735
|
}
|
|
410
736
|
}
|
|
@@ -468,7 +794,13 @@ export default {
|
|
|
468
794
|
display: flex;
|
|
469
795
|
flex-direction: row;
|
|
470
796
|
align-items: center;
|
|
471
|
-
.ele-file__suffix--icon {
|
|
797
|
+
.ele-file__suffix--icon {
|
|
798
|
+
display: flex;
|
|
799
|
+
align-items: center;
|
|
800
|
+
justify-content: center;
|
|
801
|
+
width: 24px;
|
|
802
|
+
height: 24px;
|
|
803
|
+
}
|
|
472
804
|
.ele-file__name {
|
|
473
805
|
flex: 1;
|
|
474
806
|
text-align: left;
|