@mptw/skylens-ui 1.2.13 → 1.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,409 +1,251 @@
1
1
  <template lang="pug">
2
- div(ref="el")
3
- .table(
4
- :class="{ 'table-responsive': responsive }"
5
- @mousemove="onMousemoved"
6
- )
7
- table
8
- thead
9
- slot(name="thead")
2
+ div
3
+ .table(
4
+ :class="{ 'table-responsive': responsive }"
5
+ @mousemove="onMousemoved"
6
+ )
7
+ table
8
+ thead
10
9
  tr
11
- template(v-for="field, index in fields")
10
+ template(v-for="field, index in fields" :key="field.key")
12
11
  slot(
13
- v-if="$slots[`${field.key}-header`]"
14
- :name="`${field.key}-header`"
15
- :field="field"
16
- :index="index"
17
- )
12
+ v-if="$slots[`${field.key}-header`]"
13
+ :name="`${field.key}-header`"
14
+ :field="field"
15
+ :index="index"
16
+ )
18
17
  th(
19
18
  v-else
20
19
  :style="field._style"
21
- :class="[field._class, { sortable: field.sortable, sorting: field.key === sortBy, filterable: field.filterable, filtering: filterBy !== '' }]"
20
+ :class="[field._class, { sortable: field.sortable, sorting: field.key === sortBy, 'sort-desc': sortDesc}]"
22
21
  :colspan="headerColumnSpans[index]"
23
- @click.prevent="onClickedHeaderCol(field.key)"
24
22
  :key="field.key"
25
- :name="`${field.key}-header`"
26
23
  )
27
- .inline-flex.items-center
28
- SLElementWithTitle(:title="field.tooltip")
29
- span {{ field.label ? field.label : '&nbsp;' }}
30
- SLIcon.sort-icon(
31
- v-if="field.sortable"
32
- class="ml-0.5"
33
- :icon="sortDesc ? 'descending' : 'ascending'"
34
- )
35
- SLIcon.filter-icon(
36
- v-if="field.filterable"
37
- class="ml-0.5"
38
- :icon="filterBy !== '' ? 'filter-on' : 'filter'"
39
- )
40
- tbody(:class="{ 'hoverable': hoverableRows, 'clickable': clickableRows }")
41
- template(v-for="(item, itemIndex) in items" :key="itemIndex")
42
- tr(
43
- @click="rowClicked(item, itemIndex, $event)"
44
- @mouseenter="rowMouseEntered(item, itemIndex, $event)"
45
- @mouseleave="rowMouseLeaved(item, itemIndex, $event)"
46
- :tabindex="clickableRows ? 0 : undefined"
47
- :class="[hoverableRows ? `hover:${tdHoverColor}` : '', `${hoverRowIndex === itemIndex ? tdHoverColor : ''}`, { hover: hoverRowIndex === itemIndex }]"
48
- )
49
- template(v-for="(field, index) in fields")
50
- slot(
51
- v-if="$slots[field.key]"
52
- :name="field.key"
53
- :item="item"
54
- :index="itemIndex"
55
- ).border-border
56
- td(
57
- v-else
58
- :colspan="columnSpans[index]"
59
- :key="`${itemIndex}-${field.key}`"
60
- :class='tdBorderColor'
61
- ) {{ item[field.key] }}
62
- tr(v-if="items.length === 0")
63
- td(:colspan="fields.length")
64
- div(v-if="loading", style="min-height: 200px")
65
- .flex.flex-col.items-center.py-8.px-4.space-y-3(v-else)
66
- img(src="images/commons/empty-box.png")
67
- .text-lg.text-primary-light {{ noItems }}
68
- .text-sm.text-gray-3(v-if="reason") 可能的原因:{{ reason }}
69
- SLLoading(
70
- v-if="loading"
71
- :boundaries="[{ sides: ['top'], query: 'td' }, { sides: ['bottom'], query: 'tbody' }]"
72
- )
73
- .table-header-filter-popup(ref="popupEl")
74
- .table-header-filter-popup-content
75
- a.table-header-filter-item(
76
- v-for="option in filterOptions"
77
- href="javascript:;"
78
- :class="{ active: filterBy === option.key }"
79
- :key="option.key"
80
- @click="onClickedFilter(option.key)"
81
- )
82
- .inline-flex.items-center.space-x-1
83
- SLIcon(
84
- v-if="option.icon"
85
- :icon="option.icon"
86
- :class="option._iconClass"
87
- )
88
- span(v-if="option.label") {{ option.label }}
89
- </template>
90
-
91
- <script lang="ts">
92
- import type { PropType } from "vue";
93
- import { fromEvent, Subscription } from "rxjs";
94
- import { createPopper } from "@popperjs/core";
95
- import type { Instance as PopperInstance } from "@popperjs/core";
96
- import type ITableField from "~ui/interface/ITableField";
97
-
98
- export default defineComponent({
99
- name: "SLTable",
100
- components: {},
101
- props: {
102
- items: {
103
- type: Array,
104
- default: () => [],
105
- },
106
- fields: {
107
- type: Array as PropType<Array<ITableField>>,
108
- default: () => [],
109
- },
110
- sortBy: {
111
- type: String,
112
- default: "",
113
- },
114
- sortDesc: {
115
- type: Boolean,
116
- default: false,
117
- },
118
- filterBy: {
119
- type: String,
120
- default: "",
121
- },
122
- hoverableRows: {
123
- type: Boolean,
124
- default: false,
125
- },
126
- clickableRows: {
127
- type: Boolean,
128
- default: false,
129
- },
130
- loading: {
131
- type: Boolean,
132
- default: false,
133
- },
134
- responsive: {
135
- type: Boolean,
136
- default: true,
137
- },
138
- tdBorderColor: {
139
- type: String,
140
- default: "border-border",
141
- },
142
- tdHoverColor: {
143
- type: String,
144
- default: "bg-bg",
145
- },
146
- noItems: {
147
- type: String,
148
- default: "無資料",
24
+ .inline-flex.items-center.space-x-1
25
+ component(:is='field.sortable ? "a" : "div"', href='javascript:;' class='inline-flex items-center', @click="field.sortable && sort(field.key)")
26
+ span {{ field.label }}
27
+ SLIcon.sort-icon.ml-2(v-if="field.sortable" icon="sort-up")
28
+ SLElementWithTitle(v-if='field.info', :title="field.info")
29
+ SLIcon.text-primary(icon='info-circle')
30
+ tbody(:class="{ 'hoverable': hoverableRows, 'clickable': clickableRows }")
31
+ template(v-for="(item, itemIndex) in bodyHeaderItems" :key="itemIndex")
32
+ tr.body-header-row(
33
+ @click="rowClicked(item, itemIndex, $event)"
34
+ @mouseenter="rowMouseEntered(item, itemIndex, $event)"
35
+ @mouseleave="rowMouseLeaved(item, itemIndex, $event)"
36
+ :tabindex="clickableRows ? 0 : undefined"
37
+ :class="[hoverableRows ? `hover:${tdHoverColor}` : '', `${hoverRowIndex === itemIndex ? tdHoverColor : ''}`, { hover: hoverRowIndex === itemIndex }]"
38
+ )
39
+ template(v-for="(field, index) in fields" :key="field.key")
40
+ slot(
41
+ v-if="$slots[`body-header-${field.key}`]"
42
+ :name="field.key"
43
+ :item="item"
44
+ :index="itemIndex"
45
+ ).border-border
46
+ td(
47
+ v-else
48
+ :colspan="columnSpans[index]"
49
+ :key="`${itemIndex}-${field.key}`"
50
+ :class='[tdBorderColor, field._class]'
51
+ ) {{ item[field.key] }}
52
+ template(v-for="(item, itemIndex) in items" :key="itemIndex")
53
+ tr(
54
+ @click="rowClicked(item, itemIndex, $event)"
55
+ @mouseenter="rowMouseEntered(item, itemIndex, $event)"
56
+ @mouseleave="rowMouseLeaved(item, itemIndex, $event)"
57
+ :tabindex="clickableRows ? 0 : undefined"
58
+ :class="[hoverableRows ? `hover:${tdHoverColor}` : '', `${hoverRowIndex === itemIndex ? tdHoverColor : ''}`, { hover: hoverRowIndex === itemIndex }]"
59
+ )
60
+ template(v-for="(field, index) in fields" :key="field.key")
61
+ slot(
62
+ v-if="$slots[field.key]"
63
+ :name="field.key"
64
+ :item="item"
65
+ :index="itemIndex"
66
+ ).border-border
67
+ td(
68
+ v-else
69
+ :colspan="columnSpans[index]"
70
+ :key="`${itemIndex}-${field.key}`"
71
+ :class='[tdBorderColor, field._class]'
72
+ ) {{ item[field.key] }}
73
+ tr(v-if="items.length === 0")
74
+ td(:colspan="fields.length")
75
+ .my-28.text-base.text-gray-2.text-center {{ noItems }}
76
+ SLLoading(
77
+ v-if="loading"
78
+ :boundaries="[{ sides: ['top'], query: 'td' }, { sides: ['bottom'], query: 'tbody' }]"
79
+ )
80
+ </template>
81
+
82
+ <script lang="ts">
83
+ import type { PropType } from 'vue';
84
+ import type ITableField from '@/interface/ITableField';
85
+
86
+ export default defineComponent({
87
+ name: 'SLTable',
88
+ components: {
149
89
  },
150
- reason: {
151
- type: String,
152
- default: "",
90
+ props: {
91
+ items: {
92
+ type: Array,
93
+ default: () => [],
94
+ },
95
+ bodyHeaderItems: {
96
+ type: Array,
97
+ default: () => [],
98
+ },
99
+ fields: {
100
+ type: Array as PropType<Array<ITableField>>,
101
+ default: () => [],
102
+ },
103
+ sortBy: {
104
+ type: String,
105
+ default: '',
106
+ },
107
+ sortDesc: {
108
+ type: Boolean,
109
+ default: false,
110
+ },
111
+ hoverableRows: {
112
+ type: Boolean,
113
+ default: false,
114
+ },
115
+ clickableRows: {
116
+ type: Boolean,
117
+ default: false,
118
+ },
119
+ loading: {
120
+ type: Boolean,
121
+ default: false,
122
+ },
123
+ responsive: {
124
+ type: Boolean,
125
+ default: true,
126
+ },
127
+ tdBorderColor: {
128
+ type: String,
129
+ default: 'border-border',
130
+ },
131
+ tdHoverColor: {
132
+ type: String,
133
+ default: 'bg-bg',
134
+ },
135
+ noItems: {
136
+ type: String,
137
+ default: 'No Items',
138
+ },
153
139
  },
154
- },
155
- emits: [
156
- "update:sortDesc",
157
- "update:sortBy",
158
- "update:filterBy",
159
- "row-clicked",
160
- "row-mouseentered",
161
- "row-mouseleaved",
162
- "table-mousemoved",
163
- ],
164
- setup(props, { emit }) {
165
- const { fields, sortBy, sortDesc, clickableRows, hoverableRows } =
166
- toRefs(props);
167
-
168
- const el = shallowRef<HTMLElement | null>(null);
169
- const popupEl = shallowRef<HTMLElement | null>(null);
170
- const hoverRowIndex = ref(-1);
171
- const filterOptions = ref<
172
- {
173
- key: string;
174
- label?: string;
175
- icon?: string;
176
- }[]
177
- >([]);
178
-
179
- let popperInstance: PopperInstance | null = null;
180
- let clickSubscriber: Subscription | null = null;
181
- let filterHeaderCol: HTMLElement | null = null;
182
-
183
- const headerColumnSpans = computed(() => {
184
- return fields.value.map((field) => {
185
- if (!field.colspan) return 1;
186
- return field.colspan;
140
+ setup(props, { emit }) {
141
+ const { fields, sortBy, sortDesc, clickableRows, hoverableRows } =
142
+ toRefs(props);
143
+
144
+ const hoverRowIndex = ref(-1);
145
+
146
+ const headerColumnSpans = computed(() => {
147
+ return fields.value.map((field) => {
148
+ if (!field.colspan) return 1;
149
+ return field.colspan;
150
+ });
187
151
  });
188
- });
189
- const columnSpans = computed(() => {
190
- return fields.value.map((field) => {
191
- if (!field.col) return 1;
192
- if (!field.col.span) return 1;
193
- return field.col.span;
152
+ const columnSpans = computed(() => {
153
+ return fields.value.map((field) => {
154
+ if (!field.col) return 1;
155
+ if (!field.col.span) return 1;
156
+ return field.col.span;
157
+ });
194
158
  });
195
- });
196
-
197
- function sort(fieldKey: string) {
198
- if (fieldKey === sortBy.value) {
199
- emit("update:sortDesc", !sortDesc.value);
200
- } else {
201
- emit("update:sortBy", fieldKey);
202
- }
203
- }
204
-
205
- function onClickedHeaderCol(fieldKey: string) {
206
- const field = fields.value.find((f) => f.key === fieldKey);
207
- if (!field || (!field.sortable && !field.filterable)) return;
208
-
209
- if (field.sortable) {
210
- sort(field.key);
211
- }
212
-
213
- if (field.filterable) {
214
- filterOptions.value = [...field.filterOptions];
215
- filterHeaderCol = el.value.querySelector(`th[name=${field.key}-header]`)
216
- .children[0] as HTMLElement;
217
- show();
218
- }
219
- }
220
-
221
- function rowClicked(item: any, index: number, e: Event) {
222
- if (!clickableRows.value) return;
223
-
224
- emit("row-clicked", item, index, e);
225
- e.preventDefault();
226
- }
227
-
228
- function rowMouseEntered(item: any, index: number, e: Event) {
229
- if (!hoverableRows.value) return;
230
-
231
- hoverRowIndex.value = index;
232
- emit("row-mouseentered", item, index, e);
233
- }
234
-
235
- function rowMouseLeaved(item: any, index: number, e: Event) {
236
- if (!hoverableRows.value) return;
237
-
238
- hoverRowIndex.value = -1;
239
- emit("row-mouseleaved", item, index, e);
240
- }
241
-
242
- function onMousemoved(e: Event) {
243
- if (!hoverableRows.value) return;
244
-
245
- emit("table-mousemoved", e);
246
- }
247
-
248
- function subscribeClick() {
249
- clickSubscriber = fromEvent(window, "click").subscribe((e) => {
250
- const { target } = e;
251
- if (
252
- filterHeaderCol &&
253
- popupEl.value &&
254
- !filterHeaderCol.contains(target as HTMLElement) &&
255
- !popupEl.value.contains(target as HTMLElement)
256
- ) {
257
- hide();
159
+
160
+ function sort(fieldKey: string) {
161
+ if (fieldKey === sortBy.value) {
162
+ emit('update:sortDesc', !sortDesc.value);
163
+ } else {
164
+ emit('update:sortBy', fieldKey);
258
165
  }
259
- });
260
- }
261
-
262
- function unsubscribeClick() {
263
- if (clickSubscriber) {
264
- clickSubscriber.unsubscribe();
265
166
  }
266
- clickSubscriber = null;
267
- }
268
-
269
- function show() {
270
- if (!popupEl.value || !filterHeaderCol || !popperInstance) return;
271
-
272
- popupEl.value.setAttribute("data-show", "");
273
-
274
- subscribeClick();
275
- popperInstance.state.elements.reference = filterHeaderCol;
276
- popperInstance.setOptions({
277
- modifiers: [
278
- {
279
- name: "offset",
280
- options: {
281
- offset: [0, 4],
282
- },
283
- },
284
- { name: "eventListeners", enabled: true },
285
- ],
286
- });
287
- popperInstance.update();
288
- }
289
-
290
- function hide() {
291
- if (
292
- !popupEl.value ||
293
- popupEl.value.getAttribute("data-show") === null ||
294
- !popperInstance
295
- )
296
- return;
297
-
298
- unsubscribeClick();
299
-
300
- if (popupEl.value) {
301
- popupEl.value.removeAttribute("data-show");
167
+
168
+ function rowClicked(item: any, index: number, e: Event) {
169
+ if (!clickableRows.value) return;
170
+
171
+ emit('row-clicked', item, index, e);
302
172
  }
303
-
304
- popperInstance.setOptions({
305
- modifiers: [{ name: "eventListeners", enabled: false }],
306
- });
307
- }
308
-
309
- function onClickedFilter(filterKey: string) {
310
- emit("update:filterBy", filterKey);
311
- hide();
312
- }
313
-
314
- onMounted(() => {
315
- if (el.value && popupEl.value) {
316
- popperInstance = createPopper(el.value, popupEl.value, {
317
- placement: "bottom-end",
318
- modifiers: [{ name: "eventListeners", enabled: false }],
319
- });
173
+
174
+ function rowMouseEntered(item: any, index: number, e: Event) {
175
+ if (!hoverableRows.value) return;
176
+
177
+ hoverRowIndex.value = index;
178
+ emit('row-mouseentered', item, index, e);
320
179
  }
321
- });
322
-
323
- onBeforeUnmount(() => {
324
- if (popperInstance) {
325
- popperInstance.destroy();
180
+
181
+ function rowMouseLeaved(item: any, index: number, e: Event) {
182
+ if (!hoverableRows.value) return;
183
+
184
+ hoverRowIndex.value = -1;
185
+ emit('row-mouseleaved', item, index, e);
326
186
  }
327
- popperInstance = null;
328
- });
329
-
330
- return {
331
- el,
332
- popupEl,
333
- hoverRowIndex,
334
- filterOptions,
335
- headerColumnSpans,
336
- columnSpans,
337
- onClickedHeaderCol,
338
- rowClicked,
339
- rowMouseEntered,
340
- rowMouseLeaved,
341
- onMousemoved,
342
- onClickedFilter,
343
- };
344
- },
345
- });
346
- </script>
347
-
348
- <style lang="stylus">
349
- .table
350
- @apply block relative w-full overflow-hidden
351
-
352
- &.table-responsive
353
- @apply overflow-auto
354
-
187
+
188
+ function onMousemoved(e: Event) {
189
+ if (!hoverableRows.value) return;
190
+
191
+ emit('table-mousemoved', e);
192
+ }
193
+
194
+ return {
195
+ hoverRowIndex,
196
+ headerColumnSpans,
197
+ columnSpans,
198
+ sort,
199
+ rowClicked,
200
+ rowMouseEntered,
201
+ rowMouseLeaved,
202
+ onMousemoved,
203
+ };
204
+ },
205
+ });
206
+ </script>
207
+
208
+ <style lang="stylus">
209
+ .table
210
+ @apply block relative w-full overflow-hidden
211
+
212
+ &.table-responsive
213
+ @apply overflow-auto
214
+
215
+ table
216
+ @apply table-auto
217
+
355
218
  table
356
- @apply table-auto
357
-
358
- table
359
- @apply w-full table-fixed
360
-
361
- thead
362
- tr
363
- @apply border-b border-primary-light bg-white
364
-
365
- th
366
- @apply px-2 pb-2 text-sm font-normal text-primary whitespace-nowrap
367
- &.sortable, &.filterable
368
- @apply cursor-pointer
369
- .sort-icon, .filter-icon
370
- @apply opacity-0
371
- &:hover,
372
- &.sorting
219
+ @apply w-full table-fixed
220
+
221
+ thead
222
+ tr
223
+ @apply border-b border-primary-light bg-white
224
+
225
+ th
226
+ @apply px-2 pb-2 text-base font-normal text-primary whitespace-nowrap
227
+ &.sortable
228
+ cursor pointer
229
+ &.sort-desc .sort-icon
230
+ transform rotate(180deg)
373
231
  .sort-icon
374
- @apply opacity-100
375
- &.filterable
376
- .filter-icon
377
- @apply opacity-100
378
-
379
- tbody
380
- &.clickable
232
+ opacity 0
233
+ transition transform 0.2s ease, opacity 0.1s ease
234
+ &:hover,
235
+ &.sorting
236
+ .sort-icon
237
+ opacity 1
238
+
239
+ tbody
240
+ &.clickable
241
+ tr
242
+ @apply cursor-pointer
243
+
381
244
  tr
382
- @apply cursor-pointer
383
-
384
- tr
385
- td
386
- @apply p-2 border-b text-sm text-gray-2
387
-
388
- .table-header-filter-popup
389
- @apply z-10 flex border border-primary-light rounded overflow-hidden invisible pointer-events-none
390
-
391
- &[data-show]
392
- @apply visible pointer-events-auto
393
-
394
- &-content
395
- @apply flex flex-col
396
-
397
- .table-header-filter-item
398
- @apply inline-flex justify-center p-2 bg-white text-sm text-gray-3
399
- @apply s('hover:bg-bg-middle')
400
-
401
- &.active
402
- @apply bg-bg text-primary
403
-
404
- .icon
405
- @apply text-primary
406
-
407
- .icon
408
- @apply text-base leading-none
409
- </style>
245
+ &.body-header-row
246
+ td
247
+ @apply bg-bg-middle text-lg font-bold shadow-inline
248
+ td
249
+ @apply p-2 border-b border-opacity-50 text-base text-gray-2
250
+ </style>
251
+
@@ -131,7 +131,7 @@ export default defineComponent({
131
131
  let clickSubscriber: any = null;
132
132
 
133
133
  const queryProxy = computed({
134
- get() { query.value || selfQuery.value },
134
+ get() { return query.value || selfQuery.value },
135
135
  set(newValue: string) {
136
136
  emit('update:query', newValue);
137
137
  selfQuery.value = newValue;
@@ -1,13 +1,15 @@
1
+ import type { StyleValue } from "vue";
2
+
1
3
  export default interface ITableField {
2
4
  key: string;
3
5
  label: string;
4
- _style?: string | Object;
6
+ _style?: StyleValue;
5
7
  _class?: string | Object | Array<string | Object>;
6
8
  sortable?: boolean;
7
9
  filterable?: boolean;
8
10
  colspan?: number;
9
11
  col?: {
10
- span?:number,
12
+ span?: number,
11
13
  };
12
14
  filterOptions?: {
13
15
  key: string;
@@ -16,4 +18,5 @@ export default interface ITableField {
16
18
  _iconClass?: string;
17
19
  }[];
18
20
  tooltip?: string;
21
+ info?: string;
19
22
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mptw/skylens-ui",
3
3
  "type": "module",
4
- "version": "1.2.13",
4
+ "version": "1.2.15",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "dev": "nuxi dev .playground",