dashboard-shell-shell 1.0.1000000116 → 1.0.1000000117

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 (124) hide show
  1. package/assets/images/action.svg +6 -0
  2. package/assets/images/pl/logo.png +0 -0
  3. package/assets/styles/base/_functions.scss +0 -0
  4. package/assets/styles/base/_mixins.scss +1 -1
  5. package/assets/styles/global/_button.scss +17 -10
  6. package/assets/styles/global/_form.scss +2 -2
  7. package/assets/styles/global/_labeled-input.scss +6 -2
  8. package/assets/styles/global/_select.scss +6 -7
  9. package/assets/styles/global/_table.scss +3 -2
  10. package/assets/styles/global/_tooltip.scss +8 -1
  11. package/assets/styles/themes/_dark.scss +2 -0
  12. package/assets/styles/themes/_light.scss +5 -2
  13. package/assets/styles/vendor/vue-select.scss +2 -1
  14. package/assets/translations/en-us.yaml +1 -3
  15. package/assets/translations/zh-hans.yaml +51 -28
  16. package/components/ActionDropdown.vue +1 -0
  17. package/components/ActionMenuShell.vue +6 -3
  18. package/components/BrandImage.vue +22 -0
  19. package/components/ClusterIconMenu.vue +1 -1
  20. package/components/CodeMirror.vue +1 -0
  21. package/components/CruResource.vue +1 -1
  22. package/components/CruResourceFooter.vue +1 -1
  23. package/components/ExplorerProjectsNamespaces.vue +4 -24
  24. package/components/GlobalRoleBindings.vue +112 -48
  25. package/components/IndentedPanel.vue +4 -10
  26. package/components/PromptRemove.vue +3 -3
  27. package/components/ResourceDetail/Masthead.vue +190 -242
  28. package/components/ResourceDetail/index.vue +20 -5
  29. package/components/ResourceList/Masthead.vue +146 -84
  30. package/components/ResourceList/ResourceLoadingIndicator.vue +5 -2
  31. package/components/ResourceTable.vue +76 -1
  32. package/components/SideNav.vue +66 -29
  33. package/components/SortableTable/THead.vue +6 -0
  34. package/components/SortableTable/index.vue +481 -388
  35. package/components/Tabbed/index.vue +4 -5
  36. package/components/auth/Principal.vue +3 -2
  37. package/components/auth/RoleDetailEdit.vue +58 -5
  38. package/components/auth/SelectPrincipal.vue +1 -0
  39. package/components/form/BannerSettings.vue +18 -16
  40. package/components/form/ChangePassword.vue +4 -4
  41. package/components/form/ColorInput.vue +32 -8
  42. package/components/form/Footer.vue +1 -1
  43. package/components/form/InputWithSelect.vue +2 -0
  44. package/components/form/KeyValue.vue +31 -7
  45. package/components/form/LabeledSelect.vue +178 -178
  46. package/components/form/Members/ClusterPermissionsEditor.vue +1 -2
  47. package/components/form/Members/MembershipEditor.vue +1 -1
  48. package/components/form/NameNsDescription.vue +24 -11
  49. package/components/form/Password.vue +6 -2
  50. package/components/form/ResourceQuota/Namespace.vue +1 -1
  51. package/components/form/ResourceQuota/NamespaceRow.vue +13 -10
  52. package/components/form/ResourceQuota/ProjectRow.vue +0 -1
  53. package/components/form/Select.vue +2 -2
  54. package/components/nav/Favorite.vue +5 -1
  55. package/components/nav/Group.vue +69 -23
  56. package/components/nav/Header.vue +82 -17
  57. package/components/nav/HeaderPageActionMenu.vue +1 -0
  58. package/components/nav/NamespaceFilter.vue +0 -3
  59. package/components/nav/TopLevelMenu.vue +182 -119
  60. package/components/nav/Type.vue +48 -11
  61. package/composables/useClickOutside.ts +1 -1
  62. package/config/product/auth.js +16 -7
  63. package/config/product/explorer.js +1 -1
  64. package/config/product/settings.js +17 -8
  65. package/config/settings.ts +28 -0
  66. package/edit/management.cattle.io.user.vue +17 -4
  67. package/edit/networking.k8s.io.ingress/RulePath.vue +1 -1
  68. package/edit/token.vue +1 -1
  69. package/list/harvesterhci.io.management.cluster.vue +17 -0
  70. package/list/management.cattle.io.setting.vue +22 -13
  71. package/list/management.cattle.io.user.vue +25 -14
  72. package/list/provisioning.cattle.io.cluster.vue +6 -7
  73. package/mixins/brand.js +17 -0
  74. package/package.json +1 -1
  75. package/pages/auth/login.vue +84 -29
  76. package/pages/c/_cluster/auth/roles/index.vue +61 -14
  77. package/pages/c/_cluster/settings/banners.vue +174 -101
  78. package/pages/c/_cluster/settings/brand.vue +348 -301
  79. package/pages/c/_cluster/settings/performance.vue +61 -38
  80. package/pages/home.vue +70 -21
  81. package/pages/prefs.vue +25 -23
  82. package/pkg/tsconfig.json +9 -9
  83. package/pkg/vue.config.js +1 -1
  84. package/promptRemove/mixin/roleDeletionCheck.js +2 -2
  85. package/scripts/clean +0 -0
  86. package/scripts/extension/bundle +0 -0
  87. package/scripts/extension/helm/scripts/package +0 -0
  88. package/scripts/extension/helm/scripts/patch +0 -0
  89. package/scripts/extension/helm/scripts/version +0 -0
  90. package/scripts/extension/helmpatch +0 -0
  91. package/scripts/extension/parse-tag-name +0 -0
  92. package/scripts/extension/publish +0 -0
  93. package/scripts/publish-shell.sh +86 -60
  94. package/scripts/serve-pkgs +0 -0
  95. package/scripts/sync-shell-deps +0 -0
  96. package/scripts/typegen.sh +44 -28
  97. package/store/i18n.js +5 -5
  98. package/store/prefs.js +17 -5
  99. package/store/type-map.js +2 -1
  100. package/types/shell/index.d.ts +1 -1
  101. package/utils/error.js +4 -0
  102. package/utils/router.js +3 -3
  103. package/vue.config.js +1 -6
  104. package/components/rancherResourceDetail/Masthead.vue +0 -769
  105. package/components/rancherResourceDetail/__tests__/Masthead.test.ts +0 -65
  106. package/components/rancherResourceDetail/index.vue +0 -591
  107. package/components/rancherResourceList/Masthead.vue +0 -375
  108. package/components/rancherResourceList/ResourceLoadingIndicator.vue +0 -140
  109. package/components/rancherResourceList/index.vue +0 -307
  110. package/components/rancherResourceList/resource-list.config.js +0 -7
  111. package/components/rancherResourceTable.vue +0 -783
  112. package/components/rancherSortableTable/THead.vue +0 -561
  113. package/components/rancherSortableTable/actions.js +0 -153
  114. package/components/rancherSortableTable/advanced-filtering.js +0 -272
  115. package/components/rancherSortableTable/debug.js +0 -117
  116. package/components/rancherSortableTable/filtering.js +0 -290
  117. package/components/rancherSortableTable/grouping.js +0 -48
  118. package/components/rancherSortableTable/index.vue +0 -2712
  119. package/components/rancherSortableTable/paging.js +0 -155
  120. package/components/rancherSortableTable/selection.js +0 -629
  121. package/components/rancherSortableTable/sortable-config.ts +0 -4
  122. package/components/rancherSortableTable/sorting.js +0 -129
  123. package/types/cloud-shell/index.d.ts +0 -11014
  124. /package/components/{rancherResourceList → ResourceList}/Masthead-btn.vue +0 -0
@@ -1,2712 +0,0 @@
1
- <script>
2
- import { mapGetters, useStore } from 'vuex';
3
- import { defineAsyncComponent, ref, onMounted, onBeforeUnmount } from 'vue';
4
- import day from 'dayjs';
5
- import isEmpty from 'lodash/isEmpty';
6
- import { dasherize, ucFirst } from '@shell/utils/string';
7
- import { get, clone } from '@shell/utils/object';
8
- import { removeObject } from '@shell/utils/array';
9
- import { Checkbox } from '@components/Form/Checkbox';
10
- import AsyncButton, { ASYNC_BUTTON_STATES } from '@shell/components/AsyncButton';
11
- import Select from '@shell/components/form/Select';
12
- import ActionDropdown from '@shell/components/ActionDropdown';
13
- import throttle from 'lodash/throttle';
14
- import debounce from 'lodash/debounce';
15
- import THead from './THead';
16
- import filtering from './filtering';
17
- import selection from './selection';
18
- import sorting from './sorting';
19
- import paging from './paging';
20
- import grouping from './grouping';
21
- import actions from './actions';
22
- import AdvancedFiltering from './advanced-filtering';
23
- import LabeledSelect from '@shell/components/form/LabeledSelect';
24
- import { getParent } from '@shell/utils/dom';
25
- import { FORMATTERS } from '@shell/components/SortableTable/sortable-config';
26
- import ButtonMultiAction from '@shell/components/ButtonMultiAction.vue';
27
- import ActionMenu from '@shell/components/ActionMenuShell.vue';
28
- import { useRuntimeFlag } from '@shell/composables/useRuntimeFlag';
29
- import ActionDropdownShell from '@shell/components/ActionDropdownShell.vue';
30
- import { harvesterhci2cloud, cloud2harvesterhci } from '@shell/utils/router'
31
-
32
- // Uncomment for table performance debugging
33
- // import tableDebug from './debug';
34
-
35
- // @TODO:
36
- // Fixed header/scrolling
37
-
38
- // Data Flow:
39
- // rows prop
40
- // --> sorting.js arrangedRows
41
- // --> filtering.js handleFiltering()
42
- // --> filtering.js filteredRows
43
- // --> paging.js pageRows
44
- // --> grouping.js groupedRows
45
- // --> index.vue displayRows
46
-
47
- export default {
48
- name: 'SortableTable',
49
-
50
- emits: [
51
- 'clickedActionButton',
52
- 'pagination-changed',
53
- 'group-value-change',
54
- 'selection',
55
- 'rowClick',
56
- 'enter',
57
- ],
58
-
59
- components: {
60
- THead,
61
- Checkbox,
62
- AsyncButton,
63
- Select,
64
- ActionDropdown,
65
- LabeledSelect,
66
- ButtonMultiAction,
67
- ActionMenu,
68
- ActionDropdownShell,
69
- },
70
- mixins: [
71
- filtering,
72
- sorting,
73
- paging,
74
- grouping,
75
- selection,
76
- actions,
77
- AdvancedFiltering,
78
- // For table performance debugging - uncomment and uncomment the corresponding import
79
- // tableDebug,
80
- ],
81
-
82
- props: {
83
- headers: {
84
- // {
85
- // name: Name for the column (goes in query param) and for defaultSortBy
86
- // label: Displayed column header
87
- // sort: string|array[string] Field name(s) to sort by, default: [name, keyField]
88
- // fields can be suffixed with ':desc' to flip the normal sort order
89
- // search: string|array[string] Field name(s) to search in, default: [name]
90
- // width: number
91
- // }
92
- type: Array,
93
- required: true
94
- },
95
-
96
- rows: {
97
- // The array of objects to show
98
- type: Array,
99
- required: true
100
- },
101
-
102
- keyField: {
103
- // Field that is unique for each row.
104
- type: String,
105
- default: '_key',
106
- },
107
-
108
- loading: {
109
- type: Boolean,
110
- required: false
111
- },
112
-
113
- /**
114
- * Alt Loading - True: Always show table rows and obscure them when `loading`. Intended for use with server-side pagination.
115
- *
116
- * Alt Loading - False: Hide the table rows when `loading`. Intended when all resources are provided up front.
117
- */
118
- altLoading: {
119
- type: Boolean,
120
- required: false
121
- },
122
-
123
- groupBy: {
124
- // Field to group rows by, row[groupBy] must be something that can be a map key
125
- type: String,
126
- default: null
127
- },
128
- groupRef: {
129
- // Object to provide as the reference for rendering the grouping row
130
- type: String,
131
- default: null,
132
- },
133
- groupSort: {
134
- // Field to order groups by, defaults to groupBy
135
- type: Array,
136
- default: null
137
- },
138
-
139
- defaultSortBy: {
140
- // Default field to sort by if none is specified
141
- // uses name on headers
142
- type: String,
143
- default: null
144
- },
145
-
146
- tableActions: {
147
- // Show bulk table actions
148
- type: Boolean,
149
- default: true
150
- },
151
-
152
- rowActions: {
153
- // Show action dropdown on the end of each row
154
- type: Boolean,
155
- default: true
156
- },
157
-
158
- mangleActionResources: {
159
- type: Function,
160
- default: null,
161
- },
162
-
163
- rowActionsWidth: {
164
- // How wide the action dropdown column should be
165
- type: Number,
166
- default: 40
167
- },
168
-
169
- search: {
170
- // Show search input to filter rows
171
- type: Boolean,
172
- default: true
173
- },
174
-
175
- extraSearchFields: {
176
- // Additional fields that aren't defined in the headers to search in on each row
177
- type: Array,
178
- default: null
179
- },
180
-
181
- subRows: {
182
- // If there are sub-rows, your main row must have <tr class="main-row"> to identify it
183
- type: Boolean,
184
- default: false,
185
- },
186
-
187
- subRowsDescription: {
188
- type: Boolean,
189
- default: true,
190
- },
191
-
192
- subExpandable: {
193
- type: Boolean,
194
- default: false,
195
- },
196
-
197
- subExpandColumn: {
198
- type: Boolean,
199
- default: false,
200
- },
201
-
202
- subSearch: {
203
- // A field containing an array of sub-items to also search in for each row
204
- type: String,
205
- default: null,
206
- },
207
-
208
- subFields: {
209
- // Search this list of fields within the items in "subSearch" of each row
210
- type: Array,
211
- default: null,
212
- },
213
-
214
- /**
215
- * Show the divider between the thead and tbody.
216
- */
217
- topDivider: {
218
- type: Boolean,
219
- default: true
220
- },
221
-
222
- /**
223
- * Show the dividers between rows
224
- */
225
- bodyDividers: {
226
- type: Boolean,
227
- default: false
228
- },
229
-
230
- overflowX: {
231
- type: Boolean,
232
- default: false
233
- },
234
- overflowY: {
235
- type: Boolean,
236
- default: false
237
- },
238
-
239
- /**
240
- * If pagination of the data is enabled or not
241
- */
242
- paging: {
243
- type: Boolean,
244
- default: false,
245
- },
246
-
247
- /**
248
- * What translation key to use for displaying the '1 - 10 of 100 Things' pagination info
249
- */
250
- pagingLabel: {
251
- type: String,
252
- default: 'sortableTable.paging.generic'
253
- },
254
-
255
- /**
256
- * Additional params to pass to the pagingLabel translation
257
- */
258
- pagingParams: {
259
- type: Object,
260
- default: null,
261
- },
262
-
263
- /**
264
- * Allows you to override the default preference of the number of
265
- * items to display per page. This is used by ./paging.js if you're
266
- * looking for a reference.
267
- */
268
- rowsPerPage: {
269
- type: Number,
270
- default: null, // Default comes from the user preference
271
- },
272
-
273
- /**
274
- * Allows you to override the default translation text of no rows view
275
- */
276
- noRowsKey: {
277
- type: String,
278
- default: 'sortableTable.noRows'
279
- },
280
-
281
- /**
282
- * Allows you to hide the no rows messaging.
283
- */
284
- showNoRows: {
285
- type: Boolean,
286
- default: true
287
- },
288
-
289
- /**
290
- * Allows you to override the default translation text of no search data view
291
- */
292
- noDataKey: {
293
- type: String,
294
- default: 'sortableTable.noData' // i18n-uses sortableTable.noData
295
- },
296
-
297
- /**
298
- * Allows you to override showing the THEAD section.
299
- */
300
- showHeaders: {
301
- type: Boolean,
302
- default: true
303
- },
304
-
305
- /**
306
- * Provide a unique key that will provide a new value given changes to the environment that
307
- * should kick off an update to table rows (for instance resource list generation or change of namespace)
308
- *
309
- * This does not have to update given internal facets like sort order or direction
310
- */
311
- sortGenerationFn: {
312
- type: Function,
313
- default: null,
314
- },
315
-
316
- /**
317
- * Can be used in place of sortGenerationFn
318
- */
319
- sortGeneration: {
320
- type: String,
321
- default: null
322
- },
323
-
324
- /**
325
- * The list will always be sorted by these regardless of what the user has selected
326
- */
327
- mandatorySort: {
328
- type: Array,
329
- default: null,
330
- },
331
-
332
- /**
333
- * Allows you to link to a custom detail page for data that
334
- * doesn't have a class model. For example, a receiver configuration
335
- * block within an AlertmanagerConfig resource.
336
- */
337
- getCustomDetailLink: {
338
- type: Function,
339
- default: null
340
- },
341
-
342
- /**
343
- * Inherited global identifier prefix for tests
344
- * Define a term based on the parent component to avoid conflicts on multiple components
345
- */
346
- componentTestid: {
347
- type: String,
348
- default: 'sortable-table'
349
- },
350
- /**
351
- * Allows for the usage of a query param to work for simple filtering (q)
352
- */
353
- useQueryParamsForSimpleFiltering: {
354
- type: Boolean,
355
- default: false
356
- },
357
- /**
358
- * Manaul force the update of live and delayed cells. Change this number to kick off the update
359
- */
360
- forceUpdateLiveAndDelayed: {
361
- type: Number,
362
- default: 0
363
- },
364
-
365
- /**
366
- * True if pagination is executed outside of the component
367
- */
368
- externalPaginationEnabled: {
369
- type: Boolean,
370
- default: false
371
- },
372
-
373
- /**
374
- * If `externalPaginationEnabled` is true this will be used as the current page
375
- */
376
- externalPaginationResult: {
377
- type: Object,
378
- default: null
379
- },
380
-
381
- manualRefreshButtonSize: {
382
- type: String,
383
- default: ''
384
- },
385
- isBanner: {
386
- // Show isBanner input to filter rows
387
- type: Boolean,
388
- default: false
389
- },
390
- marginTopValue: {
391
- type: Number,
392
- default: 0
393
- },
394
- isFilterLabel: {
395
- type: Boolean,
396
- default: false
397
- },
398
- searchPlaceholder: {
399
- // search框内的输入提示
400
- type: String,
401
- default: ''
402
- },
403
-
404
- },
405
-
406
- data() {
407
- let searchQuery = '';
408
- let eventualSearchQuery = '';
409
-
410
- // only allow for filter query param for simple filtering for now...
411
- if (!this.hasAdvancedFiltering && this.useQueryParamsForSimpleFiltering && this.$route.query?.q) {
412
- searchQuery = this.$route.query?.q;
413
- eventualSearchQuery = this.$route.query?.q;
414
- }
415
-
416
- const isLoading = this.loading || false;
417
-
418
- let isCreatable = false;
419
- const lastPath = this.$route.path.split('/'.pop());
420
-
421
- if (lastPath.includes('.')) {
422
- isCreatable = this.$store.getters['type-map/optionsFor'](lastPath).isCreatable;
423
- } else if (lastPath === 'namespace') {
424
- isCreatable = this.$store.getters['type-map/optionsFor'](this.$route.name).isCreatable;
425
- }
426
-
427
- return {
428
- refreshButtonPhase: isLoading ? ASYNC_BUTTON_STATES.WAITING : ASYNC_BUTTON_STATES.ACTION,
429
- expanded: {},
430
- searchQuery,
431
- eventualSearchQuery,
432
- isCreatable,
433
- subMatches: null,
434
- actionOfInterest: null,
435
- loadingDelay: false,
436
- debouncedPaginationChanged: null,
437
- /**
438
- * The is the bool the DOM uses to show loading state. it's proxied from `loading` to avoid blipping the indicator (see usages)
439
- */
440
- isLoading,
441
- isMuchSelected: false,
442
- inputPerPage: '10',
443
- inputPage: '', // 输入的要跳至的页码
444
- perPageOptions: [
445
- {
446
- label: '10条/页',
447
- value: '10',
448
- },
449
- {
450
- label: '25条/页',
451
- value: '25',
452
- },
453
- {
454
- label: '50条/页',
455
- value: '50',
456
- },
457
- {
458
- label: '100条/页',
459
- value: '100',
460
- }
461
- ]
462
- };
463
- },
464
-
465
- mounted() {
466
- this._loadingDelayTimer = setTimeout(() => {
467
- this.loadingDelay = true;
468
- }, 200);
469
-
470
- // Add scroll listener to the main element
471
- const $main = document.querySelector('main');
472
-
473
- this._onScroll = this.onScroll.bind(this);
474
- $main?.addEventListener('scroll', this._onScroll);
475
-
476
- const tables = document.querySelectorAll('.sort-table-div');
477
-
478
- tables.forEach((table) => {
479
- this._onTableScroll = this.onTableScroll.bind(this, table);
480
- table.addEventListener('scroll', this._onTableScroll.bind(this, table));
481
- });
482
-
483
- this.debouncedPaginationChanged();
484
- },
485
-
486
- beforeUnmount() {
487
- clearTimeout(this._scrollTimer);
488
- clearTimeout(this._loadingDelayTimer);
489
- clearTimeout(this._altLoadingDelayTimer);
490
- clearTimeout(this._liveColumnsTimer);
491
- clearTimeout(this._delayedColumnsTimer);
492
- clearTimeout(this.manualRefreshTimer);
493
-
494
- const $main = document.querySelector('main');
495
-
496
- $main?.removeEventListener('scroll', this._onScroll);
497
- // 移除所有表格容器的滚动事件监听器
498
- const tables = document.querySelectorAll('.sort-table-div');
499
-
500
- tables.forEach((table) => {
501
- table.removeEventListener('scroll', this._onTableScroll);
502
- });
503
- },
504
-
505
- watch: {
506
- eventualSearchQuery: debounce(function(q) {
507
- this.searchQuery = q;
508
-
509
- if (!this.hasAdvancedFiltering && this.useQueryParamsForSimpleFiltering) {
510
- const route = {
511
- name: this.$route.name,
512
- params: { ...this.$route.params },
513
- query: { ...this.$route.query, q }
514
- };
515
-
516
- if (!q && this.$route.query?.q) {
517
- route.query = {};
518
- }
519
-
520
- this.$router.replace(route);
521
- }
522
- }, 200),
523
-
524
- descending(neu, old) {
525
- this.watcherUpdateLiveAndDelayed(neu, old);
526
- },
527
-
528
- searchQuery(neu, old) {
529
- this.watcherUpdateLiveAndDelayed(neu, old);
530
- },
531
-
532
- sortFields(neu, old) {
533
- this.watcherUpdateLiveAndDelayed(neu, old);
534
- },
535
-
536
- groupBy(neu, old) {
537
- this.watcherUpdateLiveAndDelayed(neu, old);
538
- },
539
-
540
- namespaces(neu, old) {
541
- this.watcherUpdateLiveAndDelayed(neu, old);
542
- },
543
-
544
- page(neu, old) {
545
- this.watcherUpdateLiveAndDelayed(neu, old);
546
- },
547
-
548
- forceUpdateLiveAndDelayed(neu, old) {
549
- this.watcherUpdateLiveAndDelayed(neu, old);
550
- },
551
-
552
- selectedRowsText(neu, old) {
553
- if (neu) {
554
- this.isMuchSelected = true;
555
- } else {
556
- this.isMuchSelected = false;
557
- }
558
- },
559
-
560
- // Ensure we update live and delayed columns on first load
561
- initalLoad: {
562
- handler(neu) {
563
- if (neu) {
564
- this._didinit = true;
565
- this.$nextTick(() => this.updateLiveAndDelayed());
566
- }
567
- },
568
- immediate: true
569
- },
570
-
571
- // this is the flag that indicates that manual refresh data has been loaded
572
- // and we should update the deferred cols
573
- manualRefreshLoadingFinished: {
574
- handler(neu, old) {
575
- // this is merely to update the manual refresh button status
576
- this.refreshButtonPhase = !neu ? ASYNC_BUTTON_STATES.WAITING : ASYNC_BUTTON_STATES.ACTION;
577
- if (neu && neu !== old) {
578
- this.$nextTick(() => this.updateLiveAndDelayed());
579
- }
580
- },
581
- immediate: true
582
- },
583
-
584
- loading: {
585
- handler(neu, old) {
586
- // Always ensure the Refresh button phase aligns with loading state (to ensure external phase changes which can then reset the internal phase changed by click)
587
- this.refreshButtonPhase = neu ? ASYNC_BUTTON_STATES.WAITING : ASYNC_BUTTON_STATES.ACTION;
588
-
589
- if (this.altLoading) {
590
- // Delay setting the actual loading indicator. This should avoid flashing up the indicator if the API responds quickly
591
- if (neu) {
592
- this._altLoadingDelayTimer = setTimeout(() => {
593
- this.isLoading = true;
594
- }, 200); // this should be higher than the targeted quick response
595
- } else {
596
- clearTimeout(this._altLoadingDelayTimer);
597
- this.isLoading = false;
598
- }
599
- } else {
600
- this.isLoading = neu;
601
- }
602
- },
603
- immediate: true
604
- },
605
- },
606
- setup(_props, { emit }) {
607
- const table = ref(null);
608
-
609
- const handleEnterKey = (event) => {
610
- if (event.key === 'Enter' && !event.target?.classList?.contains('checkbox-custom')) {
611
- emit('enter', event);
612
- }
613
- };
614
-
615
-
616
- onBeforeUnmount(() => {
617
- table.value.removeEventListener('keyup', handleEnterKey);
618
- });
619
-
620
- const store = useStore();
621
- const { featureDropdownMenu } = useRuntimeFlag(store);
622
-
623
- onMounted(() => {
624
- table.value.addEventListener('keyup', handleEnterKey);
625
-
626
- console.log(featureDropdownMenu, ' featureDropdownMenu---------------------');
627
-
628
-
629
- });
630
-
631
- return {
632
- table,
633
- featureDropdownMenu,
634
- };
635
- },
636
-
637
- created() {
638
- this.debouncedRefreshTableData = debounce(this.refreshTableData, 500);
639
- this.debouncedPaginationChanged = debounce(this.paginationChanged, 50);
640
- },
641
-
642
- computed: {
643
- ...mapGetters({ isTooManyItemsToAutoUpdate: 'resource-fetch/isTooManyItemsToAutoUpdate' }),
644
- ...mapGetters({ isManualRefreshLoading: 'resource-fetch/manualRefreshIsLoading' }),
645
- namespaces() {
646
- return this.$store.getters['activeNamespaceCache'];
647
- },
648
-
649
- initalLoad() {
650
- return !!(!this.isLoading && !this._didinit && this.rows?.length);
651
- },
652
-
653
- manualRefreshLoadingFinished() {
654
- const res = !!(!this.isLoading && this._didinit && this.rows?.length && !this.isManualRefreshLoading);
655
-
656
- return res;
657
- },
658
-
659
- fullColspan() {
660
- let span = 0;
661
-
662
- for ( let i = 0 ; i < this.columns.length ; i++ ) {
663
- if (!this.columns[i].hide) {
664
- span++;
665
- }
666
- }
667
-
668
- if ( this.tableActions ) {
669
- span++;
670
- }
671
-
672
- if ( this.subExpandColumn ) {
673
- span++;
674
- }
675
-
676
- if ( this.rowActions ) {
677
- span++;
678
- }
679
-
680
- return span;
681
- },
682
-
683
- noResults() {
684
- return !!this.searchQuery && this.pagedRows.length === 0;
685
- },
686
-
687
- noRows() {
688
- return !this.noResults && (this.rows || []).length === 0;
689
- },
690
-
691
- showHeaderRow() {
692
- // All of these are used to show content in the header
693
- return this.search ||
694
- this.tableActions ||
695
- this.$slots['header-left'] ||
696
- this.$slots['header-middle'] ||
697
- this.$slots['header-right'] ||
698
- this.isTooManyItemsToAutoUpdate;
699
- },
700
-
701
- columns() {
702
- // Filter out any columns that are too heavy to show for large page sizes
703
- const out = this.headers.slice().filter((c) => !c.maxPageSize || (c.maxPageSize && c.maxPageSize >= this.perPage));
704
-
705
- if ( this.groupBy ) {
706
- const entry = out.find((x) => x.name === this.groupBy);
707
-
708
- if ( entry ) {
709
- removeObject(out, entry);
710
- }
711
- }
712
-
713
- // If all columns have a width, try to remove it from a column that can be variable (name)
714
- const missingWidth = out.find((x) => !x.width);
715
-
716
- if ( !missingWidth ) {
717
- const variable = out.find((x) => x.canBeVariable);
718
-
719
- if ( variable ) {
720
- const neu = clone(variable);
721
-
722
- delete neu.width;
723
-
724
- out.splice(out.indexOf(variable), 1, neu);
725
- }
726
- }
727
-
728
- // handle cols visibility and filtering if there is advanced filtering
729
- if (this.hasAdvancedFiltering) {
730
- const cols = this.handleColsVisibilyAndFiltering(out);
731
-
732
- return cols;
733
- }
734
-
735
- return out;
736
- },
737
-
738
- // For data-title properties on <td>s
739
- dt() {
740
- const out = {
741
- check: `Select: `,
742
- actions: `Actions: `,
743
- };
744
-
745
- this.columns.forEach((col) => {
746
- out[col.name] = `${ (col.label || col.name) }:`;
747
- });
748
-
749
- return out;
750
- },
751
-
752
- classObject() {
753
- return {
754
- 'top-divider': this.topDivider,
755
- 'body-dividers': this.bodyDividers,
756
- 'overflow-y': this.overflowY,
757
- 'overflow-x': this.overflowX,
758
- 'alt-loading': this.altLoading && this.isLoading
759
- };
760
- },
761
-
762
- // Do we have any live columns?
763
- hasLiveColumns() {
764
- const liveColumns = this.columns.find((c) => c.formatter?.startsWith('Live') || c.liveUpdates);
765
-
766
- return !!liveColumns;
767
- },
768
-
769
- hasDelayedColumns() {
770
- const delaeydColumns = this.columns.find((c) => c.delayLoading);
771
-
772
- return !!delaeydColumns;
773
- },
774
-
775
- columnFormmatterIDs() {
776
- const columnsIds = {};
777
-
778
- this.columns.forEach((c) => {
779
- if (c.formatter) {
780
- columnsIds[c.formatter] = dasherize(c.formatter);
781
- }
782
- });
783
-
784
- return columnsIds;
785
- },
786
-
787
- // Generate row and column data for easier rendering in the template
788
- // ensures we only call methods like `valueFor` once
789
- displayRows() {
790
- const rows = [];
791
- const columnFormmatterIDs = this.columnFormmatterIDs;
792
-
793
- this.groupedRows.forEach((grp) => {
794
- const group = {
795
- grp,
796
- key: grp.key,
797
- ref: grp.ref,
798
- rows: [],
799
- };
800
-
801
- rows.push(group);
802
-
803
- grp.rows.forEach((row) => {
804
- const rowData = {
805
- row,
806
- key: this.get(row, this.keyField),
807
- showSubRow: this.showSubRow(row, this.keyField),
808
- canRunBulkActionOfInterest: this.canRunBulkActionOfInterest(row),
809
- columns: []
810
- };
811
-
812
- group.rows.push(rowData);
813
-
814
- this.columns.forEach((c) => {
815
- const value = c.delayLoading ? undefined : this.valueFor(row, c, c.isLabel);
816
- let component;
817
- let formatted = value;
818
- let needRef = false;
819
-
820
- if (Array.isArray(value)) {
821
- formatted = value.join(', ');
822
- }
823
-
824
- if (c.formatter) {
825
- if (FORMATTERS[c.formatter]) {
826
- component = FORMATTERS[c.formatter];
827
- needRef = true;
828
- } else {
829
- // Check if we have a formatter from a plugin
830
- const pluginFormatter = this.$plugin?.getDynamic('formatters', c.formatter);
831
-
832
- if (pluginFormatter) {
833
- component = defineAsyncComponent(pluginFormatter);
834
- needRef = true;
835
- }
836
- }
837
- }
838
-
839
- rowData.columns.push({
840
- col: c,
841
- value,
842
- formatted,
843
- component,
844
- needRef,
845
- delayed: c.delayLoading,
846
- live: c.formatter?.startsWith('Live') || c.liveUpdates,
847
- label: this.labelFor(c),
848
- dasherize: columnFormmatterIDs[c.formatter] || '',
849
- });
850
- });
851
- });
852
- });
853
-
854
- return rows;
855
- },
856
- },
857
-
858
- methods: {
859
- // getTableList () {
860
- // const q = this.eventualSearchQuery;
861
-
862
- // this.searchQuery = q;
863
-
864
- // if (!this.hasAdvancedFiltering && this.useQueryParamsForSimpleFiltering) {
865
- // const route = {
866
- // name: this.$route.name,
867
- // params: { ...this.$route.params },
868
- // query: { ...this.$route.query, q }
869
- // };
870
-
871
- // if (!q && this.$route.query?.q) {
872
- // route.query = {};
873
- // }
874
-
875
- // this.$router.replace(route);
876
- // }
877
- // },
878
- onTableScroll(table, e) {
879
- // 记录最后滚动的距离
880
- let lastScrollTop = 0;
881
- let lastScrollLeft = 0;
882
-
883
- // 监听容器滚动
884
- table.addEventListener('scroll', (e) => {
885
- // 如果当前垂直滚动距离不等于上次
886
- if (e.target.scrollTop !== lastScrollTop) {
887
- // 更新最后距离
888
- lastScrollTop = e.target.scrollTop;
889
-
890
- // 移除左右阴影样式
891
- table.classList.remove('shadow-left');
892
- table.classList.remove('shadow-right');
893
-
894
- // 如果滚动到顶部,移除顶部阴影样式
895
- // if (lastScrollTop === 0) {
896
- // table.classList.remove('shadow-top');
897
- // }
898
- // // 否则添加顶部阴影样式
899
- // else {
900
- // table.classList.add('shadow-top');
901
- // }
902
-
903
- // 如果滚动到底部,移除底部阴影样式
904
- // if (lastScrollTop + table.clientHeight === table.scrollHeight) {
905
- // table.classList.remove('shadow-bottom');
906
- // }
907
- // // 否则添加底部阴影样式
908
- // else {
909
- // table.classList.add('shadow-bottom');
910
- // }
911
-
912
- // 阴影效果修正
913
- table.classList.remove('shadow-x');
914
- table.classList.add('shadow-y');
915
- }
916
-
917
- // 如果当前水平滚动距离不等于上次
918
- else if (e.target.scrollLeft !== lastScrollLeft) {
919
- // 更新最后距离
920
- lastScrollLeft = e.target.scrollLeft;
921
-
922
- // 移除上下阴影样式
923
- // table.classList.remove('shadow-top');
924
- // table.classList.remove('shadow-bottom');
925
-
926
- // 如果滚动到左边,移除左边阴影样式
927
- if (lastScrollLeft === 0) {
928
- table.classList.remove('shadow-left');
929
- }
930
- // 否则添加左边阴影样式
931
- else {
932
- table.classList.add('shadow-left');
933
- }
934
-
935
- // 如果滚动到右边,移除右边阴影样式
936
- if (lastScrollLeft + table.clientWidth === table.scrollWidth) {
937
- table.classList.remove('shadow-right');
938
- }
939
- // 否则添加右边阴影样式
940
- else {
941
- table.classList.add('shadow-right');
942
- }
943
-
944
- // 阴影效果修正
945
- table.classList.remove('shadow-y');
946
- table.classList.add('shadow-x');
947
- }
948
- });
949
- },
950
- changePerPage(value) {
951
- this.inputPerPage = value;
952
- this.setgetPerPage(value);
953
- },
954
- handleToPage() {
955
- this.setPage(parseInt(this.inputPage, 10));
956
- },
957
- refreshTableData() {
958
- this.$store.dispatch('resource-fetch/doManualRefresh');
959
- },
960
- get,
961
- dasherize,
962
- muchSelect(value) {
963
- console.log(value);
964
-
965
- this.isMuchSelected = !this.isMuchSelected;
966
- this.onToggleAll(value);
967
- },
968
-
969
- onScroll() {
970
- if (this.hasLiveColumns || this.hasDelayedColumns) {
971
- clearTimeout(this._liveColumnsTimer);
972
- clearTimeout(this._scrollTimer);
973
- clearTimeout(this._delayedColumnsTimer);
974
- this._scrollTimer = setTimeout(() => {
975
- this.updateLiveColumns();
976
- this.updateDelayedColumns();
977
- }, 300);
978
- }
979
- },
980
-
981
- watcherUpdateLiveAndDelayed(neu, old) {
982
- if (neu !== old) {
983
- this.$nextTick(() => this.updateLiveAndDelayed());
984
- }
985
- },
986
-
987
- updateLiveAndDelayed() {
988
- if (this.hasLiveColumns) {
989
- this.updateLiveColumns();
990
- }
991
-
992
- if (this.hasDelayedColumns) {
993
- this.updateDelayedColumns();
994
- }
995
- },
996
-
997
- updateDelayedColumns() {
998
- clearTimeout(this._delayedColumnsTimer);
999
-
1000
- if (!this.$refs.column || this.pagedRows.length === 0) {
1001
- return;
1002
- }
1003
-
1004
- const delayedColumns = this.$refs.column.filter((c) => c.startDelayedLoading && !c.__delayedLoading);
1005
- // We add 100 pixels here - so we will render the delayed columns for a few extra rows below what is visible
1006
- // This way if you scroll slowly, you won't see the columns being loaded
1007
- const clientHeight = (window.innerHeight || document.documentElement.clientHeight) + 100;
1008
-
1009
- let scheduled = 0;
1010
-
1011
- for (let i = 0; i < delayedColumns.length; i++) {
1012
- const dc = delayedColumns[i];
1013
- const y = dc.$el.getBoundingClientRect().y;
1014
-
1015
- if (y >= 0 && y <= clientHeight) {
1016
- dc.startDelayedLoading(true);
1017
- dc.__delayedLoading = true;
1018
-
1019
- scheduled++;
1020
-
1021
- // Only update 4 at a time
1022
- if (scheduled === 4) {
1023
- this._delayedColumnsTimer = setTimeout(this.updateDelayedColumns, 100);
1024
-
1025
- return;
1026
- }
1027
- }
1028
- }
1029
- },
1030
-
1031
- updateLiveColumns() {
1032
- clearTimeout(this._liveColumnsTimer);
1033
-
1034
- if (!this.$refs.column || !this.hasLiveColumns || this.pagedRows.length === 0) {
1035
- return;
1036
- }
1037
-
1038
- const clientHeight = window.innerHeight || document.documentElement.clientHeight;
1039
- const liveColumns = this.$refs.column.filter((c) => !!c.liveUpdate);
1040
- const now = day();
1041
- let next = Number.MAX_SAFE_INTEGER;
1042
-
1043
- for (let i = 0; i < liveColumns.length; i++) {
1044
- const column = liveColumns[i];
1045
- const y = column.$el.getBoundingClientRect().y;
1046
-
1047
- if (y >= 0 && y <= clientHeight) {
1048
- const diff = column.liveUpdate(now);
1049
-
1050
- if (diff < next) {
1051
- next = diff;
1052
- }
1053
- }
1054
- }
1055
-
1056
- if (next < 1 ) {
1057
- next = 1;
1058
- }
1059
-
1060
- // Schedule again
1061
- this._liveColumnsTimer = setTimeout(() => this.updateLiveColumns(), next * 1000);
1062
- },
1063
-
1064
- labelFor(col) {
1065
- if ( col.labelKey ) {
1066
- return this.t(col.labelKey, undefined, true);
1067
- } else if ( col.label ) {
1068
- return col.label;
1069
- }
1070
-
1071
- return ucFirst(col.name);
1072
- },
1073
-
1074
- valueFor(row, col, isLabel) {
1075
- if (typeof col.value === 'function') {
1076
- return col.value(row);
1077
- }
1078
-
1079
- if (isLabel) {
1080
- if (row.metadata?.labels && row.metadata?.labels[col.label]) {
1081
- return row.metadata?.labels[col.label];
1082
- }
1083
-
1084
- return '';
1085
- }
1086
-
1087
- // Use to debug table columns using expensive value getters
1088
- // console.warn(`Performance: Table valueFor: ${ col.name } ${ col.value }`); // eslint-disable-line no-console
1089
-
1090
- const expr = col.value || col.name;
1091
-
1092
- if (!expr) {
1093
- console.error('No path has been defined for this column, unable to get value of cell', col); // eslint-disable-line no-console
1094
-
1095
- return '';
1096
- }
1097
- const out = get(row, expr);
1098
-
1099
- if ( out === null || out === undefined ) {
1100
- return '';
1101
- }
1102
-
1103
- return out;
1104
- },
1105
-
1106
- isExpanded(row) {
1107
- const key = row[this.keyField];
1108
-
1109
- return !!this.expanded[key];
1110
- },
1111
-
1112
- toggleExpand(row) {
1113
- const key = row[this.keyField];
1114
- const val = !this.expanded[key];
1115
-
1116
- this.expanded[key] = val;
1117
- this.expanded = { ...this.expanded };
1118
-
1119
- return val;
1120
- },
1121
-
1122
- setBulkActionOfInterest(action) {
1123
- this.actionOfInterest = action;
1124
- },
1125
-
1126
- // Can the action of interest be applied to the specified resource?
1127
- canRunBulkActionOfInterest(resource) {
1128
- if ( !this.actionOfInterest || isEmpty(resource?.availableActions) ) {
1129
- return false;
1130
- }
1131
-
1132
- const matchingResourceAction = resource.availableActions?.find((a) => a.action === this.actionOfInterest.action);
1133
-
1134
- return matchingResourceAction?.enabled;
1135
- },
1136
-
1137
- focusSearch() {
1138
- if ( this.$refs.searchQuery ) {
1139
- this.$refs.searchQuery.focus();
1140
- this.$refs.searchQuery.select();
1141
- }
1142
- },
1143
-
1144
- nearestCheckbox() {
1145
- return document.activeElement.closest('tr.main-row')?.querySelector('.checkbox-custom');
1146
- },
1147
-
1148
- focusAdjacent(next = true) {
1149
- const all = Array.from(this.$el.querySelectorAll('.checkbox-custom'));
1150
-
1151
- const cur = this.nearestCheckbox();
1152
- let idx = -1;
1153
-
1154
- if ( cur ) {
1155
- idx = all.indexOf(cur) + (next ? 1 : -1 );
1156
- } else if ( next ) {
1157
- idx = 1;
1158
- } else {
1159
- idx = all.length - 1;
1160
- }
1161
-
1162
- if ( idx < 1 ) { // Don't go up to the check all button
1163
- idx = 1;
1164
-
1165
- return null;
1166
- }
1167
-
1168
- if ( idx >= all.length ) {
1169
- idx = all.length - 1;
1170
-
1171
- return null;
1172
- }
1173
-
1174
- if ( all[idx] ) {
1175
- all[idx].focus();
1176
-
1177
- return all[idx];
1178
- }
1179
- },
1180
-
1181
- focusNext: throttle(function(event, more = false) {
1182
- const elem = this.focusAdjacent(true);
1183
- const row = getParent(elem, 'tr');
1184
-
1185
- if (row?.classList.contains('row-selected')) {
1186
- return;
1187
- }
1188
-
1189
- this.keySelectRow(row, more);
1190
- }, 50),
1191
-
1192
- focusPrevious: throttle(function(event, more = false) {
1193
- const elem = this.focusAdjacent(false);
1194
- const row = getParent(elem, 'tr');
1195
-
1196
- if (row?.classList.contains('row-selected')) {
1197
- return;
1198
- }
1199
-
1200
- this.keySelectRow(row, more);
1201
- }, 50),
1202
-
1203
- showSubRow(row, keyField) {
1204
- const hasInjectedSubRows = this.subRows && (!this.subExpandable || this.expanded[get(row, keyField)]);
1205
- const hasStateDescription = this.subRowsDescription && row.stateDescription;
1206
-
1207
- return hasInjectedSubRows || hasStateDescription;
1208
- },
1209
-
1210
- handleActionButtonClick(i, event) {
1211
- // Each row in the table gets its own ref with
1212
- // a number based on its index. If you are using
1213
- // an ActionMenu that doen't have a dependency on Vuex,
1214
- // these refs are useful because you can reuse the
1215
- // same ActionMenu component on a page with many different
1216
- // target elements in a list,
1217
- // so you can still avoid the performance problems that
1218
- // could result if the ActionMenu was in every row. The menu
1219
- // will open on whichever target element is clicked.
1220
- this.$emit('clickedActionButton', {
1221
- event,
1222
- targetElement: this.$refs[`actionButton${ i }`][0],
1223
- });
1224
- },
1225
-
1226
- paginationChanged() {
1227
- if (!this.externalPaginationEnabled) {
1228
- return;
1229
- }
1230
-
1231
- this.$emit('pagination-changed', {
1232
- page: this.page,
1233
- perPage: this.perPage,
1234
- filter: {
1235
- searchFields: this.searchFields,
1236
- searchQuery: this.searchQuery
1237
- },
1238
- sort: this.sortFields,
1239
- descending: this.descending
1240
- });
1241
- }
1242
- }
1243
- };
1244
- </script>
1245
-
1246
- <template>
1247
- <div
1248
- ref="container"
1249
- :data-testid="componentTestid + '-list-container'"
1250
- >
1251
- <!-- 主标题和过滤器区域 -->
1252
- <div
1253
- :class="{'titled': $slots.title && $slots.title.length, 'mb-40': isFilterLabel}"
1254
- class="sortable-table-header mb-20"
1255
- >
1256
- <slot name="title" />
1257
-
1258
- <!-- 顶部功能行区域 -->
1259
- <div
1260
- v-if="showHeaderRow"
1261
- class="fixed-header-table-actions"
1262
- :class="{button: !!$slots['header-button'], 'advanced-filtering': hasAdvancedFiltering, }"
1263
- style="display: flex;"
1264
- >
1265
-
1266
- <!-- 搜索栏、右插槽、刷新按钮、高级筛选等区域 -->
1267
- <div
1268
- v-if="search || hasAdvancedFiltering || isTooManyItemsToAutoUpdate || $slots['header-right']"
1269
- class="search row"
1270
- data-testid="search-box-filter-row"
1271
- style="max-height: 32px;"
1272
- >
1273
-
1274
- <!-- 已应用的高级筛选 -->
1275
- <ul
1276
- v-if="hasAdvancedFiltering"
1277
- class="advanced-filters-applied"
1278
- >
1279
- <li
1280
- v-for="(filter, i) in advancedFilteringValues"
1281
- :key="i"
1282
- >
1283
- <span class="label">{{ `"${filter.value}" ${ t('sortableTable.in') } ${filter.label}` }}</span>
1284
- <span
1285
- class="cross"
1286
- @click="clearAdvancedFilter(i)"
1287
- >&#10005;</span>
1288
- <div class="bg" />
1289
- </li>
1290
- </ul>
1291
-
1292
- <slot name="header-right" />
1293
-
1294
- <div
1295
- v-if="hasAdvancedFiltering"
1296
- ref="advanced-filter-group"
1297
- class="advanced-filter-group"
1298
- >
1299
- <button
1300
- class="btn role-primary"
1301
- @click="advancedFilteringVisibility = !advancedFilteringVisibility;"
1302
- >
1303
- {{ t('sortableTable.addFilter') }}
1304
- </button>
1305
- <div
1306
- v-show="advancedFilteringVisibility"
1307
- class="advanced-filter-container"
1308
- >
1309
- <input
1310
- ref="advancedSearchQuery"
1311
- v-model="advFilterSearchTerm"
1312
- type="search"
1313
- class="advanced-search-box"
1314
- :placeholder="t('sortableTable.filterFor')"
1315
- >
1316
- <div class="middle-block">
1317
- <span>{{ t('sortableTable.in') }}</span>
1318
- <LabeledSelect
1319
- v-model:value="advFilterSelectedProp"
1320
- class="filter-select"
1321
- :clearable="true"
1322
- :options="advFilterSelectOptions"
1323
- :disabled="false"
1324
- :searchable="false"
1325
- mode="edit"
1326
- :multiple="false"
1327
- :taggable="false"
1328
- :placeholder="t('sortableTable.selectCol')"
1329
- @selecting="(col) => advFilterSelectedLabel = col.label"
1330
- />
1331
- </div>
1332
- <div class="bottom-block">
1333
- <button
1334
- class="btn role-secondary"
1335
- :disabled="!advancedFilteringValues.length"
1336
- @click="clearAllAdvancedFilters"
1337
- >
1338
- {{ t('sortableTable.resetFilters') }}
1339
- </button>
1340
- <button
1341
- class="btn role-primary"
1342
- @click="addAdvancedFilter"
1343
- >
1344
- {{ t('sortableTable.add') }}
1345
- </button>
1346
- </div>
1347
- </div>
1348
- </div>
1349
-
1350
- <!-- 搜索描述文本(隐藏) -->
1351
- <p
1352
- v-else-if="search"
1353
- id="describe-filter-sortable-table"
1354
- hidden
1355
- >
1356
- {{ t('sortableTable.filteringDescription') }}
1357
- </p>
1358
- <slot name="header-button" />
1359
-
1360
- <div style="display: flex;">
1361
-
1362
- <slot name="search-main-button" />
1363
-
1364
- <!-- 搜索输入框 -->
1365
- <input
1366
- v-if="search"
1367
- ref="searchQuery"
1368
- v-model="eventualSearchQuery"
1369
- type="search"
1370
- class="input-sm search-box"
1371
- :aria-label="t('sortableTable.searchLabel')"
1372
- aria-describedby="describe-filter-sortable-table"
1373
- :placeholder="t('sortableTable.search')+searchPlaceholder"
1374
- >
1375
- <!-- <button v-if="search" @click="getTableList(eventualSearchQuery)" calss="search-btn role-secondary">
1376
- 检索
1377
- </button> -->
1378
-
1379
- <!-- 手动刷新按钮 -->
1380
- <AsyncButton
1381
- v-if="isTooManyItemsToAutoUpdate"
1382
- mode="manual-refresh"
1383
- :size="manualRefreshButtonSize"
1384
- :current-phase="refreshButtonPhase"
1385
- @click="debouncedRefreshTableData"
1386
- />
1387
-
1388
- </div>
1389
-
1390
- </div>
1391
-
1392
- <!-- 中间区域插槽 -->
1393
- <div
1394
- v-if="!hasAdvancedFiltering && $slots['header-middle']"
1395
- class="middle"
1396
- style="margin-left: 10px;"
1397
- >
1398
- <slot name="header-middle" />
1399
- </div>
1400
- </div>
1401
- </div>
1402
-
1403
- <div v-if="$slots['banner']">
1404
- <slot name="banner"></slot>
1405
- </div>
1406
-
1407
- <div class="sort-table-div">
1408
- <table
1409
- ref="table"
1410
- class="sortable-table"
1411
- :class="classObject"
1412
- width="100%"
1413
- role="table"
1414
- >
1415
- <THead
1416
- v-if="showHeaders"
1417
- :label-for="labelFor"
1418
- :columns="columns"
1419
- :group="group"
1420
- :group-options="advGroupOptions"
1421
- :has-advanced-filtering="hasAdvancedFiltering"
1422
- :adv-filter-hide-labels-as-cols="advFilterHideLabelsAsCols"
1423
- :table-actions="tableActions"
1424
- :table-cols-options="columnOptions"
1425
- :row-actions="rowActions"
1426
- :sub-expand-column="subExpandColumn"
1427
- :row-actions-width="rowActionsWidth"
1428
- :how-much-selected="howMuchSelected"
1429
- :sort-by="sortBy"
1430
- :default-sort-by="_defaultSortBy"
1431
- :descending="descending"
1432
- :no-rows="noRows"
1433
- :loading="isLoading && !loadingDelay"
1434
- :no-results="noResults"
1435
- @on-toggle-all="onToggleAll"
1436
- @on-sort-change="changeSort"
1437
- @col-visibility-change="changeColVisibility"
1438
- @group-value-change="(val) => $emit('group-value-change', val)"
1439
- @update-cols-options="updateColsOptions"
1440
- />
1441
-
1442
- <!-- Don't display anything if we're loading and the delay has yet to pass -->
1443
- <div v-if="isLoading && !loadingDelay" />
1444
-
1445
- <tbody v-else-if="isLoading && !altLoading">
1446
- <slot name="loading">
1447
- <tr>
1448
- <td :colspan="fullColspan">
1449
- <div class="data-loading">
1450
- <i class="icon-spin icon icon-spinner" />
1451
- <t
1452
- k="generic.loading"
1453
- :raw="true"
1454
- />
1455
- </div>
1456
- </td>
1457
- </tr>
1458
- </slot>
1459
- </tbody>
1460
- <tbody v-else-if="noRows">
1461
- <slot name="no-rows">
1462
- <tr class="no-rows">
1463
- <td :colspan="fullColspan">
1464
- <t
1465
- v-if="showNoRows"
1466
- :k="noRowsKey"
1467
- />
1468
- </td>
1469
- </tr>
1470
- </slot>
1471
- </tbody>
1472
- <tbody v-else-if="noResults">
1473
- <slot name="no-results">
1474
- <tr class="no-results">
1475
- <td
1476
- :colspan="fullColspan"
1477
- class="text-center"
1478
- >
1479
- <t :k="noDataKey" />
1480
- </td>
1481
- </tr>
1482
- </slot>
1483
- </tbody>
1484
- <tbody
1485
- v-for="(groupedRows) in displayRows"
1486
- v-else
1487
- :key="groupedRows.key"
1488
- tabindex="-1"
1489
- :class="{ group: groupBy }"
1490
- >
1491
- <slot
1492
- v-if="groupBy"
1493
- name="group-row"
1494
- :group="groupedRows"
1495
- :fullColspan="fullColspan"
1496
- >
1497
- <tr class="group-row">
1498
- <td :colspan="fullColspan">
1499
- <slot
1500
- name="group-by"
1501
- :group="groupedRows.grp"
1502
- >
1503
- <div
1504
- v-trim-whitespace
1505
- class="group-tab"
1506
- >
1507
- {{ groupedRows.ref }}
1508
- </div>
1509
- </slot>
1510
- </td>
1511
- </tr>
1512
- </slot>
1513
- <template
1514
- v-for="(row, i) in groupedRows.rows"
1515
- :key="i"
1516
- >
1517
- <slot
1518
- name="main-row"
1519
- :row="row.row"
1520
- >
1521
- <slot
1522
- :name="'main-row:' + (row.row.mainRowKey || i)"
1523
- :full-colspan="fullColspan"
1524
- >
1525
- <!-- The data-cant-run-bulk-action-of-interest attribute is being used instead of :class because
1526
- because our selection.js invokes toggleClass and :class clobbers what was added by toggleClass if
1527
- the value of :class changes. -->
1528
- <tr
1529
- class="main-row"
1530
- :data-testid="componentTestid + '-' + i + '-row'"
1531
- :class="{ 'has-sub-row': row.showSubRow}"
1532
- :data-node-id="row.key"
1533
- :data-cant-run-bulk-action-of-interest="actionOfInterest && !row.canRunBulkActionOfInterest"
1534
- >
1535
- <td
1536
- v-if="tableActions"
1537
- class="row-check"
1538
- align="middle"
1539
- >
1540
- {{ row.mainRowKey }}
1541
- <Checkbox
1542
- class="selection-checkbox"
1543
- :data-node-id="row.key"
1544
- :data-testid="componentTestid + '-' + i + '-checkbox'"
1545
- :value="selectedRows.includes(row.row)"
1546
- :alternate-label="t('sortableTable.genericRowCheckbox', { item: row && row.row ? row.row.id : '' })"
1547
- />
1548
- </td>
1549
- <td
1550
- v-if="subExpandColumn"
1551
- class="row-expand"
1552
- align="middle"
1553
- >
1554
- <i
1555
- data-title="Toggle Expand"
1556
- :class="{
1557
- icon: true,
1558
- 'icon-chevron-right': !expanded[row.row[keyField]],
1559
- 'icon-chevron-down': !!expanded[row.row[keyField]]
1560
- }"
1561
- @click.stop="toggleExpand(row.row)"
1562
- />
1563
- </td>
1564
- <template
1565
- v-for="(col, j) in row.columns"
1566
- :key="j"
1567
- >
1568
- <slot
1569
- :name="'col:' + col.col.name"
1570
- :row="row.row"
1571
- :col="col.col"
1572
- :dt="dt"
1573
- :expanded="expanded"
1574
- :rowKey="row.key"
1575
- >
1576
- <td
1577
- v-show="!hasAdvancedFiltering || (hasAdvancedFiltering && col.col.isColVisible)"
1578
- :key="col.col.name"
1579
- :data-title="col.col.label"
1580
- :data-testid="`sortable-cell-${ i }-${ j }`"
1581
- :align="'left'"
1582
- :class="{['col-'+col.dasherize]: !!col.col.formatter, [col.col.breakpoint]: !!col.col.breakpoint, ['skip-select']: col.col.skipSelect}"
1583
- :width="col.col.width"
1584
- >
1585
- <slot
1586
- :name="'cell:' + col.col.name"
1587
- :row="row.row"
1588
- :col="col.col"
1589
- :value="col.value"
1590
- >
1591
- <component
1592
- :is="col.component"
1593
- v-if="col.component && col.needRef"
1594
- ref="column"
1595
- :value="col.value"
1596
- :row="row.row"
1597
- :col="col.col"
1598
- :get-custom-detail-link="getCustomDetailLink"
1599
- v-bind="col.col.formatterOpts"
1600
- :row-key="row.key"
1601
- />
1602
- <component
1603
- :is="col.component"
1604
- v-else-if="col.component"
1605
- :value="col.value"
1606
- :row="row.row"
1607
- :col="col.col"
1608
- v-bind="col.col.formatterOpts"
1609
- :row-key="row.key"
1610
- />
1611
- <component
1612
- :is="col.col.formatter"
1613
- v-else-if="col.col.formatter"
1614
- :value="col.value"
1615
- :row="row.row"
1616
- :col="col.col"
1617
- v-bind="col.col.formatterOpts"
1618
- :row-key="row.key"
1619
- />
1620
- <template v-else-if="col.value !== ''">
1621
- {{ col.formatted }}
1622
- </template>
1623
- <template v-else-if="col.col.dashIfEmpty">
1624
- <span class="text-muted">&mdash;</span>
1625
- </template>
1626
- </slot>
1627
- </td>
1628
- </slot>
1629
- </template>
1630
- <td
1631
- v-if="rowActions"
1632
- :align="'left'"
1633
- style="height:60px"
1634
- >
1635
- <div style="display: flex;align-items: center;">
1636
- <slot
1637
- name="row-actions"
1638
- :row="row.row"
1639
- :index="i"
1640
- >
1641
- </slot>
1642
- <template v-if="featureDropdownMenu">
1643
- <ActionMenu
1644
- :resource="row.row"
1645
- :data-testid="componentTestid + '-' + i + '-action-button'"
1646
- :button-aria-label="t('sortableTable.tableActionsLabel', { resource: row?.row?.id || '' })"
1647
- />
1648
- </template>
1649
- <template v-else>
1650
- <ButtonMultiAction
1651
- :id="`actionButton+${i}+${(row.row && row.row.name) ? row.row.name : ''}`"
1652
- :ref="`actionButton${i}`"
1653
- aria-haspopup="true"
1654
- aria-expanded="false"
1655
- :aria-label="t('sortableTable.tableActionsLabel', { resource: row?.row?.id || '' })"
1656
- :data-testid="componentTestid + '-' + i + '-action-button'"
1657
- :borderless="true"
1658
- @click="handleActionButtonClick(i, $event)"
1659
- @keyup.enter="handleActionButtonClick(i, $event)"
1660
- @keyup.space="handleActionButtonClick(i, $event)"
1661
- />
1662
- </template>
1663
- </div>
1664
- </td>
1665
- </tr>
1666
- </slot>
1667
- </slot>
1668
- <!-- <slot
1669
- v-if="row.showSubRow"
1670
- name="sub-row"
1671
- :full-colspan="fullColspan"
1672
- :row="row.row"
1673
- :sub-matches="subMatches"
1674
- :keyField="keyField"
1675
- :componentTestid="componentTestid"
1676
- :i="i"
1677
- :onRowMouseEnter="onRowMouseEnter"
1678
- :onRowMouseLeave="onRowMouseLeave"
1679
- >
1680
- <tr
1681
- v-if="row.row.stateDescription"
1682
- :key="row.row[keyField] + '-description'"
1683
- :data-testid="componentTestid + '-' + i + '-row-description'"
1684
- class="state-description sub-row"
1685
- @mouseenter="onRowMouseEnter"
1686
- @mouseleave="onRowMouseLeave"
1687
- >
1688
- <td
1689
- v-if="tableActions"
1690
- class="row-check"
1691
- align="middle"
1692
- />
1693
- <td
1694
- :colspan="fullColspan - (tableActions ? 1: 0)"
1695
- :class="{ 'text-error' : row.row.stateObj.error }"
1696
- >
1697
- {{ row.row.stateDescription }}
1698
- </td>
1699
- </tr>
1700
- </slot> -->
1701
- </template>
1702
- </tbody>
1703
- </table>
1704
- </div>
1705
- <div
1706
- v-if="!noRows && !noResults"
1707
- :class="$route.path=== '/account'? 'chebox-padding':''"
1708
- style="display: flex;justify-content: flex-start;align-content: center;height: 62px;position: sticky;bottom: 0;background-color: var(--header-bg);padding: 15px 20px 0 30px;margin: 0 -20px 0 -20px;z-index:13"
1709
- >
1710
- <div style="display: flex;justify-content: center;height: 32px;">
1711
- <Checkbox
1712
- v-if="tableActions&&availableActions.some(item => item.action != 'download')"
1713
- :value="isMuchSelected"
1714
- class="check"
1715
- data-testid="sortable-table_check_select_all"
1716
- :disabled="noRows || noResults"
1717
- style="display: flex;justify-content: center;align-content: center;"
1718
- @update:value = "muchSelect"
1719
- />
1720
- </div>
1721
- <div
1722
- :class="{'titled': $slots.title && $slots.title.length}"
1723
- class="sortable-table-header"
1724
- style="margin-left: 10px;min-width: 55%;"
1725
- >
1726
- <slot name="title" />
1727
- <div
1728
- v-if="showHeaderRow"
1729
- class="fixed-footer-actions"
1730
- :class="{button: !!$slots['header-button'], 'advanced-filtering': hasAdvancedFiltering,}"
1731
- >
1732
- <div
1733
- :class="bulkActionsClass"
1734
- class="bulk"
1735
- >
1736
- <slot name="header-left">
1737
- <template v-if="tableActions">
1738
- <button
1739
- v-for="(act) in availableActions"
1740
- :id="act.action"
1741
- :key="act.action"
1742
- v-clean-tooltip="actionTooltip"
1743
- type="button"
1744
- class="btn role-primary"
1745
- :class="{[bulkActionClass]:true}"
1746
- :disabled="!act.enabled"
1747
- :data-testid="componentTestid + '-' + act.action"
1748
- role="button"
1749
- :aria-label="act.label"
1750
- @click="applyTableAction(act, null, $event)"
1751
- @keydown.enter.stop
1752
- @mouseover="setBulkActionOfInterest(act)"
1753
- @mouseleave="setBulkActionOfInterest(null)"
1754
- >
1755
- <i
1756
- v-if="act.icon"
1757
- :class="act.icon"
1758
- />
1759
- <span v-clean-html="act.label" />
1760
- </button>
1761
- <template v-if="featureDropdownMenu">
1762
- <ActionDropdownShell
1763
- :disabled="!selectedRows.length"
1764
- :hidden-actions="hiddenActions"
1765
- :action-tooltip="actionTooltip"
1766
- @click="applyTableAction"
1767
- @mouseover="setBulkActionOfInterest"
1768
- @mouseleave="setBulkActionOfInterest"
1769
- />
1770
- </template>
1771
- <template v-else>
1772
- <ActionDropdown
1773
- :class="bulkActionsDropdownClass"
1774
- class="bulk-actions-dropdown"
1775
- :disable-button="!selectedRows.length"
1776
- size="sm"
1777
- >
1778
- <template #button-content>
1779
- <button
1780
- ref="actionDropDown"
1781
- class="btn bg-primary mr-0"
1782
- :disabled="!selectedRows.length"
1783
- >
1784
- <i class="icon icon-gear" />
1785
- <span>{{ t('sortableTable.bulkActions.collapsed.label') }}</span>
1786
- <i class="ml-10 icon icon-chevron-down" />
1787
- </button>
1788
- </template>
1789
- <template #popover-content>
1790
- <ul class="list-unstyled menu">
1791
- <li
1792
- v-for="(act, i) in hiddenActions"
1793
- :key="i"
1794
- v-close-popper
1795
- v-clean-tooltip="{
1796
- content: actionTooltip,
1797
- placement: 'right'
1798
- }"
1799
- :class="{ disabled: !act.enabled }"
1800
- @click="applyTableAction(act, null, $event)"
1801
- @mouseover="setBulkActionOfInterest(act)"
1802
- @mouseleave="setBulkActionOfInterest(null)"
1803
- >
1804
- <i
1805
- v-if="act.icon"
1806
- :class="act.icon"
1807
- />
1808
- <span v-clean-html="act.label" />
1809
- </li>
1810
- </ul>
1811
- </template>
1812
- </ActionDropdown>
1813
- </template>
1814
- <label
1815
- v-if="selectedRowsText"
1816
- :class="bulkActionAvailabilityClass"
1817
- class="action-availability"
1818
- >
1819
- {{ tableActions&&availableActions.some(item => item.action != 'download') ?selectedRowsText: '' }}
1820
- </label>
1821
-
1822
- </template>
1823
- </slot>
1824
- </div>
1825
- </div>
1826
- </div>
1827
-
1828
- <!-- 分页 -->
1829
- <div
1830
- v-if="showPaging"
1831
- class="paging"
1832
- >
1833
- <div style="height: 100%; align-content: center;">
1834
- 共 {{ filteredRows.length }} 条
1835
- </div>
1836
-
1837
- <button
1838
- type="button"
1839
- class="btn btn-sm role-multi-action page-btn-normal"
1840
- :disabled="page == 1"
1841
- :style="{ color: page <= totalPages ? `var(--default-text) !important` : `var(--paimary)`,border: page <= totalPages ? `solid thin var(--border)` : `solid thin var(--paimary)`}"
1842
- @click="goToPage('prev')"
1843
- >
1844
- <!-- <i class="icon icon-chevron-left" /> -->
1845
- <
1846
- </button>
1847
- <button
1848
- type="button"
1849
- class="btn btn-sm role-multi-action page-btn-normal"
1850
- :style="{ color: (page == 1) ? `var(--primary)`:`var(--default-text) !important`,border: (page == 1) ? `solid thin var(--primary)` : `solid thin var(--border)`}"
1851
- @click="goToPage('first')"
1852
- >
1853
- <!-- <i class="icon icon-chevron-beginning" /> -->
1854
- {{ 1 }}
1855
- </button>
1856
- <template v-if="totalPages > 2">
1857
- <div style="display: flex;flex-direction: row;gap: 10px;">
1858
- <button
1859
- v-if="page - 2 > 1 && page <= totalPages "
1860
- type="button"
1861
- class="btn btn-sm role-multi-action page-btn-normal"
1862
- :style="{ color: `var(--default-text) !important`,border: `solid thin white`}"
1863
- >
1864
- ...
1865
- </button>
1866
- <button
1867
- v-if="page - 1 > 1 && page <= totalPages "
1868
- type="button"
1869
- class="btn btn-sm role-multi-action page-btn-normal"
1870
- :style="{ color: `var(--default-text) !important`,border: `solid thin var(--border)`}"
1871
- @click="setPage(page-1)"
1872
- >
1873
- {{ page-1 }}
1874
- </button>
1875
- <button
1876
- v-if="page > 1 && page < totalPages"
1877
- type="button"
1878
- class="btn btn-sm role-multi-action page-btn-normal"
1879
- :style="{ color: `var(--default-text)`,border: `solid thin var(--primary)`}"
1880
- @click="setPage(page)"
1881
- >
1882
- {{ page }}
1883
- </button>
1884
- <button
1885
- v-if="page + 1 < totalPages "
1886
- type="button"
1887
- class="btn btn-sm role-multi-action page-btn-normal"
1888
- :style="{ color: `var(--default-text) !important`,border: `solid thin var(--border)`}"
1889
- @click="setPage(page+1)"
1890
- >
1891
- {{ page+1 }}
1892
- </button>
1893
- <button
1894
- v-if="page +2 < totalPages "
1895
- type="button"
1896
- class="btn btn-sm role-multi-action page-btn-normal"
1897
- :style="{ color: `var(--default-text) !important`,border: `solid thin white`}"
1898
- >
1899
- ...
1900
- </button>
1901
- </div>
1902
- </template>
1903
- <!-- <button
1904
- type="button"
1905
- class="btn btn-sm role-multi-action"
1906
- style="padding: 0;max-width: 32px;background-color: white !important;"
1907
- >
1908
- {{ page }}
1909
- </button> -->
1910
- <button
1911
- v-if="totalPages > 1"
1912
- type="button"
1913
- class="btn btn-sm role-multi-action page-btn-normal"
1914
- :style="{ color: (page == totalPages) ? `var(--primary)`:`var(--default-text) !important`,border: (page == totalPages) ? `solid thin var(--primary)` : `solid thin var(--border)`}"
1915
- @click="goToPage('last')"
1916
- >
1917
- <!-- <i class="icon icon-chevron-end" /> -->
1918
- {{ totalPages }}
1919
- </button>
1920
- <button
1921
- type="button"
1922
- class="btn btn-sm role-multi-action page-btn-normal"
1923
- :disabled="page == totalPages"
1924
- :style="{ color: page <= totalPages ? `var(--default-text) !important` : `var(--paimary)`,border: page <= totalPages ? `solid thin var(--border)` : `solid thin var(--paimary)`}"
1925
- @click="goToPage('next')"
1926
- >
1927
- <!-- <i class="icon icon-chevron-right" /> -->
1928
- >
1929
- </button>
1930
-
1931
- <!-- 分页页码切换 -->
1932
- <Select
1933
- :mode="inputPerPage"
1934
- :searchable="false"
1935
- :clearable="false"
1936
- :options="perPageOptions"
1937
- v-model:value="inputPerPage"
1938
- class="pageSelect"
1939
- @update:value="changePerPage"
1940
- />
1941
-
1942
- <div style="height: 100%; align-content: center;">
1943
- 跳至
1944
- </div>
1945
- <input
1946
- v-model="inputPage"
1947
- type="number"
1948
- min="1"
1949
- step="1"
1950
- style="padding: 0px 10px;"
1951
- @keyup.enter="handleToPage"
1952
- >
1953
- <div style="height: 100%; align-content: center;">
1954
-
1955
- </div>
1956
- <!-- <button
1957
- type="button"
1958
- class="btn btn-sm role-multi-action"
1959
- style="padding: 0;max-width: 80px;background-color: white !important;"
1960
- @click="setPage(inputPage)"
1961
- >
1962
-
1963
- </button> -->
1964
- </div>
1965
- </div>
1966
- <button
1967
- v-if="search"
1968
- v-shortkey.once="['/']"
1969
- class="hide"
1970
- @shortkey="focusSearch()"
1971
- />
1972
- <template v-if="tableActions">
1973
- <button
1974
- v-shortkey="['j']"
1975
- class="hide"
1976
- @shortkey="focusNext($event)"
1977
- />
1978
- <button
1979
- v-shortkey="['k']"
1980
- class="hide"
1981
- @shortkey="focusPrevious($event)"
1982
- />
1983
- <button
1984
- v-shortkey="['shift','j']"
1985
- class="hide"
1986
- @shortkey="focusNext($event, true)"
1987
- />
1988
- <button
1989
- v-shortkey="['shift','k']"
1990
- class="hide"
1991
- @shortkey="focusPrevious($event, true)"
1992
- />
1993
- <slot name="shortkeys" />
1994
- </template>
1995
- </div>
1996
- </template>
1997
-
1998
- <style lang="scss" scoped>
1999
- .sortable-table.alt-loading {
2000
- opacity: 0.5;
2001
- pointer-events: none;
2002
- }
2003
- .advanced-filter-group {
2004
- position: relative;
2005
- margin-left: 10px;
2006
- .advanced-filter-container {
2007
- position: absolute;
2008
- top: 38px;
2009
- right: 0;
2010
- width: 300px;
2011
- border: 1px solid var(--primary);
2012
- background-color: var(--body-bg);
2013
- padding: 20px;
2014
- z-index: 2;
2015
-
2016
- .middle-block {
2017
- display: flex;
2018
- align-items: center;
2019
- margin-top: 20px;
2020
-
2021
- span {
2022
- margin-right: 20px;
2023
- }
2024
-
2025
- button {
2026
- margin-left: 20px;
2027
- }
2028
- }
2029
-
2030
- .bottom-block {
2031
- display: flex;
2032
- align-items: center;
2033
- margin-top: 40px;
2034
- justify-content: space-between;
2035
- }
2036
- }
2037
- }
2038
-
2039
- .advanced-filters-applied {
2040
- display: inline-flex;
2041
- margin: 0;
2042
- padding: 0;
2043
- list-style: none;
2044
- max-width: 100%;
2045
- flex-wrap: wrap;
2046
- justify-content: flex-end;
2047
-
2048
- li {
2049
- margin: 0 20px 10px 0;
2050
- padding: 2px 5px;
2051
- border: 1px solid;
2052
- display: flex;
2053
- align-items: center;
2054
- position: relative;
2055
- height: 20px;
2056
-
2057
- &:nth-child(4n+1) {
2058
- border-color: var(--success);
2059
-
2060
- .bg {
2061
- background-color: var(--success);
2062
- }
2063
- }
2064
-
2065
- &:nth-child(4n+2) {
2066
- border-color: var(--warning);
2067
-
2068
- .bg {
2069
- background-color: var(--warning);
2070
- }
2071
- }
2072
-
2073
- &:nth-child(4n+3) {
2074
- border-color: var(--info);
2075
-
2076
- .bg {
2077
- background-color: var(--info);
2078
- }
2079
- }
2080
-
2081
- &:nth-child(4n+4) {
2082
- border-color: var(--error);
2083
-
2084
- .bg {
2085
- background-color: var(--error);
2086
- }
2087
- }
2088
-
2089
- .bg {
2090
- position: absolute;
2091
- top: 0;
2092
- left: 0;
2093
- width: 100%;
2094
- height: 100%;
2095
- opacity: 0.2;
2096
- z-index: -1;
2097
- }
2098
-
2099
- .label {
2100
- margin-right: 10px;
2101
- font-size: 11px;
2102
- }
2103
- .cross {
2104
- font-size: 12px;
2105
- font-weight: bold;
2106
- cursor: pointer;
2107
- }
2108
- }
2109
- }
2110
-
2111
- td {
2112
- // Aligns with COLUMN_BREAKPOINTS
2113
- @media only screen and (max-width: map-get($breakpoints, '--viewport-4')) {
2114
- // HIDE column on sizes below 480px
2115
- &.tablet, &.laptop, &.desktop {
2116
- display: none;
2117
- }
2118
- }
2119
- @media only screen and (max-width: map-get($breakpoints, '--viewport-9')) {
2120
- // HIDE column on sizes below 992px
2121
- &.laptop, &.desktop {
2122
- display: none;
2123
- }
2124
- }
2125
- @media only screen and (max-width: map-get($breakpoints, '--viewport-12')) {
2126
- // HIDE column on sizes below 1281px
2127
- &.desktop {
2128
- display: none;
2129
- }
2130
- }
2131
- }
2132
-
2133
- // Loading indicator row
2134
- tr td div.data-loading {
2135
- align-items: center;
2136
- display: flex;
2137
- justify-content: center;
2138
- padding: 20px 0;
2139
- > i {
2140
- font-size: 20px;
2141
- height: 20px;
2142
- margin-right: 5px;
2143
- width: 20px;
2144
- }
2145
- }
2146
-
2147
- .search-box {
2148
- height: 32px;
2149
- margin-left: 0px;
2150
- /* min-width: 180px; */
2151
- min-width: 280px;
2152
- width: 280px !important;
2153
- border: 1px solid rgb(217, 217, 217);
2154
- }
2155
- </style>
2156
-
2157
- <style lang="scss">
2158
- //
2159
- // Important: Almost all selectors in here need to be ">"-ed together so they
2160
- // apply only to the current table, not one nested inside another table.
2161
- //
2162
-
2163
- $group-row-height: 40px;
2164
- $group-separation: 40px;
2165
- $divider-height: 1px;
2166
-
2167
- $separator: 20;
2168
- $remove: 100;
2169
- $spacing: 10px;
2170
-
2171
- .filter-select .vs__selected-options .vs__selected {
2172
- text-align: left;
2173
- }
2174
-
2175
- .sortable-table {
2176
- border-collapse: collapse;
2177
- min-width: 400px;
2178
- border-radius: 5px 5px 0 0;
2179
- border-bottom: 1px solid var(--border);
2180
- /* outline: 1px solid var(--border); */
2181
- background: var(--sortable-table-bg);
2182
- border-radius: 4px;
2183
-
2184
- &.overflow-x {
2185
- overflow-x: visible;
2186
- }
2187
- &.overflow-y {
2188
- overflow-y: visible;
2189
- }
2190
-
2191
- td {
2192
- padding: 8px 5px;
2193
- border: 0;
2194
-
2195
- &:first-child {
2196
- padding-left: 10px;
2197
- }
2198
-
2199
- &:last-child {
2200
- padding-right: 10px;
2201
- }
2202
-
2203
- &.row-check {
2204
- padding-top: 12px;
2205
- }
2206
- }
2207
-
2208
- tbody {
2209
- tr {
2210
- border-bottom: 1px solid var(--sortable-table-top-divider);
2211
- background-color: var(--sortable-table-row-bg);
2212
-
2213
- &.main-row.has-sub-row {
2214
- border-bottom: 0;
2215
- }
2216
-
2217
- // if a main-row is hovered also hover it's sibling sub row. note - the reverse is handled in selection.js
2218
- &.main-row:not(.row-selected):hover + .sub-row {
2219
- background-color: var(--sortable-table-hover-bg);
2220
- }
2221
-
2222
- &:last-of-type {
2223
- border-bottom: 0;
2224
- }
2225
-
2226
- &:hover, &.sub-row-hovered {
2227
- /* background-color: var(--sortable-table-hover-bg); */
2228
- }
2229
-
2230
- &.state-description > td {
2231
- font-size: 13px;
2232
- padding-top: 0;
2233
- overflow-wrap: anywhere;
2234
- }
2235
- }
2236
-
2237
- tr.active-row {
2238
- color: var(--sortable-table-header-bg);
2239
- }
2240
-
2241
- tr.row-selected {
2242
- /* background: var(--sortable-table-selected-bg); */
2243
- }
2244
-
2245
- .no-rows {
2246
- td {
2247
- padding: 30px 0;
2248
- text-align: center;
2249
- }
2250
- }
2251
-
2252
- .no-rows, .no-results {
2253
- &:hover {
2254
- background-color: var(--body-bg);
2255
- }
2256
- }
2257
- .no-results{
2258
- height: 60px;
2259
- }
2260
-
2261
- &.group {
2262
- &:before {
2263
- content: "";
2264
- display: block;
2265
- height: 0px;
2266
- background-color: transparent;
2267
- }
2268
- }
2269
-
2270
- tr.group-row {
2271
- background-color: initial;
2272
-
2273
- &:first-child {
2274
- border-bottom: 2px solid var(--sortable-table-row-bg);
2275
- }
2276
-
2277
- &:not(:first-child) {
2278
- margin-top: 20px;
2279
- }
2280
-
2281
- td {
2282
- padding: 0;
2283
-
2284
- &:first-of-type {
2285
- border-left: 1px solid var(--sortable-table-accent-bg);
2286
- }
2287
- }
2288
-
2289
- .group-tab {
2290
- @include clearfix;
2291
- height: $group-row-height;
2292
- line-height: $group-row-height;
2293
- padding: 0 10px;
2294
- border-radius: 4px 4px 0px 0px;
2295
- background-color: var(--sortable-table-row-bg);
2296
- position: relative;
2297
- top: 1px;
2298
- display: inline-block;
2299
- z-index: z-index('tableGroup');
2300
- min-width: $group-row-height * 1.8;
2301
-
2302
- > SPAN {
2303
- color: var(--sortable-table-group-label);
2304
- }
2305
- }
2306
-
2307
- .group-tab:after {
2308
- height: $group-row-height;
2309
- width: 70px;
2310
- border-radius: 5px 5px 0px 0px;
2311
- background-color: var(--sortable-table-row-bg);
2312
- content: "";
2313
- position: absolute;
2314
- right: -15px;
2315
- top: 0px;
2316
- transform: skewX(40deg);
2317
- z-index: -1;
2318
- }
2319
- }
2320
- }
2321
- }
2322
-
2323
- .for-inputs{
2324
- & TABLE.sortable-table {
2325
- width: 100%;
2326
- border-collapse: collapse;
2327
- margin-bottom: $spacing;
2328
-
2329
- >TBODY>TR>TD, >THEAD>TR>TH {
2330
- padding-right: $spacing;
2331
- padding-bottom: $spacing;
2332
-
2333
- &:last-of-type {
2334
- padding-right: 0;
2335
- }
2336
- }
2337
-
2338
- >TBODY>TR:first-of-type>TD {
2339
- padding-top: $spacing;
2340
- }
2341
-
2342
- >TBODY>TR:last-of-type>TD {
2343
- padding-bottom: 0;
2344
- }
2345
- }
2346
-
2347
- &.edit, &.create, &.clone {
2348
- TABLE.sortable-table>THEAD>TR>TH {
2349
- border-color: transparent;
2350
- }
2351
- }
2352
- }
2353
-
2354
- .sortable-table-header {
2355
- position: relative;
2356
- z-index: z-index('fixedTableHeader');
2357
-
2358
- &.titled {
2359
- display: flex;
2360
- align-items: center;
2361
- }
2362
- }
2363
- .fixed-footer-actions.button{
2364
- grid-template-columns: [bulk] auto [middle] min-content [search] minmax(min-content, 350px);
2365
- }
2366
-
2367
- .fixed-footer-actions {
2368
- /* padding: 0 0 20px 0; */
2369
- width: 100%;
2370
- z-index: z-index('fixedTableHeader');
2371
- background: transparent;
2372
- display: grid;
2373
- grid-template-columns: [bulk] auto [middle] min-content [search] minmax(min-content, 200px);
2374
- grid-column-gap: 10px;
2375
-
2376
- &.advanced-filtering {
2377
- grid-template-columns: [bulk] auto [middle] minmax(min-content, auto) [search] minmax(min-content, auto);
2378
- }
2379
-
2380
- .bulk {
2381
- grid-area: bulk;
2382
-
2383
- $gap: 10px;
2384
-
2385
- & > BUTTON {
2386
- display: none; // Handled dynamically
2387
- }
2388
-
2389
- & > BUTTON:not(:last-of-type) {
2390
- margin-right: $gap;
2391
- }
2392
-
2393
- .action-availability {
2394
- display: none; // Handled dynamically
2395
- margin-left: $gap;
2396
- vertical-align: middle;
2397
- margin-top: 2px;
2398
- }
2399
-
2400
- .dropdown-button {
2401
- $disabled-color: var(--disabled-text);
2402
- $disabled-cursor: not-allowed;
2403
- li.disabled {
2404
- color: $disabled-color;
2405
- cursor: $disabled-cursor;
2406
-
2407
- &:hover {
2408
- color: $disabled-color;
2409
- background-color: unset;
2410
- cursor: $disabled-cursor;
2411
- }
2412
- }
2413
- }
2414
-
2415
- .bulk-action {
2416
- .icon {
2417
- vertical-align: -10%;
2418
- }
2419
- }
2420
- }
2421
-
2422
- .middle {
2423
- grid-area: middle;
2424
- white-space: nowrap;
2425
-
2426
- .icon.icon-backup.animate {
2427
- animation-name: spin;
2428
- animation-duration: 1000ms;
2429
- animation-iteration-count: infinite;
2430
- animation-timing-function: linear;
2431
- }
2432
-
2433
- @keyframes spin {
2434
- from {
2435
- transform:rotate(0deg);
2436
- }
2437
- to {
2438
- transform:rotate(360deg);
2439
- }
2440
- }
2441
- }
2442
-
2443
- .search {
2444
- grid-area: search;
2445
- text-align: right;
2446
- justify-content: flex-end;
2447
- }
2448
-
2449
- .bulk-actions-dropdown {
2450
- display: none; // Handled dynamically
2451
-
2452
- .dropdown-button {
2453
- background-color: var(--primary);
2454
-
2455
- &:hover {
2456
- background-color: var(--primary-hover-bg);
2457
- color: var(--primary-hover-text);
2458
- }
2459
-
2460
- > *, .icon-chevron-down {
2461
- color: var(--primary-text);
2462
- }
2463
-
2464
- .button-divider {
2465
- border-color: var(--primary-text);
2466
- }
2467
-
2468
- &.disabled {
2469
- border-color: var(--disabled-bg);
2470
-
2471
- .icon-chevron-down {
2472
- color: var(--disabled-text) !important;
2473
- }
2474
-
2475
- .button-divider {
2476
- border-color: var(--disabled-text);
2477
- }
2478
- }
2479
- }
2480
- }
2481
- }
2482
-
2483
-
2484
- .fixed-header-table-actions {
2485
- width: 100%;
2486
- z-index: z-index('fixedTableHeader');
2487
- background: transparent;
2488
- display: flex;
2489
- justify-content: space-between;
2490
-
2491
-
2492
- .bulk {
2493
- $gap: 10px;
2494
-
2495
- & > BUTTON {
2496
- display: none; // Handled dynamically
2497
- }
2498
-
2499
- & > BUTTON:not(:last-of-type) {
2500
- margin-right: $gap;
2501
- }
2502
-
2503
- .action-availability {
2504
- display: none; // Handled dynamically
2505
- margin-left: $gap;
2506
- vertical-align: middle;
2507
- margin-top: 2px;
2508
- }
2509
-
2510
- .dropdown-button {
2511
- $disabled-color: var(--disabled-text);
2512
- $disabled-cursor: not-allowed;
2513
- li.disabled {
2514
- color: $disabled-color;
2515
- cursor: $disabled-cursor;
2516
-
2517
- &:hover {
2518
- color: $disabled-color;
2519
- background-color: unset;
2520
- cursor: $disabled-cursor;
2521
- }
2522
- }
2523
- }
2524
-
2525
- .bulk-action {
2526
- .icon {
2527
- vertical-align: -10%;
2528
- }
2529
- }
2530
- }
2531
-
2532
- .middle {
2533
- white-space: nowrap;
2534
-
2535
- .icon.icon-backup.animate {
2536
- animation-name: spin;
2537
- animation-duration: 1000ms;
2538
- animation-iteration-count: infinite;
2539
- animation-timing-function: linear;
2540
- }
2541
-
2542
- @keyframes spin {
2543
- from {
2544
- transform:rotate(0deg);
2545
- }
2546
- to {
2547
- transform:rotate(360deg);
2548
- }
2549
- }
2550
- }
2551
-
2552
- .search {
2553
- text-align: right;
2554
- justify-content: flex-end;
2555
- }
2556
-
2557
- .bulk-actions-dropdown {
2558
- display: none; // Handled dynamically
2559
-
2560
- .dropdown-button {
2561
- background-color: var(--primary);
2562
-
2563
- &:hover {
2564
- background-color: var(--primary-hover-bg);
2565
- color: var(--primary-hover-text);
2566
- }
2567
-
2568
- > *, .icon-chevron-down {
2569
- color: var(--primary-text);
2570
- }
2571
-
2572
- .button-divider {
2573
- border-color: var(--primary-text);
2574
- }
2575
-
2576
- &.disabled {
2577
- border-color: var(--disabled-bg);
2578
-
2579
- .icon-chevron-down {
2580
- color: var(--disabled-text) !important;
2581
- }
2582
-
2583
- .button-divider {
2584
- border-color: var(--disabled-text);
2585
- }
2586
- }
2587
- }
2588
- }
2589
- }
2590
-
2591
- .paging {
2592
- //text-align: center;
2593
- display: flex;
2594
- justify-content: flex-end;
2595
- gap: 0 10px;
2596
- text-align: center;
2597
- max-height: 32px;
2598
- background-color: transparent;
2599
- flex: 1;
2600
- SPAN {
2601
- display: inline-block;
2602
- //min-width: 200px;
2603
- min-width: auto;
2604
- }
2605
-
2606
- /* 针对Webkit浏览器(如Chrome, Safari) */
2607
- input[type="number"]::-webkit-inner-spin-button,
2608
- input[type="number"]::-webkit-outer-spin-button {
2609
- -webkit-appearance: none;
2610
- margin: 0;
2611
- }
2612
-
2613
- /* 针对Firefox */
2614
- input[type="number"] {
2615
- -moz-appearance: textfield;
2616
- // background-color: var(--disabled-bg) !important;
2617
- border: 1px var(--border) solid;
2618
- border-radius: 2px;
2619
- width: 50px;
2620
- height: 100%;
2621
- }
2622
- }
2623
-
2624
- </style>
2625
- <style scoped lang="scss">
2626
-
2627
- :deep() .role-link{
2628
- min-width: unset !important;
2629
- width: 38px;
2630
- min-height: 20px !important;
2631
- height: 20px !important;
2632
- line-height: 20px !important;
2633
- &:hover{
2634
- background-color: unset !important;
2635
- }
2636
- }
2637
- :deep() .checkbox-outer-container-extra{
2638
- margin-top: 0px !important;
2639
- }
2640
- .pageSelect {
2641
- max-width: 100px;
2642
- }
2643
- .page-btn-normal {
2644
- padding: 0;
2645
- max-width: 32px;
2646
- background-color:white !important;
2647
- }
2648
-
2649
- .pageSelect{
2650
- &:deep() .vs__actions:after{
2651
- padding-top: 10px;
2652
- }
2653
- }
2654
- .sort-table-div{
2655
- width:100%;
2656
- white-space:nowrap;
2657
- overflow-x: auto;
2658
- }
2659
-
2660
- /* 滚动阴影左边 */
2661
- .shadow-left td:nth-child(2)::after{
2662
- content: "";
2663
- position: absolute;
2664
- top: 0;
2665
- width: 10px;
2666
- height: 100%;
2667
- right: -10px;
2668
- background: linear-gradient(to right, rgba(5, 5, 5, 0.06), transparent);
2669
- }
2670
- .shadow-left{
2671
- &:deep() th:nth-child(2)::after{
2672
- content: "";
2673
- position: absolute;
2674
- top: 0;
2675
- width: 10px;
2676
- height: 100%;
2677
- right: -10px;
2678
- background: linear-gradient(to right, rgba(5, 5, 5, 0.06), transparent);
2679
- }
2680
- }
2681
-
2682
- /* 滚动阴影右边 */
2683
- .shadow-right td:nth-last-child(1)::after{
2684
- content: "";
2685
- position: absolute;
2686
- top: 0;
2687
- width: 10px;
2688
- height: 100%;
2689
- left: -10px;
2690
- background: linear-gradient(to left, rgba(5, 5, 5, 0.06), transparent);
2691
- }
2692
-
2693
- .shadow-right{
2694
- &:deep() th:nth-last-child(1)::after {
2695
- content: "";
2696
- position: absolute;
2697
- top: 0;
2698
- width: 10px;
2699
- height: 100%;
2700
- left: -10px;
2701
- background: linear-gradient(to left, rgba(5, 5, 5, 0.06), transparent);
2702
- }
2703
- }
2704
-
2705
- /* 滚动阴影垂直修正 */
2706
- .shadow-y td:nth-child(-n + 2), /* 前两列 */
2707
- .shadow-y td:nth-last-child(1) /* 后一列 */
2708
- {
2709
- z-index: 3;
2710
- }
2711
- </style>
2712
-