stk-table-vue 0.0.2 → 0.2.0

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.
@@ -15,15 +15,15 @@
15
15
  'border-body-v': props.bordered === 'body-v',
16
16
  stripe: props.stripe,
17
17
  }"
18
- :style="virtual && { '--row-height': virtualScroll.rowHeight + 'px' }"
18
+ :style="
19
+ virtual && {
20
+ '--row-height': virtualScroll.rowHeight + 'px',
21
+ '--header-row-height': (props.headerRowHeight || props.rowHeight) + 'px',
22
+ }
23
+ "
19
24
  @scroll="onTableScroll"
20
25
  @wheel="onTableWheel"
21
26
  >
22
- <!-- 横向滚动时固定列的阴影,TODO: 覆盖一层在整个表上,使用linear-gradient 绘制阴影-->
23
- <!-- <div
24
- :class="showFixedLeftShadow && 'stk-table-fixed-left-col-box-shadow'"
25
- :style="{ width: fixedLeftColWidth + 'px' }"
26
- ></div> -->
27
27
  <!-- 这个元素用于虚拟滚动时,撑开父容器的高度 (已弃用,因为滚动条拖动过快,下方tr为加载出来时,会导致表头sticky闪动)
28
28
  <div
29
29
  v-if="virtual"
@@ -67,8 +67,7 @@
67
67
  col.dataIndex === sortCol && sortOrderIndex !== 0 && 'sorter-' + sortSwitchOrder[sortOrderIndex],
68
68
  showHeaderOverflow ? 'text-overflow' : '',
69
69
  col.headerClassName,
70
- col.fixed ? 'fixed-cell' : '',
71
- col.fixed ? 'fixed-cell--' + col.fixed : '',
70
+ getFixedColClass(col),
72
71
  ]"
73
72
  @click="
74
73
  e => {
@@ -83,7 +82,7 @@
83
82
  <div class="table-header-cell-wrapper">
84
83
  <component :is="col.customHeaderCell" v-if="col.customHeaderCell" :col="col" />
85
84
  <template v-else>
86
- <slot name="tableHeader" :column="col">
85
+ <slot name="tableHeader" :col="col">
87
86
  <span class="table-header-title">{{ col.title }}</span>
88
87
  </slot>
89
88
  </template>
@@ -91,14 +90,12 @@
91
90
  <!-- 排序图图标 -->
92
91
  <span v-if="col.sorter" class="table-header-sorter">
93
92
  <svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 16 16">
94
- <g id="sort-btn">
95
- <polygon id="arrow-up" fill="#757699" points="8 2 4.8 6 11.2 6"></polygon>
96
- <polygon
97
- id="arrow-down"
98
- transform="translate(8, 12) rotate(-180) translate(-8, -12) "
99
- points="8 10 4.8 14 11.2 14"
100
- ></polygon>
101
- </g>
93
+ <polygon class="arrow-up" fill="#757699" points="8 2 4.8 6 11.2 6"></polygon>
94
+ <polygon
95
+ class="arrow-down"
96
+ transform="translate(8, 12) rotate(-180) translate(-8, -12) "
97
+ points="8 10 4.8 14 11.2 14"
98
+ ></polygon>
102
99
  </svg>
103
100
  </span>
104
101
  <!-- 列宽拖动handler -->
@@ -146,7 +143,7 @@
146
143
  :key="rowKey ? rowKeyGen(row) : i"
147
144
  :data-row-key="rowKey ? rowKeyGen(row) : i"
148
145
  :class="{
149
- active: rowKey ? rowKeyGen(row) === (currentItem && rowKeyGen(currentItem)) : row === currentItem,
146
+ active: rowKey ? rowKeyGen(row) === rowKeyGen(currentItem) : row === currentItem,
150
147
  hover: rowKey ? rowKeyGen(row) === currentHover : row === currentHover,
151
148
  [rowClassName(row, i)]: true,
152
149
  }"
@@ -164,12 +161,7 @@
164
161
  v-for="col in virtualX_columnPart"
165
162
  :key="col.dataIndex"
166
163
  :data-index="col.dataIndex"
167
- :class="[
168
- col.className,
169
- showOverflow ? 'text-overflow' : '',
170
- col.fixed ? 'fixed-cell' : '',
171
- col.fixed ? 'fixed-cell--' + col.fixed : '',
172
- ]"
164
+ :class="[col.className, showOverflow ? 'text-overflow' : '', getFixedColClass(col)]"
173
165
  :style="getCellStyle(2, col)"
174
166
  @click="e => onCellClick(e, row, col)"
175
167
  >
@@ -188,7 +180,7 @@
188
180
  </div>
189
181
  </template>
190
182
 
191
- <script setup lang="ts">
183
+ <script setup lang="tsx">
192
184
  /**
193
185
  * @author JA+
194
186
  * 不支持低版本浏览器非虚拟滚动表格的表头固定,列固定,因为会卡。
@@ -198,9 +190,11 @@
198
190
  * [] highlight-row 颜色不能恢复到active的颜色
199
191
  */
200
192
  import { CSSProperties, onMounted, ref, shallowRef, toRaw, watch } from 'vue';
193
+ import { Default_Row_Height } from './const';
201
194
  import { Order, SortOption, StkTableColumn, UniqKey } from './types/index';
202
195
  import { useAutoResize } from './useAutoResize';
203
196
  import { useColResize } from './useColResize';
197
+ import { useFixedCol } from './useFixedCol';
204
198
  import { useFixedStyle } from './useFixedStyle';
205
199
  import { useHighlight } from './useHighlight';
206
200
  import { useKeyboardArrowScroll } from './useKeyboardArrowScroll';
@@ -230,14 +224,16 @@ const props = withDefaults(
230
224
  theme?: 'light' | 'dark';
231
225
  /** 行高 */
232
226
  rowHeight?: number;
227
+ /** 表头行高。default = rowHeight */
228
+ headerRowHeight?: number | null;
233
229
  /** 虚拟滚动 */
234
230
  virtual?: boolean;
235
231
  /** x轴虚拟滚动 */
236
232
  virtualX?: boolean;
237
233
  /** 表格列配置 */
238
- columns?: StkTableColumn<any>[];
234
+ columns?: StkTableColumn<DT>[];
239
235
  /** 表格数据源 */
240
- dataSource?: any[];
236
+ dataSource?: DT[];
241
237
  /** 行唯一键 */
242
238
  rowKey?: UniqKey;
243
239
  /** 列唯一键 */
@@ -285,6 +281,10 @@ const props = withDefaults(
285
281
  * 传入方法表示resize后的回调
286
282
  */
287
283
  autoResize?: boolean | (() => void);
284
+ /** 是否展示固定列阴影。默认不展示。 */
285
+ fixedColShadow?: boolean;
286
+ /** 优化vue2 滚动 */
287
+ optimizeVue2Scroll?: boolean;
288
288
  }>(),
289
289
  {
290
290
  width: '',
@@ -294,7 +294,8 @@ const props = withDefaults(
294
294
  maxWidth: '',
295
295
  headless: false,
296
296
  theme: 'light',
297
- rowHeight: 28,
297
+ rowHeight: Default_Row_Height,
298
+ headerRowHeight: null,
298
299
  virtual: false,
299
300
  virtualX: false,
300
301
  columns: () => [],
@@ -314,43 +315,98 @@ const props = withDefaults(
314
315
  colMinWidth: 10,
315
316
  bordered: true,
316
317
  autoResize: true,
318
+ fixedColShadow: false,
319
+ optimizeVue2Scroll: false,
317
320
  },
318
321
  );
319
322
 
320
323
  const emits = defineEmits<{
321
- /** 排序变更触发 */
324
+ /**
325
+ * 排序变更触发
326
+ * ```(col: StkTableColumn<DT>, order: Order, data: DT[])```
327
+ */
322
328
  (e: 'sort-change', col: StkTableColumn<DT>, order: Order, data: DT[]): void;
323
- /** 一行点击事件 */
329
+ /**
330
+ * 一行点击事件
331
+ * ```(ev: MouseEvent, row: DT)```
332
+ */
324
333
  (e: 'row-click', ev: MouseEvent, row: DT): void;
325
- /** 选中一行触发。ev返回null表示不是点击事件触发的 */
334
+ /**
335
+ * 选中一行触发。ev返回null表示不是点击事件触发的
336
+ * ```(ev: MouseEvent | null, row: DT)```
337
+ */
326
338
  (e: 'current-change', ev: MouseEvent | null, row: DT): void;
327
- /** 行双击事件 */
339
+ /**
340
+ * 行双击事件
341
+ * ```(ev: MouseEvent, row: DT)```
342
+ */
328
343
  (e: 'row-dblclick', ev: MouseEvent, row: DT): void;
329
- /** 表头右键事件 */
344
+ /**
345
+ * 表头右键事件
346
+ * ```(ev: MouseEvent)```
347
+ */
330
348
  (e: 'header-row-menu', ev: MouseEvent): void;
331
- /** 表体行右键点击事件 */
349
+ /**
350
+ * 表体行右键点击事件
351
+ * ```(ev: MouseEvent, row: DT)```
352
+ */
332
353
  (e: 'row-menu', ev: MouseEvent, row: DT): void;
333
- /** 单元格点击事件 */
354
+ /**
355
+ * 单元格点击事件
356
+ * ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>)```
357
+ */
334
358
  (e: 'cell-click', ev: MouseEvent, row: DT, col: StkTableColumn<DT>): void;
335
- /**表头单元格点击事件 */
359
+ /**
360
+ * 表头单元格点击事件
361
+ * ```(ev: MouseEvent, col: StkTableColumn<DT>)```
362
+ */
336
363
  (e: 'header-cell-click', ev: MouseEvent, col: StkTableColumn<DT>): void;
337
- /** 表格滚动事件 */
364
+ /**
365
+ * 表格滚动事件
366
+ * ```(ev: Event, data: { startIndex: number; endIndex: number })```
367
+ */
338
368
  (e: 'scroll', ev: Event, data: { startIndex: number; endIndex: number }): void;
339
- /** 表格横向滚动事件 */
369
+ /**
370
+ * 表格横向滚动事件
371
+ * ```(ev: Event)```
372
+ */
340
373
  (e: 'scroll-x', ev: Event): void;
341
- /** 表头列拖动事件 */
374
+ /**
375
+ * 表头列拖动事件
376
+ * ```(dragStartKey: string, targetColKey: string)```
377
+ */
342
378
  (e: 'col-order-change', dragStartKey: string, targetColKey: string): void;
343
- /** 表头列拖动开始 */
379
+ /**
380
+ * 表头列拖动开始
381
+ * ```(dragStartKey: string)```
382
+ */
344
383
  (e: 'th-drag-start', dragStartKey: string): void;
345
- /** 表头列拖动drop */
384
+ /**
385
+ * 表头列拖动drop
386
+ * ```(targetColKey: string)```
387
+ */
346
388
  (e: 'th-drop', targetColKey: string): void;
389
+ /** v-model:columns col resize 时更新宽度*/
347
390
  (e: 'update:columns', cols: StkTableColumn<DT>[]): void;
348
391
  }>();
349
392
 
393
+ // 仅支持vue3.3+
394
+ // const slots = defineSlots<{
395
+ // /** 表头插槽 */
396
+ // tableHeader(props: { col: StkTableColumn<DT> }): void;
397
+ // /** 空状态插槽 */
398
+ // empty(): void;
399
+ // }>();
400
+
350
401
  const tableContainer = ref<HTMLDivElement>();
351
402
  const colResizeIndicator = ref<HTMLDivElement>();
352
403
  /** 当前选中的一行*/
353
404
  const currentItem = ref<DT | null>(null);
405
+ /**
406
+ * 保存当前选中行的key<br>
407
+ * 原因:vue3 不用ref包dataSource时,row为原始对象,与currentItem(Ref)相比会不相等。
408
+ */
409
+ const currentItemKey = ref<any>(null);
354
410
  /** 当前hover的行 */
355
411
  const currentHover = ref<DT | null>(null);
356
412
 
@@ -376,7 +432,7 @@ const tableHeaders = ref<StkTableColumn<DT>[][]>([]);
376
432
  /** 若有多级表头时,最后一行的tableHeaders.内容是 props.columns 的引用集合 */
377
433
  const tableHeaderLast = ref<StkTableColumn<DT>[]>([]);
378
434
 
379
- const dataSourceCopy = shallowRef([...props.dataSource]);
435
+ const dataSourceCopy = shallowRef<DT[]>([...props.dataSource]);
380
436
 
381
437
  /**高亮帧间隔
382
438
  const highlightStepDuration = Highlight_Color_Change_Freq / 1000 + 's';*/
@@ -431,9 +487,18 @@ if (props.autoResize) {
431
487
 
432
488
  /** 键盘箭头滚动 */
433
489
  useKeyboardArrowScroll(tableContainer, {
490
+ props,
434
491
  scrollTo,
435
492
  virtualScroll,
436
493
  virtualScrollX,
494
+ tableHeaders,
495
+ });
496
+
497
+ /** 固定列处理 */
498
+ const { getFixedColClass, dealFixedColShadow, updateFixedShadow } = useFixedCol({
499
+ props,
500
+ tableContainer,
501
+ tableHeaderLast,
437
502
  });
438
503
 
439
504
  watch(
@@ -453,7 +518,6 @@ watch(
453
518
  console.warn('invalid dataSource');
454
519
  return;
455
520
  }
456
- // dealColumns(val);
457
521
  let needInitVirtualScrollY = false;
458
522
  if (dataSourceCopy.value.length !== val.length) {
459
523
  needInitVirtualScrollY = true;
@@ -467,18 +531,22 @@ watch(
467
531
  const column = tableHeaderLast.value.find(it => it.dataIndex === sortCol.value);
468
532
  onColumnSort(column, false);
469
533
  }
534
+ updateFixedShadow();
470
535
  },
471
536
  {
472
537
  deep: false,
473
538
  },
474
539
  );
540
+
541
+ watch(() => props.fixedColShadow, dealFixedColShadow);
542
+
475
543
  onMounted(() => {
476
544
  initVirtualScroll();
545
+ updateFixedShadow();
477
546
  });
478
547
 
479
548
  /**
480
549
  * 处理多级表头
481
- * FIXME: 仅支持到两级表头。不支持多级。
482
550
  */
483
551
  function dealColumns() {
484
552
  // reset
@@ -497,9 +565,10 @@ function dealColumns() {
497
565
  /**
498
566
  * @param arr
499
567
  * @param depth 深度
568
+ * @param parent 父节点引用,用于构建双向链表。
500
569
  * @param parentFixed 父节点固定列继承。
501
570
  */
502
- function flat(arr: StkTableColumn<any>[], depth = 0 /* , parentFixed: 'left' | 'right' | null = null */) {
571
+ function flat(arr: StkTableColumn<DT>[], parent: StkTableColumn<DT> | null, depth = 0 /* , parentFixed: 'left' | 'right' | null = null */) {
503
572
  if (!tableHeaders.value[depth]) {
504
573
  tableHeaders.value[depth] = [];
505
574
  }
@@ -510,11 +579,13 @@ function dealColumns() {
510
579
  // if (parentFixed) {
511
580
  // col.fixed = parentFixed;
512
581
  // }
582
+ // 构建指向父节点的引用
583
+ col.__PARENT__ = parent;
513
584
  /** 一列中的子节点数量 */
514
585
  let colChildrenLen = 1;
515
586
  if (col.children) {
516
587
  // DFS
517
- colChildrenLen = flat(col.children, depth + 1 /* , col.fixed */);
588
+ colChildrenLen = flat(col.children, col, depth + 1 /* , col.fixed */);
518
589
  } else {
519
590
  tempHeaderLast.push(col); // 没有children的列作为colgroup
520
591
  }
@@ -532,15 +603,18 @@ function dealColumns() {
532
603
  });
533
604
  return allChildrenLen;
534
605
  }
535
- flat(copyColumn);
606
+
607
+ flat(copyColumn, null);
536
608
 
537
609
  tableHeaderLast.value = tempHeaderLast;
610
+ dealFixedColShadow();
538
611
  }
539
612
 
540
613
  /**
541
614
  * 行唯一值生成
542
615
  */
543
- function rowKeyGen(row: any) {
616
+ function rowKeyGen(row: DT) {
617
+ if (!row) return;
544
618
  let key = rowKeyGenStore.get(row);
545
619
  if (!key) {
546
620
  key = typeof props.rowKey === 'function' ? props.rowKey(row) : row[props.rowKey];
@@ -629,8 +703,9 @@ function onColumnSort(col?: StkTableColumn<any>, click = true, options: { force?
629
703
  function onRowClick(e: MouseEvent, row: DT) {
630
704
  emits('row-click', e, row);
631
705
  // 选中同一行不触发current-change 事件
632
- if (currentItem.value === row) return;
706
+ if (props.rowKey ? currentItemKey.value === rowKeyGen(row) : currentItem.value === row) return;
633
707
  currentItem.value = row;
708
+ currentItemKey.value = rowKeyGen(row);
634
709
  emits('current-change', e, row);
635
710
  }
636
711
 
@@ -678,29 +753,27 @@ function onTableWheel(e: MouseEvent) {
678
753
  function onTableScroll(e: Event) {
679
754
  if (!e?.target) return;
680
755
 
681
- // 此处可优化,因为访问e.target.scrollXX消耗性能
682
756
  const { scrollTop, scrollLeft } = e.target as HTMLElement;
683
- const isYScroll = scrollTop !== virtualScroll.value.scrollTop;
684
- const isXScroll = scrollLeft !== virtualScrollX.value.scrollLeft;
757
+ const { scrollTop: vScrollTop } = virtualScroll.value;
758
+ const { scrollLeft: vScrollLeft } = virtualScrollX.value;
759
+ const isYScroll = scrollTop !== vScrollTop;
760
+ const isXScroll = scrollLeft !== vScrollLeft;
761
+
685
762
  // 纵向滚动有变化
686
- if (isYScroll) {
687
- virtualScroll.value.scrollTop = scrollTop;
688
- }
689
- if (virtual_on.value) {
763
+ if (isYScroll && virtual_on.value) {
690
764
  updateVirtualScrollY(scrollTop);
691
765
  }
692
766
 
693
767
  // 横向滚动有变化
694
768
  if (isXScroll) {
695
- virtualScrollX.value.scrollLeft = scrollLeft;
696
- }
697
- if (virtualX_on.value) {
698
- updateVirtualScrollX(scrollLeft);
769
+ updateFixedShadow();
770
+ if (virtualX_on.value) {
771
+ updateVirtualScrollX(scrollLeft);
772
+ }
699
773
  }
700
- const data = {
701
- startIndex: virtualScroll.value.startIndex,
702
- endIndex: virtualScroll.value.endIndex,
703
- };
774
+
775
+ const { startIndex, endIndex } = virtualScroll.value;
776
+ const data = { startIndex, endIndex };
704
777
  if (isYScroll) {
705
778
  emits('scroll', e, data);
706
779
  }
@@ -724,6 +797,7 @@ function onTrMouseOver(_e: MouseEvent, row: DT) {
724
797
  function setCurrentRow(rowKey: string, option = { silent: false }) {
725
798
  if (!dataSourceCopy.value.length) return;
726
799
  currentItem.value = dataSourceCopy.value.find(it => rowKeyGen(it) === rowKey);
800
+ currentItemKey.value = rowKeyGen(currentItem.value);
727
801
  if (!option.silent) {
728
802
  emits('current-change', null, currentItem.value);
729
803
  }
@@ -775,16 +849,27 @@ function getTableData() {
775
849
  }
776
850
 
777
851
  defineExpose({
852
+ /** 初始化横向纵向虚拟滚动 */
778
853
  initVirtualScroll,
854
+ /** 初始化横向虚拟滚动 */
779
855
  initVirtualScrollX,
856
+ /** 初始化纵向虚拟滚动 */
780
857
  initVirtualScrollY,
858
+ /** 设置当前选中行 */
781
859
  setCurrentRow,
860
+ /** 设置高亮渐暗单元格 */
782
861
  setHighlightDimCell,
862
+ /** 设置高亮渐暗行 */
783
863
  setHighlightDimRow,
864
+ /** 表格排序列dataIndex */
784
865
  sortCol,
866
+ /** 设置排序 */
785
867
  setSorter,
868
+ /** 重置排序 */
786
869
  resetSorter,
870
+ /** 滚动至 */
787
871
  scrollTo,
872
+ /** 获取表格数据 */
788
873
  getTableData,
789
874
  });
790
875
  </script>
@@ -2,6 +2,7 @@ export const Default_Col_Width = '100';
2
2
 
3
3
  export const Default_Table_Height = 100;
4
4
  export const Default_Table_Width = 200;
5
+ export const Default_Row_Height = 28;
5
6
 
6
7
  /** 高亮背景色 */
7
8
  export const Highlight_Color = {