@hostlink/nuxt-light 1.21.5 → 1.21.7

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.
Files changed (28) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/L/System/Setting/mail.vue +1 -7
  3. package/dist/runtime/components/{l-user-eventlog.vue → L/User/eventlog.vue} +2 -1
  4. package/dist/runtime/components/{l-user-overview.vue → L/User/overview.vue} +5 -1
  5. package/dist/runtime/components/l-app-main.vue +2 -1
  6. package/dist/runtime/components/l-btn.vue +7 -29
  7. package/dist/runtime/components/l-card.vue +3 -6
  8. package/dist/runtime/components/l-checkbox.vue +2 -18
  9. package/dist/runtime/components/l-date-picker.vue +5 -18
  10. package/dist/runtime/components/l-field.vue +1 -4
  11. package/dist/runtime/components/l-file-upload.vue +14 -35
  12. package/dist/runtime/components/l-file.vue +7 -9
  13. package/dist/runtime/components/l-icon-picker.vue +3253 -3272
  14. package/dist/runtime/components/l-input-xlsx.vue +16 -30
  15. package/dist/runtime/components/l-input.vue +6 -41
  16. package/dist/runtime/components/l-select.vue +3 -26
  17. package/dist/runtime/components/l-table.vue +13 -16
  18. package/dist/runtime/components/l-time-picker.vue +5 -32
  19. package/dist/runtime/formkit/File.vue +6 -12
  20. package/dist/runtime/light.d.ts +6 -10
  21. package/dist/runtime/light.js +61 -22
  22. package/dist/runtime/pages/System/menu/index.vue +61 -55
  23. package/dist/runtime/pages/User/setting/style.vue +88 -113
  24. package/dist/runtime/pages/User/setting/two-factor-auth.vue +4 -13
  25. package/package.json +1 -1
  26. package/dist/runtime/components/l-table-old.vue +0 -718
  27. package/dist/runtime/components/l-table2.vue +0 -754
  28. /package/dist/runtime/components/{l-user-userlog.vue → L/User/userlog.vue} +0 -0
@@ -1,754 +0,0 @@
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
- }
457
-
458
- const slots = useSlots();
459
-
460
- const ss = Object.entries(slots).map(([key, value]) => {
461
- return key;
462
- });
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('');
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;
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
- }
595
- </script>
596
-
597
-
598
- <template>
599
- <l-card flat bordered :maximizable="maximizable" :minimizable="minimizable" :title="title"
600
- v-model:minimized="minimized" v-model:maximized="maximized">
601
-
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>
605
-
606
-
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>
613
- </template>
614
-
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
-
634
-
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>
676
- </template>
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>
684
-
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>
754
- </template>