@idooel/components 0.0.2-beta.3 → 0.0.2-beta.5

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