@hostlink/nuxt-light 1.20.3 → 1.20.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,459 @@
1
- <script setup>
2
- import { useSlots } from "vue";
1
+ <script setup lang="ts">
2
+ import { useQuasar, QTable, type QTableColumn, type QTableProps, Dialog, QTab } from 'quasar';
3
+
4
+ import { ref, computed, onMounted, useSlots, useAttrs, watch, defineComponent, h } from "vue";
5
+ import { t, q, useLight, GQLFieldBuilder, model } from '../';
6
+ import { toQuery } from '@hostlink/light';
7
+ import { collect } from "#imports"
8
+
9
+ const $q = useQuasar();
10
+
11
+
12
+ const minimized = defineModel<boolean>("minimized", { default: false })
13
+ const maximized = defineModel<boolean>("maximized", { default: false })
14
+
15
+ // extends QTableColumn
16
+ export type LTableColumn = QTableColumn & {
17
+ searchable?: boolean,
18
+ searchType?: string,
19
+ searchOptions?: Array<any> | Function,
20
+ searchMultiple?: boolean,
21
+ gqlField?: string | Array<string> | Object,
22
+ backgroundColor?: string | Function
23
+ }
24
+
25
+
26
+ const errors = ref<InstanceType<any>>([
27
+ ]);
28
+
29
+ export type LTableProps = QTableProps & {
30
+ columns?: Array<LTableColumn>,
31
+ actions?: Array<string>,
32
+ sortBy?: string | null | undefined,
33
+ rowKey?: string,
34
+ modelName?: string | null | undefined,
35
+ searchable?: boolean | null | undefined,
36
+ selected?: Array<any>,
37
+ maximizable?: boolean,
38
+ minimizable?: boolean,
39
+ rows?: Array<any>,
40
+ onRequestData?: (request: LTableRequest) => void
41
+ addComponent?: Dialog
42
+ addComponentProps?: any
43
+ }
44
+
45
+
46
+ const props = withDefaults(defineProps<LTableProps>(), {
47
+ actions: () => [],
48
+ rowsPerPageLabel: "Records per page:",
49
+ selection: "none",
50
+ rowsPerPageOptions: () => [3, 5, 7, 10, 15, 20, 25, 50, 0],
51
+ pagination: () => {
52
+ return {
53
+ sortBy: null,
54
+ descending: true,
55
+ page: 1,
56
+ rowsPerPage: 10,
57
+ }
58
+ },
59
+ fullscreen: false,
60
+ searchable: false,
61
+ maximizable: false,
62
+ minimizable: false,
63
+ selected: () => [],
64
+ loadingLabel: "Loading...",
65
+ noDataLabel: "No data available",
66
+ dark: undefined,
67
+ });
68
+
69
+
70
+ const isServerSide = (props.rows == undefined);
71
+
72
+
73
+ const light = useLight();
74
+
75
+ const pagination = ref(props.pagination);
76
+
77
+ if (props.rowsPerPageOptions[0] == 0) {
78
+ pagination.value.rowsPerPage = 0;
79
+ }
80
+
81
+ //apply all aligns to the columns
82
+ props.columns?.forEach((col) => {
83
+ if (!col.align) {
84
+ col.align = "left";
85
+ }
86
+ if (!col.field && col.name) {
87
+ col.field = col.name
88
+ }
89
+
90
+ col.label = t(col.label)
91
+ })
92
+
93
+ if (props.columns) {
94
+ for (let i = 0; i < props.columns.length; i++) {
95
+ if (props.columns[i].searchOptions) {
96
+ let opts = props.columns[i].searchOptions;
97
+ if (typeof opts == "function") {
98
+ props.columns[i].searchOptions = await opts();
99
+ }
100
+
101
+
102
+ }
103
+ }
104
+ }
105
+
106
+ export interface LTableRequest {
107
+ sort: string,
108
+ fields: Array<string>,
109
+ gql: {
110
+ __args: {
111
+ filters: any,
112
+ sort: string
113
+ },
114
+ data: {
115
+ __args: {
116
+ limit: number
117
+ },
118
+ [key: string]: any
119
+ },
120
+ meta: {
121
+ total: boolean,
122
+ key: boolean,
123
+ name: boolean
124
+ }
125
+ },
126
+ offset: number,
127
+ limit: number,
128
+ loadData: (model: string, filters: any, fields: Array<any>) => void,
129
+ loadObjects: (model: string, filters: any, fields: Array<any>) => void,
130
+ setData: (data: { data: Array<{ data: any }>, meta: { total: Number, key: string, name: string } }) => void,
131
+ }
132
+
133
+ const emits = defineEmits<{
134
+ "request-data": [p: LTableRequest],
135
+ "update:selected": [p: Array<any>]
136
+ }>()
137
+
138
+
139
+ const loading = ref(false);
140
+
141
+ let activeEdit = false;
142
+ let actionView = false;
143
+ let actionDelete = false;
144
+ if (props.actions.length > 0) {
145
+ actionView = props.actions.includes("view");
146
+ activeEdit = props.actions.includes("edit");
147
+ actionDelete = props.actions.includes("delete");
148
+ }
149
+
150
+
151
+
152
+ if (props.sortBy) {
153
+ let [sortBy, descending] = props.sortBy.split(":");
154
+ pagination.value.sortBy = sortBy;
155
+ pagination.value.descending = descending == "desc";
156
+ }
157
+
158
+ const hasSearch = computed(() => {
159
+ return props.columns?.some((col) => {
160
+ return col.searchable;
161
+ })
162
+ })
163
+
164
+ const table = ref<InstanceType<typeof QTable>>();
165
+
166
+ const filters = ref<InstanceType<any>>({});
167
+ const rows = ref<InstanceType<any>>(props.rows);
168
+ if (rows.value == undefined) {
169
+ rows.value = [];
170
+ }
171
+
172
+ onMounted(() => {
173
+ table.value && table.value.requestServerInteraction();
174
+ })
175
+
176
+
177
+ let primaryKey = ref(props.rowKey);
178
+ const modelName = ref(props.modelName);
179
+
180
+
181
+ const validateData = () => {
182
+ if (primaryKey.value == null) return;
183
+
184
+ if (props.actions.includes("edit")) {
185
+ if (rows.value.length > 0) {
186
+ //get first row
187
+ let row = rows.value[0];
188
+ //check has primary key
189
+
190
+ if (!row[primaryKey.value]) {
191
+ errors.value.push("[edit] Primary key not found in the data");
192
+ }
193
+ }
194
+ }
195
+
196
+ if (props.actions.includes("delete")) {
197
+ if (rows.value.length > 0) {
198
+ //get first row
199
+ let row = rows.value[0];
200
+ //check has primary key
201
+
202
+ if (!row[primaryKey.value]) {
203
+ errors.value.push("[delete] Primary key not found in the data");
204
+ }
205
+ }
206
+ }
207
+
208
+ }
209
+
210
+ const getData = async (
211
+ operation: string,
212
+ filters = {},
213
+ sort: String,
214
+ offset: number,
215
+ limit: number,
216
+ fields: Array<string> = []) => {
217
+
218
+ const query: any = {};
219
+ query[operation] = {
220
+ meta: {
221
+ total: true,
222
+ key: true,
223
+ name: true
224
+ }
225
+ }
226
+
227
+ let offset_limit: any = {}
228
+ if (offset) {
229
+ offset_limit.offset = offset;
230
+ }
231
+
232
+ if (limit) {
233
+ offset_limit.limit = limit;
234
+ }
235
+
236
+ if (offset_limit) {
237
+ query[operation].data = {
238
+ __args: offset_limit
239
+ };
240
+ }
241
+
242
+ let params: any = {}
243
+ if (sort) {
244
+ params.sort = sort;
245
+ }
246
+
247
+ if (filters) {
248
+ params.filters = filters
249
+ }
250
+
251
+ query[operation].__args = params;
252
+
253
+ query[operation].data = query[operation].data || {};
254
+ Object.assign(query[operation].data, fields);
255
+
256
+ const resp = await q(query);
257
+ return resp[operation];
258
+
259
+ }
260
+
261
+ const onLocalRequest = async (p: any) => {
262
+ if (!isServerSide) return;
263
+
264
+
265
+ let sort = "";
266
+ if (p.pagination.sortBy) {
267
+ sort = p.pagination.sortBy + ":" + (p.pagination.descending ? "desc" : "asc");
268
+ }
269
+
270
+ //fields
271
+ const builder = GQLFieldBuilder();
272
+ if (props.rowKey) {
273
+ builder.add(props.rowKey);
274
+ }
275
+
276
+ props.columns?.forEach((col) => {
277
+ if (col.gqlField) {
278
+ builder.add(col.gqlField);
279
+ return;
280
+ }
281
+
282
+ if (!col.name) return;
283
+ if (col.name.startsWith("_")) {
284
+ return;
285
+ }
286
+ builder.add(col.name);
287
+
288
+ });
289
+
290
+ if (actionView) {
291
+ builder.add("canView");
292
+ }
293
+
294
+ if (actionDelete) {
295
+ builder.add("canDelete");
296
+ }
297
+
298
+ if (activeEdit) {
299
+ builder.add("canUpdate");
300
+ }
301
+
302
+
303
+ const callback: LTableRequest = {
304
+ sort: sort,
305
+ fields: builder.get(),
306
+ offset: (p.pagination.page - 1) * p.pagination.rowsPerPage,
307
+ limit: p.pagination.rowsPerPage,
308
+ gql: {
309
+ __args: {
310
+ filters: getFilterValue(),
311
+ sort: sort
312
+ },
313
+ data: {
314
+ __args: {
315
+ limit: p.pagination.rowsPerPage,
316
+ offset: (p.pagination.page - 1) * p.pagination.rowsPerPage
317
+ },
318
+ ...toQuery(builder.get())
319
+ },
320
+ meta: {
321
+ total: true,
322
+ key: true,
323
+ name: true
324
+ }
325
+ },
326
+ setData(data: { data: Array<{ data: any }>, meta: { total: number, key: string, name: string } }) {
327
+ rows.value = data.data;
328
+ primaryKey.value = data.meta.key;
329
+ modelName.value = data.meta.name;
330
+ pagination.value.rowsNumber = data.meta.total;
331
+
332
+ pagination.value.page = p.pagination.page;
333
+ pagination.value.sortBy = p.pagination.sortBy;
334
+ pagination.value.descending = p.pagination.descending;
335
+ pagination.value.rowsPerPage = p.pagination.rowsPerPage;
336
+
337
+ loading.value = false;
338
+
339
+ validateData();
340
+ },
341
+ loadObjects(model: string, filters: any = null, fields: Array<any> = []) {
342
+ return this.loadData(model, filters, fields);
343
+ },
344
+ async loadData(model: string, filters: any = null, fields: Array<any> = []) {
345
+ fields.forEach((f) => {
346
+ builder.add(f);
347
+ })
348
+
349
+ let localFilters = getFilterValue();
350
+ if (filters) {
351
+ localFilters = {
352
+ ...localFilters,
353
+ ...filters
354
+ }
355
+ }
356
+ loading.value = true;
357
+
358
+ try {
359
+ let c = collect(model, builder.get())
360
+
361
+ console.log(localFilters)
362
+
363
+ if (localFilters) {
364
+ for (let [key, value] of Object.entries(localFilters)) {
365
+ if (typeof value == "object") {
366
+ if (value.contains) {
367
+ c = c.whereContains(key, value.contains);
368
+ }
369
+ if (value.between) {
370
+ c = c.whereBetween(key, value.between);
371
+ }
372
+ } else {
373
+ c = c.where(key, value);
374
+ }
375
+ }
376
+ }
377
+
378
+ if (sort) {
379
+ if (sort.split(":")[1] == "asc") {
380
+ c = c.sortBy(sort.split(":")[0]);
381
+ } else {
382
+ c = c.sortByDesc(sort.split(":")[0]);
383
+ }
384
+ }
385
+
386
+ c = c.forPage(p.pagination.page, p.pagination.rowsPerPage);
387
+
388
+ const data = await c.all();
389
+ const allData = {
390
+ data,
391
+ meta: c.meta
392
+ }
393
+ //const allData = await getData("list" + model, localFilters, sort, this.offset, this.limit, builder.get());
394
+ this.setData(allData);
395
+ } catch (e: any) {
396
+ quasar.dialog({
397
+ message: e,
398
+ color: "negative"
399
+ })
400
+ this.setData({ data: [], meta: { total: 0, key: "", name: "" } });
401
+ }
402
+
403
+
404
+
405
+ }
406
+ }
407
+
408
+
409
+ loading.value = true;
410
+ //emits("request", p);
411
+ emits("request-data", callback);
412
+
413
+ }
414
+
415
+ const getFilterValue = () => {
416
+ let f: any = {};
417
+
418
+ props.columns?.forEach((col) => {
419
+ if (col.searchable) {
420
+ if (filters.value[col.name] !== null && filters.value[col.name] !== "" && filters.value[col.name] !== undefined) {
421
+
422
+ if (col.searchType == "number") {
423
+ f[col.name] = filters.value[col.name]
424
+ } else if (col.searchType == "date") {
425
+ if (filters.value[col.name].from) {
426
+ f[col.name] = {
427
+ between: [filters.value[col.name].from, filters.value[col.name].to]
428
+ }
429
+ }
430
+ } else if (col.searchType == "select") {
431
+ f[col.name] = filters.value[col.name]
432
+
433
+ } else {
434
+ f[col.name] = {
435
+ contains: filters.value[col.name]
436
+ }
437
+ }
438
+ }
439
+ }
440
+ });
441
+ return f;
442
+ }
443
+
444
+
445
+ const onFilters = () => {
446
+
447
+ //clone the filters
448
+ onLocalRequest({
449
+ pagination: {
450
+ page: 1,
451
+ rowsPerPage: props.pagination.rowsPerPage,
452
+ sortBy: props.pagination.sortBy,
453
+ descending: props.pagination.descending
454
+ }
455
+ })
456
+ }
3
457
 
4
458
  const slots = useSlots();
5
459
 
@@ -7,44 +461,294 @@ const ss = Object.entries(slots).map(([key, value]) => {
7
461
  return key;
8
462
  });
9
463
 
464
+ const quasar = useQuasar();
465
+ const onDelete = async (id: any) => {
466
+ if (modelName.value == null) return;
467
+
468
+ try {
469
+ await model(modelName.value).delete(id);
470
+ } catch (e: any) {
471
+ quasar.notify({
472
+ message: e.message,
473
+ color: "negative"
474
+ })
475
+ return;
476
+ }
477
+ // refresh the table
478
+ if (table.value) {
479
+ table.value.requestServerInteraction();
480
+ }
481
+ }
482
+ const attrs = computed(() => {
483
+ return {
484
+ ...props,
485
+
486
+ ...{
487
+ dense: light.getStyle("tableDense"),
488
+ flat: light.getStyle("tableFlat"),
489
+ bordered: light.getStyle("tableBorder"),
490
+ separator: light.getStyle("tableSeparator"),
491
+ },
492
+ loadingLabel: t(props.loadingLabel),
493
+ noDataLabel: t(props.noDataLabel),
494
+ rowsPerPageOptions: props.rowsPerPageOptions,
495
+ rowsPerPageLabel: t(props.rowsPerPageLabel),
496
+ selection: props.selection,
497
+ rowKey: props.rowKey,
498
+ }
499
+ });
500
+
501
+ const filter = ref('');
10
502
 
503
+ const toColumns = props.columns?.filter((col: any) => {
504
+ return col.to;
505
+ }).map((col: any) => {
506
+ col.slot_name = 'body-cell-' + col.name;
507
+ return col;
11
508
 
509
+ })
510
+ /*
511
+ const filterFn = (val, update, abort) {
512
+ update(() => {
513
+ const needle = val.toLocaleLowerCase()
514
+ options.value = stringOptions.filter(v => v.toLocaleLowerCase().indexOf(needle) > -1)
515
+ })
516
+ },
517
+ */
518
+
519
+ const isDark = computed(() => {
520
+ return light.theme == "dark";
521
+ })
522
+
523
+ const hasRowExpand = computed(() => {
524
+ return ss.indexOf("row-expand") >= 0;
525
+ })
526
+
527
+ const hasActions = computed(() => {
528
+ return props.actions.length > 0;
529
+ })
530
+
531
+ const getCellStyle = (col: any, row: any) => {
532
+ const style: any = col.cellStyle ?? {};
533
+ if (col.backgroundColor) {
534
+ if (typeof col.backgroundColor == "function") {
535
+ style.backgroundColor = col.backgroundColor(row);
536
+ } else {
537
+ style.backgroundColor = col.backgroundColor;
538
+ }
539
+
540
+ }
541
+ return style;
542
+ }
543
+
544
+ const getCellClass = (col: any, row: any) => {
545
+ const cl: any = [];
546
+
547
+ if (col.cellClass) {
548
+ if (typeof col.cellClass == "function") {
549
+ cl.push(col.cellClass(row));
550
+ } else {
551
+ cl.push(col.cellClass);
552
+ }
553
+
554
+ }
555
+ return cl;
556
+ }
557
+
558
+ const localSelected = computed({
559
+ get() {
560
+ return props.selected;
561
+ },
562
+ set(val) {
563
+ emits("update:selected", val);
564
+ }
565
+ });
566
+
567
+ watch(() => props.rows, (val) => {
568
+ rows.value = val;
569
+ });
570
+
571
+ const computedColumns = computed(() => {
572
+ return props.columns;
573
+ })
574
+
575
+
576
+ function requestServerInteraction() {
577
+ table.value && table.value.requestServerInteraction();
578
+ }
579
+
580
+ defineExpose({
581
+ requestServerInteraction
582
+ })
583
+
584
+ const onAdd = () => {
585
+ if (!props.addComponent) return;
586
+
587
+ $q.dialog({
588
+ component: props.addComponent,
589
+ componentProps: props.addComponentProps,
590
+
591
+ }).onOk(() => {
592
+ requestServerInteraction();
593
+ })
594
+ }
12
595
  </script>
13
- <template>
14
- <q-table row-key="name">
15
596
 
16
- <template #header="props">
17
597
 
598
+ <template>
599
+ <l-card flat bordered :maximizable="maximizable" :minimizable="minimizable" :title="title"
600
+ v-model:minimized="minimized" v-model:maximized="maximized">
18
601
 
19
- <q-tr :props="props">
602
+ <q-toolbar v-if="addComponent">
603
+ <q-btn icon="sym_o_add" :label="$t('Add')" :color="$light.color" @click="onAdd" v-if="addComponent" />
604
+ </q-toolbar>
20
605
 
21
- <q-th auto-width key="_expand" :props="{
22
- colsMap: {
23
- _expand: {
24
- align: 'right'
25
- }
26
- }
27
- }">1</q-th>
28
606
 
29
- <q-th auto-width key="_actions">act</q-th>
30
- <q-th v-for="col in props.cols" :key="col.name" :props="props">{{ col.label }}</q-th>
31
- </q-tr>
607
+ <template v-if="errors.length > 0">
608
+ <div class="q-mb-sm">
609
+ <div class="q-gutter-sm">
610
+ <l-alert type="negative" v-for="e in errors">{{ e }}</l-alert>
611
+ </div>
612
+ </div>
32
613
  </template>
33
614
 
34
- <template #body="props">
615
+ <q-table v-bind="attrs" :loading="loading" :rows="rows" ref="table" @request="onLocalRequest" :columns="columns"
616
+ v-model:pagination="pagination" :filter="filter" v-model:selected="localSelected">
617
+
618
+
619
+
620
+
621
+
622
+ <template #header="props">
623
+ <q-tr :props="props">
624
+ <q-td v-if="selection != 'none'" auto-width>
625
+ <q-checkbox v-model="props.selected" />
626
+ </q-td>
627
+ <q-th v-if="hasRowExpand" auto-width></q-th>
628
+ <q-th v-if="hasActions" auto-width></q-th>
629
+ <q-th v-for="col in props.cols" :key="col.name" :props="props">{{ col.label }}</q-th>
630
+ </q-tr>
631
+ </template>
632
+
633
+
35
634
 
36
- <q-tr :props="props">
37
- <template v-for="col in props.cols">
38
- <slot v-if="slots['body-cell-' + col.name]" :name="'body-cell-' + col.name" v-bind="props"
39
- :key="col.name"></slot>
40
- <template v-else>
41
- <q-td :props="props" :key="col.name">
42
- {{ col.name }}
43
- {{ col.value }}</q-td>
635
+ <template #body="props">
636
+ <q-tr :props="props">
637
+ <q-td v-if="selection != 'none'" auto-width>
638
+ <q-checkbox v-model="props.selected" />
639
+ </q-td>
640
+ <q-td v-if="hasRowExpand" auto-width>
641
+ <q-btn :class="{ 'text-grey-8': !isDark }" flat round size="sm"
642
+ :icon="props.expand ? 'sym_o_expand_more' : 'sym_o_expand_less'"
643
+ @click="props.expand = !props.expand"></q-btn>
644
+ </q-td>
645
+
646
+ <q-td v-if="hasActions" auto-width>
647
+ <div :class="{ 'text-grey-8': !isDark }" v-if="primaryKey">
648
+
649
+ <l-view-btn v-if="actionView && props.row.canView"
650
+ :to="`/${modelName}/${props.row[primaryKey]}/view`" size="sm" />
651
+
652
+ <l-edit-btn v-if="activeEdit && props.row.canUpdate"
653
+ :to="`/${modelName}/${props.row[primaryKey]}/edit`" size="sm" />
654
+
655
+ <l-delete-btn v-if="actionDelete && props.row.canDelete"
656
+ @submit="onDelete(props.row[primaryKey])" size="sm"></l-delete-btn>
657
+
658
+ <slot name="actions" v-bind="props"></slot>
659
+ </div>
660
+
661
+ </q-td>
662
+
663
+
664
+
665
+ <template v-for="col in props.cols">
666
+ <template v-if="ss.indexOf('body-cell-' + col.name) >= 0">
667
+ <slot :name="'body-cell-' + col.name" v-bind="props"></slot>
668
+ </template>
669
+ <template v-else>
670
+ <q-td :key="col.name" :props="props" :auto-width="col.autoWidth ?? false"
671
+ :style="getCellStyle(col, props.row)" :class="getCellClass(col, props.row)"><template
672
+ v-if="col.to" class="bg-primary">
673
+ <l-link :to="col.to(props.row)">{{ col.value }}</l-link>
674
+ </template><template v-else>{{ col.value }}</template></q-td>
675
+ </template>
44
676
  </template>
45
- </template>
46
- </q-tr>
677
+ </q-tr>
678
+ <q-tr v-show="props.expand" :props="props">
679
+ <q-td colspan="100%">
680
+ <slot name="row-expand" v-bind="props"></slot>
681
+ </q-td>
682
+ </q-tr>
683
+ </template>
47
684
 
48
- </template>
49
- </q-table>
685
+
686
+ <template #top-right="props" v-if="searchable">
687
+ <q-input v-if="searchable" outlined dense debounce="300" v-model="filter" :placeholder="$t('Search')"
688
+ :color="$light.color">
689
+ <template v-slot:append>
690
+ <q-icon name="search" />
691
+ </template>
692
+ </q-input>
693
+
694
+
695
+
696
+
697
+ </template>
698
+
699
+
700
+ <!-- template v-for="col in toColumns" v-slot:[col.slot_name]="props">
701
+ <q-td :props="props">
702
+ <l-link :to="col.to(props.row)">
703
+ {{ col.field(props.row) }}
704
+ </l-link>
705
+ </q-td>
706
+ </template -->
707
+
708
+
709
+ <template #top-row="props" v-if="hasSearch && isServerSide">
710
+ <q-tr>
711
+ <q-td v-if="selection != 'none'" auto-width />
712
+ <q-td v-if="hasRowExpand" auto-width />
713
+ <q-td v-if="hasActions" auto-width />
714
+ <q-td v-for="col in props.cols">
715
+ <template v-if="col.searchable">
716
+
717
+ <template v-if="col.searchType == 'number'">
718
+ <q-input style="min-width: 80px;" dense clearable filled square
719
+ v-model.number="filters[col.name]" @keydown.enter.prevent="onFilters"
720
+ @clear="onFilters" mask="##########" :enterkeyhint="$t('search')"></q-input>
721
+ </template>
722
+
723
+ <template v-if="col.searchType == 'select'">
724
+ <q-select dense clearable filled square v-model="filters[col.name]"
725
+ @update:model-value="onFilters" options-dense :options="col.searchOptions"
726
+ emit-value map-options :multiple="col.searchMultiple" :color="$light.color" />
727
+
728
+ </template>
729
+
730
+
731
+ <template v-if="col.searchType == 'date'">
732
+ <l-date-picker dense clearable filled square :outlined="false" hide-bottom-space
733
+ v-model="filters[col.name]" @update:model-value="onFilters" range
734
+ @clear="onFilters" />
735
+ </template>
736
+
737
+ <template v-if="!col.searchType || col.searchType == 'text'">
738
+ <q-input style="min-width: 80px;" dense clearable filled square
739
+ v-model="filters[col.name]" @keydown.enter.prevent="onFilters" @clear="onFilters"
740
+ :enterkeyhint="$t('search')" :color="$light.color"></q-input>
741
+
742
+ </template>
743
+
744
+ </template>
745
+ </q-td>
746
+ </q-tr>
747
+ </template>
748
+
749
+ <template v-for="slot of ss" v-slot:[slot]="props">
750
+ <slot :name="slot" v-bind="props"></slot>
751
+ </template>
752
+ </q-table>
753
+ </l-card>
50
754
  </template>