@mptw/skylens-ui 1.3.0 → 1.3.1
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.
- package/components/SLTable.vue +202 -13
- package/package.json +1 -1
package/components/SLTable.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
div
|
|
2
|
+
div(ref="el")
|
|
3
3
|
.table(
|
|
4
4
|
:class="{ 'table-responsive': responsive }"
|
|
5
5
|
@mousemove="onMousemoved"
|
|
@@ -16,10 +16,12 @@
|
|
|
16
16
|
)
|
|
17
17
|
th(
|
|
18
18
|
v-else
|
|
19
|
-
:style="field._style"
|
|
20
|
-
:class="[field._class, { sortable: field.sortable, sorting: field.key === sortBy, 'sort-desc': sortDesc}]"
|
|
21
|
-
:colspan="headerColumnSpans[index]"
|
|
22
|
-
:
|
|
19
|
+
:style="field._style",
|
|
20
|
+
:class="[field._class, { sortable: field.sortable, sorting: field.key === sortBy, 'sort-desc': sortDesc, filterable: field.filterable, filtering: filterBy !== ''}]",
|
|
21
|
+
:colspan="headerColumnSpans[index]",
|
|
22
|
+
:name="`${field.key}-header`",
|
|
23
|
+
:key="field.key",
|
|
24
|
+
@click.prevent="onClickedHeaderCol(field.key)"
|
|
23
25
|
)
|
|
24
26
|
.inline-flex.items-center.space-x-1
|
|
25
27
|
SLElementWithTitle(v-if='field.info', :title="field.info")
|
|
@@ -27,6 +29,12 @@
|
|
|
27
29
|
component(:is='field.sortable ? "a" : "div"', href='javascript:;' class='inline-flex items-center', @click="field.sortable && sort(field.key)")
|
|
28
30
|
span {{ field.label }}
|
|
29
31
|
SLIcon.sort-icon.ml-2(v-if="field.sortable" :icon="sortDesc ? 'descending' : 'ascending'")
|
|
32
|
+
|
|
33
|
+
SLIcon.filter-icon(
|
|
34
|
+
v-if="field.filterable"
|
|
35
|
+
class="ml-0.5"
|
|
36
|
+
:icon="filterBy !== '' ? 'filter-on' : 'filter'"
|
|
37
|
+
)
|
|
30
38
|
tbody(:class="{ 'hoverable': hoverableRows, 'clickable': clickableRows }")
|
|
31
39
|
template(v-for="(item, itemIndex) in bodyHeaderItems" :key="itemIndex")
|
|
32
40
|
tr.body-header-row(
|
|
@@ -72,15 +80,38 @@
|
|
|
72
80
|
) {{ item[field.key] }}
|
|
73
81
|
tr(v-if="items.length === 0")
|
|
74
82
|
td(:colspan="fields.length")
|
|
75
|
-
|
|
83
|
+
div(v-if="loading", style="min-height: 200px")
|
|
84
|
+
.flex.flex-col.items-center.py-8.px-4.space-y-3(v-else)
|
|
85
|
+
img(src="~/assets/images/commons/empty-box.png")
|
|
86
|
+
.text-lg.text-primary-light {{ noItems }}
|
|
87
|
+
.text-sm.text-gray-3(v-if="reason") 可能的原因:{{ reason }}
|
|
76
88
|
SLLoading(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
v-if="loading"
|
|
90
|
+
:boundaries="[{ sides: ['top'], query: 'td' }, { sides: ['bottom'], query: 'tbody' }]"
|
|
91
|
+
)
|
|
92
|
+
.table-header-filter-popup(ref="popupEl")
|
|
93
|
+
.table-header-filter-popup-content
|
|
94
|
+
a.table-header-filter-item(
|
|
95
|
+
v-for="option in filterOptions"
|
|
96
|
+
href="javascript:;"
|
|
97
|
+
:class="{ active: filterBy === option.key }"
|
|
98
|
+
:key="option.key"
|
|
99
|
+
@click="onClickedFilter(option.key)"
|
|
100
|
+
)
|
|
101
|
+
.inline-flex.items-center.space-x-1
|
|
102
|
+
SLIcon(
|
|
103
|
+
v-if="option.icon"
|
|
104
|
+
:icon="option.icon"
|
|
105
|
+
:class="option._iconClass"
|
|
106
|
+
)
|
|
107
|
+
span(v-if="option.label") {{ option.label }}
|
|
80
108
|
</template>
|
|
81
109
|
|
|
82
110
|
<script lang="ts">
|
|
83
111
|
import type { PropType } from 'vue';
|
|
112
|
+
import { fromEvent, Subscription } from 'rxjs';
|
|
113
|
+
import { createPopper } from '@popperjs/core';
|
|
114
|
+
import type { Instance as PopperInstance } from '@popperjs/core';
|
|
84
115
|
import type ITableField from '@/interface/ITableField';
|
|
85
116
|
|
|
86
117
|
export default defineComponent({
|
|
@@ -108,6 +139,10 @@
|
|
|
108
139
|
type: Boolean,
|
|
109
140
|
default: false,
|
|
110
141
|
},
|
|
142
|
+
filterBy: {
|
|
143
|
+
type: String,
|
|
144
|
+
default: '',
|
|
145
|
+
},
|
|
111
146
|
hoverableRows: {
|
|
112
147
|
type: Boolean,
|
|
113
148
|
default: false,
|
|
@@ -136,12 +171,38 @@
|
|
|
136
171
|
type: String,
|
|
137
172
|
default: 'No Items',
|
|
138
173
|
},
|
|
174
|
+
reason: {
|
|
175
|
+
type: String,
|
|
176
|
+
default: '',
|
|
177
|
+
},
|
|
139
178
|
},
|
|
179
|
+
emits: [
|
|
180
|
+
'update:sortDesc',
|
|
181
|
+
'update:sortBy',
|
|
182
|
+
'update:filterBy',
|
|
183
|
+
'row-clicked',
|
|
184
|
+
'row-mouseentered',
|
|
185
|
+
'row-mouseleaved',
|
|
186
|
+
'table-mousemoved',
|
|
187
|
+
],
|
|
140
188
|
setup(props, { emit }) {
|
|
141
189
|
const { fields, sortBy, sortDesc, clickableRows, hoverableRows } =
|
|
142
190
|
toRefs(props);
|
|
143
191
|
|
|
192
|
+
const el = shallowRef<HTMLElement | null>(null);
|
|
193
|
+
const popupEl = shallowRef<HTMLElement | null>(null);
|
|
144
194
|
const hoverRowIndex = ref(-1);
|
|
195
|
+
const filterOptions = ref<
|
|
196
|
+
{
|
|
197
|
+
key: string;
|
|
198
|
+
label?: string;
|
|
199
|
+
icon?: string;
|
|
200
|
+
}[]
|
|
201
|
+
>([]);
|
|
202
|
+
|
|
203
|
+
let popperInstance: PopperInstance | null = null;
|
|
204
|
+
let clickSubscriber: Subscription | null = null;
|
|
205
|
+
let filterHeaderCol: HTMLElement | null = null;
|
|
145
206
|
|
|
146
207
|
const headerColumnSpans = computed(() => {
|
|
147
208
|
return fields.value.map((field) => {
|
|
@@ -164,6 +225,22 @@
|
|
|
164
225
|
emit('update:sortBy', fieldKey);
|
|
165
226
|
}
|
|
166
227
|
}
|
|
228
|
+
|
|
229
|
+
function onClickedHeaderCol(fieldKey: string) {
|
|
230
|
+
const field = fields.value.find((f) => f.key === fieldKey);
|
|
231
|
+
if (!field || (!field.sortable && !field.filterable)) return;
|
|
232
|
+
|
|
233
|
+
if (field.sortable) {
|
|
234
|
+
sort(field.key);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (field.filterable) {
|
|
238
|
+
filterOptions.value = [...field.filterOptions];
|
|
239
|
+
filterHeaderCol = el.value.querySelector(`th[name=${field.key}-header]`)
|
|
240
|
+
.children[0] as HTMLElement;
|
|
241
|
+
show();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
167
244
|
|
|
168
245
|
function rowClicked(item: any, index: number, e: Event) {
|
|
169
246
|
if (!clickableRows.value) return;
|
|
@@ -190,16 +267,103 @@
|
|
|
190
267
|
|
|
191
268
|
emit('table-mousemoved', e);
|
|
192
269
|
}
|
|
270
|
+
|
|
271
|
+
function subscribeClick() {
|
|
272
|
+
clickSubscriber = fromEvent(window, 'click').subscribe((e) => {
|
|
273
|
+
const { target } = e;
|
|
274
|
+
if (
|
|
275
|
+
filterHeaderCol &&
|
|
276
|
+
popupEl.value &&
|
|
277
|
+
!filterHeaderCol.contains(target as HTMLElement) &&
|
|
278
|
+
!popupEl.value.contains(target as HTMLElement)
|
|
279
|
+
) {
|
|
280
|
+
hide();
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function unsubscribeClick() {
|
|
286
|
+
if (clickSubscriber) {
|
|
287
|
+
clickSubscriber.unsubscribe();
|
|
288
|
+
}
|
|
289
|
+
clickSubscriber = null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function show() {
|
|
293
|
+
if (!popupEl.value || !filterHeaderCol || !popperInstance) return;
|
|
294
|
+
|
|
295
|
+
popupEl.value.setAttribute('data-show', '');
|
|
296
|
+
|
|
297
|
+
subscribeClick();
|
|
298
|
+
popperInstance.state.elements.reference = filterHeaderCol;
|
|
299
|
+
popperInstance.setOptions({
|
|
300
|
+
modifiers: [
|
|
301
|
+
{
|
|
302
|
+
name: 'offset',
|
|
303
|
+
options: {
|
|
304
|
+
offset: [0, 4],
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
{ name: 'eventListeners', enabled: true },
|
|
308
|
+
],
|
|
309
|
+
});
|
|
310
|
+
popperInstance.update();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function hide() {
|
|
314
|
+
if (
|
|
315
|
+
!popupEl.value ||
|
|
316
|
+
popupEl.value.getAttribute('data-show') === null ||
|
|
317
|
+
!popperInstance
|
|
318
|
+
)
|
|
319
|
+
return;
|
|
320
|
+
|
|
321
|
+
unsubscribeClick();
|
|
322
|
+
|
|
323
|
+
if (popupEl.value) {
|
|
324
|
+
popupEl.value.removeAttribute('data-show');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
popperInstance.setOptions({
|
|
328
|
+
modifiers: [{ name: 'eventListeners', enabled: false }],
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function onClickedFilter(filterKey: string) {
|
|
333
|
+
emit('update:filterBy', filterKey);
|
|
334
|
+
hide();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
onMounted(() => {
|
|
338
|
+
if (el.value && popupEl.value) {
|
|
339
|
+
popperInstance = createPopper(el.value, popupEl.value, {
|
|
340
|
+
placement: 'bottom-end',
|
|
341
|
+
modifiers: [{ name: 'eventListeners', enabled: false }],
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
onBeforeUnmount(() => {
|
|
347
|
+
if (popperInstance) {
|
|
348
|
+
popperInstance.destroy();
|
|
349
|
+
}
|
|
350
|
+
popperInstance = null;
|
|
351
|
+
});
|
|
193
352
|
|
|
194
353
|
return {
|
|
354
|
+
el,
|
|
355
|
+
popupEl,
|
|
195
356
|
hoverRowIndex,
|
|
357
|
+
filterOptions,
|
|
196
358
|
headerColumnSpans,
|
|
197
359
|
columnSpans,
|
|
360
|
+
onClickedHeaderCol,
|
|
198
361
|
sort,
|
|
199
362
|
rowClicked,
|
|
200
363
|
rowMouseEntered,
|
|
201
364
|
rowMouseLeaved,
|
|
202
365
|
onMousemoved,
|
|
366
|
+
onClickedFilter,
|
|
203
367
|
};
|
|
204
368
|
},
|
|
205
369
|
});
|
|
@@ -225,15 +389,18 @@
|
|
|
225
389
|
th
|
|
226
390
|
@apply px-2 pb-2 text-base font-normal text-primary whitespace-nowrap
|
|
227
391
|
|
|
228
|
-
&.sortable
|
|
229
|
-
cursor
|
|
392
|
+
&.sortable, &.filterable
|
|
393
|
+
@apply cursor-pointer
|
|
230
394
|
.sort-icon
|
|
231
|
-
opacity
|
|
395
|
+
@apply opacity-0
|
|
232
396
|
transition transform 0.2s ease, opacity 0.1s ease
|
|
233
397
|
&:hover,
|
|
234
398
|
&.sorting
|
|
235
399
|
.sort-icon
|
|
236
|
-
opacity
|
|
400
|
+
@apply opacity-100
|
|
401
|
+
&.filterable
|
|
402
|
+
.filter-icon
|
|
403
|
+
@apply opacity-100
|
|
237
404
|
|
|
238
405
|
tbody
|
|
239
406
|
&.clickable
|
|
@@ -250,5 +417,27 @@
|
|
|
250
417
|
|
|
251
418
|
&.text-right.sortable
|
|
252
419
|
@apply pr-8
|
|
420
|
+
|
|
421
|
+
.table-header-filter-popup
|
|
422
|
+
@apply z-10 flex border border-primary-light rounded overflow-hidden invisible pointer-events-none
|
|
423
|
+
|
|
424
|
+
&[data-show]
|
|
425
|
+
@apply visible pointer-events-auto
|
|
426
|
+
|
|
427
|
+
&-content
|
|
428
|
+
@apply flex flex-col
|
|
429
|
+
|
|
430
|
+
.table-header-filter-item
|
|
431
|
+
@apply inline-flex justify-center p-2 bg-white text-sm text-gray-3
|
|
432
|
+
@apply s('hover:bg-bg-middle')
|
|
433
|
+
|
|
434
|
+
&.active
|
|
435
|
+
@apply bg-bg text-primary
|
|
436
|
+
|
|
437
|
+
.icon
|
|
438
|
+
@apply text-primary
|
|
439
|
+
|
|
440
|
+
.icon
|
|
441
|
+
@apply text-base leading-none
|
|
253
442
|
</style>
|
|
254
443
|
|