@netang/quasar 0.1.57 → 0.1.59

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,757 @@
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
+ <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
+ v-if="! noEdit && ! disable && ! readonly && ! fileItem.isNet"
290
+ >
291
+ <q-popup-edit
292
+ :model-value="fileItem.title"
293
+ buttons
294
+ label-set="保存"
295
+ @save="uploader.editFileTitle($event, fileItem)"
296
+ v-slot="scope"
297
+ >
298
+ <q-input
299
+ v-model="scope.value"
300
+ dense
301
+ autofocus
302
+ counter
303
+ :maxlength="50"
304
+ @keyup.enter="scope.set"
305
+ >
306
+ <template v-slot:append>
307
+ <span class="text-subtitle2 text-weight-bold">.{{fileItem.ext}}</span>
308
+ </template>
309
+ </q-input>
310
+ </q-popup-edit>
311
+ </q-icon>
312
+ </template>
313
+
314
+ <!-- 删除 -->
315
+ <q-icon
316
+ class="n-uploader-query__item__settings__icon cursor-pointer"
317
+ name="close"
318
+ color="white"
319
+ size="xs"
320
+ title="删除"
321
+ @click="uploader.deleteFileItem(fileItem)"
322
+ v-if="! noDelete && ! disable && ! readonly"
323
+ />
324
+ </div>
325
+ </div>
326
+ </template>
327
+ </n-dragger>
328
+ </div>
329
+ </template>
330
+
331
+ <script>
332
+ import { onMounted, computed, inject } from 'vue'
333
+ import { useQuasar } from 'quasar'
334
+
335
+ import $n_has from 'lodash/has'
336
+ import $n_get from 'lodash/get'
337
+
338
+ import $n_px from '@netang/utils/px'
339
+ import $n_isValidArray from '@netang/utils/isValidArray'
340
+ import $n_isValidString from '@netang/utils/isValidString'
341
+
342
+ import $n_getImage from '../../utils/getImage'
343
+
344
+ import NDragger from '../dragger'
345
+
346
+ import { NUploaderKey } from '../../utils/symbols'
347
+
348
+ import {
349
+ // 上传状态
350
+ UPLOAD_STATUS,
351
+ } from '../../utils/useUploader'
352
+
353
+ export default {
354
+
355
+ /**
356
+ * 标识
357
+ */
358
+ name: 'NUploaderQuery',
359
+
360
+ /**
361
+ * 组件
362
+ */
363
+ components: {
364
+ NDragger,
365
+ },
366
+
367
+ /**
368
+ * 声明属性
369
+ */
370
+ props: {
371
+ // 按钮类型, 可选值 square button
372
+ buttonType: {
373
+ type: String,
374
+ validator: v => [ 'square', 'button' ].includes(v),
375
+ },
376
+ // 按钮文字
377
+ buttonText: String,
378
+ // 按钮声明属性
379
+ buttonProps: Object,
380
+ // 图片/按钮/文件 尺寸
381
+ size: Number,
382
+ // 是否开启拖拽
383
+ drag: {
384
+ type: Boolean,
385
+ default: true,
386
+ },
387
+ // 是否禁用
388
+ disable: Boolean,
389
+ // 是否只读
390
+ readonly: Boolean,
391
+ // 是否隐藏按钮
392
+ noButton: Boolean,
393
+ // 是否隐藏预览按钮
394
+ noPreview: Boolean,
395
+ // 是否隐藏修改按钮
396
+ noEdit: Boolean,
397
+ // 是否隐藏删除按钮
398
+ noDelete: Boolean,
399
+ // 自动显示方块按钮
400
+ autoShowSquareButton: Boolean,
401
+ // 方块按钮在右边显示
402
+ rightSquareButton: Boolean,
403
+ },
404
+
405
+ /**
406
+ * 声明事件
407
+ */
408
+ emits: [
409
+ 'update:modelValue',
410
+ ],
411
+
412
+ /**
413
+ * 组合式
414
+ */
415
+ setup(props) {
416
+
417
+ // ==========【数据】============================================================================================
418
+
419
+ // quasar 对象
420
+ const $q = useQuasar()
421
+
422
+ // 获取上传器注入数据
423
+ const {
424
+ // 声明属性
425
+ props: uploaderProps,
426
+ // 上传器
427
+ uploader,
428
+ // 文件队列
429
+ query,
430
+ } = inject(NUploaderKey)
431
+
432
+ // ==========【计算属性】=========================================================================================
433
+
434
+ /**
435
+ * 当前是否开启拖拽
436
+ */
437
+ const currentDrag = computed(function() {
438
+ return props.drag
439
+ && $n_isValidArray(query.value)
440
+ && query.value.length > 1
441
+ && ! props.readonly
442
+ && ! props.disable
443
+ })
444
+
445
+ /**
446
+ * 当前按钮类型
447
+ */
448
+ const currentButtonType = computed(function () {
449
+ if (props.buttonType) {
450
+ return props.buttonType
451
+ }
452
+ return uploaderProps.type === 'image' ? 'square' : 'button'
453
+ })
454
+
455
+ /**
456
+ * 当前尺寸
457
+ */
458
+ const currentSize = computed(function () {
459
+ if (props.size) {
460
+ return props.size
461
+ }
462
+ return uploaderProps.type === 'image' ? 70 : 50
463
+ })
464
+
465
+ /**
466
+ * 是否显示方块按钮
467
+ */
468
+ const showSquareButton = computed(function () {
469
+ // 自动显示方块按钮 && 有上传文件限制数量
470
+ return props.autoShowSquareButton && uploaderProps.count > 0 ?
471
+ // 如果 上传文件列表数量 < 上传文件限制数量
472
+ query.value.length < uploaderProps.count
473
+ // 始终显示
474
+ : true
475
+ })
476
+
477
+ // ==========【方法】=============================================================================================
478
+
479
+ /**
480
+ * 获取图片地址
481
+ */
482
+ function getImage(fileItem) {
483
+ return $n_has(fileItem, '__img') ?
484
+ fileItem.__img
485
+ : (
486
+ $n_isValidString(fileItem.hash) ?
487
+ $n_getImage(fileItem.hash, { w: $q.platform.is.mobile ? currentSize.value * 2 : currentSize.value })
488
+ : ''
489
+ )
490
+ }
491
+
492
+ /**
493
+ * 获取文件名称
494
+ */
495
+ function getFileName(fileItem) {
496
+ return fileItem.title + ($n_get(fileItem, 'ext') ? '.' + fileItem.ext : '')
497
+ }
498
+
499
+ // ==========【生命周期】=========================================================================================
500
+
501
+ /**
502
+ * 实例被挂载后调用
503
+ */
504
+ onMounted( async function() {
505
+
506
+ })
507
+
508
+ // ==========【返回】=============================================================================================
509
+
510
+ return {
511
+ // 上传状态
512
+ UPLOAD_STATUS,
513
+ // 上传文件类型, 可选值 file image video audio
514
+ type: uploaderProps.type,
515
+ // 上传文件数量(0:不限)
516
+ count: uploaderProps.count,
517
+ // 上传文件队列
518
+ query,
519
+
520
+ // 当前是否开启拖拽
521
+ currentDrag,
522
+ // 当前按钮类型
523
+ currentButtonType,
524
+ // 当前尺寸
525
+ currentSize,
526
+ // 是否显示方块按钮
527
+ showSquareButton,
528
+
529
+ // 上传器
530
+ uploader,
531
+
532
+ // 获取图片地址
533
+ getImage,
534
+ // 获取文件名称
535
+ getFileName,
536
+
537
+ toPx: $n_px,
538
+ }
539
+ },
540
+ }
541
+ </script>
542
+
543
+ <style lang="scss">
544
+ @import "@/assets/sass/variables.scss";
545
+
546
+ // 上传器队列
547
+ .n-uploader-query {
548
+
549
+ // 上传按钮
550
+ &__button {
551
+
552
+ // 方块
553
+ &--square {
554
+ display: inline-flex;
555
+ vertical-align: middle;
556
+ overflow: hidden;
557
+ border: 1px dashed rgba(var(--n-reverse-color-rgb), 0.2);
558
+ flex-direction: column;
559
+ justify-content: center;
560
+ align-items: center;
561
+ color: rgba(var(--n-reverse-color-rgb), 0.4);
562
+ border-radius: 4px;
563
+
564
+ &:hover {
565
+ border-color: $primary;
566
+ }
567
+
568
+ // 文字
569
+ &__text {
570
+ font-size: 12px;
571
+ }
572
+ }
573
+
574
+ // 按钮
575
+ &--button {
576
+ + .n-uploader-query__query {
577
+ margin-top: 0;
578
+ }
579
+ }
580
+ }
581
+
582
+ // 上传单个文件
583
+ &__item {
584
+ position: relative;
585
+
586
+ // 开启拖拽
587
+ &[draggable="true"] {
588
+ cursor: move;
589
+ }
590
+
591
+ // 当前拖拽占位元素
592
+ &.ghost {
593
+ &:after {
594
+ content: "";
595
+ position: absolute;
596
+ top: 0;
597
+ left: 0;
598
+ right: 0;
599
+ bottom: 0;
600
+ border: 2px dashed mix(#ffffff, $primary, 40%);
601
+ border-radius: 4px;
602
+ background-color: rgba(255, 255, 255, 0.75);
603
+ }
604
+
605
+ .n-uploader-query__item__inner,
606
+ .n-uploader-query__item__settings {
607
+ display: none;
608
+ }
609
+ }
610
+
611
+ &:hover {
612
+ .n-uploader-query__item__settings {
613
+ visibility: visible;
614
+ }
615
+ }
616
+
617
+ // 单个图片
618
+ &--image {
619
+ border-radius: 4px;
620
+ background-color: rgba(0,0,0,0.1);
621
+ overflow: hidden;
622
+ }
623
+
624
+ // 单个文件
625
+ &--file {
626
+ position: relative;
627
+ width: 300px;
628
+ display: flex;
629
+ flex-direction: row;
630
+ align-items: center;
631
+ border-radius: 4px;
632
+ color: rgba(var(--n-reverse-color-rgb), 0.8);
633
+ background-color: rgba(var(--n-reverse-color-rgb), 0.05);
634
+
635
+ // 图标
636
+ .n-uploader-query__item__icon {
637
+ position: relative;
638
+ display: flex;
639
+ align-items: center;
640
+ justify-content: center;
641
+ z-index: 1;
642
+
643
+ &__icon {
644
+ color: rgba(var(--n-reverse-color-rgb), 0.2);
645
+ }
646
+ }
647
+
648
+ // 信息
649
+ .n-uploader-query__item__info {
650
+ display: flex;
651
+ flex-direction: column;
652
+ line-height: 18px;
653
+
654
+ &__title {
655
+ max-width: 150px;
656
+ }
657
+
658
+ &__msg--error {
659
+ color: $negative;
660
+ }
661
+ }
662
+ }
663
+
664
+ //操作
665
+ &__settings {
666
+ position: absolute;
667
+ top: 5px;
668
+ right: 5px;
669
+ visibility: hidden;
670
+
671
+ &__icon {
672
+ background-color: rgba(0,0,0,0.5);
673
+ border-radius: 50%;
674
+ padding: 5px;
675
+
676
+ + .n-uploader-query__item__settings__icon {
677
+ margin-left: 4px;
678
+ }
679
+
680
+ &:hover {
681
+ background-color: rgba(0,0,0,0.8);
682
+ }
683
+ }
684
+ }
685
+
686
+ // 内容
687
+ &__inner {
688
+
689
+ &__msg {
690
+ margin: 3px;
691
+ padding: 2px 3px;
692
+ line-height: 18px;
693
+ font-size: 12px;
694
+ background-color: rgba(0,0,0,0.6);
695
+ border-radius: 6px;
696
+
697
+ &--error {
698
+ background-color: $warning;
699
+ }
700
+ }
701
+ }
702
+ }
703
+ }
704
+
705
+ @media (max-width: $breakpoint-xs-max){
706
+ // 上传器队列
707
+ .n-uploader-query {
708
+ // 上传单个文件
709
+ &__item {
710
+ // 单个文件
711
+ &--file {
712
+ width: 100%;
713
+ }
714
+ }
715
+
716
+ // 信息
717
+ .n-uploader-query__item__info {
718
+ &__title {
719
+ max-width: 200px;
720
+ }
721
+ }
722
+ }
723
+ }
724
+
725
+ /**
726
+ * 手机版
727
+ */
728
+ body.mobile {
729
+ // 上传器队列
730
+ .n-uploader-query {
731
+ // 上传单个文件
732
+ &__item {
733
+ &__settings {
734
+ visibility: visible;
735
+ }
736
+ }
737
+ }
738
+ }
739
+
740
+ /**
741
+ * 暗黑
742
+ */
743
+ .body--dark {
744
+ .n-uploader-query__item--file {
745
+ .n-uploader-query__item__settings {
746
+ // 图标
747
+ &__icon {
748
+ background-color: rgba(255,255,255, 0.1);
749
+
750
+ &:hover {
751
+ background-color: rgba(255,255,255, 0.2);
752
+ }
753
+ }
754
+ }
755
+ }
756
+ }
757
+ </style>