@netang/quasar 0.1.90 → 0.1.92

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,1394 +1,1460 @@
1
- <template>
2
-
3
- <!-- 如果有默认插槽 -->
4
- <template v-if="$slots.default">
5
- <slot
6
- :showValue="showValue"
7
- :selected="selected"
8
- :onRemove="onRemoveSelected"
9
- :onShowDialog="onShowDialog"
10
- :onClear="onFieldClear"
11
- />
12
- </template>
13
-
14
- <!--:class="fieldFocused ? 'q-field&#45;&#45;float q-field&#45;&#45;focused q-field&#45;&#45;highlighted' : ''"-->
15
- <!--:clearable="clearable && (! multiple || collapseTags)"-->
16
- <q-field
17
- class="n-field-table"
18
- :model-value="showValue"
19
- :disable="disable"
20
- :readonly="readonly"
21
- :clearable="clearable"
22
- @focus="onFieldFocus"
23
- @blur="onFieldBlur"
24
- @clear="onFieldClear"
25
- v-bind="$attrs"
26
- v-else
27
- >
28
- <template v-slot:control>
29
-
30
- <template v-if="multiple">
31
- <template v-if="selected.length">
32
-
33
- <!-- 多选插槽 -->
34
- <slot
35
- name="selected"
36
- :selected="selected"
37
- :remove="onRemoveSelected"
38
- v-if="$slots.selected"
39
- />
40
-
41
- <!-- 显示折叠的值数量 -->
42
- <q-chip
43
- dense
44
- :label="`+${selected.length}`"
45
- v-else-if="collapseTags"
46
- />
47
-
48
- <!-- 多选标签 -->
49
- <template v-else>
50
- <q-chip
51
- v-for="(item, index) in selected"
52
- :key="`options-${index}`"
53
- :label="currentFormatLabel(item)"
54
- dense
55
- :removable="! readonly && ! disable"
56
- @remove="onRemoveSelected(index)"
57
- >
58
- <q-tooltip>{{currentFormatLabel(item)}}</q-tooltip>
59
- </q-chip>
60
- </template>
61
- </template>
62
-
63
- <!-- 占位符-->
64
- <span class="n-placeholder" v-else-if="placeholder">{{placeholder}}</span>
65
- </template>
66
-
67
- <!-- 显示文字 -->
68
- <span v-else-if="showValue">{{showValue}}</span>
69
-
70
- <!-- 占位符-->
71
- <span class="n-placeholder" v-else-if="placeholder">{{placeholder}}</span>
72
-
73
- <!-- 筛选输入框 -->
74
- <input
75
- ref="inputRef"
76
- class="q-field__input q-placeholder col q-field__input--padding"
77
- v-model="inputValue"
78
- v-if="filter && ! readonly && ! disable"
79
- />
80
-
81
- </template>
82
-
83
- <!-- 弹出对话框图标 -->
84
- <template v-slot:append v-if="! noDialog && ! readonly && ! disable">
85
- <q-icon
86
- class="cursor-pointer"
87
- name="search"
88
- @click.prevent.stop="onShowDialog"
89
- />
90
- </template>
91
-
92
- <!-- 默认插槽 -->
93
- <template
94
- v-for="slotName in slotNames.normal"
95
- v-slot:[slotName]
96
- >
97
- <slot :name="slotName" />
98
- </template>
99
-
100
- <!-- 弹出层代理 -->
101
- <q-popup-proxy
102
- ref="popupRef"
103
- no-refocus
104
- no-focus
105
- fit
106
- @focus="onFieldBlur"
107
- @show="onPopupShow"
108
- @before-hide="showPopup = false"
109
- v-if="! readonly && ! disable"
110
- >
111
- <!-- 快捷表格 -->
112
- <n-table
113
- class="n-table n-field-table__popup-table"
114
- v-model:pagination="tablePagination"
115
- :selected="selected"
116
- @update:selected="emitModelValue"
117
- :row-key="tableRowKey"
118
- :rows="tableRows"
119
- :columns="columns"
120
- :selection="multiple ? 'multiple' : 'none'"
121
- :loading="tableLoading"
122
- :rows-per-page-options="tableRowsPerPageOptions"
123
- @row-click="quickTableRowClick"
124
- @request="tableRequest"
125
- flat
126
- virtual-scroll
127
- dense
128
- v-bind="tableProps"
129
- >
130
- <!-- 图片 -->
131
- <template
132
- v-for="imgItem in tableImgs"
133
- v-slot:[`body-cell-${imgItem.name}`]="props"
134
- >
135
- <n-data
136
- :data="formatImg(props.row[imgItem.name], imgItem)"
137
- v-slot="{ data }"
138
- >
139
- <!-- 缩略图 -->
140
- <n-thumbnail
141
- v-for="(item, index) in data"
142
- :key="`thumbnail-item-${item}`"
143
- class="n-table__thumbnail"
144
- :src="item"
145
- preview
146
- :preview-props="{
147
- startPosition: index,
148
- images: data,
149
- }"
150
- />
151
- </n-data>
152
- </template>
153
-
154
- <!-- 表格插槽 -->
155
- <template
156
- v-for="slotName in slotNames.table"
157
- v-slot:[slotName]="props"
158
- >
159
- <q-td :props="props">
160
- <slot
161
- :name="slotName"
162
- v-bind="props"
163
- />
164
- </q-td>
165
- </template>
166
-
167
- <!-- 翻页 -->
168
- <template v-slot:pagination="props">
169
- <n-table-pagination
170
- :props="props"
171
- no-power
172
- dense
173
- />
174
- </template>
175
- </n-table>
176
- </q-popup-proxy>
177
- </q-field>
178
-
179
- <!-- 弹出对话框 -->
180
- <n-dialog
181
- v-model="showDialog"
182
- width="80%"
183
- :on-confirm="onDialogConfirm"
184
- @before-show="onDialogBeforeShow"
185
- @show="onDialogShow"
186
- @hide="onDialogHide"
187
- cancel
188
- v-bind="dialogProps"
189
- >
190
- <q-page>
191
- <n-mixed-table />
192
- </q-page>
193
- </n-dialog>
194
- </template>
195
-
196
- <script>
197
- import { ref, computed, watch, onUpdated } from 'vue'
198
-
199
- import $n_has from 'lodash/has'
200
- import $n_uniq from 'lodash/uniq'
201
- import $n_cloneDeep from 'lodash/cloneDeep'
202
- import $n_isFunction from 'lodash/isFunction'
203
- import $n_findIndex from 'lodash/findIndex'
204
- import $n_get from 'lodash/get'
205
-
206
- import $n_indexOf from '@netang/utils/indexOf'
207
- import $n_forEach from '@netang/utils/forEach'
208
- import $n_isValidArray from '@netang/utils/isValidArray'
209
- import $n_join from '@netang/utils/join'
210
- import $n_split from '@netang/utils/split'
211
- import $n_isValidObject from '@netang/utils/isValidObject'
212
- import $n_isValidValue from '@netang/utils/isValidValue'
213
- import $n_isValidString from '@netang/utils/isValidString'
214
- import $n_numberDeep from '@netang/utils/numberDeep'
215
- import $n_sleep from '@netang/utils/sleep'
216
- import $n_http from '@netang/utils/http'
217
- import $n_runAsync from '@netang/utils/runAsync'
218
-
219
- import $n_$power from '../../utils/$power'
220
- import $n_$table from '../../utils/$table'
221
-
222
- import { configs } from '../../utils/config'
223
-
224
- import $n_getImage from '../../utils/getImage'
225
-
226
- const {
227
- // 字典常量
228
- dicts,
229
- } = configs
230
-
231
- export default {
232
-
233
- /**
234
- * 标识
235
- */
236
- name: 'NFieldTable',
237
-
238
- /**
239
- * 关闭组件 attribute 透传行为
240
- */
241
- inheritAttrs: false,
242
-
243
- /**
244
- * 声明属性
245
- */
246
- props: {
247
- // 值 v-model
248
- modelValue: {
249
- required: true,
250
- },
251
- // 值字段(必填)
252
- valueKey: {
253
- type: String,
254
- required: true,
255
- },
256
- // 标签字段
257
- labelKey: String,
258
- // 值类型
259
- // string: 字符串或数字
260
- // stringArray: 普通数组(包含字符串或数字的一维数组)
261
- // objectArray: 对象数组(包含对象的一维数组)
262
- valueType: {
263
- type: String,
264
- default: 'objectArray'
265
- },
266
- // 值分隔符(值类型为 string 有效)
267
- valueSeparator: {
268
- type: String,
269
- default: ',',
270
- },
271
-
272
- // 请求路由路径
273
- path: String,
274
- // 请求地址(默认为 path)
275
- url: String,
276
- // 请求参数
277
- query: Object,
278
- // 附加请求数据
279
- data: Object,
280
- // 初始加载已选数据数组
281
- // 如果有数组数据, 则初始化时从数组中选取已有的数据
282
- defaultLoadSelected: Array,
283
- // 初始是否不加载已选数据
284
- // true, 则初始时不加载数据(同时 defaultLoadSelected 无效)
285
- noDefaultLoadSelected: Boolean,
286
- // 更新值时不加载已选数据
287
- noUpdateLoadSelected: Boolean,
288
- // 格式化显示标签
289
- formatLabel: Function,
290
- // 下拉表格显示的字段数组(空为:[值字段, 标签字段])
291
- showKeys: Array,
292
- // 隐藏搜索字段数组
293
- hideSearchKeys: Array,
294
- // 默认筛选字段(空为:标签字段)
295
- filterKey: String,
296
- // 是否开启筛选
297
- filter: Boolean,
298
- // 表格声明属性
299
- tableProps: Object,
300
- // 对话框声明属性
301
- dialogProps: Object,
302
-
303
- // 关闭对话框
304
- noDialog: Boolean,
305
-
306
- // 表格列数据
307
- columns: Array,
308
- // 行数据
309
- rows: Array,
310
- // 是否多选
311
- multiple: Boolean,
312
- // 多选模式下是否折叠标签
313
- collapseTags: Boolean,
314
- // 占位符
315
- placeholder: String,
316
- // 是否可清除
317
- clearable: Boolean,
318
- // 是否禁用
319
- disable: Boolean,
320
- // 是否只读
321
- readonly: Boolean,
322
- // 输入防抖(毫秒)
323
- inputDebounce: {
324
- type: [ Number, String ],
325
- default: 500
326
- },
327
- // 自定义请求方法
328
- request: Function,
329
- // 每次对话框显示都请求
330
- requestEveryDialogShow: Boolean,
331
- },
332
-
333
- /**
334
- * 声明事件
335
- */
336
- emits: [
337
- 'loaded',
338
- 'update:modelValue',
339
- 'update:selected',
340
- ],
341
-
342
- /**
343
- * 组合式
344
- */
345
- setup(props, { emit, slots }) {
346
-
347
- // ==========【计算属性】=========================================================================================
348
-
349
- /**
350
- * 插槽标识
351
- */
352
- const slotNames = computed(function() {
353
-
354
- const table = []
355
- const normal = []
356
-
357
- // 如果有插槽
358
- if ($n_isValidObject(slots)) {
359
- for (const key in slots) {
360
- if (key !== 'append' && key !== 'control') {
361
- if (key.startsWith('table-')) {
362
- table.push(key.replace('table-', ''))
363
- } else {
364
- normal.push(key)
365
- }
366
- }
367
- }
368
- }
369
-
370
- return {
371
- table,
372
- normal,
373
- }
374
- })
375
-
376
- /**
377
- * 当前标签字段
378
- */
379
- const currentlabelKey = computed(function() {
380
- return props.labelKey || props.valueKey
381
- })
382
-
383
- /**
384
- * 当前显示字段
385
- */
386
- const currentShowKeys = computed(function() {
387
- return $n_uniq($n_isValidArray(props.showKeys)
388
- ? props.showKeys
389
- : [ props.valueKey, currentlabelKey.value ])
390
- })
391
-
392
- /**
393
- * 当前搜索字段
394
- */
395
- const currentFilterKey = computed(function() {
396
- return props.filterKey || currentlabelKey.value
397
- })
398
-
399
- /**
400
- * 显示值
401
- */
402
- const showValue = computed(function () {
403
-
404
- // 如果有已选数据
405
- return $n_isValidArray(selected.value)
406
- // 取已选数据第一条
407
- ? currentFormatLabel(selected.value[0])
408
- : ''
409
- })
410
-
411
- // ==========【数据】============================================================================================
412
-
413
- // 创建权限实例
414
- const $power = $n_$power.create({
415
- // 路由路径
416
- path: $n_isValidString(props.path) ? props.path : false,
417
- // 路由参数
418
- query: props.query,
419
- // 关闭权限页面
420
- power: false,
421
- // 禁止对话框注入
422
- $dialog: null,
423
- })
424
-
425
- const {
426
- // 当前路由路径
427
- routePath,
428
- } = $power
429
-
430
- // 创建表格实例
431
- const $table = $n_$table.create({
432
- // 权限实例
433
- $power,
434
- // 请求地址
435
- url: props.url,
436
- // 附加请求数据
437
- data: props.data,
438
- // 获取表格列数据
439
- columns: getTableColumns(),
440
- // 表格行唯一键值
441
- rowKey: props.valueKey,
442
- // 行数据
443
- rows: props.rows,
444
- // 选择类型, 可选值 single multiple none
445
- selection: props.multiple ? 'multiple' : 'single',
446
- // 已选数据
447
- selected: [],
448
- // http 设置
449
- httpSettings: {
450
- // 头部请求
451
- headers: {
452
- // 添加头部查看请求
453
- Pview: 1,
454
- },
455
- },
456
- // 刷新后清空已选数据
457
- refreshResetSelected: false,
458
- // 自定义请求方法
459
- async request({ httpOptions, props: httpProps }) {
460
- return $n_isFunction(props.request) ?
461
- // 如果有自定义请求方法
462
- await $n_runAsync(props.request)({
463
- // http 请求参数
464
- httpOptions,
465
- // 对话框是否已显示
466
- showDialog: $n_get(httpProps, 'showDialog') === 1 ? true : showDialog.value,
467
- }) :
468
- // 否则请求数据
469
- await $n_http(httpOptions)
470
- },
471
- })
472
-
473
- // 创建睡眠实例
474
- const sleep = $n_sleep()
475
-
476
- // 输入框节点
477
- const inputRef = ref(null)
478
-
479
- // 输入框值
480
- const inputValue = ref('')
481
-
482
- // 弹出层节点
483
- const popupRef = ref(null)
484
-
485
- // 是否显示对话框
486
- const showDialog = ref(false)
487
-
488
- // 是否显示弹出层
489
- const showPopup = ref(false)
490
-
491
- // 当前表格列数据
492
- const columns = getQuickTableColumns()
493
-
494
- // 停止观察值
495
- let stopValueWatcher = false
496
-
497
- // 临时已选数据
498
- let tempSelected = []
499
-
500
- // 初始化已选数据
501
- const selected = ref(valueToSelected(props.modelValue, true, true))
502
-
503
- // 加载已选数据
504
- if (
505
- ! props.noDefaultLoadSelected
506
- && props.defaultLoadSelected === void 0
507
- ) {
508
- loadSelected()
509
- .finally()
510
- } else {
511
- // 初始化加载成功
512
- emit('loaded', selected.value)
513
- }
514
-
515
- // ==========【监听数据】=========================================================================================
516
-
517
- /**
518
- * 监听声明值
519
- */
520
- watch(() => props.modelValue, async function(val) {
521
-
522
- // 如果停止观察值
523
- if (stopValueWatcher === true) {
524
- // 取消停止观察值
525
- stopValueWatcher = false
526
- return
527
- }
528
-
529
- // 值转已选数据
530
- let newSelected = valueToSelected(val, false, false)
531
-
532
- // 如果值类型是数组对象
533
- if (props.valueType === 'objectArray') {
534
-
535
- // 设置已选数据
536
- setSelected(newSelected)
537
-
538
- // 否则值类型是字符串或数组
539
- } else {
540
-
541
- // 初始已选数据
542
- let _selected = []
543
-
544
- // 如果值转已选数据是有效数组
545
- if (newSelected.length) {
546
-
547
- // 当前已选数据
548
- const currentSelected = tempSelected.length ? tempSelected : selected.value
549
-
550
- // 如果有已选数据
551
- if (currentSelected.length) {
552
-
553
- // 新已选数据
554
- _selected = currentSelected.filter(e => newSelected.indexOf(e[props.valueKey]) > -1)
555
-
556
- // 需增加的值
557
- newSelected = newSelected.filter(e => _selected.map(e => e[props.valueKey]).indexOf(e) === -1)
558
- }
559
-
560
- // 需增加的值
561
- if (newSelected.length) {
562
-
563
- // 如果更新值时不加载已选数据
564
- if (props.noUpdateLoadSelected) {
565
- // 请求选择数据
566
- _selected.push(...newSelected.map(e => setSelectedItem(e)))
567
- } else {
568
- // 请求选择数据
569
- _selected.push(...await onRequestSelected(newSelected))
570
- }
571
- }
572
- }
573
-
574
- // 设置已选数据
575
- setSelected(_selected)
576
-
577
- // 清空临时已选数据
578
- tempSelected = []
579
- }
580
-
581
- // 将已选数据转为值
582
- const _value = selectedToValue(selected.value)
583
-
584
- // 如果声明值发生变化
585
- if (_value !== props.modelValue) {
586
- // 停止观察值
587
- stopValueWatcher = true
588
- // 触发更新已选数据
589
- emit('update:modelValue', _value)
590
- }
591
-
592
- // 设置输入框焦点
593
- setInputFocus()
594
-
595
- // 设置输入框文字选中
596
- setInputSelection()
597
-
598
- }, {
599
- // 深度监听
600
- deep: true,
601
- })
602
-
603
- /**
604
- * 监听输入框值
605
- */
606
- watch(inputValue, async function (val) {
607
-
608
- // 延迟执行
609
- await sleep(props.inputDebounce)
610
-
611
- // 是否有值
612
- const hasValue = $n_isValidValue(val)
613
-
614
- const n_search = {}
615
- n_search[currentFilterKey.value] = [
616
- {
617
- // 比较类型
618
- compare: dicts.SEARCH_COMPARE_TYPE__LIKE,
619
- // 值
620
- value: hasValue ? val : '',
621
- }
622
- ]
623
-
624
- // 设置表格传参
625
- $table.setQuery({
626
- n_search,
627
- })
628
-
629
- if (
630
- // 如果弹出层是隐藏的
631
- ! showPopup.value
632
- // 如果输入框有值
633
- && hasValue
634
- ) {
635
- // 显示弹出层节点
636
- showPopupRef()
637
- }
638
-
639
- // 表格重新加载
640
- await $table.tableReload()
641
- })
642
-
643
- /**
644
- * 监听其他值
645
- */
646
- // watch([
647
- // ()=>props.path,
648
- // ()=>props.url,
649
- // ()=>props.query,
650
- // ()=>props.data,
651
- // ()=>props.showKeys,
652
- // ()=>props.hideSearchKeys,
653
- // ], function () {
654
- // _dialogShowed = false
655
- // _popupShowed = false
656
- //
657
- // }, {
658
- // deep: true
659
- // })
660
-
661
- // ==========【方法】=============================================================================================
662
-
663
- /**
664
- * 加载已选数据
665
- */
666
- async function loadSelected() {
667
- if (
668
- // 如果值类型不是数组对象
669
- props.valueType !== 'objectArray'
670
- // 如果初始加载已选数据
671
- && ! props.noDefaultLoadSelected
672
- // 如果有请求路由路径
673
- && routePath
674
- ) {
675
- // 获取值数组
676
- const values = valueToSelected(props.modelValue, false, false)
677
- if (values.length) {
678
- // 初始的已选数据
679
- const _selected = await onRequestSelected(values)
680
- const _value = selectedToValue(_selected)
681
-
682
- // 如果声明值未发生变化
683
- if (_value === props.modelValue) {
684
- // 设置已选数据
685
- setSelected(_selected)
686
-
687
- } else {
688
- // 设置临时已选数据
689
- tempSelected = _selected
690
- // 触发更新值
691
- emit('update:modelValue', _value)
692
- }
693
- // 初始化加载成功
694
- emit('loaded', _selected)
695
- return
696
- }
697
- }
698
-
699
- // 触发更新已选数据
700
- emit('update:selected', selected.value)
701
- // 初始化加载成功
702
- emit('loaded', selected.value)
703
- }
704
-
705
- /**
706
- * 触发更新值
707
- */
708
- function emitModelValue(val) {
709
-
710
- // 设置临时已选数据
711
- tempSelected = val
712
-
713
- // 触发更新值
714
- emit('update:modelValue', selectedToValue(val))
715
- }
716
-
717
- /**
718
- * 设置已选数据
719
- */
720
- function setSelected(val) {
721
-
722
- // 设置已选数据
723
- selected.value = val
724
-
725
- // 触发更新已选数据
726
- emit('update:selected', val)
727
- }
728
-
729
- /**
730
- * 当前格式化显示标签
731
- */
732
- function currentFormatLabel(item) {
733
-
734
- // 如果有格式化显示标签方法
735
- if ($n_isFunction(props.formatLabel)) {
736
- // 执行格式化显示标签方法
737
- return props.formatLabel(item)
738
- }
739
-
740
- // 否则显示该值的标签字段
741
- const val = item[currentlabelKey.value]
742
- return $n_isValidValue(val) ? val : item[props.valueKey]
743
- }
744
-
745
- /**
746
- * 设置已选数据的单个元素
747
- */
748
- function setSelectedItem(val) {
749
- const obj = {}
750
- obj[props.valueKey] = val
751
- obj[currentlabelKey.value] = val
752
- return obj
753
- }
754
-
755
- /**
756
- * 值转已选数据
757
- */
758
- function valueToSelected(val, isFirst, toSelected) {
759
-
760
- // 如果值类型是数组对象
761
- if (props.valueType === 'objectArray') {
762
-
763
- // 如果是有效数组
764
- if ($n_isValidArray(val)) {
765
- for (const item of val) {
766
- if (
767
- // 如果元素不是有效对象
768
- ! $n_isValidObject(item)
769
- // 如果元素没有值字段
770
- || ! $n_has(item, props.valueKey)
771
- ) {
772
- return []
773
- }
774
- }
775
- }
776
-
777
- // 否则直接返回
778
- return val
779
- }
780
-
781
- if (
782
- // 如果初始化
783
- isFirst
784
- // 如果初始加载已选数据方法
785
- && ! props.noDefaultLoadSelected
786
- // 如果有初始加载已选数据数组
787
- && props.defaultLoadSelected !== void 0
788
- && $n_isValidArray(props.defaultLoadSelected)
789
- ) {
790
- // 将值转为数组
791
- val = props.valueType === 'string' ? $n_split(val, props.valueSeparator) : val
792
-
793
- // 如果是有效数组
794
- if ($n_isValidArray(val)) {
795
- val = val.filter(e => $n_isValidValue(e))
796
- if (val.length) {
797
- const _selected = []
798
- for (const item of props.defaultLoadSelected) {
799
- if (
800
- $n_has(item, props.valueKey)
801
- && $n_indexOf(val, item[props.valueKey]) > -1
802
- ) {
803
- _selected.push($n_cloneDeep(item))
804
- }
805
- }
806
-
807
- return _selected
808
- }
809
- }
810
- }
811
-
812
- if (
813
- // 非初始化
814
- ! isFirst
815
- // 或初始不加载已选数据
816
- || props.noDefaultLoadSelected
817
- // 或没有路由路径
818
- || ! routePath
819
- ) {
820
- // 将值转为数组
821
- val = props.valueType === 'string' ? $n_split(val, props.valueSeparator) : val
822
-
823
- // 如果是有效数组
824
- if ($n_isValidArray(val)) {
825
- val = val.filter(e => $n_isValidValue(e))
826
- return toSelected ? val.map(e => setSelectedItem(e)) : val
827
- }
828
- }
829
-
830
- return []
831
- }
832
-
833
- /**
834
- * 已选数据转值
835
- */
836
- function selectedToValue(val) {
837
-
838
- // 如果值类型是数组对象
839
- if (props.valueType === 'objectArray') {
840
-
841
- // 则直接返回
842
- return val
843
- }
844
-
845
- // 值数组
846
- const values = val.length
847
- // 如果有已选数据
848
- ? (
849
- props.multiple
850
- // 如果是多选
851
- ? val.map(e => e[props.valueKey])
852
- // 否则是单选
853
- : [ val[0][props.valueKey] ]
854
- )
855
- // 否则为空
856
- : []
857
-
858
- // 如果值类型是数组
859
- if (props.valueType === 'stringArray') {
860
-
861
- // 直接返回数组
862
- return values
863
- }
864
-
865
- // 返回转为分隔符隔开的字符串
866
- return $n_numberDeep($n_join(values, props.valueSeparator))
867
- }
868
-
869
- /**
870
- * 请求选择数据
871
- */
872
- async function onRequestSelected(value) {
873
-
874
- // 请求参数
875
- const httpOptions = {
876
- url: $table.routePath,
877
- data: Object.assign(
878
- // 获取表格请求数据
879
- $table.getTableRequestData({
880
- // filter,
881
- pagination: {
882
- // 页码
883
- page: 1,
884
- // 每页的数据条数
885
- rowsPerPage: value.length,
886
- // 排序字段
887
- sortBy: null,
888
- // 是否降序排列
889
- descending: true,
890
- }
891
- }, false),
892
- {
893
- // 查看字段
894
- n_view: {
895
- // 查看字段
896
- field: props.valueKey,
897
- // 查看值
898
- value,
899
- },
900
- }
901
- ),
902
- // 是否开启防抖(防止重复请求)
903
- debounce: false,
904
- }
905
-
906
- // 请求数据
907
- const { status, data } = $n_isFunction(props.request) ?
908
- // 如果有自定义请求方法
909
- await $n_runAsync(props.request)({
910
- // http 请求参数
911
- httpOptions,
912
- // 对话框是否已显示
913
- showDialog: showDialog.value,
914
- }) :
915
- // 否则请求数据
916
- await $n_http(httpOptions)
917
-
918
- return status && $n_isValidArray($n_get(data, 'rows')) ? data.rows : []
919
- }
920
-
921
- /**
922
- * 获取表格列数据
923
- */
924
- function getTableColumns() {
925
-
926
- let columns
927
-
928
- // 如果有声明路由表格列数据
929
- if ($n_isValidArray(props.columns)) {
930
- columns = $n_cloneDeep(props.columns)
931
-
932
- // 如果有路由路径
933
- } else if (routePath) {
934
- // 否则如果有路由表格列数据
935
- const rawTableColumns = $n_$table.config(routePath, 'columns')
936
- if ($n_isValidArray(rawTableColumns)) {
937
- columns = $n_cloneDeep(rawTableColumns)
938
- }
939
- }
940
-
941
- if ($n_isValidArray(columns)) {
942
- if ($n_isValidArray(props.hideSearchKeys)) {
943
- for (const item of columns) {
944
- if (
945
- props.hideSearchKeys.indexOf(item.name) > -1
946
- && $n_has(item, 'search')
947
- ) {
948
- item.search.hide = true
949
- }
950
- }
951
- }
952
- return columns
953
- }
954
-
955
- return []
956
- }
957
-
958
- /**
959
- * 获取快捷表格列数据
960
- */
961
- function getQuickTableColumns() {
962
-
963
- const columns = []
964
-
965
- // 如果有原始表格列数据
966
- if ($n_isValidArray($table.tableColumns.value)) {
967
-
968
- // 克隆原始表格列数据
969
- const rawTableColumns = $n_cloneDeep($table.tableColumns.value)
970
-
971
- // 快捷表格显示的属性名称数组
972
- $n_forEach(currentShowKeys.value, function (key) {
973
- for (const item of rawTableColumns) {
974
- if (item.name === key) {
975
- // 删除搜索字段
976
- if ($n_has(item, 'search')) {
977
- delete item.search
978
- }
979
- // 删除可见字段
980
- if ($n_has(item, 'visible')) {
981
- delete item.visible
982
- }
983
- columns.push(item)
984
- }
985
- }
986
- })
987
- }
988
-
989
- return columns
990
- }
991
-
992
- /**
993
- * 移除已选数据
994
- */
995
- function onRemoveSelected(index) {
996
-
997
- const _selected = [...selected.value]
998
- _selected.splice(index, 1)
999
-
1000
- // 触发更新值
1001
- emitModelValue(_selected)
1002
- }
1003
-
1004
- /**
1005
- * 字段获取焦点触发
1006
- */
1007
- function onFieldFocus(e) {
1008
-
1009
- // 停止冒泡
1010
- e.stopPropagation()
1011
-
1012
- // 设置输入框焦点
1013
- setInputFocus()
1014
-
1015
- // window.scrollTo(window.pageXOffset || window.scrollX || document.body.scrollLeft || 0, 0)
1016
- }
1017
-
1018
- /**
1019
- * 字段失去焦点触发
1020
- */
1021
- function onFieldBlur(e) {
1022
-
1023
- // 停止冒泡
1024
- e.stopPropagation()
1025
-
1026
- if (
1027
- // 如果开启筛选
1028
- props.filter
1029
- // 如果没有显示弹出层
1030
- && ! showPopup.value
1031
- ) {
1032
- // 清空输入框值
1033
- inputValue.value = ''
1034
- }
1035
- }
1036
-
1037
- /**
1038
- * 字段清空触发
1039
- */
1040
- function onFieldClear() {
1041
-
1042
- // 触发更新值
1043
- // 清空快捷表格已选数据
1044
- emitModelValue([])
1045
-
1046
- // 隐藏弹出层节点
1047
- hidePopupRef()
1048
- }
1049
-
1050
- /**
1051
- * 显示弹出层节点
1052
- */
1053
- function showPopupRef() {
1054
-
1055
- // 如果有弹出层节点
1056
- if (popupRef.value) {
1057
- // 显示弹出层
1058
- popupRef.value.show()
1059
- }
1060
- }
1061
-
1062
- /**
1063
- * 隐藏弹出层节点
1064
- */
1065
- function hidePopupRef() {
1066
-
1067
- // 如果有弹出层节点
1068
- if (popupRef.value) {
1069
- // 隐藏弹出层
1070
- popupRef.value.hide()
1071
- }
1072
- }
1073
-
1074
- /**
1075
- * 弹出层显示回调
1076
- */
1077
- let _popupShowed = false
1078
- function onPopupShow() {
1079
-
1080
- // 显示弹出层
1081
- showPopup.value = true
1082
-
1083
- // 设置输入框焦点
1084
- setInputFocus()
1085
-
1086
- // 如果每次对话框显示都请求
1087
- if (props.requestEveryDialogShow) {
1088
- // 表格重新加载
1089
- $table.tableReload()
1090
- .finally()
1091
- return
1092
- }
1093
-
1094
- if (_popupShowed) {
1095
- return
1096
- }
1097
- _popupShowed = true
1098
-
1099
- // 表格重新加载
1100
- $table.tableReload()
1101
- .finally()
1102
- }
1103
-
1104
- /**
1105
- * 显示对话框
1106
- */
1107
- function onShowDialog() {
1108
- // 显示对话框
1109
- showDialog.value = true
1110
- }
1111
-
1112
- /**
1113
- * 对话框显示前回调
1114
- */
1115
- function onDialogBeforeShow() {
1116
-
1117
- // 设置当前已选数据
1118
- $table.tableSelected.value = [...selected.value]
1119
-
1120
- // 隐藏弹出层节点
1121
- hidePopupRef()
1122
- }
1123
-
1124
- /**
1125
- * 对话框显示回调
1126
- */
1127
- let _dialogShowed = false
1128
- function onDialogShow() {
1129
-
1130
- // 如果每次对话框显示都请求
1131
- if (props.requestEveryDialogShow) {
1132
- // 表格重新加载
1133
- $table.tableReload()
1134
- .finally()
1135
- return
1136
- }
1137
-
1138
- if (_dialogShowed) {
1139
- return
1140
- }
1141
- _dialogShowed = true
1142
-
1143
- // 表格重新加载
1144
- $table.tableReload()
1145
- .finally()
1146
- }
1147
-
1148
- /**
1149
- * 对话框隐藏后回调
1150
- */
1151
- function onDialogHide() {
1152
-
1153
- let isReload = true
1154
-
1155
- // 清空输入框值
1156
- if (
1157
- // 如果开启筛选
1158
- props.filter
1159
- // 如果有输入框值
1160
- && inputValue.value
1161
- ) {
1162
- // 此时清空输入框后, 会自动刷新表格
1163
- inputValue.value = ''
1164
-
1165
- // 所以只需要重置搜索值即可, 不需要再重置后刷新表格
1166
- isReload = false
1167
- }
1168
-
1169
- // 获取表格搜索值
1170
- let searchValue = $table.getTableSearchValue()
1171
- if (searchValue.length) {
1172
-
1173
- // 如果有隐藏搜索字段数组
1174
- if ($n_isValidArray(props.hideSearchKeys)) {
1175
- // 从搜索值数组中去除隐藏搜索字段的数组
1176
- searchValue = searchValue.filter(e => $n_indexOf(e.field, props.hideSearchKeys) === -1)
1177
- }
1178
-
1179
- // 表格搜索重置
1180
- $table.tableSearchReset(isReload && searchValue.length, {
1181
- showDialog: 1,
1182
- })
1183
- }
1184
- }
1185
-
1186
- /**
1187
- * 对话框点击确认回调
1188
- */
1189
- function onDialogConfirm(data) {
1190
-
1191
- // 触发更新值
1192
- emitModelValue([...data])
1193
- }
1194
-
1195
- /**
1196
- * 单击快捷表格行
1197
- */
1198
- function quickTableRowClick(e, row) {
1199
-
1200
- // 如果为多选
1201
- if (props.multiple) {
1202
-
1203
- // 克隆已选数据
1204
- const _selected = [...selected.value]
1205
-
1206
- const opt = {}
1207
- opt[props.valueKey] = row[props.valueKey]
1208
-
1209
- // 获取当前数据索引
1210
- const itemIndex = $n_findIndex(_selected, opt)
1211
-
1212
- // 如果不存在
1213
- if (itemIndex === -1) {
1214
- // 则添加
1215
- _selected.push(row)
1216
-
1217
- // 否则
1218
- } else {
1219
- // 删除
1220
- _selected.splice(itemIndex, 1)
1221
- }
1222
-
1223
- // 触发更新值
1224
- emitModelValue(_selected)
1225
-
1226
- // 否则为单选
1227
- } else {
1228
-
1229
- // 触发更新值
1230
- emitModelValue([ row ])
1231
-
1232
- // 隐藏弹出层节点
1233
- hidePopupRef()
1234
- }
1235
- }
1236
-
1237
- /**
1238
- * 设置输入框文字选中
1239
- */
1240
- function setInputSelection() {
1241
- if (
1242
- // 如果开启筛选
1243
- props.filter
1244
- // 如果有输入框节点
1245
- && inputRef.value
1246
- // 如果输入框有值
1247
- && inputValue.value.length
1248
- ) {
1249
- // 全选文字
1250
- inputRef.value.select()
1251
- // inputRef.value.setSelectionRange(0, inputValue.value.length)
1252
- }
1253
- }
1254
-
1255
- /**
1256
- * 设置输入框焦点
1257
- */
1258
- function setInputFocus() {
1259
- if (
1260
- // 如果开启筛选
1261
- props.filter
1262
- // 如果有输入框节点
1263
- && inputRef.value
1264
- ) {
1265
- inputRef.value.focus()
1266
- }
1267
- }
1268
-
1269
- /**
1270
- * 格式化图片
1271
- */
1272
- function formatImg(img, { count }) {
1273
-
1274
- // 图片数组
1275
- const imgs = []
1276
-
1277
- // 转为图片数组
1278
- const arr = $n_split(img, ',')
1279
- for (const item of arr) {
1280
- const src = $n_getImage(item)
1281
- if (src) {
1282
- imgs.push(item)
1283
- if (
1284
- count > 0
1285
- && imgs.length === count
1286
- ) {
1287
- break
1288
- }
1289
- }
1290
- }
1291
-
1292
- return imgs
1293
- }
1294
-
1295
- // ==========【生命周期】=========================================================================================
1296
-
1297
- /**
1298
- * 在组件因为响应式状态变更而更新其 DOM 树之后调用
1299
- */
1300
- onUpdated(function () {
1301
- if (
1302
- popupRef.value
1303
- && $n_has(popupRef.value, 'currentComponent.ref.updatePosition')
1304
- ) {
1305
- popupRef.value.currentComponent.ref.updatePosition()
1306
- }
1307
- })
1308
-
1309
- // ==========【返回】=============================================================================================
1310
-
1311
- return {
1312
- // 解构表格实例
1313
- ...$table,
1314
-
1315
- // 插槽标识
1316
- slotNames,
1317
- // 当前标签字段
1318
- currentlabelKey,
1319
- // 显示值
1320
- showValue,
1321
-
1322
- // 输入框节点
1323
- inputRef,
1324
- // 输入框值
1325
- inputValue,
1326
- // 弹出层节点
1327
- popupRef,
1328
- // 是否显示对话框
1329
- showDialog,
1330
- // 是否显示弹出层
1331
- showPopup,
1332
- // 当前已选数据
1333
- selected,
1334
- // 当前表格列数据
1335
- columns,
1336
-
1337
- // 当前格式化显示标签
1338
- currentFormatLabel,
1339
- // 移除已选数据
1340
- onRemoveSelected,
1341
-
1342
- // 字段获取焦点触发
1343
- onFieldFocus,
1344
- // 字段失去焦点触发
1345
- onFieldBlur,
1346
- // 字段清空触发
1347
- onFieldClear,
1348
-
1349
- // 弹出层显示回调
1350
- onPopupShow,
1351
-
1352
- // 显示对话框
1353
- onShowDialog,
1354
- // 对话框显示前回调
1355
- onDialogBeforeShow,
1356
- // 对话框显示回调
1357
- onDialogShow,
1358
- // 对话框隐藏后回调
1359
- onDialogHide,
1360
- // 对话框点击确认回调
1361
- onDialogConfirm,
1362
-
1363
- // 单击快捷表格行
1364
- quickTableRowClick,
1365
-
1366
- // 触发更新值
1367
- emitModelValue,
1368
- // 格式化图片
1369
- formatImg,
1370
- }
1371
- },
1372
- }
1373
- </script>
1374
-
1375
- <style lang="scss">
1376
- .n-field-table {
1377
- .q-field__input--padding {
1378
- padding-left: 4px;
1379
- min-width: 50px !important;
1380
- cursor: text;
1381
- }
1382
- }
1383
-
1384
- /**
1385
- * 桌面
1386
- */
1387
- body.desktop {
1388
- .n-field-table {
1389
- &__popup-table {
1390
- height: 300px;
1391
- }
1392
- }
1393
- }
1394
- </style>
1
+ <template>
2
+
3
+ <!-- 如果有默认插槽 -->
4
+ <template v-if="$slots.default">
5
+ <slot
6
+ :showValue="showValue"
7
+ :selected="selected"
8
+ :onRemove="onRemoveSelected"
9
+ :onShowDialog="onShowDialog"
10
+ :onClear="onFieldClear"
11
+ />
12
+ </template>
13
+
14
+ <!--:class="fieldFocused ? 'q-field&#45;&#45;float q-field&#45;&#45;focused q-field&#45;&#45;highlighted' : ''"-->
15
+ <!--:clearable="clearable && (! multiple || collapseTags)"-->
16
+ <q-field
17
+ class="n-field-table"
18
+ :model-value="showValue"
19
+ :disable="disable"
20
+ :readonly="readonly"
21
+ :clearable="clearable"
22
+ @focus="onFieldFocus"
23
+ @blur="onFieldBlur"
24
+ @clear="onFieldClear"
25
+ v-bind="$attrs"
26
+ v-else
27
+ >
28
+ <template v-slot:control>
29
+
30
+ <template v-if="multiple">
31
+ <template v-if="selected.length">
32
+
33
+ <!-- 多选插槽 -->
34
+ <slot
35
+ name="selected"
36
+ :selected="selected"
37
+ :remove="onRemoveSelected"
38
+ v-if="$slots.selected"
39
+ />
40
+
41
+ <!-- 显示折叠的值数量 -->
42
+ <q-chip
43
+ dense
44
+ :label="`+${selected.length}`"
45
+ v-else-if="collapseTags"
46
+ />
47
+
48
+ <!-- 多选标签 -->
49
+ <template v-else>
50
+ <q-chip
51
+ v-for="(item, index) in selected"
52
+ :key="`options-${index}`"
53
+ :label="currentFormatLabel(item)"
54
+ dense
55
+ :removable="! readonly && ! disable"
56
+ @remove="onRemoveSelected(index)"
57
+ >
58
+ <q-tooltip>{{currentFormatLabel(item)}}</q-tooltip>
59
+ </q-chip>
60
+ </template>
61
+ </template>
62
+
63
+ <!-- 占位符-->
64
+ <span class="n-placeholder" v-else-if="placeholder">{{placeholder}}</span>
65
+ </template>
66
+
67
+ <!-- 显示文字 -->
68
+ <span v-else-if="showValue">{{showValue}}</span>
69
+
70
+ <!-- 占位符-->
71
+ <span class="n-placeholder" v-else-if="placeholder">{{placeholder}}</span>
72
+
73
+ <!-- 筛选输入框 -->
74
+ <input
75
+ ref="inputRef"
76
+ class="q-field__input q-placeholder col q-field__input--padding"
77
+ v-model="inputValue"
78
+ v-if="filter && ! readonly && ! disable"
79
+ />
80
+
81
+ </template>
82
+
83
+ <!-- 弹出对话框图标 -->
84
+ <template v-slot:append v-if="! noDialog && ! readonly && ! disable">
85
+ <q-icon
86
+ class="cursor-pointer"
87
+ name="search"
88
+ @click.prevent.stop="onShowDialog"
89
+ />
90
+ </template>
91
+
92
+ <!-- 默认插槽 -->
93
+ <template
94
+ v-for="slotName in slotNames.normal"
95
+ v-slot:[slotName]
96
+ >
97
+ <slot :name="slotName" />
98
+ </template>
99
+
100
+ <!-- 弹出层代理 -->
101
+ <q-popup-proxy
102
+ ref="popupRef"
103
+ no-refocus
104
+ no-focus
105
+ fit
106
+ @focus="onFieldBlur"
107
+ @show="onPopupShow"
108
+ @before-hide="showPopup = false"
109
+ v-if="! readonly && ! disable"
110
+ >
111
+ <!-- 快捷表格 -->
112
+ <n-table
113
+ class="n-table n-field-table__popup-table"
114
+ v-model:pagination="tablePagination"
115
+ :selected="selected"
116
+ @update:selected="emitModelValue"
117
+ :row-key="tableRowKey"
118
+ :rows="tableRows"
119
+ :columns="columns"
120
+ :selection="multiple ? 'multiple' : 'none'"
121
+ :loading="tableLoading"
122
+ :rows-per-page-options="tableRowsPerPageOptions"
123
+ @row-click="quickTableRowClick"
124
+ @request="tableRequest"
125
+ flat
126
+ virtual-scroll
127
+ dense
128
+ v-bind="tableProps"
129
+ >
130
+ <!-- 图片 -->
131
+ <template
132
+ v-for="imgItem in tableImgs"
133
+ v-slot:[`body-cell-${imgItem.name}`]="props"
134
+ >
135
+ <n-data
136
+ :data="formatImg(props.row[imgItem.name], imgItem)"
137
+ v-slot="{ data }"
138
+ >
139
+ <!-- 缩略图 -->
140
+ <n-thumbnail
141
+ v-for="(item, index) in data"
142
+ :key="`thumbnail-item-${item}`"
143
+ class="n-table__thumbnail"
144
+ :src="item"
145
+ preview
146
+ :preview-props="{
147
+ startPosition: index,
148
+ images: data,
149
+ }"
150
+ />
151
+ </n-data>
152
+ </template>
153
+
154
+ <!-- 表格插槽 -->
155
+ <template
156
+ v-for="slotName in slotNames.table"
157
+ v-slot:[slotName]="props"
158
+ >
159
+ <q-td :props="props">
160
+ <slot
161
+ :name="slotName"
162
+ v-bind="props"
163
+ />
164
+ </q-td>
165
+ </template>
166
+
167
+ <!-- 翻页 -->
168
+ <template v-slot:pagination="props">
169
+ <n-table-pagination
170
+ :props="props"
171
+ no-power
172
+ dense
173
+ />
174
+ </template>
175
+ </n-table>
176
+ </q-popup-proxy>
177
+ </q-field>
178
+
179
+ <!-- 弹出对话框 -->
180
+ <n-dialog
181
+ v-model="showDialog"
182
+ width="80%"
183
+ :on-confirm="onDialogConfirm"
184
+ @before-show="onDialogBeforeShow"
185
+ @show="onDialogShow"
186
+ @hide="onDialogHide"
187
+ cancel
188
+ v-bind="dialogProps"
189
+ >
190
+ <q-page>
191
+ <n-mixed-table />
192
+ </q-page>
193
+ </n-dialog>
194
+ </template>
195
+
196
+ <script>
197
+ import { ref, computed, watch, onUpdated } from 'vue'
198
+
199
+ import $n_has from 'lodash/has'
200
+ import $n_uniq from 'lodash/uniq'
201
+ import $n_cloneDeep from 'lodash/cloneDeep'
202
+ import $n_isFunction from 'lodash/isFunction'
203
+ import $n_findIndex from 'lodash/findIndex'
204
+ import $n_get from 'lodash/get'
205
+
206
+ import $n_indexOf from '@netang/utils/indexOf'
207
+ import $n_forEach from '@netang/utils/forEach'
208
+ import $n_isValidArray from '@netang/utils/isValidArray'
209
+ import $n_join from '@netang/utils/join'
210
+ import $n_split from '@netang/utils/split'
211
+ import $n_isValidObject from '@netang/utils/isValidObject'
212
+ import $n_isValidValue from '@netang/utils/isValidValue'
213
+ import $n_isValidString from '@netang/utils/isValidString'
214
+ import $n_numberDeep from '@netang/utils/numberDeep'
215
+ import $n_sleep from '@netang/utils/sleep'
216
+ import $n_http from '@netang/utils/http'
217
+ import $n_runAsync from '@netang/utils/runAsync'
218
+
219
+ import $n_$power from '../../utils/$power'
220
+ import $n_$table from '../../utils/$table'
221
+
222
+ import { configs } from '../../utils/config'
223
+
224
+ import $n_getImage from '../../utils/getImage'
225
+
226
+ const {
227
+ // 字典常量
228
+ dicts,
229
+ } = configs
230
+
231
+ export default {
232
+
233
+ /**
234
+ * 标识
235
+ */
236
+ name: 'NFieldTable',
237
+
238
+ /**
239
+ * 关闭组件 attribute 透传行为
240
+ */
241
+ inheritAttrs: false,
242
+
243
+ /**
244
+ * 声明属性
245
+ */
246
+ props: {
247
+ // 值 v-model
248
+ modelValue: {
249
+ required: true,
250
+ },
251
+ // 值字段(必填)
252
+ valueKey: {
253
+ type: String,
254
+ required: true,
255
+ },
256
+ // 标签字段
257
+ labelKey: String,
258
+ // 值类型
259
+ // string: 字符串或数字
260
+ // stringArray: 普通数组(包含字符串或数字的一维数组)
261
+ // objectArray: 对象数组(包含对象的一维数组)
262
+ valueType: {
263
+ type: String,
264
+ default: 'objectArray'
265
+ },
266
+ // 值分隔符(值类型为 string 有效)
267
+ valueSeparator: {
268
+ type: String,
269
+ default: ',',
270
+ },
271
+
272
+ // 请求路由路径
273
+ path: String,
274
+ // 请求地址(默认为 path)
275
+ url: String,
276
+ // 请求参数
277
+ query: Object,
278
+ // 附加请求数据
279
+ data: Object,
280
+ // 加载已选数据数组
281
+ // 如果有数组数据, 则初始化时从数组中选取已有的数据
282
+ loadSelected: [Array, Function],
283
+ // 初始是否不加载已选数据
284
+ // true, 则初始时不加载数据(同时 loadSelected 无效)
285
+ noDefaultLoadSelected: Boolean,
286
+ // 更新值时不加载已选数据
287
+ noUpdateLoadSelected: Boolean,
288
+ // 格式化显示标签
289
+ formatLabel: Function,
290
+ // 下拉表格显示的字段数组(空为:[值字段, 标签字段])
291
+ showKeys: Array,
292
+ // 隐藏搜索字段数组
293
+ hideSearchKeys: Array,
294
+ // 默认筛选字段(空为:标签字段)
295
+ filterKey: String,
296
+ // 是否开启筛选
297
+ filter: Boolean,
298
+ // 表格声明属性
299
+ tableProps: Object,
300
+ // 对话框声明属性
301
+ dialogProps: Object,
302
+
303
+ // 关闭对话框
304
+ noDialog: Boolean,
305
+
306
+ // 表格列数据
307
+ columns: Array,
308
+ // 行数据
309
+ rows: Array,
310
+ // 是否多选
311
+ multiple: Boolean,
312
+ // 多选模式下是否折叠标签
313
+ collapseTags: Boolean,
314
+ // 占位符
315
+ placeholder: String,
316
+ // 是否可清除
317
+ clearable: Boolean,
318
+ // 是否禁用
319
+ disable: Boolean,
320
+ // 是否只读
321
+ readonly: Boolean,
322
+ // 输入防抖(毫秒)
323
+ inputDebounce: {
324
+ type: [ Number, String ],
325
+ default: 500
326
+ },
327
+ // 自定义请求方法
328
+ request: Function,
329
+ // 每次对话框显示都请求
330
+ requestEveryDialogShow: Boolean,
331
+ },
332
+
333
+ /**
334
+ * 声明事件
335
+ */
336
+ emits: [
337
+ 'loaded',
338
+ 'update:modelValue',
339
+ 'update:selected',
340
+ ],
341
+
342
+ /**
343
+ * 组合式
344
+ */
345
+ setup(props, { emit, slots }) {
346
+
347
+ // ==========【计算属性】=========================================================================================
348
+
349
+ /**
350
+ * 插槽标识
351
+ */
352
+ const slotNames = computed(function() {
353
+
354
+ const table = []
355
+ const normal = []
356
+
357
+ // 如果有插槽
358
+ if ($n_isValidObject(slots)) {
359
+ for (const key in slots) {
360
+ if (key !== 'append' && key !== 'control') {
361
+ if (key.startsWith('table-')) {
362
+ table.push(key.replace('table-', ''))
363
+ } else {
364
+ normal.push(key)
365
+ }
366
+ }
367
+ }
368
+ }
369
+
370
+ return {
371
+ table,
372
+ normal,
373
+ }
374
+ })
375
+
376
+ /**
377
+ * 当前标签字段
378
+ */
379
+ const currentlabelKey = computed(function() {
380
+ return props.labelKey || props.valueKey
381
+ })
382
+
383
+ /**
384
+ * 当前显示字段
385
+ */
386
+ const currentShowKeys = computed(function() {
387
+ return $n_uniq($n_isValidArray(props.showKeys)
388
+ ? props.showKeys
389
+ : [ props.valueKey, currentlabelKey.value ])
390
+ })
391
+
392
+ /**
393
+ * 当前搜索字段
394
+ */
395
+ const currentFilterKey = computed(function() {
396
+ return props.filterKey || currentlabelKey.value
397
+ })
398
+
399
+ /**
400
+ * 显示值
401
+ */
402
+ const showValue = computed(function () {
403
+
404
+ // 如果有已选数据
405
+ return $n_isValidArray(selected.value)
406
+ // 取已选数据第一条
407
+ ? currentFormatLabel(selected.value[0])
408
+ : ''
409
+ })
410
+
411
+ // ==========【数据】============================================================================================
412
+
413
+ // 创建权限实例
414
+ const $power = $n_$power.create({
415
+ // 路由路径
416
+ path: $n_isValidString(props.path) ? props.path : false,
417
+ // 路由参数
418
+ query: props.query,
419
+ // 关闭权限页面
420
+ power: false,
421
+ // 禁止对话框注入
422
+ $dialog: null,
423
+ })
424
+
425
+ const {
426
+ // 当前路由路径
427
+ routePath,
428
+ } = $power
429
+
430
+ // 创建表格实例
431
+ const $table = $n_$table.create({
432
+ // 权限实例
433
+ $power,
434
+ // 请求地址
435
+ url: props.url,
436
+ // 附加请求数据
437
+ data: props.data,
438
+ // 获取表格列数据
439
+ columns: getTableColumns(),
440
+ // 表格行唯一键值
441
+ rowKey: props.valueKey,
442
+ // 行数据
443
+ rows: props.rows,
444
+ // 选择类型, 可选值 single multiple none
445
+ selection: props.multiple ? 'multiple' : 'single',
446
+ // 已选数据
447
+ selected: [],
448
+ // http 设置
449
+ httpSettings: {
450
+ // 头部请求
451
+ headers: {
452
+ // 添加头部查看请求
453
+ Pview: 1,
454
+ },
455
+ },
456
+ // 刷新后清空已选数据
457
+ refreshResetSelected: false,
458
+ // 自定义请求方法
459
+ async request({ httpOptions, props: httpProps }) {
460
+ return $n_isFunction(props.request) ?
461
+ // 如果有自定义请求方法
462
+ await $n_runAsync(props.request)({
463
+ // http 请求参数
464
+ httpOptions,
465
+ // 对话框是否已显示
466
+ showDialog: $n_get(httpProps, 'showDialog') === 1 ? true : showDialog.value,
467
+ }) :
468
+ // 否则请求数据
469
+ await $n_http(httpOptions)
470
+ },
471
+ })
472
+
473
+ // 创建睡眠实例
474
+ const sleep = $n_sleep()
475
+
476
+ // 输入框节点
477
+ const inputRef = ref(null)
478
+
479
+ // 输入框值
480
+ const inputValue = ref('')
481
+
482
+ // 弹出层节点
483
+ const popupRef = ref(null)
484
+
485
+ // 是否显示对话框
486
+ const showDialog = ref(false)
487
+
488
+ // 是否显示弹出层
489
+ const showPopup = ref(false)
490
+
491
+ // 当前表格列数据
492
+ const columns = getQuickTableColumns()
493
+
494
+ // 停止观察值
495
+ let stopValueWatcher = false
496
+
497
+ // 临时已选数据
498
+ let tempSelected = []
499
+
500
+ // 初始化已选数据
501
+ const selected = ref(valueToSelected(props.modelValue, true, true))
502
+
503
+ // 加载已选数据
504
+ if (
505
+ ! props.noDefaultLoadSelected
506
+ && props.loadSelected === void 0
507
+ ) {
508
+ loadSelected()
509
+ .finally()
510
+ } else {
511
+ // 初始化加载成功
512
+ emit('loaded', selected.value)
513
+ }
514
+
515
+ // ==========【监听数据】=========================================================================================
516
+
517
+ /**
518
+ * 监听声明值
519
+ */
520
+ watch(() => props.modelValue, async function(val) {
521
+
522
+ // 如果停止观察值
523
+ if (stopValueWatcher === true) {
524
+ // 取消停止观察值
525
+ stopValueWatcher = false
526
+ return
527
+ }
528
+
529
+ // 值转已选数据
530
+ let newSelected = valueToSelected(val, false, false)
531
+
532
+ // 如果值类型是数组对象
533
+ if (props.valueType === 'objectArray') {
534
+
535
+ // 设置已选数据
536
+ setSelected(newSelected)
537
+
538
+ // 否则值类型是字符串或数组
539
+ } else {
540
+
541
+ // 初始已选数据
542
+ let _selected = []
543
+
544
+ // 如果值转已选数据是有效数组
545
+ if (newSelected.length) {
546
+
547
+ // 当前已选数据
548
+ const currentSelected = tempSelected.length ? tempSelected : selected.value
549
+
550
+ // 如果有已选数据
551
+ if (currentSelected.length) {
552
+
553
+ // 新已选数据
554
+ _selected = currentSelected.filter(e => newSelected.indexOf(e[props.valueKey]) > -1)
555
+
556
+ // 需增加的值
557
+ newSelected = newSelected.filter(e => _selected.map(e => e[props.valueKey]).indexOf(e) === -1)
558
+ }
559
+
560
+ // 需增加的值
561
+ if (newSelected.length) {
562
+
563
+ // 如果更新值时不加载已选数据
564
+ if (props.noUpdateLoadSelected) {
565
+ // 请求选择数据
566
+ _selected.push(...newSelected.map(e => setSelectedItem(e)))
567
+ } else {
568
+ // 请求选择数据
569
+ _selected.push(...await onRequestSelected(newSelected))
570
+ }
571
+ }
572
+ }
573
+
574
+ // 设置已选数据
575
+ setSelected(_selected)
576
+
577
+ // 清空临时已选数据
578
+ tempSelected = []
579
+ }
580
+
581
+ // 将已选数据转为值
582
+ const _value = selectedToValue(selected.value)
583
+
584
+ // 如果声明值发生变化
585
+ if (_value !== props.modelValue) {
586
+ // 停止观察值
587
+ stopValueWatcher = true
588
+ // 触发更新已选数据
589
+ emit('update:modelValue', _value)
590
+ }
591
+
592
+ // 设置输入框焦点
593
+ setInputFocus()
594
+
595
+ // 设置输入框文字选中
596
+ setInputSelection()
597
+
598
+ }, {
599
+ // 深度监听
600
+ deep: true,
601
+ })
602
+
603
+ /**
604
+ * 监听输入框值
605
+ */
606
+ watch(inputValue, async function (val) {
607
+
608
+ // 延迟执行
609
+ await sleep(props.inputDebounce)
610
+
611
+ // 是否有值
612
+ const hasValue = $n_isValidValue(val)
613
+
614
+ const n_search = {}
615
+ n_search[currentFilterKey.value] = [
616
+ {
617
+ // 比较类型
618
+ compare: dicts.SEARCH_COMPARE_TYPE__LIKE,
619
+ // 值
620
+ value: hasValue ? val : '',
621
+ }
622
+ ]
623
+
624
+ // 设置表格传参
625
+ $table.setQuery({
626
+ n_search,
627
+ })
628
+
629
+ if (
630
+ // 如果弹出层是隐藏的
631
+ ! showPopup.value
632
+ // 如果输入框有值
633
+ && hasValue
634
+ ) {
635
+ // 显示弹出层节点
636
+ showPopupRef()
637
+ }
638
+
639
+ // 表格重新加载
640
+ await $table.tableReload()
641
+ })
642
+
643
+ /**
644
+ * 监听其他值
645
+ */
646
+ // watch([
647
+ // ()=>props.path,
648
+ // ()=>props.url,
649
+ // ()=>props.query,
650
+ // ()=>props.data,
651
+ // ()=>props.showKeys,
652
+ // ()=>props.hideSearchKeys,
653
+ // ], function () {
654
+ // _dialogShowed = false
655
+ // _popupShowed = false
656
+ //
657
+ // }, {
658
+ // deep: true
659
+ // })
660
+
661
+ // ==========【方法】=============================================================================================
662
+
663
+ /**
664
+ * 加载已选数据
665
+ */
666
+ async function loadSelected() {
667
+ if (
668
+ // 如果值类型不是数组对象
669
+ props.valueType !== 'objectArray'
670
+ // 如果初始加载已选数据
671
+ && ! props.noDefaultLoadSelected
672
+ // 如果有请求路由路径
673
+ && routePath
674
+ ) {
675
+ // 获取值数组
676
+ const values = valueToSelected(props.modelValue, false, false)
677
+ if (values.length) {
678
+ // 初始的已选数据
679
+ const _selected = await onRequestSelected(values)
680
+ const _value = selectedToValue(_selected)
681
+
682
+ // 如果声明值未发生变化
683
+ if (_value === props.modelValue) {
684
+ // 设置已选数据
685
+ setSelected(_selected)
686
+
687
+ } else {
688
+ // 设置临时已选数据
689
+ tempSelected = _selected
690
+ // 触发更新值
691
+ emit('update:modelValue', _value)
692
+ }
693
+ // 初始化加载成功
694
+ emit('loaded', _selected)
695
+ return
696
+ }
697
+ }
698
+
699
+ // 触发更新已选数据
700
+ emit('update:selected', selected.value)
701
+ // 初始化加载成功
702
+ emit('loaded', selected.value)
703
+ }
704
+
705
+ /**
706
+ * 触发更新值
707
+ */
708
+ function emitModelValue(val) {
709
+
710
+ // 设置临时已选数据
711
+ tempSelected = val
712
+
713
+ // 触发更新值
714
+ emit('update:modelValue', selectedToValue(val))
715
+ }
716
+
717
+ /**
718
+ * 设置已选数据
719
+ */
720
+ function setSelected(val) {
721
+
722
+ // 设置已选数据
723
+ selected.value = val
724
+
725
+ // 触发更新已选数据
726
+ emit('update:selected', val)
727
+ }
728
+
729
+ /**
730
+ * 当前格式化显示标签
731
+ */
732
+ function currentFormatLabel(item) {
733
+
734
+ // 如果有格式化显示标签方法
735
+ if ($n_isFunction(props.formatLabel)) {
736
+ // 执行格式化显示标签方法
737
+ return props.formatLabel(item)
738
+ }
739
+
740
+ // 否则显示该值的标签字段
741
+ const val = item[currentlabelKey.value]
742
+ return $n_isValidValue(val) ? val : item[props.valueKey]
743
+ }
744
+
745
+ /**
746
+ * 设置已选数据的单个元素
747
+ */
748
+ function setSelectedItem(val) {
749
+ const obj = {}
750
+ obj[props.valueKey] = val
751
+ obj[currentlabelKey.value] = val
752
+ return obj
753
+ }
754
+
755
+ /**
756
+ * 值转已选数据
757
+ */
758
+ function valueToSelected(val, isFirst, toSelected) {
759
+
760
+ // 如果值类型是数组对象
761
+ if (props.valueType === 'objectArray') {
762
+
763
+ // 如果是有效数组
764
+ if ($n_isValidArray(val)) {
765
+ for (const item of val) {
766
+ if (
767
+ // 如果元素不是有效对象
768
+ ! $n_isValidObject(item)
769
+ // 如果元素没有值字段
770
+ || ! $n_has(item, props.valueKey)
771
+ ) {
772
+ return []
773
+ }
774
+ }
775
+ }
776
+
777
+ // 否则直接返回
778
+ return val
779
+ }
780
+
781
+ if (
782
+ // 如果初始化
783
+ isFirst
784
+ // 如果初始加载已选数据方法
785
+ && ! props.noDefaultLoadSelected
786
+ // 如果有初始加载已选数据
787
+ && props.loadSelected !== void 0
788
+ ) {
789
+ // 将值转为数组
790
+ val = props.valueType === 'string' ? $n_split(val, props.valueSeparator) : val
791
+
792
+ // 如果是有效数组
793
+ if ($n_isValidArray(val)) {
794
+ val = val.filter(e => $n_isValidValue(e))
795
+ if (val.length) {
796
+ return onLoadSelected(val, isFirst)
797
+ }
798
+ }
799
+ return []
800
+ }
801
+
802
+ if (
803
+ // 非初始化
804
+ ! isFirst
805
+ // 或初始不加载已选数据
806
+ || props.noDefaultLoadSelected
807
+ // 或没有路由路径
808
+ || ! routePath
809
+ ) {
810
+ // 将值转为数组
811
+ val = props.valueType === 'string' ? $n_split(val, props.valueSeparator) : val
812
+
813
+ // 如果是有效数组
814
+ if ($n_isValidArray(val)) {
815
+ val = val.filter(e => $n_isValidValue(e))
816
+ return toSelected ? val.map(e => setSelectedItem(e)) : val
817
+ }
818
+ }
819
+
820
+ return []
821
+ }
822
+
823
+ /**
824
+ * 已选数据转值
825
+ */
826
+ function selectedToValue(val) {
827
+
828
+ // 如果值类型是数组对象
829
+ if (props.valueType === 'objectArray') {
830
+
831
+ // 则直接返回
832
+ return val
833
+ }
834
+
835
+ // 值数组
836
+ const values = val.length
837
+ // 如果有已选数据
838
+ ? (
839
+ props.multiple
840
+ // 如果是多选
841
+ ? val.map(e => e[props.valueKey])
842
+ // 否则是单选
843
+ : [ val[0][props.valueKey] ]
844
+ )
845
+ // 否则为空
846
+ : []
847
+
848
+ // 如果值类型是数组
849
+ if (props.valueType === 'stringArray') {
850
+
851
+ // 直接返回数组
852
+ return values
853
+ }
854
+
855
+ // 返回转为分隔符隔开的字符串
856
+ return $n_numberDeep($n_join(values, props.valueSeparator))
857
+ }
858
+
859
+ /**
860
+ * 加载已选数据
861
+ */
862
+ function onLoadSelected(values, isFirst) {
863
+
864
+ function next(lists) {
865
+ const _selected = []
866
+ $n_forEach(lists, function (item) {
867
+ if (
868
+ $n_has(item, props.valueKey)
869
+ && $n_indexOf(values, item[props.valueKey]) > -1
870
+ && $n_findIndex(_selected, e => e[props.valueKey] === item[props.valueKey]) === 0
871
+ ) {
872
+ _selected.push($n_cloneDeep(item))
873
+ }
874
+ })
875
+ return _selected
876
+ }
877
+
878
+ // 如果是加载已选数据方法
879
+ if ($n_isFunction(props.loadSelected)) {
880
+ const res = props.loadSelected(values, next, isFirst)
881
+ if ($n_isValidArray(res)) {
882
+ return res
883
+ }
884
+ return []
885
+ }
886
+
887
+ return next(props.loadSelected)
888
+ }
889
+
890
+ /**
891
+ * 请求选择数据
892
+ */
893
+ async function onRequestSelected(value) {
894
+
895
+ let requestValues = value
896
+
897
+ const all = {}
898
+ let hasAll = false
899
+
900
+ // 如果有初始加载已选数据数组
901
+ if (props.loadSelected !== void 0) {
902
+ const rows = onLoadSelected(value, false)
903
+ if ($n.isValidArray(rows)) {
904
+
905
+ requestValues = []
906
+
907
+ for (const item of rows) {
908
+ all[item[props.valueKey]] = item
909
+ }
910
+ for (const val of value) {
911
+ if (! $n_has(all, val)) {
912
+ requestValues.push(val)
913
+ }
914
+ }
915
+ if (! requestValues.length) {
916
+ return rows
917
+ }
918
+
919
+ hasAll = true
920
+ }
921
+ }
922
+
923
+ // 请求参数
924
+ const httpOptions = {
925
+ url: $table.routePath,
926
+ data: Object.assign(
927
+ // 获取表格请求数据
928
+ $table.getTableRequestData({
929
+ // filter,
930
+ pagination: {
931
+ // 页码
932
+ page: 1,
933
+ // 每页的数据条数
934
+ rowsPerPage: value.length,
935
+ // 排序字段
936
+ sortBy: null,
937
+ // 是否降序排列
938
+ descending: true,
939
+ }
940
+ }, false),
941
+ {
942
+ // 查看字段
943
+ n_view: {
944
+ // 查看字段
945
+ field: props.valueKey,
946
+ // 查看值
947
+ value: requestValues,
948
+ },
949
+ }
950
+ ),
951
+ // 是否开启防抖(防止重复请求)
952
+ debounce: false,
953
+ }
954
+
955
+ // 请求数据
956
+ const { status, data } = $n_isFunction(props.request) ?
957
+ // 如果有自定义请求方法
958
+ await $n_runAsync(props.request)({
959
+ // http 请求参数
960
+ httpOptions,
961
+ // 对话框是否已显示
962
+ showDialog: showDialog.value,
963
+ }) :
964
+ // 否则请求数据
965
+ await $n_http(httpOptions)
966
+
967
+ if (status) {
968
+ if ($n_isValidArray($n_get(data, 'rows'))) {
969
+ if (! hasAll) {
970
+ return data.rows
971
+ }
972
+ for (const item of data.rows) {
973
+ all[item[props.valueKey]] = item
974
+ }
975
+ }
976
+ }
977
+
978
+ const newRows = []
979
+ for (const val of value) {
980
+ if ($n_has(all, val)) {
981
+ newRows.push(all[val])
982
+ }
983
+ }
984
+ return newRows
985
+ }
986
+
987
+ /**
988
+ * 获取表格列数据
989
+ */
990
+ function getTableColumns() {
991
+
992
+ let columns
993
+
994
+ // 如果有声明路由表格列数据
995
+ if ($n_isValidArray(props.columns)) {
996
+ columns = $n_cloneDeep(props.columns)
997
+
998
+ // 如果有路由路径
999
+ } else if (routePath) {
1000
+ // 否则如果有路由表格列数据
1001
+ const rawTableColumns = $n_$table.config(routePath, 'columns')
1002
+ if ($n_isValidArray(rawTableColumns)) {
1003
+ columns = $n_cloneDeep(rawTableColumns)
1004
+ }
1005
+ }
1006
+
1007
+ if ($n_isValidArray(columns)) {
1008
+ if ($n_isValidArray(props.hideSearchKeys)) {
1009
+ for (const item of columns) {
1010
+ if (
1011
+ props.hideSearchKeys.indexOf(item.name) > -1
1012
+ && $n_has(item, 'search')
1013
+ ) {
1014
+ item.search.hide = true
1015
+ }
1016
+ }
1017
+ }
1018
+ return columns
1019
+ }
1020
+
1021
+ return []
1022
+ }
1023
+
1024
+ /**
1025
+ * 获取快捷表格列数据
1026
+ */
1027
+ function getQuickTableColumns() {
1028
+
1029
+ const columns = []
1030
+
1031
+ // 如果有原始表格列数据
1032
+ if ($n_isValidArray($table.tableColumns.value)) {
1033
+
1034
+ // 克隆原始表格列数据
1035
+ const rawTableColumns = $n_cloneDeep($table.tableColumns.value)
1036
+
1037
+ // 快捷表格显示的属性名称数组
1038
+ $n_forEach(currentShowKeys.value, function (key) {
1039
+ for (const item of rawTableColumns) {
1040
+ if (item.name === key) {
1041
+ // 删除搜索字段
1042
+ if ($n_has(item, 'search')) {
1043
+ delete item.search
1044
+ }
1045
+ // 删除可见字段
1046
+ if ($n_has(item, 'visible')) {
1047
+ delete item.visible
1048
+ }
1049
+ columns.push(item)
1050
+ }
1051
+ }
1052
+ })
1053
+ }
1054
+
1055
+ return columns
1056
+ }
1057
+
1058
+ /**
1059
+ * 移除已选数据
1060
+ */
1061
+ function onRemoveSelected(index) {
1062
+
1063
+ const _selected = [...selected.value]
1064
+ _selected.splice(index, 1)
1065
+
1066
+ // 触发更新值
1067
+ emitModelValue(_selected)
1068
+ }
1069
+
1070
+ /**
1071
+ * 字段获取焦点触发
1072
+ */
1073
+ function onFieldFocus(e) {
1074
+
1075
+ // 停止冒泡
1076
+ e.stopPropagation()
1077
+
1078
+ // 设置输入框焦点
1079
+ setInputFocus()
1080
+
1081
+ // window.scrollTo(window.pageXOffset || window.scrollX || document.body.scrollLeft || 0, 0)
1082
+ }
1083
+
1084
+ /**
1085
+ * 字段失去焦点触发
1086
+ */
1087
+ function onFieldBlur(e) {
1088
+
1089
+ // 停止冒泡
1090
+ e.stopPropagation()
1091
+
1092
+ if (
1093
+ // 如果开启筛选
1094
+ props.filter
1095
+ // 如果没有显示弹出层
1096
+ && ! showPopup.value
1097
+ ) {
1098
+ // 清空输入框值
1099
+ inputValue.value = ''
1100
+ }
1101
+ }
1102
+
1103
+ /**
1104
+ * 字段清空触发
1105
+ */
1106
+ function onFieldClear() {
1107
+
1108
+ // 触发更新值
1109
+ // 清空快捷表格已选数据
1110
+ emitModelValue([])
1111
+
1112
+ // 隐藏弹出层节点
1113
+ hidePopupRef()
1114
+ }
1115
+
1116
+ /**
1117
+ * 显示弹出层节点
1118
+ */
1119
+ function showPopupRef() {
1120
+
1121
+ // 如果有弹出层节点
1122
+ if (popupRef.value) {
1123
+ // 显示弹出层
1124
+ popupRef.value.show()
1125
+ }
1126
+ }
1127
+
1128
+ /**
1129
+ * 隐藏弹出层节点
1130
+ */
1131
+ function hidePopupRef() {
1132
+
1133
+ // 如果有弹出层节点
1134
+ if (popupRef.value) {
1135
+ // 隐藏弹出层
1136
+ popupRef.value.hide()
1137
+ }
1138
+ }
1139
+
1140
+ /**
1141
+ * 弹出层显示回调
1142
+ */
1143
+ let _popupShowed = false
1144
+ function onPopupShow() {
1145
+
1146
+ // 显示弹出层
1147
+ showPopup.value = true
1148
+
1149
+ // 设置输入框焦点
1150
+ setInputFocus()
1151
+
1152
+ // 如果每次对话框显示都请求
1153
+ if (props.requestEveryDialogShow) {
1154
+ // 表格重新加载
1155
+ $table.tableReload()
1156
+ .finally()
1157
+ return
1158
+ }
1159
+
1160
+ if (_popupShowed) {
1161
+ return
1162
+ }
1163
+ _popupShowed = true
1164
+
1165
+ // 表格重新加载
1166
+ $table.tableReload()
1167
+ .finally()
1168
+ }
1169
+
1170
+ /**
1171
+ * 显示对话框
1172
+ */
1173
+ function onShowDialog() {
1174
+ // 显示对话框
1175
+ showDialog.value = true
1176
+ }
1177
+
1178
+ /**
1179
+ * 对话框显示前回调
1180
+ */
1181
+ function onDialogBeforeShow() {
1182
+
1183
+ // 设置当前已选数据
1184
+ $table.tableSelected.value = [...selected.value]
1185
+
1186
+ // 隐藏弹出层节点
1187
+ hidePopupRef()
1188
+ }
1189
+
1190
+ /**
1191
+ * 对话框显示回调
1192
+ */
1193
+ let _dialogShowed = false
1194
+ function onDialogShow() {
1195
+
1196
+ // 如果每次对话框显示都请求
1197
+ if (props.requestEveryDialogShow) {
1198
+ // 表格重新加载
1199
+ $table.tableReload()
1200
+ .finally()
1201
+ return
1202
+ }
1203
+
1204
+ if (_dialogShowed) {
1205
+ return
1206
+ }
1207
+ _dialogShowed = true
1208
+
1209
+ // 表格重新加载
1210
+ $table.tableReload()
1211
+ .finally()
1212
+ }
1213
+
1214
+ /**
1215
+ * 对话框隐藏后回调
1216
+ */
1217
+ function onDialogHide() {
1218
+
1219
+ let isReload = true
1220
+
1221
+ // 清空输入框值
1222
+ if (
1223
+ // 如果开启筛选
1224
+ props.filter
1225
+ // 如果有输入框值
1226
+ && inputValue.value
1227
+ ) {
1228
+ // 此时清空输入框后, 会自动刷新表格
1229
+ inputValue.value = ''
1230
+
1231
+ // 所以只需要重置搜索值即可, 不需要再重置后刷新表格
1232
+ isReload = false
1233
+ }
1234
+
1235
+ // 获取表格搜索值
1236
+ let searchValue = $table.getTableSearchValue()
1237
+ if (searchValue.length) {
1238
+
1239
+ // 如果有隐藏搜索字段数组
1240
+ if ($n_isValidArray(props.hideSearchKeys)) {
1241
+ // 从搜索值数组中去除隐藏搜索字段的数组
1242
+ searchValue = searchValue.filter(e => $n_indexOf(e.field, props.hideSearchKeys) === -1)
1243
+ }
1244
+
1245
+ // 表格搜索重置
1246
+ $table.tableSearchReset(isReload && searchValue.length, {
1247
+ showDialog: 1,
1248
+ })
1249
+ }
1250
+ }
1251
+
1252
+ /**
1253
+ * 对话框点击确认回调
1254
+ */
1255
+ function onDialogConfirm(data) {
1256
+
1257
+ // 触发更新值
1258
+ emitModelValue([...data])
1259
+ }
1260
+
1261
+ /**
1262
+ * 单击快捷表格行
1263
+ */
1264
+ function quickTableRowClick(e, row) {
1265
+
1266
+ // 如果为多选
1267
+ if (props.multiple) {
1268
+
1269
+ // 克隆已选数据
1270
+ const _selected = [...selected.value]
1271
+
1272
+ const opt = {}
1273
+ opt[props.valueKey] = row[props.valueKey]
1274
+
1275
+ // 获取当前数据索引
1276
+ const itemIndex = $n_findIndex(_selected, opt)
1277
+
1278
+ // 如果不存在
1279
+ if (itemIndex === -1) {
1280
+ // 则添加
1281
+ _selected.push(row)
1282
+
1283
+ // 否则
1284
+ } else {
1285
+ // 删除
1286
+ _selected.splice(itemIndex, 1)
1287
+ }
1288
+
1289
+ // 触发更新值
1290
+ emitModelValue(_selected)
1291
+
1292
+ // 否则为单选
1293
+ } else {
1294
+
1295
+ // 触发更新值
1296
+ emitModelValue([ row ])
1297
+
1298
+ // 隐藏弹出层节点
1299
+ hidePopupRef()
1300
+ }
1301
+ }
1302
+
1303
+ /**
1304
+ * 设置输入框文字选中
1305
+ */
1306
+ function setInputSelection() {
1307
+ if (
1308
+ // 如果开启筛选
1309
+ props.filter
1310
+ // 如果有输入框节点
1311
+ && inputRef.value
1312
+ // 如果输入框有值
1313
+ && inputValue.value.length
1314
+ ) {
1315
+ // 全选文字
1316
+ inputRef.value.select()
1317
+ // inputRef.value.setSelectionRange(0, inputValue.value.length)
1318
+ }
1319
+ }
1320
+
1321
+ /**
1322
+ * 设置输入框焦点
1323
+ */
1324
+ function setInputFocus() {
1325
+ if (
1326
+ // 如果开启筛选
1327
+ props.filter
1328
+ // 如果有输入框节点
1329
+ && inputRef.value
1330
+ ) {
1331
+ inputRef.value.focus()
1332
+ }
1333
+ }
1334
+
1335
+ /**
1336
+ * 格式化图片
1337
+ */
1338
+ function formatImg(img, { count }) {
1339
+
1340
+ // 图片数组
1341
+ const imgs = []
1342
+
1343
+ // 转为图片数组
1344
+ const arr = $n_split(img, ',')
1345
+ for (const item of arr) {
1346
+ const src = $n_getImage(item)
1347
+ if (src) {
1348
+ imgs.push(item)
1349
+ if (
1350
+ count > 0
1351
+ && imgs.length === count
1352
+ ) {
1353
+ break
1354
+ }
1355
+ }
1356
+ }
1357
+
1358
+ return imgs
1359
+ }
1360
+
1361
+ // ==========【生命周期】=========================================================================================
1362
+
1363
+ /**
1364
+ * 在组件因为响应式状态变更而更新其 DOM 树之后调用
1365
+ */
1366
+ onUpdated(function () {
1367
+ if (
1368
+ popupRef.value
1369
+ && $n_has(popupRef.value, 'currentComponent.ref.updatePosition')
1370
+ ) {
1371
+ popupRef.value.currentComponent.ref.updatePosition()
1372
+ }
1373
+ })
1374
+
1375
+ // ==========【返回】=============================================================================================
1376
+
1377
+ return {
1378
+ // 解构表格实例
1379
+ ...$table,
1380
+
1381
+ // 插槽标识
1382
+ slotNames,
1383
+ // 当前标签字段
1384
+ currentlabelKey,
1385
+ // 显示值
1386
+ showValue,
1387
+
1388
+ // 输入框节点
1389
+ inputRef,
1390
+ // 输入框值
1391
+ inputValue,
1392
+ // 弹出层节点
1393
+ popupRef,
1394
+ // 是否显示对话框
1395
+ showDialog,
1396
+ // 是否显示弹出层
1397
+ showPopup,
1398
+ // 当前已选数据
1399
+ selected,
1400
+ // 当前表格列数据
1401
+ columns,
1402
+
1403
+ // 当前格式化显示标签
1404
+ currentFormatLabel,
1405
+ // 移除已选数据
1406
+ onRemoveSelected,
1407
+
1408
+ // 字段获取焦点触发
1409
+ onFieldFocus,
1410
+ // 字段失去焦点触发
1411
+ onFieldBlur,
1412
+ // 字段清空触发
1413
+ onFieldClear,
1414
+
1415
+ // 弹出层显示回调
1416
+ onPopupShow,
1417
+
1418
+ // 显示对话框
1419
+ onShowDialog,
1420
+ // 对话框显示前回调
1421
+ onDialogBeforeShow,
1422
+ // 对话框显示回调
1423
+ onDialogShow,
1424
+ // 对话框隐藏后回调
1425
+ onDialogHide,
1426
+ // 对话框点击确认回调
1427
+ onDialogConfirm,
1428
+
1429
+ // 单击快捷表格行
1430
+ quickTableRowClick,
1431
+
1432
+ // 触发更新值
1433
+ emitModelValue,
1434
+ // 格式化图片
1435
+ formatImg,
1436
+ }
1437
+ },
1438
+ }
1439
+ </script>
1440
+
1441
+ <style lang="scss">
1442
+ .n-field-table {
1443
+ .q-field__input--padding {
1444
+ padding-left: 4px;
1445
+ min-width: 50px !important;
1446
+ cursor: text;
1447
+ }
1448
+ }
1449
+
1450
+ /**
1451
+ * 桌面
1452
+ */
1453
+ body.desktop {
1454
+ .n-field-table {
1455
+ &__popup-table {
1456
+ height: 300px;
1457
+ }
1458
+ }
1459
+ }
1460
+ </style>