@netang/quasar 0.1.58 → 0.1.60

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.
@@ -1,758 +1,776 @@
1
- <template>
2
- <div class="n-uploader-query">
3
-
4
- <!-- 上传按钮 -->
5
- <slot
6
- name="button"
7
- :disable="disable || readonly"
8
- :size="currentSize"
9
- v-if="$slots.button"
10
- />
11
- <div
12
- class="n-uploader-query__button--button"
13
- v-else-if="! noButton && currentButtonType === 'button'"
14
- >
15
- <q-btn
16
- class="n-button-icon"
17
- :label="buttonText || '上传'"
18
- @click="uploader.chooseUpload"
19
- color="primary"
20
- outline
21
- :disable="disable || readonly"
22
- unelevated
23
- v-bind="buttonProps"
24
- />
25
- </div>
26
-
27
- <!-- 拖拽器 -->
28
- <n-dragger
29
- class="n-uploader-query__query row q-gutter-sm"
30
- v-model="query"
31
- :drag="currentDrag"
32
- @update:model-value="uploader.updateValue"
33
- v-slot="{ mousedown, fromIndex, dragStart, dragEnter, dragEnd }"
34
- >
35
- <!-- 上传图片队列 -->
36
- <template v-if="type === 'image'">
37
-
38
- <!-- 左边方块按钮 -->
39
- <template v-if="! disable && ! readonly && ! rightSquareButton">
40
- <slot
41
- name="square-button"
42
- :size="currentSize"
43
- :show="showSquareButton"
44
- v-if="$slots['square-button']"
45
- />
46
- <div
47
- class="n-uploader-query__button--square cursor-pointer"
48
- :style="{
49
- width: toPx(currentSize),
50
- height: toPx(currentSize),
51
- }"
52
- @click="uploader.chooseUpload"
53
- v-show="showSquareButton"
54
- v-else-if="! noButton && currentButtonType === 'square'"
55
- >
56
- <q-icon
57
- name="add"
58
- :size="toPx(currentSize / 2)"
59
- />
60
- <div class="n-uploader-query__button--square__text" v-if="buttonText">{{buttonText}}</div>
61
- </div>
62
- </template>
63
-
64
- <!-- 单个图片 -->
65
- <div
66
- v-for="(fileItem, fileItemIndex) in query"
67
- :key="`query-item-${fileItem.id}`"
68
- class="n-uploader-query__item n-uploader-query__item--image"
69
- :class="{
70
- ghost: fileItemIndex === fromIndex,
71
- }"
72
- :draggable="currentDrag"
73
- @mousedown.self="mousedown($event, fileItemIndex)"
74
- @mouseup="dragEnd"
75
- @dragstart="dragStart($event, fileItemIndex)"
76
- @dragenter="dragEnter($event, fileItemIndex)"
77
- @dragend="dragEnd"
78
- >
79
- <n-img
80
- :src="getImage(fileItem)"
81
- :spinner-size="toPx(currentSize / 2)"
82
- :width="toPx(currentSize)"
83
- :height="toPx(currentSize)"
84
- fit="fill"
85
- >
86
- <!-- 内容 -->
87
- <div
88
- class="n-uploader-query__item__inner absolute-full flex flex-center no-padding transparent"
89
- :class="{
90
- 'transparent': fileItem.status < UPLOAD_STATUS.success,
91
- }"
92
- v-if="fileItem.status !== UPLOAD_STATUS.success"
93
- >
94
- <!-- 如果上传失败 -->
95
- <div
96
- class="n-uploader-query__item__inner__msg n-uploader-query__item__inner__msg--error"
97
- v-if="fileItem.status === UPLOAD_STATUS.fail"
98
- >{{fileItem.msg}}</div>
99
-
100
- <!-- 上传中前 -->
101
- <q-circular-progress
102
- indeterminate
103
- rounded
104
- :size="toPx(currentSize / 1.5)"
105
- :thickness="0.14"
106
- color="white"
107
- v-if="fileItem.status < UPLOAD_STATUS.uploading"
108
- />
109
-
110
- <!-- 上传中 -->
111
- <q-circular-progress
112
- :value="fileItem.progress"
113
- :size="toPx(currentSize / 1.5)"
114
- :thickness="0.14"
115
- color="white"
116
- track-color="grey-5"
117
- show-value
118
- v-else-if="fileItem.status === UPLOAD_STATUS.uploading"
119
- >
120
- <q-icon
121
- class="cursor-pointer"
122
- name="pause"
123
- :size="toPx(currentSize / 3)"
124
- @click="uploader.deleteFileItem(fileItem)"
125
- />
126
- </q-circular-progress>
127
- </div>
128
-
129
- <!-- 操作 -->
130
- <div
131
- class="n-uploader-query__item__settings transparent no-padding"
132
- v-if="fileItem.status !== UPLOAD_STATUS.uploading"
133
- >
134
- <!-- 预览 -->
135
- <q-icon
136
- class="n-uploader-query__item__settings__icon cursor-pointer"
137
- name="search"
138
- size="xs"
139
- title="预览"
140
- @click="uploader.previewImage(fileItem)"
141
- v-if="! noPreview && getImage(fileItem)"
142
- />
143
-
144
- <!-- 删除 -->
145
- <q-icon
146
- class="n-uploader-query__item__settings__icon cursor-pointer"
147
- name="close"
148
- size="xs"
149
- title="删除"
150
- @click="uploader.deleteFileItem(fileItem)"
151
- v-if="! noDelete && ! disable && ! readonly"
152
- />
153
- </div>
154
- </n-img>
155
- </div>
156
-
157
- <!-- 右边方块按钮 -->
158
- <template v-if="! disable && ! readonly && rightSquareButton">
159
- <slot
160
- name="square-button"
161
- :size="currentSize"
162
- :show="showSquareButton"
163
- v-if="$slots['square-button']"
164
- />
165
- <div
166
- class="n-uploader-query__button--square cursor-pointer"
167
- :style="{
168
- width: toPx(currentSize),
169
- height: toPx(currentSize),
170
- }"
171
- @click="uploader.chooseUpload"
172
- v-show="showSquareButton"
173
- v-else-if="! noButton && currentButtonType === 'square'"
174
- >
175
- <q-icon
176
- name="add"
177
- :size="toPx(currentSize / 2)"
178
- />
179
- <div class="n-uploader-query__button--square__text" v-if="buttonText">{{buttonText}}</div>
180
- </div>
181
- </template>
182
- </template>
183
-
184
- <!-- 上传文件队列 -->
185
- <template v-else>
186
-
187
- <!-- 单个文件 -->
188
- <div
189
- v-for="(fileItem, fileItemIndex) in query"
190
- :key="`query-item-${fileItem.id}`"
191
- class="n-uploader-query__item n-uploader-query__item--file"
192
- :class="{
193
- ghost: fileItemIndex === fromIndex,
194
- }"
195
- :style="{
196
- height: toPx(currentSize),
197
- }"
198
- :draggable="currentDrag"
199
- @mousedown.self="mousedown($event, fileItemIndex)"
200
- @mouseup="dragEnd"
201
- @dragstart="dragStart($event, fileItemIndex)"
202
- @dragenter="dragEnter($event, fileItemIndex)"
203
- @dragend="dragEnd"
204
- >
205
- <!-- 图标 -->
206
- <div
207
- class="n-uploader-query__item__icon"
208
- :style="{
209
- width: toPx(currentSize),
210
- height: toPx(currentSize),
211
- }"
212
- >
213
- <!-- 上传中前 -->
214
- <q-circular-progress
215
- class="n-uploader-query__item__icon__icon"
216
- indeterminate
217
- rounded
218
- :size="toPx(currentSize / 1.8)"
219
- :thickness="0.18"
220
- v-if="fileItem.status < UPLOAD_STATUS.uploading"
221
- />
222
-
223
- <!-- 上传中 -->
224
- <q-circular-progress
225
- class="n-uploader-query__item__icon__icon"
226
- :value="fileItem.progress"
227
- :size="toPx(currentSize / 1.8)"
228
- :thickness="0.18"
229
- show-value
230
- v-else-if="fileItem.status === UPLOAD_STATUS.uploading"
231
- >
232
- <q-icon
233
- class="cursor-pointer"
234
- name="pause"
235
- :size="toPx(currentSize / 3)"
236
- @click="uploader.deleteFileItem(fileItem)"
237
- />
238
- </q-circular-progress>
239
-
240
- <!-- 文件图标 -->
241
- <q-icon
242
- class="n-uploader-query__item__icon__icon"
243
- name="description"
244
- :size="toPx(currentSize / 1.5)"
245
- v-else-if="type === 'file'"
246
- />
247
-
248
- <!-- 播放图标 -->
249
- <q-icon
250
- class="n-uploader-query__item__icon__icon cursor-pointer"
251
- name="play_circle"
252
- title="播放"
253
- :size="toPx(currentSize / 1.5)"
254
- @click="uploader.play(fileItem)"
255
- v-else
256
- />
257
- </div>
258
-
259
- <!-- 信息 -->
260
- <div class="n-uploader-query__item__info">
261
- <!-- 标题 -->
262
- <div class="n-uploader-query__item__info__title ellipsis">{{getFileName(fileItem)}}</div>
263
- <!-- 错误提示 -->
264
- <div class="n-uploader-query__item__info__msg--error" v-if="fileItem.status === UPLOAD_STATUS.fail">{{fileItem.msg}}</div>
265
- </div>
266
-
267
- <!-- 操作 -->
268
- <div class="n-uploader-query__item__settings">
269
-
270
- <template v-if="fileItem.status === UPLOAD_STATUS.success">
271
-
272
- <!-- 复制地址 -->
273
- <q-icon
274
- class="n-uploader-query__item__settings__icon cursor-pointer"
275
- name="content_copy"
276
- color="white"
277
- size="xs"
278
- title="复制地址"
279
- @click="uploader.copyUrl(fileItem)"
280
- />
281
-
282
- <!-- 修改 -->
283
- <q-icon
284
- class="n-uploader-query__item__settings__icon cursor-pointer"
285
- name="edit"
286
- color="white"
287
- size="xs"
288
- title="修改"
289
- @click="uploader.previewImage(fileItem)"
290
- v-if="! noEdit && ! disable && ! readonly"
291
- >
292
- <q-popup-edit
293
- :model-value="fileItem.title"
294
- buttons
295
- label-set="保存"
296
- @save="uploader.editFileTitle($event, fileItem)"
297
- v-slot="scope"
298
- >
299
- <q-input
300
- v-model="scope.value"
301
- dense
302
- autofocus
303
- counter
304
- :maxlength="50"
305
- @keyup.enter="scope.set"
306
- >
307
- <template v-slot:append>
308
- <span class="text-subtitle2 text-weight-bold">.{{fileItem.ext}}</span>
309
- </template>
310
- </q-input>
311
- </q-popup-edit>
312
- </q-icon>
313
- </template>
314
-
315
- <!-- 删除 -->
316
- <q-icon
317
- class="n-uploader-query__item__settings__icon cursor-pointer"
318
- name="close"
319
- color="white"
320
- size="xs"
321
- title="删除"
322
- @click="uploader.deleteFileItem(fileItem)"
323
- v-if="! noDelete && ! disable && ! readonly"
324
- />
325
- </div>
326
- </div>
327
- </template>
328
- </n-dragger>
329
- </div>
330
- </template>
331
-
332
- <script>
333
- import { onMounted, computed, inject } from 'vue'
334
- import { useQuasar } from 'quasar'
335
-
336
- import $n_has from 'lodash/has'
337
- import $n_get from 'lodash/get'
338
-
339
- import $n_px from '@netang/utils/px'
340
- import $n_isValidArray from '@netang/utils/isValidArray'
341
- import $n_isValidString from '@netang/utils/isValidString'
342
-
343
- import $n_getImage from '../../utils/getImage'
344
-
345
- import NDragger from '../dragger'
346
-
347
- import { NUploaderKey } from '../../utils/symbols'
348
-
349
- import {
350
- // 上传状态
351
- UPLOAD_STATUS,
352
- } from '../../utils/useUploader'
353
-
354
- export default {
355
-
356
- /**
357
- * 标识
358
- */
359
- name: 'NUploaderQuery',
360
-
361
- /**
362
- * 组件
363
- */
364
- components: {
365
- NDragger,
366
- },
367
-
368
- /**
369
- * 声明属性
370
- */
371
- props: {
372
- // 按钮类型, 可选值 square button
373
- buttonType: {
374
- type: String,
375
- validator: v => [ 'square', 'button' ].includes(v),
376
- },
377
- // 按钮文字
378
- buttonText: String,
379
- // 按钮声明属性
380
- buttonProps: Object,
381
- // 图片/按钮/文件 尺寸
382
- size: Number,
383
- // 是否开启拖拽
384
- drag: {
385
- type: Boolean,
386
- default: true,
387
- },
388
- // 是否禁用
389
- disable: Boolean,
390
- // 是否只读
391
- readonly: Boolean,
392
- // 是否隐藏按钮
393
- noButton: Boolean,
394
- // 是否隐藏预览按钮
395
- noPreview: Boolean,
396
- // 是否隐藏修改按钮
397
- noEdit: Boolean,
398
- // 是否隐藏删除按钮
399
- noDelete: Boolean,
400
- // 自动显示方块按钮
401
- autoShowSquareButton: Boolean,
402
- // 方块按钮在右边显示
403
- rightSquareButton: Boolean,
404
- },
405
-
406
- /**
407
- * 声明事件
408
- */
409
- emits: [
410
- 'update:modelValue',
411
- ],
412
-
413
- /**
414
- * 组合式
415
- */
416
- setup(props) {
417
-
418
- // ==========【数据】============================================================================================
419
-
420
- // quasar 对象
421
- const $q = useQuasar()
422
-
423
- // 获取上传器注入数据
424
- const {
425
- // 声明属性
426
- props: uploaderProps,
427
- // 上传器
428
- uploader,
429
- // 文件队列
430
- query,
431
- } = inject(NUploaderKey)
432
-
433
- // ==========【计算属性】=========================================================================================
434
-
435
- /**
436
- * 当前是否开启拖拽
437
- */
438
- const currentDrag = computed(function() {
439
- return props.drag
440
- && $n_isValidArray(query.value)
441
- && query.value.length > 1
442
- && ! props.readonly
443
- && ! props.disable
444
- })
445
-
446
- /**
447
- * 当前按钮类型
448
- */
449
- const currentButtonType = computed(function () {
450
- if (props.buttonType) {
451
- return props.buttonType
452
- }
453
- return uploaderProps.type === 'image' ? 'square' : 'button'
454
- })
455
-
456
- /**
457
- * 当前尺寸
458
- */
459
- const currentSize = computed(function () {
460
- if (props.size) {
461
- return props.size
462
- }
463
- return uploaderProps.type === 'image' ? 70 : 50
464
- })
465
-
466
- /**
467
- * 是否显示方块按钮
468
- */
469
- const showSquareButton = computed(function () {
470
- // 自动显示方块按钮 && 有上传文件限制数量
471
- return props.autoShowSquareButton && uploaderProps.count > 0 ?
472
- // 如果 上传文件列表数量 < 上传文件限制数量
473
- query.value.length < uploaderProps.count
474
- // 始终显示
475
- : true
476
- })
477
-
478
- // ==========【方法】=============================================================================================
479
-
480
- /**
481
- * 获取图片地址
482
- */
483
- function getImage(fileItem) {
484
- return $n_has(fileItem, '__img') ?
485
- fileItem.__img
486
- : (
487
- $n_isValidString(fileItem.hash) ?
488
- $n_getImage(fileItem.hash, { w: $q.platform.is.mobile ? currentSize.value * 2 : currentSize.value })
489
- : ''
490
- )
491
- }
492
-
493
- /**
494
- * 获取文件名称
495
- */
496
- function getFileName(fileItem) {
497
- return fileItem.title + ($n_get(fileItem, 'ext') ? '.' + fileItem.ext : '')
498
- }
499
-
500
- // ==========【生命周期】=========================================================================================
501
-
502
- /**
503
- * 实例被挂载后调用
504
- */
505
- onMounted( async function() {
506
-
507
- })
508
-
509
- // ==========【返回】=============================================================================================
510
-
511
- return {
512
- // 上传状态
513
- UPLOAD_STATUS,
514
- // 上传文件类型, 可选值 file image video audio
515
- type: uploaderProps.type,
516
- // 上传文件数量(0:不限)
517
- count: uploaderProps.count,
518
- // 上传文件队列
519
- query,
520
-
521
- // 当前是否开启拖拽
522
- currentDrag,
523
- // 当前按钮类型
524
- currentButtonType,
525
- // 当前尺寸
526
- currentSize,
527
- // 是否显示方块按钮
528
- showSquareButton,
529
-
530
- // 上传器
531
- uploader,
532
-
533
- // 获取图片地址
534
- getImage,
535
- // 获取文件名称
536
- getFileName,
537
-
538
- toPx: $n_px,
539
- }
540
- },
541
- }
542
- </script>
543
-
544
- <style lang="scss">
545
- @import "@/assets/sass/variables.scss";
546
-
547
- // 上传器队列
548
- .n-uploader-query {
549
-
550
- // 上传按钮
551
- &__button {
552
-
553
- // 方块
554
- &--square {
555
- display: inline-flex;
556
- vertical-align: middle;
557
- overflow: hidden;
558
- border: 1px dashed rgba(var(--n-reverse-color-rgb), 0.2);
559
- flex-direction: column;
560
- justify-content: center;
561
- align-items: center;
562
- color: rgba(var(--n-reverse-color-rgb), 0.4);
563
- border-radius: 4px;
564
-
565
- &:hover {
566
- border-color: $primary;
567
- }
568
-
569
- // 文字
570
- &__text {
571
- font-size: 12px;
572
- }
573
- }
574
-
575
- // 按钮
576
- &--button {
577
- + .n-uploader-query__query {
578
- margin-top: 0;
579
- }
580
- }
581
- }
582
-
583
- // 上传单个文件
584
- &__item {
585
- position: relative;
586
-
587
- // 开启拖拽
588
- &[draggable="true"] {
589
- cursor: move;
590
- }
591
-
592
- // 当前拖拽占位元素
593
- &.ghost {
594
- &:after {
595
- content: "";
596
- position: absolute;
597
- top: 0;
598
- left: 0;
599
- right: 0;
600
- bottom: 0;
601
- border: 2px dashed mix(#ffffff, $primary, 40%);
602
- border-radius: 4px;
603
- background-color: rgba(255, 255, 255, 0.75);
604
- }
605
-
606
- .n-uploader-query__item__inner,
607
- .n-uploader-query__item__settings {
608
- display: none;
609
- }
610
- }
611
-
612
- &:hover {
613
- .n-uploader-query__item__settings {
614
- visibility: visible;
615
- }
616
- }
617
-
618
- // 单个图片
619
- &--image {
620
- border-radius: 4px;
621
- background-color: rgba(0,0,0,0.1);
622
- overflow: hidden;
623
- }
624
-
625
- // 单个文件
626
- &--file {
627
- position: relative;
628
- width: 300px;
629
- display: flex;
630
- flex-direction: row;
631
- align-items: center;
632
- border-radius: 4px;
633
- color: rgba(var(--n-reverse-color-rgb), 0.8);
634
- background-color: rgba(var(--n-reverse-color-rgb), 0.05);
635
-
636
- // 图标
637
- .n-uploader-query__item__icon {
638
- position: relative;
639
- display: flex;
640
- align-items: center;
641
- justify-content: center;
642
- z-index: 1;
643
-
644
- &__icon {
645
- color: rgba(var(--n-reverse-color-rgb), 0.2);
646
- }
647
- }
648
-
649
- // 信息
650
- .n-uploader-query__item__info {
651
- display: flex;
652
- flex-direction: column;
653
- line-height: 18px;
654
-
655
- &__title {
656
- max-width: 150px;
657
- }
658
-
659
- &__msg--error {
660
- color: $negative;
661
- }
662
- }
663
- }
664
-
665
- //操作
666
- &__settings {
667
- position: absolute;
668
- top: 5px;
669
- right: 5px;
670
- visibility: hidden;
671
-
672
- &__icon {
673
- background-color: rgba(0,0,0,0.5);
674
- border-radius: 50%;
675
- padding: 5px;
676
-
677
- + .n-uploader-query__item__settings__icon {
678
- margin-left: 4px;
679
- }
680
-
681
- &:hover {
682
- background-color: rgba(0,0,0,0.8);
683
- }
684
- }
685
- }
686
-
687
- // 内容
688
- &__inner {
689
-
690
- &__msg {
691
- margin: 3px;
692
- padding: 2px 3px;
693
- line-height: 18px;
694
- font-size: 12px;
695
- background-color: rgba(0,0,0,0.6);
696
- border-radius: 6px;
697
-
698
- &--error {
699
- background-color: $warning;
700
- }
701
- }
702
- }
703
- }
704
- }
705
-
706
- @media (max-width: $breakpoint-xs-max){
707
- // 上传器队列
708
- .n-uploader-query {
709
- // 上传单个文件
710
- &__item {
711
- // 单个文件
712
- &--file {
713
- width: 100%;
714
- }
715
- }
716
-
717
- // 信息
718
- .n-uploader-query__item__info {
719
- &__title {
720
- max-width: 200px;
721
- }
722
- }
723
- }
724
- }
725
-
726
- /**
727
- * 手机版
728
- */
729
- body.mobile {
730
- // 上传器队列
731
- .n-uploader-query {
732
- // 上传单个文件
733
- &__item {
734
- &__settings {
735
- visibility: visible;
736
- }
737
- }
738
- }
739
- }
740
-
741
- /**
742
- * 暗黑
743
- */
744
- .body--dark {
745
- .n-uploader-query__item--file {
746
- .n-uploader-query__item__settings {
747
- // 图标
748
- &__icon {
749
- background-color: rgba(255,255,255, 0.1);
750
-
751
- &:hover {
752
- background-color: rgba(255,255,255, 0.2);
753
- }
754
- }
755
- }
756
- }
757
- }
758
- </style>
1
+ <template>
2
+ <div class="n-uploader-query">
3
+
4
+ <!-- 上传按钮 -->
5
+ <slot
6
+ name="button"
7
+ :disable="disable || readonly"
8
+ :size="currentSize"
9
+ v-if="$slots.button"
10
+ />
11
+ <div
12
+ class="n-uploader-query__button--button"
13
+ v-else-if="! noButton && currentButtonType === 'button'"
14
+ >
15
+ <q-btn
16
+ class="n-button-icon"
17
+ :label="buttonText || '上传'"
18
+ @click="uploader.chooseUpload"
19
+ color="primary"
20
+ outline
21
+ :disable="disable || readonly"
22
+ unelevated
23
+ v-bind="buttonProps"
24
+ />
25
+ </div>
26
+
27
+ <!-- 拖拽器 -->
28
+ <n-dragger
29
+ class="n-uploader-query__query row q-gutter-sm"
30
+ v-model="query"
31
+ :drag="currentDrag"
32
+ @update:model-value="uploader.updateValue"
33
+ v-slot="{ mousedown, fromIndex, dragStart, dragEnter, dragEnd }"
34
+ >
35
+ <!-- 上传图片队列 -->
36
+ <template v-if="type === 'image'">
37
+
38
+ <!-- 左边方块按钮 -->
39
+ <template v-if="! disable && ! readonly && ! rightSquareButton">
40
+ <slot
41
+ name="square-button"
42
+ :size="currentSize"
43
+ :show="showSquareButton"
44
+ v-if="$slots['square-button']"
45
+ />
46
+ <div
47
+ class="n-uploader-query__button--square cursor-pointer"
48
+ :style="{
49
+ width: toPx(currentSize),
50
+ height: toPx(currentSize),
51
+ }"
52
+ @click="uploader.chooseUpload"
53
+ v-show="showSquareButton"
54
+ v-else-if="! noButton && currentButtonType === 'square'"
55
+ >
56
+ <q-icon
57
+ name="add"
58
+ :size="toPx(currentSize / 2)"
59
+ />
60
+ <div class="n-uploader-query__button--square__text" v-if="buttonText">{{buttonText}}</div>
61
+ </div>
62
+ </template>
63
+
64
+ <!-- 单个图片 -->
65
+ <div
66
+ v-for="(fileItem, fileItemIndex) in query"
67
+ :key="`query-item-${fileItem.id}`"
68
+ class="n-uploader-query__item n-uploader-query__item--image"
69
+ :class="{
70
+ ghost: fileItemIndex === fromIndex,
71
+ }"
72
+ :draggable="currentDrag"
73
+ @mousedown.self="mousedown($event, fileItemIndex)"
74
+ @mouseup="dragEnd"
75
+ @dragstart="dragStart($event, fileItemIndex)"
76
+ @dragenter="dragEnter($event, fileItemIndex)"
77
+ @dragend="dragEnd"
78
+ >
79
+ <n-img
80
+ :src="getImage(fileItem)"
81
+ :spinner-size="toPx(currentSize / 2)"
82
+ :width="toPx(currentSize)"
83
+ :height="toPx(currentSize)"
84
+ fit="fill"
85
+ >
86
+ <!-- 如果是外链 -->
87
+ <span class="n-uploader-query__item__net" v-if="fileItem.isNet">链接</span>
88
+
89
+ <!-- 内容 -->
90
+ <div
91
+ class="n-uploader-query__item__inner absolute-full flex flex-center no-padding transparent"
92
+ :class="{
93
+ 'transparent': fileItem.status < UPLOAD_STATUS.success,
94
+ }"
95
+ v-if="fileItem.status !== UPLOAD_STATUS.success"
96
+ >
97
+ <!-- 如果上传失败 -->
98
+ <div
99
+ class="n-uploader-query__item__inner__msg n-uploader-query__item__inner__msg--error"
100
+ v-if="fileItem.status === UPLOAD_STATUS.fail"
101
+ >{{fileItem.msg}}</div>
102
+
103
+ <!-- 上传中前 -->
104
+ <q-circular-progress
105
+ indeterminate
106
+ rounded
107
+ :size="toPx(currentSize / 1.5)"
108
+ :thickness="0.14"
109
+ color="white"
110
+ v-if="fileItem.status < UPLOAD_STATUS.uploading"
111
+ />
112
+
113
+ <!-- 上传中 -->
114
+ <q-circular-progress
115
+ :value="fileItem.progress"
116
+ :size="toPx(currentSize / 1.5)"
117
+ :thickness="0.14"
118
+ color="white"
119
+ track-color="grey-5"
120
+ show-value
121
+ v-else-if="fileItem.status === UPLOAD_STATUS.uploading"
122
+ >
123
+ <q-icon
124
+ class="cursor-pointer"
125
+ name="pause"
126
+ :size="toPx(currentSize / 3)"
127
+ @click="uploader.deleteFileItem(fileItem)"
128
+ />
129
+ </q-circular-progress>
130
+ </div>
131
+
132
+ <!-- 操作 -->
133
+ <div
134
+ class="n-uploader-query__item__settings transparent no-padding"
135
+ v-if="fileItem.status !== UPLOAD_STATUS.uploading"
136
+ >
137
+ <!-- 预览 -->
138
+ <q-icon
139
+ class="n-uploader-query__item__settings__icon cursor-pointer"
140
+ name="search"
141
+ size="xs"
142
+ title="预览"
143
+ @click="uploader.previewImage(fileItem)"
144
+ v-if="! noPreview && getImage(fileItem)"
145
+ />
146
+
147
+ <!-- 删除 -->
148
+ <q-icon
149
+ class="n-uploader-query__item__settings__icon cursor-pointer"
150
+ name="close"
151
+ size="xs"
152
+ title="删除"
153
+ @click="uploader.deleteFileItem(fileItem)"
154
+ v-if="! noDelete && ! disable && ! readonly"
155
+ />
156
+ </div>
157
+
158
+ </n-img>
159
+ </div>
160
+
161
+ <!-- 右边方块按钮 -->
162
+ <template v-if="! disable && ! readonly && rightSquareButton">
163
+ <slot
164
+ name="square-button"
165
+ :size="currentSize"
166
+ :show="showSquareButton"
167
+ v-if="$slots['square-button']"
168
+ />
169
+ <div
170
+ class="n-uploader-query__button--square cursor-pointer"
171
+ :style="{
172
+ width: toPx(currentSize),
173
+ height: toPx(currentSize),
174
+ }"
175
+ @click="uploader.chooseUpload"
176
+ v-show="showSquareButton"
177
+ v-else-if="! noButton && currentButtonType === 'square'"
178
+ >
179
+ <q-icon
180
+ name="add"
181
+ :size="toPx(currentSize / 2)"
182
+ />
183
+ <div class="n-uploader-query__button--square__text" v-if="buttonText">{{buttonText}}</div>
184
+ </div>
185
+ </template>
186
+ </template>
187
+
188
+ <!-- 上传文件队列 -->
189
+ <template v-else>
190
+
191
+ <!-- 单个文件 -->
192
+ <div
193
+ v-for="(fileItem, fileItemIndex) in query"
194
+ :key="`query-item-${fileItem.id}`"
195
+ class="n-uploader-query__item n-uploader-query__item--file"
196
+ :class="{
197
+ ghost: fileItemIndex === fromIndex,
198
+ }"
199
+ :style="{
200
+ height: toPx(currentSize),
201
+ }"
202
+ :draggable="currentDrag"
203
+ @mousedown.self="mousedown($event, fileItemIndex)"
204
+ @mouseup="dragEnd"
205
+ @dragstart="dragStart($event, fileItemIndex)"
206
+ @dragenter="dragEnter($event, fileItemIndex)"
207
+ @dragend="dragEnd"
208
+ >
209
+ <!-- 如果是外链 -->
210
+ <span class="n-uploader-query__item__net" v-if="fileItem.isNet">链接</span>
211
+
212
+ <!-- 图标 -->
213
+ <div
214
+ class="n-uploader-query__item__icon"
215
+ :style="{
216
+ width: toPx(currentSize),
217
+ height: toPx(currentSize),
218
+ }"
219
+ >
220
+ <!-- 上传中前 -->
221
+ <q-circular-progress
222
+ class="n-uploader-query__item__icon__icon"
223
+ indeterminate
224
+ rounded
225
+ :size="toPx(currentSize / 1.8)"
226
+ :thickness="0.18"
227
+ v-if="fileItem.status < UPLOAD_STATUS.uploading"
228
+ />
229
+
230
+ <!-- 上传中 -->
231
+ <q-circular-progress
232
+ class="n-uploader-query__item__icon__icon"
233
+ :value="fileItem.progress"
234
+ :size="toPx(currentSize / 1.8)"
235
+ :thickness="0.18"
236
+ show-value
237
+ v-else-if="fileItem.status === UPLOAD_STATUS.uploading"
238
+ >
239
+ <q-icon
240
+ class="cursor-pointer"
241
+ name="pause"
242
+ :size="toPx(currentSize / 3)"
243
+ @click="uploader.deleteFileItem(fileItem)"
244
+ />
245
+ </q-circular-progress>
246
+
247
+ <!-- 文件图标 -->
248
+ <q-icon
249
+ class="n-uploader-query__item__icon__icon"
250
+ name="description"
251
+ :size="toPx(currentSize / 1.5)"
252
+ v-else-if="type === 'file'"
253
+ />
254
+
255
+ <!-- 播放图标 -->
256
+ <q-icon
257
+ class="n-uploader-query__item__icon__icon cursor-pointer"
258
+ name="play_circle"
259
+ title="播放"
260
+ :size="toPx(currentSize / 1.5)"
261
+ @click="uploader.play(fileItem)"
262
+ v-else
263
+ />
264
+ </div>
265
+
266
+ <!-- 信息 -->
267
+ <div class="n-uploader-query__item__info">
268
+ <!-- 标题 -->
269
+ <div class="n-uploader-query__item__info__title ellipsis">{{getFileName(fileItem)}}</div>
270
+ <!-- 错误提示 -->
271
+ <div class="n-uploader-query__item__info__msg--error" v-if="fileItem.status === UPLOAD_STATUS.fail">{{fileItem.msg}}</div>
272
+ </div>
273
+
274
+ <!-- 操作 -->
275
+ <div class="n-uploader-query__item__settings">
276
+
277
+ <template v-if="fileItem.status === UPLOAD_STATUS.success">
278
+
279
+ <!-- 复制地址 -->
280
+ <q-icon
281
+ class="n-uploader-query__item__settings__icon cursor-pointer"
282
+ name="content_copy"
283
+ color="white"
284
+ size="xs"
285
+ title="复制地址"
286
+ @click="uploader.copyUrl(fileItem)"
287
+ />
288
+
289
+ <!-- 修改 -->
290
+ <q-icon
291
+ class="n-uploader-query__item__settings__icon cursor-pointer"
292
+ name="edit"
293
+ color="white"
294
+ size="xs"
295
+ title="修改"
296
+ v-if="! noEdit && ! disable && ! readonly && ! fileItem.isNet"
297
+ >
298
+ <q-popup-edit
299
+ :model-value="fileItem.title"
300
+ buttons
301
+ label-set="保存"
302
+ @save="uploader.editFileTitle($event, fileItem)"
303
+ v-slot="scope"
304
+ >
305
+ <q-input
306
+ v-model="scope.value"
307
+ dense
308
+ autofocus
309
+ counter
310
+ :maxlength="50"
311
+ @keyup.enter="scope.set"
312
+ >
313
+ <template v-slot:append>
314
+ <span class="text-subtitle2 text-weight-bold">.{{fileItem.ext}}</span>
315
+ </template>
316
+ </q-input>
317
+ </q-popup-edit>
318
+ </q-icon>
319
+ </template>
320
+
321
+ <!-- 删除 -->
322
+ <q-icon
323
+ class="n-uploader-query__item__settings__icon cursor-pointer"
324
+ name="close"
325
+ color="white"
326
+ size="xs"
327
+ title="删除"
328
+ @click="uploader.deleteFileItem(fileItem)"
329
+ v-if="! noDelete && ! disable && ! readonly"
330
+ />
331
+ </div>
332
+ </div>
333
+ </template>
334
+ </n-dragger>
335
+ </div>
336
+ </template>
337
+
338
+ <script>
339
+ import { onMounted, computed, inject } from 'vue'
340
+ import { useQuasar } from 'quasar'
341
+
342
+ import $n_has from 'lodash/has'
343
+ import $n_get from 'lodash/get'
344
+
345
+ import $n_px from '@netang/utils/px'
346
+ import $n_isValidArray from '@netang/utils/isValidArray'
347
+ import $n_isValidString from '@netang/utils/isValidString'
348
+
349
+ import $n_getImage from '../../utils/getImage'
350
+
351
+ import NDragger from '../dragger'
352
+
353
+ import { NUploaderKey } from '../../utils/symbols'
354
+
355
+ import {
356
+ // 上传状态
357
+ UPLOAD_STATUS,
358
+ } from '../../utils/useUploader'
359
+
360
+ export default {
361
+
362
+ /**
363
+ * 标识
364
+ */
365
+ name: 'NUploaderQuery',
366
+
367
+ /**
368
+ * 组件
369
+ */
370
+ components: {
371
+ NDragger,
372
+ },
373
+
374
+ /**
375
+ * 声明属性
376
+ */
377
+ props: {
378
+ // 按钮类型, 可选值 square button
379
+ buttonType: {
380
+ type: String,
381
+ validator: v => [ 'square', 'button' ].includes(v),
382
+ },
383
+ // 按钮文字
384
+ buttonText: String,
385
+ // 按钮声明属性
386
+ buttonProps: Object,
387
+ // 图片/按钮/文件 尺寸
388
+ size: Number,
389
+ // 是否开启拖拽
390
+ drag: {
391
+ type: Boolean,
392
+ default: true,
393
+ },
394
+ // 是否禁用
395
+ disable: Boolean,
396
+ // 是否只读
397
+ readonly: Boolean,
398
+ // 是否隐藏按钮
399
+ noButton: Boolean,
400
+ // 是否隐藏预览按钮
401
+ noPreview: Boolean,
402
+ // 是否隐藏修改按钮
403
+ noEdit: Boolean,
404
+ // 是否隐藏删除按钮
405
+ noDelete: Boolean,
406
+ // 自动显示方块按钮
407
+ autoShowSquareButton: Boolean,
408
+ // 方块按钮在右边显示
409
+ rightSquareButton: Boolean,
410
+ },
411
+
412
+ /**
413
+ * 声明事件
414
+ */
415
+ emits: [
416
+ 'update:modelValue',
417
+ ],
418
+
419
+ /**
420
+ * 组合式
421
+ */
422
+ setup(props) {
423
+
424
+ // ==========【数据】============================================================================================
425
+
426
+ // quasar 对象
427
+ const $q = useQuasar()
428
+
429
+ // 获取上传器注入数据
430
+ const {
431
+ // 声明属性
432
+ props: uploaderProps,
433
+ // 上传器
434
+ uploader,
435
+ // 文件队列
436
+ query,
437
+ } = inject(NUploaderKey)
438
+
439
+ // ==========【计算属性】=========================================================================================
440
+
441
+ /**
442
+ * 当前是否开启拖拽
443
+ */
444
+ const currentDrag = computed(function() {
445
+ return props.drag
446
+ && $n_isValidArray(query.value)
447
+ && query.value.length > 1
448
+ && ! props.readonly
449
+ && ! props.disable
450
+ })
451
+
452
+ /**
453
+ * 当前按钮类型
454
+ */
455
+ const currentButtonType = computed(function () {
456
+ if (props.buttonType) {
457
+ return props.buttonType
458
+ }
459
+ return uploaderProps.type === 'image' ? 'square' : 'button'
460
+ })
461
+
462
+ /**
463
+ * 当前尺寸
464
+ */
465
+ const currentSize = computed(function () {
466
+ if (props.size) {
467
+ return props.size
468
+ }
469
+ return uploaderProps.type === 'image' ? 70 : 50
470
+ })
471
+
472
+ /**
473
+ * 是否显示方块按钮
474
+ */
475
+ const showSquareButton = computed(function () {
476
+ // 自动显示方块按钮 && 有上传文件限制数量
477
+ return props.autoShowSquareButton && uploaderProps.count > 0 ?
478
+ // 如果 上传文件列表数量 < 上传文件限制数量
479
+ query.value.length < uploaderProps.count
480
+ // 始终显示
481
+ : true
482
+ })
483
+
484
+ // ==========【方法】=============================================================================================
485
+
486
+ /**
487
+ * 获取图片地址
488
+ */
489
+ function getImage(fileItem) {
490
+ return $n_has(fileItem, '__img') ?
491
+ fileItem.__img
492
+ : (
493
+ $n_isValidString(fileItem.hash) ?
494
+ $n_getImage(fileItem.hash, { w: $q.platform.is.mobile ? currentSize.value * 2 : currentSize.value })
495
+ : ''
496
+ )
497
+ }
498
+
499
+ /**
500
+ * 获取文件名称
501
+ */
502
+ function getFileName(fileItem) {
503
+ return fileItem.title + ($n_get(fileItem, 'ext') ? '.' + fileItem.ext : '')
504
+ }
505
+
506
+ // ==========【生命周期】=========================================================================================
507
+
508
+ /**
509
+ * 实例被挂载后调用
510
+ */
511
+ onMounted( async function() {
512
+
513
+ })
514
+
515
+ // ==========【返回】=============================================================================================
516
+
517
+ return {
518
+ // 上传状态
519
+ UPLOAD_STATUS,
520
+ // 上传文件类型, 可选值 file image video audio
521
+ type: uploaderProps.type,
522
+ // 上传文件数量(0:不限)
523
+ count: uploaderProps.count,
524
+ // 上传文件队列
525
+ query,
526
+
527
+ // 当前是否开启拖拽
528
+ currentDrag,
529
+ // 当前按钮类型
530
+ currentButtonType,
531
+ // 当前尺寸
532
+ currentSize,
533
+ // 是否显示方块按钮
534
+ showSquareButton,
535
+
536
+ // 上传器
537
+ uploader,
538
+
539
+ // 获取图片地址
540
+ getImage,
541
+ // 获取文件名称
542
+ getFileName,
543
+
544
+ toPx: $n_px,
545
+ }
546
+ },
547
+ }
548
+ </script>
549
+
550
+ <style lang="scss">
551
+ @import "@/assets/sass/variables.scss";
552
+
553
+ // 上传器队列
554
+ .n-uploader-query {
555
+
556
+ // 上传按钮
557
+ &__button {
558
+
559
+ // 方块
560
+ &--square {
561
+ display: inline-flex;
562
+ vertical-align: middle;
563
+ overflow: hidden;
564
+ border: 1px dashed rgba(var(--n-reverse-color-rgb), 0.2);
565
+ flex-direction: column;
566
+ justify-content: center;
567
+ align-items: center;
568
+ color: rgba(var(--n-reverse-color-rgb), 0.4);
569
+ border-radius: 4px;
570
+
571
+ &:hover {
572
+ border-color: $primary;
573
+ }
574
+
575
+ // 文字
576
+ &__text {
577
+ font-size: 12px;
578
+ }
579
+ }
580
+
581
+ // 按钮
582
+ &--button {
583
+ + .n-uploader-query__query {
584
+ margin-top: 0;
585
+ }
586
+ }
587
+ }
588
+
589
+ // 上传单个文件
590
+ &__item {
591
+ position: relative;
592
+
593
+ // 开启拖拽
594
+ &[draggable="true"] {
595
+ cursor: move;
596
+ }
597
+
598
+ // 当前拖拽占位元素
599
+ &.ghost {
600
+ &:after {
601
+ content: "";
602
+ position: absolute;
603
+ top: 0;
604
+ left: 0;
605
+ right: 0;
606
+ bottom: 0;
607
+ border: 2px dashed mix(#ffffff, $primary, 40%);
608
+ border-radius: 4px;
609
+ background-color: rgba(255, 255, 255, 0.75);
610
+ }
611
+
612
+ .n-uploader-query__item__inner,
613
+ .n-uploader-query__item__settings {
614
+ display: none;
615
+ }
616
+ }
617
+
618
+ &:hover {
619
+ .n-uploader-query__item__settings {
620
+ visibility: visible;
621
+ }
622
+ }
623
+
624
+ // 单个图片
625
+ &--image {
626
+ border-radius: 4px;
627
+ background-color: rgba(0,0,0,0.1);
628
+ overflow: hidden;
629
+ }
630
+
631
+ // 单个文件
632
+ &--file {
633
+ position: relative;
634
+ width: 300px;
635
+ display: flex;
636
+ flex-direction: row;
637
+ align-items: center;
638
+ border-radius: 4px;
639
+ color: rgba(var(--n-reverse-color-rgb), 0.8);
640
+ background-color: rgba(var(--n-reverse-color-rgb), 0.05);
641
+
642
+ // 图标
643
+ .n-uploader-query__item__icon {
644
+ position: relative;
645
+ display: flex;
646
+ align-items: center;
647
+ justify-content: center;
648
+ z-index: 1;
649
+
650
+ &__icon {
651
+ color: rgba(var(--n-reverse-color-rgb), 0.2);
652
+ }
653
+ }
654
+
655
+ // 信息
656
+ .n-uploader-query__item__info {
657
+ display: flex;
658
+ flex-direction: column;
659
+ line-height: 18px;
660
+
661
+ &__title {
662
+ max-width: 150px;
663
+ }
664
+
665
+ &__msg--error {
666
+ color: $negative;
667
+ }
668
+ }
669
+ }
670
+
671
+ // 外链
672
+ &__net {
673
+ position: absolute;
674
+ bottom: -1px;
675
+ right: -1px;
676
+ color: #ffffff;
677
+ padding: 1px 3px;
678
+ border-radius: 3px;
679
+ background-color: var(--q-primary);
680
+ transform: scale(0.7);
681
+ }
682
+
683
+ //操作
684
+ &__settings {
685
+ position: absolute;
686
+ top: 5px;
687
+ right: 5px;
688
+ visibility: hidden;
689
+
690
+ &__icon {
691
+ background-color: rgba(0,0,0,0.5);
692
+ border-radius: 50%;
693
+ padding: 5px;
694
+
695
+ + .n-uploader-query__item__settings__icon {
696
+ margin-left: 4px;
697
+ }
698
+
699
+ &:hover {
700
+ background-color: rgba(0,0,0,0.8);
701
+ }
702
+ }
703
+ }
704
+
705
+ // 内容
706
+ &__inner {
707
+
708
+ &__msg {
709
+ margin: 3px;
710
+ padding: 2px 3px;
711
+ line-height: 18px;
712
+ font-size: 12px;
713
+ background-color: rgba(0,0,0,0.6);
714
+ border-radius: 6px;
715
+
716
+ &--error {
717
+ background-color: $warning;
718
+ }
719
+ }
720
+ }
721
+ }
722
+ }
723
+
724
+ @media (max-width: $breakpoint-xs-max){
725
+ // 上传器队列
726
+ .n-uploader-query {
727
+ // 上传单个文件
728
+ &__item {
729
+ // 单个文件
730
+ &--file {
731
+ width: 100%;
732
+ }
733
+ }
734
+
735
+ // 信息
736
+ .n-uploader-query__item__info {
737
+ &__title {
738
+ max-width: 200px;
739
+ }
740
+ }
741
+ }
742
+ }
743
+
744
+ /**
745
+ * 手机版
746
+ */
747
+ body.mobile {
748
+ // 上传器队列
749
+ .n-uploader-query {
750
+ // 上传单个文件
751
+ &__item {
752
+ &__settings {
753
+ visibility: visible;
754
+ }
755
+ }
756
+ }
757
+ }
758
+
759
+ /**
760
+ * 暗黑
761
+ */
762
+ .body--dark {
763
+ .n-uploader-query__item--file {
764
+ .n-uploader-query__item__settings {
765
+ // 图标
766
+ &__icon {
767
+ background-color: rgba(255,255,255, 0.1);
768
+
769
+ &:hover {
770
+ background-color: rgba(255,255,255, 0.2);
771
+ }
772
+ }
773
+ }
774
+ }
775
+ }
776
+ </style>