@netang/quasar 0.1.41 → 0.1.42

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/utils/uploader.js CHANGED
@@ -1,1059 +1,1063 @@
1
- import { ref, isRef } from 'vue'
2
- import SparkMD5 from 'spark-md5'
3
-
4
- import $n_has from 'lodash/has'
5
- import $n_get from 'lodash/get'
6
- import $n_toLower from 'lodash/toLower'
7
- import $n_findIndex from 'lodash/findIndex'
8
- import $n_uniq from 'lodash/uniq'
9
- import $n_find from 'lodash/find'
10
-
11
- import $n_isValidArray from '@netang/utils/isValidArray'
12
- import $n_isValidObject from '@netang/utils/isValidObject'
13
- import $n_isValidString from '@netang/utils/isValidString'
14
- import $n_isRequired from '@netang/utils/isRequired'
15
- import $n_forEach from '@netang/utils/forEach'
16
- import $n_json from '@netang/utils/json'
17
- import $n_join from '@netang/utils/join'
18
- import $n_split from '@netang/utils/split'
19
- import $n_trimString from '@netang/utils/trimString'
20
- import $n_run from '@netang/utils/run'
21
- import $n_isValidValue from '@netang/utils/isValidValue'
22
- import $n_copy from '@netang/utils/copy'
23
- import $n_http from '@netang/utils/http'
24
- import $n_getThrowMessage from '@netang/utils/getThrowMessage'
25
-
26
- import $n_toast from './toast'
27
- import $n_confirm from './confirm'
28
- import $n_alert from './alert'
29
- import $n_previewImage from './previewImage'
30
- import $n_getImage from './getImage'
31
- import $n_getFile from './getFile'
32
- import $n_config from './config'
33
-
34
- import {
35
- // 文件类型映射
36
- FilE_TYPE,
37
- // 文件名称映射
38
- FilE_NAME,
39
- // 上传状态
40
- UPLOAD_STATUS,
41
- // 上传器
42
- UPLOADERS,
43
- } from './useUploader'
44
-
45
- // 文件数量
46
- let _fileNum = 0
47
-
48
- /**
49
- * 创建上传器
50
- */
51
- function create(options) {
52
-
53
- // ==========【数据】=========================================================================================
54
-
55
- const {
56
- // 上传文件输入框节点
57
- fileRef,
58
- // 更新值方法(初始化上传列表时不更新值)
59
- onUpdateModelValue,
60
- // 更新方法
61
- onUpdate,
62
-
63
- } = Object.assign({
64
- // 更新值方法
65
- onUpdateModelValue: null,
66
- // 更新方法
67
- onUpdate: null,
68
- }, options)
69
-
70
- // 声明属性
71
- const props = Object.assign({
72
- // 值
73
- modelValue: '',
74
- // 上传文件类型, 可选值 file image video audio
75
- type: 'image',
76
- // 上传文件数量(0:不限)
77
- count: 0,
78
- // 单个文件的最大大小(单位: MB)
79
- maxSize: 0,
80
- // 单个文件的限制后缀
81
- exts: [],
82
- // true: 值格式为数组, 如 ['xxxxxx', 'xxxxxx', 'xxxxxx']
83
- // false: 值格式为字符串, 如 xxxxxx,xxxxxx,xxxxxx
84
- valueArray: false,
85
- // 是否去重
86
- unique: false,
87
- // 是否初始加载文件信息(仅图片有效, 其他类型自动会加载文件信息)
88
- loadInfo: false,
89
- // 单文件上传提示
90
- confirm: false,
91
- }, $n_get(options, 'props'))
92
-
93
- // 上传文件列表
94
- const uploadFileLists = $n_has(options, 'uploadFileLists') && isRef(options.uploadFileLists) ? options.uploadFileLists : ref([])
95
-
96
- /**
97
- * 上传配置
98
- */
99
- const configUpload = Object.assign(
100
- {
101
- type: 'local',
102
- },
103
- $n_config('uploader.upload')
104
- )
105
- const configLimit = Object.assign({
106
- maxSize: 100,
107
- exts: [],
108
- }, $n_config('uploader.limit.' + props.type))
109
-
110
- // 如果有单个文件的最大大小
111
- if (props.maxSize) {
112
- configLimit.maxSize = props.maxSize
113
- }
114
-
115
- // 如果有单个文件的限制后缀
116
- if ($n_isValidArray(props.exts)) {
117
- configLimit.exts = props.exts
118
- }
119
-
120
- // ==========【计算属性】=========================================================================================
121
-
122
- /**
123
- * 上传文件后缀名
124
- */
125
- // const accept = computed(function () {
126
- //
127
- // })
128
-
129
- // ==========【监听数据】==============================================================================================
130
-
131
- /**
132
- * 监听上传文件列表
133
- */
134
-
135
- // ==========【方法】=================================================================================================
136
-
137
- /**
138
- * 获取值
139
- */
140
- function getValue() {
141
-
142
- const hashs = []
143
- const files = []
144
-
145
- for (const fileItem of uploadFileLists.value) {
146
- if (fileItem.status === UPLOAD_STATUS.success) {
147
- hashs.push(fileItem.hash)
148
- files.push(fileItem)
149
- }
150
- }
151
-
152
- const hashsString = $n_join(hashs, ',')
153
-
154
- return {
155
- value: props.valueArray ? hashs : hashsString,
156
- hashsString,
157
- hashs,
158
- files,
159
- }
160
- }
161
-
162
- /**
163
- * 更新值
164
- */
165
- function updateValue() {
166
-
167
- // 获取值
168
- const result = getValue()
169
-
170
- // 更新值
171
- $n_run(onUpdateModelValue)(result)
172
-
173
- // 更新
174
- $n_run(onUpdate)(result)
175
- }
176
-
177
- /**
178
- * 更新
179
- */
180
- function update() {
181
- // 更新
182
- $n_run(onUpdate)(getValue())
183
- }
184
-
185
- /**
186
- * 初始化上传列表
187
- */
188
- async function initUploadFileLists() {
189
- if ($n_isRequired(props.modelValue)) {
190
-
191
- // 获取值数组
192
- const hashs = props.valueArray ? props.modelValue : $n_split(props.modelValue, ',')
193
-
194
- // 如果类型不是图片 || 初始加载文件信息, 则请求文件信息
195
- if (props.type !== 'image' || props.loadInfo) {
196
-
197
- // 请求 - 获取文件
198
- const { status, data: resExisted } = await $n_http({
199
- url: $n_config('apiFileUrl') + 'get_file',
200
- data: {
201
- hashs,
202
- },
203
- // 关闭错误
204
- warn: false,
205
- })
206
- if (status) {
207
-
208
- $n_forEach(resExisted, function (existedItem) {
209
-
210
- // 创建原始单个文件
211
- const fileItem = createRawFileItem()
212
-
213
- // 设置已存在文件
214
- setExistedFileItem(fileItem, existedItem)
215
-
216
- // 添加至上传文件列表
217
- uploadFileLists.value.push(Object.assign(fileItem, {
218
- key: fileItem.hash,
219
- }))
220
- })
221
-
222
- // 更新
223
- update()
224
- }
225
- return
226
- }
227
-
228
- $n_forEach(hashs, function(hash) {
229
-
230
- // 添加至上传文件列表
231
- uploadFileLists.value.push(Object.assign(createRawFileItem(), {
232
- // 文件唯一 key
233
- key: hash,
234
- // hash
235
- hash,
236
- // 状态
237
- status: UPLOAD_STATUS.success,
238
- // 进度
239
- progress: 100,
240
- // 信息
241
- msg: '',
242
- }))
243
- })
244
- }
245
- }
246
-
247
- /**
248
- * 检查是否正在上传文件
249
- */
250
- function checkUploading() {
251
- for (const fileItem of uploadFileLists.value) {
252
- if (fileItem.status < UPLOAD_STATUS.success) {
253
- return true
254
- }
255
- }
256
- return false
257
- }
258
-
259
- /**
260
- * 选择文件上传
261
- */
262
- function chooseUpload() {
263
- // 点击文件输入框
264
- fileRef.value.click()
265
- }
266
-
267
- /**
268
- * 文件输入框更新
269
- */
270
- function fileChange(e) {
271
-
272
- try {
273
- // 获取上传文件
274
- const files = Array.from(e.target.files)
275
-
276
- // 清空上传文件输入框内容
277
- fileRef.value.value = ''
278
-
279
- // 如果没有选择文件
280
- if (! files.length) {
281
- // 则无任何操作
282
- return
283
- }
284
-
285
- // 遍历选择的文件列表
286
- for (const file of files) {
287
-
288
- // 创建单个文件
289
- const fileItem = createFileItem(file)
290
- if (fileItem !== false) {
291
-
292
- // 如果只能上传一个
293
- if (props.count === 1) {
294
-
295
- // 如果有上传文件列表
296
- if (uploadFileLists.value.length) {
297
-
298
- // 如果开启单文件上传提示
299
- if (props.confirm) {
300
-
301
- // 确认框
302
- $n_confirm({
303
- message: '最多只能上传1个文件,确认上传并替换吗?',
304
- })
305
- // 点击确认执行
306
- .onOk(function () {
307
-
308
- // 删除所有文件
309
- deleteAll()
310
-
311
- // 添加至上传文件列表
312
- uploadFileLists.value.push(fileItem)
313
-
314
- // 开始上传
315
- startUpload()
316
- .finally()
317
- })
318
- return
319
- }
320
-
321
- // 删除所有文件
322
- deleteAll()
323
- }
324
-
325
- } else if (
326
- // 如果有上传数量限制
327
- props.count > 1
328
- // 上传文件列表数量 === 上传数量限制
329
- && uploadFileLists.value.length >= props.count
330
- ) {
331
- // 轻提示
332
- $n_toast({
333
- message: `最多只能上传${props.count}个文件,请先删除后再上传`,
334
- })
335
- return
336
- }
337
-
338
- // 添加至上传文件列表
339
- uploadFileLists.value.push(fileItem)
340
- }
341
- }
342
-
343
- // 开始上传
344
- startUpload()
345
- .finally()
346
-
347
- } catch (e) {
348
-
349
- // 错误提示
350
- $n_alert({
351
- message: $n_getThrowMessage(e),
352
- })
353
- }
354
-
355
- }
356
-
357
- /**
358
- * 开始上传
359
- */
360
- async function startUpload() {
361
-
362
- // 【设置待上传文件的 hash】
363
- // --------------------------------------------------
364
- const promises = []
365
- for (const fileItem of uploadFileLists.value) {
366
- // 如果是等待上传的文件
367
- if (fileItem.status === UPLOAD_STATUS.waiting) {
368
-
369
- // 检查文件错误
370
- const errMsg = checkFileError(fileItem)
371
- if (errMsg) {
372
- // 设置文件上传失败
373
- setFileFail(fileItem, errMsg)
374
-
375
- } else {
376
- // 设置文件 hash
377
- promises.push(setFileHash(fileItem))
378
- }
379
- }
380
- }
381
- await Promise.all(promises)
382
-
383
- // 检查待上传文件在服务器上是否存在
384
- // --------------------------------------------------
385
- if (! await checkWaitUploadFileExists()) {
386
- return
387
- }
388
-
389
- // 上传
390
- await upload()
391
- }
392
-
393
- /**
394
- * 上传
395
- */
396
- let _upload = null
397
- async function upload() {
398
- try {
399
- if (! _upload) {
400
- const run = $n_get(UPLOADERS, configUpload.type)
401
- if (run) {
402
- _upload = (await run()).default
403
- }
404
- if (! _upload) {
405
- // 错误提示
406
- $n_alert({
407
- message: '没有定义上传器',
408
- })
409
- return
410
- }
411
- }
412
-
413
- // 待上传文件列表
414
- const waitUploadFileLists = []
415
- for (const fileItem of uploadFileLists.value) {
416
- // 检查存在服务器完成
417
- if (fileItem.status === UPLOAD_STATUS.existChecked) {
418
- waitUploadFileLists.push(fileItem)
419
- }
420
- }
421
- if (! waitUploadFileLists.length) {
422
- return
423
- }
424
-
425
- // 上传
426
- await _upload({
427
- config: configUpload,
428
- waitUploadFileLists,
429
- uploadFileLists,
430
- checkFileError,
431
- setFileSuccess,
432
- setFileFail,
433
- })
434
-
435
- } catch (e) {
436
- // 错误提示
437
- $n_alert({
438
- message: $n_getThrowMessage(e),
439
- })
440
- }
441
- }
442
-
443
- /**
444
- * 设置文件上传成功
445
- */
446
- const setFileSuccess = (fileItem) => {
447
-
448
- // 设置文件状态
449
- fileItem.status = UPLOAD_STATUS.success
450
- // 设置文件信息
451
- fileItem.msg = ''
452
- // 设置文件检查进度
453
- fileItem.progress = 0
454
-
455
- // // 单个文件上传结束回调
456
- // uploadQueryCallback(fileItem)
457
- //
458
- // // 上传更新回调
459
- // uploadChange(getResultData())
460
-
461
- // 更新值
462
- updateValue()
463
- }
464
-
465
- /**
466
- * 设置文件上传失败
467
- */
468
- function setFileFail(fileItem, errMsg) {
469
-
470
- // 设置文件状态
471
- fileItem.status = UPLOAD_STATUS.fail
472
- // 设置文件信息
473
- fileItem.msg = errMsg || `无效${FilE_NAME[fileItem.type]}`
474
- // 设置文件检查进度
475
- fileItem.progress = 0
476
-
477
- // // 单个文件上传结束回调
478
- // uploadQueryCallback(fileItem)
479
- }
480
-
481
- /**
482
- * 设置文件 hash
483
- */
484
- function setFileHash(fileItem) {
485
- return new Promise(function (resolve) {
486
-
487
- // 设置文件状态
488
- fileItem.status = UPLOAD_STATUS.hashChecking
489
- // 设置文件检查进度
490
- fileItem.progress = 0
491
-
492
- const {
493
- file,
494
- } = fileItem
495
-
496
- // blob 切片
497
- const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
498
- // 以 2MB 的分片读取
499
- const chunkSize = 2097152
500
- // 总分片数量
501
- const chunks = Math.ceil(file.size / chunkSize)
502
- // 初始化 SparkMD5
503
- const spark = new SparkMD5.ArrayBuffer()
504
- // 当前分片数
505
- let currentChunk = 0
506
- // 创建文件读取器
507
- const fileReader = new FileReader()
508
-
509
- // 文件加载
510
- fileReader.onload = function(e) {
511
-
512
- // 追加 array buffer
513
- spark.append(e.target.result)
514
-
515
- // 当前分片数++
516
- currentChunk++
517
-
518
- // 如果分块数量 < 总分片数量
519
- if (currentChunk < chunks) {
520
-
521
- // 设置文件检查进度
522
- fileItem.progress = Math.floor((currentChunk / chunks) * 100)
523
-
524
- // 读取下一个分片
525
- loadNext()
526
-
527
- } else {
528
- // 获取文件 hash
529
- const hash = spark.end(false)
530
-
531
- if (
532
- // 如果开启去重
533
- props.unique
534
- // 如果该文件 hash 在上传文件列表中
535
- && $n_findIndex(uploadFileLists.value, { hash }) > -1
536
- ) {
537
- // 轻提示
538
- $n_toast({
539
- message: '该文件已存在,不可重复上传',
540
- })
541
-
542
- // 设置文件上传失败
543
- setFileFail(fileItem, '已存在')
544
-
545
- // 删除单个文件
546
- deleteFileItem(fileItem)
547
-
548
- } else {
549
- // 设置文件 hash
550
- fileItem.hash = hash
551
- // 设置文件状态
552
- fileItem.status = UPLOAD_STATUS.hashChecked
553
- // 设置文件检查进度
554
- fileItem.progress = 100
555
- }
556
-
557
- // 完成回调
558
- resolve()
559
- }
560
- }
561
-
562
- // 文件加载错误
563
- fileReader.onerror = function() {
564
- // 设置文件上传失败
565
- setFileFail(fileItem)
566
- }
567
-
568
- /**
569
- * 读取下一个分片
570
- */
571
- function loadNext() {
572
- const start = currentChunk * chunkSize
573
- const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
574
- fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
575
- }
576
-
577
- // 读取下一个分片
578
- loadNext()
579
- })
580
- }
581
-
582
- /**
583
- * 检查文件错误
584
- */
585
- function checkFileError({ type, size, ext }) {
586
-
587
- // 需要上传的文件类型
588
- const fileType = FilE_TYPE[props.type]
589
-
590
- // 如果 类型为文件, 则不受限制 || 当前上传的类型允许
591
- if (props.type === 'file' || type === fileType) {
592
-
593
- const {
594
- maxSize,
595
- exts,
596
- } = configLimit
597
-
598
- // 检查文件后缀名
599
- if (
600
- // 如果没有后缀名, 则无效
601
- ! ext
602
- // 如果后缀名不在允许范围内, 则无效
603
- || (
604
- $n_isValidArray(exts)
605
- && exts.indexOf(ext) === -1
606
- )
607
- ) {
608
- return '后缀名不允许'
609
- }
610
-
611
- // 检查文件大小
612
- if (size > 0) {
613
- if (
614
- maxSize > 0
615
- && size > (maxSize * 1048576)
616
- ) {
617
- return `${FilE_NAME[fileType]}大于${maxSize}M`
618
- }
619
- return
620
- }
621
- }
622
-
623
- return `无效${FilE_NAME[fileType]}`
624
- }
625
-
626
- /**
627
- * 检查待上传文件在服务器上是否存在
628
- */
629
- async function checkWaitUploadFileExists() {
630
-
631
- // 需检查的 hashs
632
- const checkHashs = []
633
- // 需检查的文件列表
634
- const checkFileLists = []
635
-
636
- for (const fileItem of uploadFileLists.value) {
637
- if (fileItem.status === UPLOAD_STATUS.hashChecked) {
638
- // 设置文件状态
639
- fileItem.status = UPLOAD_STATUS.existChecking
640
- // 添加检查 hash 数组
641
- checkHashs.push(fileItem.hash)
642
- // 添加检查文件列表
643
- checkFileLists.push(fileItem)
644
- }
645
- }
646
-
647
- // 如果没有需要检查的 hash
648
- if (! checkHashs.length) {
649
- // 返回失败
650
- return false
651
- }
652
-
653
- // 请求 - 检查文件是否存在 hash
654
- const { status, data: resExisted } = await $n_http({
655
- url: $n_config('apiFileUrl') + 'check_exist',
656
- data: {
657
- hashs: $n_uniq(checkHashs),
658
- },
659
- // 关闭错误
660
- warn: false,
661
- })
662
-
663
- // 如果请求失败
664
- if (! status) {
665
- // 设置文件上传失败
666
- for (const fileItem of checkFileLists) {
667
- setFileFail(fileItem, resExisted.msg)
668
- }
669
- // 返回失败
670
- return false
671
- }
672
-
673
- // 设置文件状态
674
- for (const fileItem of checkFileLists) {
675
- fileItem.status = UPLOAD_STATUS.existChecked
676
- }
677
-
678
- // 如果有存在的文件列表
679
- if ($n_isValidArray(resExisted)) {
680
-
681
- // 已存在文件数量
682
- let existedNum = 0
683
- // 不存在文件数量
684
- let noExistNum = 0
685
-
686
- for (const fileItem of checkFileLists) {
687
-
688
- // 如果文件已存在(已经上传过了, 就是检查是否秒传文件)
689
- const existedItem = $n_find(resExisted, { hash: fileItem.hash })
690
- if (existedItem) {
691
-
692
- // 如果 类型为文件, 则不受限制 || 文件类型正确
693
- if (props.type === 'file' || fileItem.type === $n_get(existedItem, 'type')) {
694
-
695
- // 设置已存在文件
696
- setExistedFileItem(fileItem, existedItem)
697
-
698
- // 单个文件上传结束回调
699
- // uploadQueryCallback(fileItem)
700
-
701
- // 已存在文件数量++
702
- existedNum++
703
-
704
- // 否则有不存在文件
705
- } else {
706
- // 不存在文件数量++
707
- noExistNum++
708
- }
709
-
710
- // 否则有不存在文件
711
- } else {
712
- // 不存在文件数量++
713
- noExistNum++
714
- }
715
- }
716
-
717
- // 如果有已存在的文件
718
- if (existedNum) {
719
-
720
- // 更新值
721
- updateValue()
722
-
723
- // 上传更新回调
724
- // uploadChange(getResultData())
725
- }
726
-
727
- return noExistNum > 0
728
- }
729
-
730
- return true
731
- }
732
-
733
- /**
734
- * 创建原始单个文件
735
- */
736
- function createRawFileItem() {
737
- return {
738
- // id
739
- id: ++_fileNum,
740
- // 文件唯一 key
741
- // key,
742
- // 文件名
743
- title: '',
744
- // 文件原始数据
745
- // file,
746
- // 类型(1:文件,2:图片,3:视频,4:音频)
747
- type: FilE_TYPE[props.type],
748
- // 后缀
749
- ext: '',
750
- // 大小
751
- size: 0,
752
- // hash
753
- hash: '',
754
- // 信息
755
- json: {},
756
- // 状态: waiting checking checked uploading success fail
757
- status: UPLOAD_STATUS.waiting,
758
- // 进度
759
- progress: 0,
760
- // 信息
761
- msg: '待上传',
762
- // 中断上传
763
- abort() {},
764
- }
765
- }
766
-
767
- /**
768
- * 创建单个文件
769
- */
770
- function createFileItem(file) {
771
-
772
- // 单个文件示例
773
- // name: "123.jpg"
774
- // size: 101206
775
- // type: "image/jpeg"
776
- // lastModified: 1629099994411
777
- // lastModifiedDate: Mon Aug 16 2021 15:46:34 GMT+0800 (中国标准时间) {}
778
- // webkitRelativePath: ""
779
-
780
- const {
781
- name,
782
- size,
783
- lastModified,
784
- webkitRelativePath,
785
- } = file
786
-
787
- // 文件唯一 key
788
- const key = webkitRelativePath + lastModified + name + size
789
-
790
- if (
791
- // 如果开启去重
792
- props.unique
793
- // 如果该文件 key 在上传文件列表中
794
- && $n_findIndex(uploadFileLists.value, { key }) > -1
795
- ) {
796
- // 轻提示
797
- $n_toast({
798
- message: '该文件已存在,不可重复上传',
799
- })
800
-
801
- // 则返回 false
802
- return false
803
- }
804
-
805
- // 文件名
806
- let title = name
807
- // 文件后缀名
808
- let ext = ''
809
-
810
- const index = name.lastIndexOf('.')
811
- if (index > -1) {
812
- title = name.substring(0, index)
813
- ext = $n_toLower(name.substring(index + 1))
814
- }
815
-
816
- // 创建单个文件
817
- const fileItem = Object.assign(createRawFileItem(), {
818
- // 文件唯一 key
819
- key,
820
- // 文件原始数据
821
- file,
822
- // 文件名
823
- title,
824
- // 后缀
825
- ext,
826
- // 大小
827
- size,
828
- })
829
-
830
- // 如果上传类型为图片
831
- if (
832
- props.type === 'image'
833
- && file.type.toLowerCase().startsWith('image')
834
- ) {
835
- // 获取图片预览地址
836
- const img = new Image()
837
- img.src = window.URL.createObjectURL(file)
838
- fileItem.__img = img.src
839
- }
840
-
841
- return fileItem
842
- }
843
-
844
- /**
845
- * 设置已存在文件
846
- */
847
- function setExistedFileItem(fileItem, existedItem) {
848
-
849
- // 返回数据示例
850
- // ------------------------------
851
- // title: 1
852
- // type: 2
853
- // hash: "799e6bb77f638e192b9079e6a55bc2de"
854
- // ext: "jpg"
855
- // size: 306037
856
- // json: "{\"w\":1920,\"h\":1200,\"o\":1}"
857
- // status: 1
858
-
859
- const {
860
- // 标题
861
- title,
862
- // 类型(1:文件,2:图片,3:视频,4:音频)
863
- type,
864
- // hash
865
- hash,
866
- // 后缀
867
- ext,
868
- // 大小
869
- size,
870
- // 信息
871
- json,
872
- } = existedItem
873
-
874
- const fileJson = $n_json.parse(json)
875
-
876
- // 设置文件
877
- Object.assign(fileItem, {
878
- // 标题
879
- title,
880
- // 类型(1:文件,2:图片,3:视频,4:音频)
881
- type,
882
- // hash
883
- hash,
884
- // 后缀
885
- ext,
886
- // 大小
887
- size,
888
- // 信息
889
- json: $n_isValidObject(fileJson) ? fileJson : {},
890
- // 状态
891
- status: UPLOAD_STATUS.success,
892
- // 进度
893
- progress: 100,
894
- // 信息
895
- msg: '',
896
- })
897
- }
898
-
899
- /**
900
- * 删除所有文件
901
- */
902
- function deleteAll() {
903
-
904
- // 如果有上传文件列表
905
- if (uploadFileLists.value.length) {
906
-
907
- for (const fileItem of uploadFileLists.value) {
908
- // 退出上传
909
- fileItem.abort()
910
- }
911
-
912
- // 清空上传文件列表
913
- uploadFileLists.value = []
914
- }
915
- }
916
-
917
- /**
918
- * 删除单个文件
919
- */
920
- function deleteFileItem(fileItem) {
921
- const index = $n_findIndex(uploadFileLists.value, { id: fileItem.id })
922
- if (index > -1) {
923
-
924
- const {
925
- status,
926
- } = fileItem
927
-
928
- // 退出上传
929
- fileItem.abort()
930
-
931
- // 从上传文件列表中删除
932
- uploadFileLists.value.splice(index, 1)
933
-
934
- // 如果该文件已上传成功
935
- if (status === UPLOAD_STATUS.success) {
936
- // 更新值
937
- updateValue()
938
- }
939
- }
940
- }
941
-
942
- /**
943
- * 预览图片
944
- */
945
- function previewImage(fileItem) {
946
- // 预览图片
947
- if (fileItem.type === FilE_TYPE.image) {
948
- $n_previewImage($n_has(fileItem, '__img') ? fileItem.__img : fileItem.hash)
949
- }
950
- }
951
-
952
- /**
953
- * 修改文件名
954
- */
955
- async function editFileTitle(newTitle, fileItem) {
956
-
957
- if ($n_isValidValue(newTitle)) {
958
-
959
- newTitle = $n_trimString(newTitle)
960
-
961
- const {
962
- hash,
963
- title,
964
- } = fileItem
965
-
966
- if (title === newTitle) {
967
- return
968
- }
969
-
970
- // 先设置新文件名
971
- fileItem.title = newTitle
972
-
973
- // 请求 - 修改文件名
974
- const { status } = await $n_http({
975
- url: $n_config('apiFileUrl') + 'edit_file_title',
976
- data: {
977
- hash,
978
- title: newTitle,
979
- },
980
- })
981
-
982
- // 如果修改失败
983
- if (! status) {
984
- // 还原文件名
985
- fileItem.title = title
986
- return
987
- }
988
-
989
- // 轻提示
990
- $n_toast({
991
- type: 'positive',
992
- message: '修改成功',
993
- })
994
- }
995
- }
996
-
997
- /**
998
- * 播放
999
- */
1000
- function play(fileItem) {
1001
- // 轻提示
1002
- $n_toast({
1003
- message: '播放还没做',
1004
- })
1005
- }
1006
-
1007
- /**
1008
- * 复制地址
1009
- */
1010
- function copyUrl(fileItem) {
1011
-
1012
- const url = fileItem.type === FilE_TYPE.image ?
1013
- // 如果是图片
1014
- $n_getImage(fileItem.hash)
1015
- // 否则是文件
1016
- : $n_getFile(fileItem.hash)
1017
-
1018
- if ($n_isValidString(url)) {
1019
- $n_copy(url, '复制地址成功')
1020
- }
1021
- }
1022
-
1023
- return {
1024
- // 上传文件列表
1025
- query: uploadFileLists,
1026
- // 更新值
1027
- updateValue,
1028
- // 初始化上传列表
1029
- initUploadFileLists,
1030
- // 检查是否正在上传文件
1031
- checkUploading,
1032
- // 选择文件上传
1033
- chooseUpload,
1034
- // 文件输入框更新
1035
- fileChange,
1036
- // 删除所有文件
1037
- deleteAll,
1038
- // 删除单个文件
1039
- deleteFileItem,
1040
- // 预览图片
1041
- previewImage,
1042
- // 修改文件名
1043
- editFileTitle,
1044
- // 播放视频/音频
1045
- play,
1046
- // 复制地址
1047
- copyUrl,
1048
- }
1049
- }
1050
-
1051
- /**
1052
- * 上传器
1053
- */
1054
- const uploader = {
1055
- // 创建对话框
1056
- create,
1057
- }
1058
-
1059
- export default uploader
1
+ import { ref, isRef } from 'vue'
2
+ import SparkMD5 from 'spark-md5'
3
+
4
+ import $n_has from 'lodash/has'
5
+ import $n_get from 'lodash/get'
6
+ import $n_toLower from 'lodash/toLower'
7
+ import $n_findIndex from 'lodash/findIndex'
8
+ import $n_uniq from 'lodash/uniq'
9
+ import $n_find from 'lodash/find'
10
+
11
+ import $n_isValidArray from '@netang/utils/isValidArray'
12
+ import $n_isValidObject from '@netang/utils/isValidObject'
13
+ import $n_isValidString from '@netang/utils/isValidString'
14
+ import $n_isRequired from '@netang/utils/isRequired'
15
+ import $n_forEach from '@netang/utils/forEach'
16
+ import $n_json from '@netang/utils/json'
17
+ import $n_join from '@netang/utils/join'
18
+ import $n_split from '@netang/utils/split'
19
+ import $n_trimString from '@netang/utils/trimString'
20
+ import $n_run from '@netang/utils/run'
21
+ import $n_isValidValue from '@netang/utils/isValidValue'
22
+ import $n_copy from '@netang/utils/copy'
23
+ import $n_http from '@netang/utils/http'
24
+ import $n_getThrowMessage from '@netang/utils/getThrowMessage'
25
+
26
+ import $n_toast from './toast'
27
+ import $n_confirm from './confirm'
28
+ import $n_alert from './alert'
29
+ import $n_previewImage from './previewImage'
30
+ import $n_getImage from './getImage'
31
+ import $n_getFile from './getFile'
32
+ import $n_config from './config'
33
+
34
+ import {
35
+ // 文件类型映射
36
+ FilE_TYPE,
37
+ // 文件名称映射
38
+ FilE_NAME,
39
+ // 上传状态
40
+ UPLOAD_STATUS,
41
+ // 上传器
42
+ UPLOADERS,
43
+ } from './useUploader'
44
+
45
+ // 文件数量
46
+ let _fileNum = 0
47
+
48
+ /**
49
+ * 创建上传器
50
+ */
51
+ function create(options) {
52
+
53
+ // ==========【数据】=========================================================================================
54
+
55
+ const {
56
+ // 上传文件输入框节点
57
+ fileRef,
58
+ // 更新值方法(初始化上传列表时不更新值)
59
+ onUpdateModelValue,
60
+ // 更新方法
61
+ onUpdate,
62
+
63
+ } = Object.assign({
64
+ // 更新值方法
65
+ onUpdateModelValue: null,
66
+ // 更新方法
67
+ onUpdate: null,
68
+ }, options)
69
+
70
+ // 声明属性
71
+ const props = Object.assign({
72
+ // 值
73
+ modelValue: '',
74
+ // 上传文件类型, 可选值 file image video audio
75
+ type: 'image',
76
+ // 上传文件数量(0:不限)
77
+ count: 0,
78
+ // 单个文件的最大大小(单位: MB)
79
+ maxSize: 0,
80
+ // 单个文件的限制后缀
81
+ exts: [],
82
+ // true: 值格式为数组, 如 ['xxxxxx', 'xxxxxx', 'xxxxxx']
83
+ // false: 值格式为字符串, 如 xxxxxx,xxxxxx,xxxxxx
84
+ valueArray: false,
85
+ // 是否去重
86
+ unique: false,
87
+ // 是否初始加载文件信息(仅图片有效, 其他类型自动会加载文件信息)
88
+ loadInfo: false,
89
+ // 单文件上传提示
90
+ confirm: false,
91
+ }, $n_get(options, 'props'))
92
+
93
+ // 上传文件列表
94
+ const uploadFileLists = $n_has(options, 'uploadFileLists') && isRef(options.uploadFileLists) ? options.uploadFileLists : ref([])
95
+
96
+ /**
97
+ * 上传配置
98
+ */
99
+ const configUpload = Object.assign(
100
+ {
101
+ type: 'local',
102
+ },
103
+ $n_config('uploader.upload')
104
+ )
105
+ const configLimit = Object.assign({
106
+ maxSize: 100,
107
+ exts: [],
108
+ }, $n_config('uploader.limit.' + props.type))
109
+
110
+ // 如果有单个文件的最大大小
111
+ if (props.maxSize) {
112
+ configLimit.maxSize = props.maxSize
113
+ }
114
+
115
+ // 如果有单个文件的限制后缀
116
+ if ($n_isValidArray(props.exts)) {
117
+ configLimit.exts = props.exts
118
+ }
119
+
120
+ // ==========【计算属性】=========================================================================================
121
+
122
+ /**
123
+ * 上传文件后缀名
124
+ */
125
+ // const accept = computed(function () {
126
+ //
127
+ // })
128
+
129
+ // ==========【监听数据】==============================================================================================
130
+
131
+ /**
132
+ * 监听上传文件列表
133
+ */
134
+
135
+ // ==========【方法】=================================================================================================
136
+
137
+ /**
138
+ * 获取值
139
+ */
140
+ function getValue() {
141
+
142
+ const hashs = []
143
+ const files = []
144
+
145
+ for (const fileItem of uploadFileLists.value) {
146
+ if (fileItem.status === UPLOAD_STATUS.success) {
147
+ hashs.push(fileItem.hash)
148
+ files.push(fileItem)
149
+ }
150
+ }
151
+
152
+ const hashsString = $n_join(hashs, ',')
153
+
154
+ return {
155
+ value: props.valueArray ? hashs : hashsString,
156
+ hashsString,
157
+ hashs,
158
+ files,
159
+ }
160
+ }
161
+
162
+ /**
163
+ * 更新值
164
+ */
165
+ function updateValue() {
166
+
167
+ // 获取值
168
+ const result = getValue()
169
+
170
+ // 更新值
171
+ $n_run(onUpdateModelValue)(result)
172
+
173
+ // 更新
174
+ $n_run(onUpdate)(result)
175
+ }
176
+
177
+ /**
178
+ * 更新
179
+ */
180
+ function update() {
181
+ // 更新
182
+ $n_run(onUpdate)(getValue())
183
+ }
184
+
185
+ /**
186
+ * 初始化上传列表
187
+ */
188
+ async function initUploadFileLists() {
189
+ if ($n_isRequired(props.modelValue)) {
190
+
191
+ // 获取值数组
192
+ const hashs = props.valueArray ? props.modelValue : $n_split(props.modelValue, ',')
193
+
194
+ // 如果类型不是图片 || 初始加载文件信息, 则请求文件信息
195
+ if (props.type !== 'image' || props.loadInfo) {
196
+
197
+ // 请求 - 获取文件
198
+ const { status, data: resExisted } = await $n_http({
199
+ url: $n_config('apiFileUrl') + 'get_file',
200
+ data: {
201
+ hashs,
202
+ },
203
+ // 关闭错误
204
+ warn: false,
205
+ })
206
+ if (status) {
207
+
208
+ $n_forEach(resExisted, function (existedItem) {
209
+
210
+ // 创建原始单个文件
211
+ const fileItem = createRawFileItem()
212
+
213
+ // 设置已存在文件
214
+ setExistedFileItem(fileItem, existedItem)
215
+
216
+ // 添加至上传文件列表
217
+ uploadFileLists.value.push(Object.assign(fileItem, {
218
+ key: fileItem.hash,
219
+ }))
220
+ })
221
+
222
+ // 更新
223
+ update()
224
+ }
225
+ return
226
+ }
227
+
228
+ $n_forEach(hashs, function(hash) {
229
+
230
+ // 添加至上传文件列表
231
+ uploadFileLists.value.push(Object.assign(createRawFileItem(), {
232
+ // 文件唯一 key
233
+ key: hash,
234
+ // hash
235
+ hash,
236
+ // 状态
237
+ status: UPLOAD_STATUS.success,
238
+ // 进度
239
+ progress: 100,
240
+ // 信息
241
+ msg: '',
242
+ }))
243
+ })
244
+ }
245
+ }
246
+
247
+ /**
248
+ * 检查是否正在上传文件
249
+ */
250
+ function checkUploading() {
251
+ for (const fileItem of uploadFileLists.value) {
252
+ if (fileItem.status < UPLOAD_STATUS.success) {
253
+ return true
254
+ }
255
+ }
256
+ return false
257
+ }
258
+
259
+ /**
260
+ * 选择文件上传
261
+ */
262
+ function chooseUpload() {
263
+ // 点击文件输入框
264
+ fileRef.value.click()
265
+ }
266
+
267
+ /**
268
+ * 文件输入框更新
269
+ */
270
+ function fileChange(e) {
271
+
272
+ try {
273
+ // 获取上传文件
274
+ const files = Array.from(e.target.files)
275
+
276
+ // 清空上传文件输入框内容
277
+ fileRef.value.value = ''
278
+
279
+ // 如果没有选择文件
280
+ if (! files.length) {
281
+ // 则无任何操作
282
+ return
283
+ }
284
+
285
+ // 遍历选择的文件列表
286
+ for (const file of files) {
287
+
288
+ // 创建单个文件
289
+ const fileItem = createFileItem(file)
290
+ if (fileItem !== false) {
291
+
292
+ // 如果只能上传一个
293
+ if (props.count === 1) {
294
+
295
+ // 如果有上传文件列表
296
+ if (uploadFileLists.value.length) {
297
+
298
+ // 如果开启单文件上传提示
299
+ if (props.confirm) {
300
+
301
+ // 确认框
302
+ $n_confirm({
303
+ message: '最多只能上传1个文件,确认上传并替换吗?',
304
+ })
305
+ // 点击确认执行
306
+ .onOk(function () {
307
+
308
+ // 删除所有文件
309
+ deleteAll()
310
+
311
+ // 添加至上传文件列表
312
+ uploadFileLists.value.push(fileItem)
313
+
314
+ // 开始上传
315
+ startUpload()
316
+ .finally()
317
+ })
318
+ return
319
+ }
320
+
321
+ // 删除所有文件
322
+ deleteAll()
323
+ }
324
+
325
+ } else if (
326
+ // 如果有上传数量限制
327
+ props.count > 1
328
+ // 上传文件列表数量 === 上传数量限制
329
+ && uploadFileLists.value.length >= props.count
330
+ ) {
331
+ // 轻提示
332
+ $n_toast({
333
+ message: `最多只能上传${props.count}个文件,请先删除后再上传`,
334
+ })
335
+ return
336
+ }
337
+
338
+ // 添加至上传文件列表
339
+ uploadFileLists.value.push(fileItem)
340
+ }
341
+ }
342
+
343
+ // 开始上传
344
+ startUpload()
345
+ .finally()
346
+
347
+ } catch (e) {
348
+
349
+ // 错误提示
350
+ $n_alert({
351
+ message: $n_getThrowMessage(e),
352
+ })
353
+ }
354
+
355
+ }
356
+
357
+ /**
358
+ * 开始上传
359
+ */
360
+ async function startUpload() {
361
+
362
+ // 【设置待上传文件的 hash】
363
+ // --------------------------------------------------
364
+ const promises = []
365
+ for (const fileItem of uploadFileLists.value) {
366
+ // 如果是等待上传的文件
367
+ if (fileItem.status === UPLOAD_STATUS.waiting) {
368
+
369
+ // 检查文件错误
370
+ const errMsg = checkFileError(fileItem)
371
+ if (errMsg) {
372
+ // 设置文件上传失败
373
+ setFileFail(fileItem, errMsg)
374
+
375
+ } else {
376
+ // 设置文件 hash
377
+ promises.push(setFileHash(fileItem))
378
+ }
379
+ }
380
+ }
381
+ await Promise.all(promises)
382
+
383
+ // 检查待上传文件在服务器上是否存在
384
+ // --------------------------------------------------
385
+ if (! await checkWaitUploadFileExists()) {
386
+ return
387
+ }
388
+
389
+ // 上传
390
+ await upload()
391
+ }
392
+
393
+ /**
394
+ * 上传
395
+ */
396
+ let _upload = null
397
+ async function upload() {
398
+ try {
399
+ if (! _upload) {
400
+ const run = $n_get(UPLOADERS, configUpload.type)
401
+ if (run) {
402
+ _upload = (await run()).default
403
+ }
404
+ if (! _upload) {
405
+ // 错误提示
406
+ $n_alert({
407
+ message: '没有定义上传器',
408
+ })
409
+ return
410
+ }
411
+ }
412
+
413
+ // 待上传文件列表
414
+ const waitUploadFileLists = []
415
+ for (const fileItem of uploadFileLists.value) {
416
+ // 检查存在服务器完成
417
+ if (fileItem.status === UPLOAD_STATUS.existChecked) {
418
+ waitUploadFileLists.push(fileItem)
419
+ }
420
+ }
421
+ if (! waitUploadFileLists.length) {
422
+ return
423
+ }
424
+
425
+ // 上传
426
+ await _upload({
427
+ config: configUpload,
428
+ waitUploadFileLists,
429
+ uploadFileLists,
430
+ checkFileError,
431
+ setFileSuccess,
432
+ setFileFail,
433
+ })
434
+
435
+ } catch (e) {
436
+ // 错误提示
437
+ $n_alert({
438
+ message: $n_getThrowMessage(e),
439
+ })
440
+ }
441
+ }
442
+
443
+ /**
444
+ * 设置文件上传成功
445
+ */
446
+ const setFileSuccess = (fileItem) => {
447
+
448
+ // 设置文件状态
449
+ fileItem.status = UPLOAD_STATUS.success
450
+ // 设置文件信息
451
+ fileItem.msg = ''
452
+ // 设置文件检查进度
453
+ fileItem.progress = 0
454
+
455
+ // // 单个文件上传结束回调
456
+ // uploadQueryCallback(fileItem)
457
+ //
458
+ // // 上传更新回调
459
+ // uploadChange(getResultData())
460
+
461
+ // 更新值
462
+ updateValue()
463
+ }
464
+
465
+ /**
466
+ * 设置文件上传失败
467
+ */
468
+ function setFileFail(fileItem, errMsg) {
469
+
470
+ // 设置文件状态
471
+ fileItem.status = UPLOAD_STATUS.fail
472
+ // 设置文件信息
473
+ fileItem.msg = errMsg || `无效${FilE_NAME[fileItem.type]}`
474
+ // 设置文件检查进度
475
+ fileItem.progress = 0
476
+
477
+ // // 单个文件上传结束回调
478
+ // uploadQueryCallback(fileItem)
479
+ }
480
+
481
+ /**
482
+ * 设置文件 hash
483
+ */
484
+ function setFileHash(fileItem) {
485
+ return new Promise(function (resolve) {
486
+
487
+ // 设置文件状态
488
+ fileItem.status = UPLOAD_STATUS.hashChecking
489
+ // 设置文件检查进度
490
+ fileItem.progress = 0
491
+
492
+ const {
493
+ file,
494
+ } = fileItem
495
+
496
+ // blob 切片
497
+ const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
498
+ // 以 2MB 的分片读取
499
+ const chunkSize = 2097152
500
+ // 总分片数量
501
+ const chunks = Math.ceil(file.size / chunkSize)
502
+ // 初始化 SparkMD5
503
+ const spark = new SparkMD5.ArrayBuffer()
504
+ // 当前分片数
505
+ let currentChunk = 0
506
+ // 创建文件读取器
507
+ const fileReader = new FileReader()
508
+
509
+ // 文件加载
510
+ fileReader.onload = function(e) {
511
+
512
+ // 追加 array buffer
513
+ spark.append(e.target.result)
514
+
515
+ // 当前分片数++
516
+ currentChunk++
517
+
518
+ // 如果分块数量 < 总分片数量
519
+ if (currentChunk < chunks) {
520
+
521
+ // 设置文件检查进度
522
+ fileItem.progress = Math.floor((currentChunk / chunks) * 100)
523
+
524
+ // 读取下一个分片
525
+ loadNext()
526
+
527
+ } else {
528
+ // 获取文件 hash
529
+ const hash = spark.end(false)
530
+
531
+ if (
532
+ // 如果开启去重
533
+ props.unique
534
+ // 如果该文件 hash 在上传文件列表中
535
+ && $n_findIndex(uploadFileLists.value, { hash }) > -1
536
+ ) {
537
+ // 轻提示
538
+ $n_toast({
539
+ message: '该文件已存在,不可重复上传',
540
+ })
541
+
542
+ // 设置文件上传失败
543
+ setFileFail(fileItem, '已存在')
544
+
545
+ // 删除单个文件
546
+ deleteFileItem(fileItem)
547
+
548
+ } else {
549
+ // 设置文件 hash
550
+ fileItem.hash = hash
551
+ // 设置文件状态
552
+ fileItem.status = UPLOAD_STATUS.hashChecked
553
+ // 设置文件检查进度
554
+ fileItem.progress = 100
555
+ }
556
+
557
+ // 完成回调
558
+ resolve()
559
+ }
560
+ }
561
+
562
+ // 文件加载错误
563
+ fileReader.onerror = function() {
564
+ // 设置文件上传失败
565
+ setFileFail(fileItem)
566
+ }
567
+
568
+ /**
569
+ * 读取下一个分片
570
+ */
571
+ function loadNext() {
572
+ const start = currentChunk * chunkSize
573
+ const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
574
+ fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
575
+ }
576
+
577
+ // 读取下一个分片
578
+ loadNext()
579
+ })
580
+ }
581
+
582
+ /**
583
+ * 检查文件错误
584
+ */
585
+ function checkFileError({ type, size, ext }) {
586
+
587
+ // 需要上传的文件类型
588
+ const fileType = FilE_TYPE[props.type]
589
+
590
+ // 如果 类型为文件, 则不受限制 || 当前上传的类型允许
591
+ if (props.type === 'file' || type === fileType) {
592
+
593
+ const {
594
+ maxSize,
595
+ exts,
596
+ } = configLimit
597
+
598
+ // 检查文件后缀名
599
+ if (
600
+ // 如果没有后缀名, 则无效
601
+ ! ext
602
+ // 如果后缀名不在允许范围内, 则无效
603
+ || (
604
+ $n_isValidArray(exts)
605
+ && exts.indexOf(ext) === -1
606
+ )
607
+ ) {
608
+ return '后缀名不允许'
609
+ }
610
+
611
+ // 检查文件大小
612
+ if (size > 0) {
613
+ if (
614
+ maxSize > 0
615
+ && size > (maxSize * 1048576)
616
+ ) {
617
+ return `${FilE_NAME[fileType]}大于${maxSize}M`
618
+ }
619
+ return
620
+ }
621
+ }
622
+
623
+ return `无效${FilE_NAME[fileType]}`
624
+ }
625
+
626
+ /**
627
+ * 检查待上传文件在服务器上是否存在
628
+ */
629
+ async function checkWaitUploadFileExists() {
630
+
631
+ // 需检查的 hashs
632
+ const checkHashs = []
633
+ // 需检查的文件列表
634
+ const checkFileLists = []
635
+
636
+ for (const fileItem of uploadFileLists.value) {
637
+ if (fileItem.status === UPLOAD_STATUS.hashChecked) {
638
+ // 设置文件状态
639
+ fileItem.status = UPLOAD_STATUS.existChecking
640
+ // 添加检查 hash 数组
641
+ checkHashs.push(fileItem.hash)
642
+ // 添加检查文件列表
643
+ checkFileLists.push(fileItem)
644
+ }
645
+ }
646
+
647
+ // 如果没有需要检查的 hash
648
+ if (! checkHashs.length) {
649
+ // 返回失败
650
+ return false
651
+ }
652
+
653
+ // 请求 - 检查文件是否存在 hash
654
+ const { status, data: resExisted } = await $n_http({
655
+ url: $n_config('apiFileUrl') + 'check_exist',
656
+ data: {
657
+ hashs: $n_uniq(checkHashs),
658
+ },
659
+ // 关闭错误
660
+ warn: false,
661
+ })
662
+
663
+ // 如果请求失败
664
+ if (! status) {
665
+ // 设置文件上传失败
666
+ for (const fileItem of checkFileLists) {
667
+ setFileFail(fileItem, resExisted.msg)
668
+ }
669
+ // 返回失败
670
+ return false
671
+ }
672
+
673
+ // 设置文件状态
674
+ for (const fileItem of checkFileLists) {
675
+ fileItem.status = UPLOAD_STATUS.existChecked
676
+ }
677
+
678
+ // 如果有存在的文件列表
679
+ if ($n_isValidArray(resExisted)) {
680
+
681
+ // 已存在文件数量
682
+ let existedNum = 0
683
+ // 不存在文件数量
684
+ let noExistNum = 0
685
+
686
+ for (const fileItem of checkFileLists) {
687
+
688
+ // 如果文件已存在(已经上传过了, 就是检查是否秒传文件)
689
+ const existedItem = $n_find(resExisted, { hash: fileItem.hash })
690
+ if (existedItem) {
691
+
692
+ // 如果 类型为文件, 则不受限制 || 文件类型正确
693
+ if (props.type === 'file' || fileItem.type === $n_get(existedItem, 'type')) {
694
+
695
+ // 设置已存在文件
696
+ setExistedFileItem(fileItem, existedItem)
697
+
698
+ // 单个文件上传结束回调
699
+ // uploadQueryCallback(fileItem)
700
+
701
+ // 已存在文件数量++
702
+ existedNum++
703
+
704
+ // 否则有不存在文件
705
+ } else {
706
+ // 不存在文件数量++
707
+ noExistNum++
708
+ }
709
+
710
+ // 否则有不存在文件
711
+ } else {
712
+ // 不存在文件数量++
713
+ noExistNum++
714
+ }
715
+ }
716
+
717
+ // 如果有已存在的文件
718
+ if (existedNum) {
719
+
720
+ // 更新值
721
+ updateValue()
722
+
723
+ // 上传更新回调
724
+ // uploadChange(getResultData())
725
+ }
726
+
727
+ return noExistNum > 0
728
+ }
729
+
730
+ return true
731
+ }
732
+
733
+ /**
734
+ * 创建原始单个文件
735
+ */
736
+ function createRawFileItem() {
737
+ return {
738
+ // id
739
+ id: ++_fileNum,
740
+ // 文件唯一 key
741
+ // key,
742
+ // 文件名
743
+ title: '',
744
+ // 文件原始数据
745
+ // file,
746
+ // 类型(1:文件,2:图片,3:视频,4:音频)
747
+ type: FilE_TYPE[props.type],
748
+ // 后缀
749
+ ext: '',
750
+ // 大小
751
+ size: 0,
752
+ // hash
753
+ hash: '',
754
+ // 信息
755
+ json: {},
756
+ // 状态: waiting checking checked uploading success fail
757
+ status: UPLOAD_STATUS.waiting,
758
+ // 进度
759
+ progress: 0,
760
+ // 信息
761
+ msg: '待上传',
762
+ // 中断上传
763
+ abort() {},
764
+ }
765
+ }
766
+
767
+ /**
768
+ * 创建单个文件
769
+ */
770
+ function createFileItem(file) {
771
+
772
+ // 单个文件示例
773
+ // name: "123.jpg"
774
+ // size: 101206
775
+ // type: "image/jpeg"
776
+ // lastModified: 1629099994411
777
+ // lastModifiedDate: Mon Aug 16 2021 15:46:34 GMT+0800 (中国标准时间) {}
778
+ // webkitRelativePath: ""
779
+
780
+ const {
781
+ name,
782
+ size,
783
+ lastModified,
784
+ webkitRelativePath,
785
+ } = file
786
+
787
+ // 文件唯一 key
788
+ const key = webkitRelativePath + lastModified + name + size
789
+
790
+ if (
791
+ // 如果开启去重
792
+ props.unique
793
+ // 如果该文件 key 在上传文件列表中
794
+ && $n_findIndex(uploadFileLists.value, { key }) > -1
795
+ ) {
796
+ // 轻提示
797
+ $n_toast({
798
+ message: '该文件已存在,不可重复上传',
799
+ })
800
+
801
+ // 则返回 false
802
+ return false
803
+ }
804
+
805
+ // 文件名
806
+ let title = name
807
+ // 文件后缀名
808
+ let ext = ''
809
+
810
+ const index = name.lastIndexOf('.')
811
+ if (index > -1) {
812
+ title = name.substring(0, index)
813
+ ext = $n_toLower(name.substring(index + 1))
814
+ }
815
+
816
+ // 创建单个文件
817
+ const fileItem = Object.assign(createRawFileItem(), {
818
+ // 文件唯一 key
819
+ key,
820
+ // 文件原始数据
821
+ file,
822
+ // 文件名
823
+ title,
824
+ // 后缀
825
+ ext,
826
+ // 大小
827
+ size,
828
+ })
829
+
830
+ // 如果上传类型为图片
831
+ if (
832
+ props.type === 'image'
833
+ && file.type.toLowerCase().startsWith('image')
834
+ ) {
835
+ // 获取图片预览地址
836
+ const img = new Image()
837
+ img.src = window.URL.createObjectURL(file)
838
+ fileItem.__img = img.src
839
+ }
840
+
841
+ return fileItem
842
+ }
843
+
844
+ /**
845
+ * 设置已存在文件
846
+ */
847
+ function setExistedFileItem(fileItem, existedItem) {
848
+
849
+ // 返回数据示例
850
+ // ------------------------------
851
+ // title: 1
852
+ // type: 2
853
+ // hash: "799e6bb77f638e192b9079e6a55bc2de"
854
+ // ext: "jpg"
855
+ // size: 306037
856
+ // json: "{\"w\":1920,\"h\":1200,\"o\":1}"
857
+ // status: 1
858
+
859
+ const {
860
+ // 标题
861
+ title,
862
+ // 类型(1:文件,2:图片,3:视频,4:音频)
863
+ type,
864
+ // hash
865
+ hash,
866
+ // 后缀
867
+ ext,
868
+ // 大小
869
+ size,
870
+ // 信息
871
+ json,
872
+ } = existedItem
873
+
874
+ const fileJson = $n_json.parse(json)
875
+
876
+ // 设置文件
877
+ Object.assign(fileItem, {
878
+ // 标题
879
+ title,
880
+ // 类型(1:文件,2:图片,3:视频,4:音频)
881
+ type,
882
+ // hash
883
+ hash,
884
+ // 后缀
885
+ ext,
886
+ // 大小
887
+ size,
888
+ // 信息
889
+ json: $n_isValidObject(fileJson) ? fileJson : {},
890
+ // 状态
891
+ status: UPLOAD_STATUS.success,
892
+ // 进度
893
+ progress: 100,
894
+ // 信息
895
+ msg: '',
896
+ })
897
+ }
898
+
899
+ /**
900
+ * 删除所有文件
901
+ */
902
+ function deleteAll() {
903
+
904
+ // 如果有上传文件列表
905
+ if (uploadFileLists.value.length) {
906
+
907
+ for (const fileItem of uploadFileLists.value) {
908
+ // 退出上传
909
+ fileItem.abort()
910
+ }
911
+
912
+ // 清空上传文件列表
913
+ uploadFileLists.value = []
914
+ }
915
+ }
916
+
917
+ /**
918
+ * 删除单个文件
919
+ */
920
+ function deleteFileItem(fileItem) {
921
+ const index = $n_findIndex(uploadFileLists.value, { id: fileItem.id })
922
+ if (index > -1) {
923
+
924
+ const {
925
+ status,
926
+ } = fileItem
927
+
928
+ // 退出上传
929
+ fileItem.abort()
930
+
931
+ // 从上传文件列表中删除
932
+ uploadFileLists.value.splice(index, 1)
933
+
934
+ // 如果该文件已上传成功
935
+ if (status === UPLOAD_STATUS.success) {
936
+ // 更新值
937
+ updateValue()
938
+ }
939
+ }
940
+ }
941
+
942
+ /**
943
+ * 预览图片
944
+ */
945
+ function previewImage(fileItem) {
946
+ // 预览图片
947
+ if (fileItem.type === FilE_TYPE.image) {
948
+ $n_previewImage({
949
+ images: [
950
+ $n_has(fileItem, '__img') ? fileItem.__img : fileItem.hash,
951
+ ],
952
+ })
953
+ }
954
+ }
955
+
956
+ /**
957
+ * 修改文件名
958
+ */
959
+ async function editFileTitle(newTitle, fileItem) {
960
+
961
+ if ($n_isValidValue(newTitle)) {
962
+
963
+ newTitle = $n_trimString(newTitle)
964
+
965
+ const {
966
+ hash,
967
+ title,
968
+ } = fileItem
969
+
970
+ if (title === newTitle) {
971
+ return
972
+ }
973
+
974
+ // 先设置新文件名
975
+ fileItem.title = newTitle
976
+
977
+ // 请求 - 修改文件名
978
+ const { status } = await $n_http({
979
+ url: $n_config('apiFileUrl') + 'edit_file_title',
980
+ data: {
981
+ hash,
982
+ title: newTitle,
983
+ },
984
+ })
985
+
986
+ // 如果修改失败
987
+ if (! status) {
988
+ // 还原文件名
989
+ fileItem.title = title
990
+ return
991
+ }
992
+
993
+ // 轻提示
994
+ $n_toast({
995
+ type: 'positive',
996
+ message: '修改成功',
997
+ })
998
+ }
999
+ }
1000
+
1001
+ /**
1002
+ * 播放
1003
+ */
1004
+ function play(fileItem) {
1005
+ // 轻提示
1006
+ $n_toast({
1007
+ message: '播放还没做',
1008
+ })
1009
+ }
1010
+
1011
+ /**
1012
+ * 复制地址
1013
+ */
1014
+ function copyUrl(fileItem) {
1015
+
1016
+ const url = fileItem.type === FilE_TYPE.image ?
1017
+ // 如果是图片
1018
+ $n_getImage(fileItem.hash)
1019
+ // 否则是文件
1020
+ : $n_getFile(fileItem.hash)
1021
+
1022
+ if ($n_isValidString(url)) {
1023
+ $n_copy(url, '复制地址成功')
1024
+ }
1025
+ }
1026
+
1027
+ return {
1028
+ // 上传文件列表
1029
+ query: uploadFileLists,
1030
+ // 更新值
1031
+ updateValue,
1032
+ // 初始化上传列表
1033
+ initUploadFileLists,
1034
+ // 检查是否正在上传文件
1035
+ checkUploading,
1036
+ // 选择文件上传
1037
+ chooseUpload,
1038
+ // 文件输入框更新
1039
+ fileChange,
1040
+ // 删除所有文件
1041
+ deleteAll,
1042
+ // 删除单个文件
1043
+ deleteFileItem,
1044
+ // 预览图片
1045
+ previewImage,
1046
+ // 修改文件名
1047
+ editFileTitle,
1048
+ // 播放视频/音频
1049
+ play,
1050
+ // 复制地址
1051
+ copyUrl,
1052
+ }
1053
+ }
1054
+
1055
+ /**
1056
+ * 上传器
1057
+ */
1058
+ const uploader = {
1059
+ // 创建对话框
1060
+ create,
1061
+ }
1062
+
1063
+ export default uploader