stk-table-vue 0.8.3 → 0.8.4

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/README.md CHANGED
@@ -12,57 +12,27 @@
12
12
  </p>
13
13
  </p>
14
14
 
15
- > StK Table (Sticky Table) 是一个基于Vue 的高性能虚拟列表组件。
16
- > 用于实时数据展示,数据高亮动效。
15
+ > Stk Table Vue(Sticky Table) is a high-performance virtual list component based on Vue.
16
+ >
17
+ > Used for real-time data display, with data highlighting and dynamic effects
18
+ >
19
+ > Support Vue3 and Vue2.7
17
20
 
18
- > Vue2.7支持引入源码(**ts**)使用。
19
21
 
20
- ## Doc
21
- 文档: [Stk Table Vue 高性能虚拟表格](https://ja-plus.github.io/stk-table-vue/)
22
+ ## Documentation
23
+ ### [Stk Table Vue Official CN](https://ja-plus.github.io/stk-table-vue/)
24
+
22
25
 
23
26
  ## Repo:
24
27
  - [Github](https://github.com/ja-plus/stk-table-vue)
25
28
  - [Gitee](https://gitee.com/japlus/stk-table-vue) 🇨🇳
26
29
 
27
30
  ## Demo
31
+ [<span style="font-size: 16px;font-weight: bold;">Online Demo in stackblitz</span>](https://stackblitz.com/edit/vitejs-vite-ad91hh?file=src%2FDemo%2Findex.vue)
32
+
33
+ ## Compare
34
+ Compare performance with other vue table [vue-table-compare](https://github.com/ja-plus/vue-table-compare)
28
35
 
29
- [<span style="font-size: 16px;font-weight: bold;">Online Demo</span>](https://stackblitz.com/edit/vitejs-vite-ad91hh?file=src%2FDemo%2Findex.vue)
30
-
31
- ## Feature TODO:
32
- * [x] 高亮行,单元格。
33
- - [x] 使用 `Web Animations API` 实现高亮。(`v0.3.4` 变更为默认值)
34
- - [x] 支持配置高亮参数(持续时间,颜色,频率)。(`v0.2.9`)
35
- - [x] `setHighlightDimRow`/`setHighlightCellRow`支持自定义高亮css类名。(`v0.2.9`)
36
- * [x] 虚拟列表。
37
- - [x] 纵向。
38
- - [x] 横向(必须设置列宽)。
39
- - [x] 支持不定行高。(`v0.6.0`)
40
- * [x] 列固定。
41
- - [x] 固定列阴影。
42
- - [x] 多级表头固定列阴影。
43
- - [x] sticky column 动态计算阴影位置。(`v0.4.0`)
44
- * [x] 行展开。(`v0.5.0`)
45
- * [x] 行拖动。(`v0.5.0`)
46
- * [x] 树形。(`v0.7.0`)
47
- * [x] 单元格合并。(`v0.8.0`)
48
- * [] 列筛选。
49
- * [x] 斑马纹。
50
- * [x] 拖动更改列顺序。
51
- * [x] 拖动调整列宽。
52
- * [x] 排序
53
- - [x] 支持配置 `null` | `undefined` 永远排最后。
54
- - [x] 支持配置 string 使用 `String.prototype.localCompare` 排序。
55
- * [x] 多级表头。
56
- - [] 横向虚拟滚动。
57
- * [x] 支持table-layout: fixed 配置。
58
- * [x] 鼠标悬浮在表格上,键盘滚动虚拟表格。
59
- - [x] 键盘 `ArrowUp`/`ArrowDown`/`ArrowLeft`/`ArrowRight`/`PageUp`/ `PageDown` 按键支持。
60
- * [] 非虚拟滚动时,大数据分批加载。
61
- * [x] vue2.7支持(引入源码使用)。
62
- - [x] `props.optimizeVue2Scroll` 优化vue2虚拟滚动流畅度。(`v0.2.0`)
63
- * [x] 支持配置序号列。`StkTableColumn['type']`。(`v0.3.0`)
64
- * [x] `props.cellHover`单元格悬浮样式。(`v0.3.2`)
65
- * [] 惯性滚动优化。
66
36
 
67
37
 
68
38
  ## Usage
@@ -78,17 +48,17 @@ const stkTableRef = useTemplateRef('stkTableRef');
78
48
 
79
49
  // highlight row
80
50
  stkTableRef.value.setHighlightDimRow([rowKey],{
81
- method: 'css'|'animation',// 默认 animation。
82
- className: 'custom-class-name', // method css 时生效。
51
+ method: 'css'|'animation',// default animation。
52
+ className: 'custom-class-name', // method css
83
53
  keyframe: [{backgroundColor:'#aaa'}, {backgroundColor: '#222'}],//same as https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Animations_API/Keyframe_Formats
84
- duration: 2000,// 动画时长。
54
+ duration: 2000,。
85
55
  });
86
56
  // highlight cell
87
57
  stkTableRef.value.setHighlightDimCell(rowKey, colDataIndex, {
88
58
  method: 'css'|'animation',
89
- className:'custom-class-name', // method css 时生效。
90
- keyframe: [{backgroundColor:'#aaa'}, {backgroundColor: '#222'}], // method animation 时生效。
91
- duration: 2000,// 动画时长。
59
+ className:'custom-class-name', // method css
60
+ keyframe: [{backgroundColor:'#aaa'}, {backgroundColor: '#222'}], // method animation
61
+ duration: 2000,。
92
62
  });
93
63
 
94
64
  const columns = [
@@ -114,10 +84,7 @@ const dataSource = [
114
84
  ```
115
85
 
116
86
  ### Vue2.7 Usage
117
- [在vue2中使用](https://ja-plus.github.io/stk-table-vue/main/start/vue2-usage.html)
118
-
119
- ## Notice
120
- 注意,组件会改动 `props.columns` 中的对象。如果多个组件 `columns` 数组元素存在引用同一个 `StkTableColumn` 对象。则考虑赋值 `columns` 前进行深拷贝。
87
+ [Vue2.7 Usage](https://ja-plus.github.io/stk-table-vue/main/start/vue2-usage.html)
121
88
 
122
89
  ## API
123
90
  ### Props
@@ -136,7 +103,7 @@ const dataSource = [
136
103
  [StkTableColumn 列配置](https://ja-plus.github.io/stk-table-vue/main/api/stk-table-column.html)
137
104
 
138
105
  ### setHighlightDimCell & setHighlightDimRow
139
- [高亮使用文档](https://ja-plus.github.io/stk-table-vue/main/api/expose.html#sethighlightdimcell)
106
+ [Highlight 高亮](https://ja-plus.github.io/stk-table-vue/main/api/expose.html#sethighlightdimcell)
140
107
 
141
108
 
142
109
  ### Example
@@ -209,4 +176,4 @@ const dataSource = [
209
176
 
210
177
 
211
178
  ## Other
212
- * `$*$` 兼容注释
179
+ * `$*$`
@@ -0,0 +1,14 @@
1
+ import { ShallowRef } from 'vue';
2
+ import { PrivateStkTableColumn, RowKeyGen, UniqKey } from './types';
3
+
4
+ type Options = {
5
+ props: any;
6
+ tableHeaderLast: ShallowRef<PrivateStkTableColumn<any>[]>;
7
+ rowKeyGen: RowKeyGen;
8
+ dataSourceCopy: ShallowRef<any[]>;
9
+ };
10
+ export declare function useMaxRowSpan({ props, tableHeaderLast, rowKeyGen, dataSourceCopy }: Options): {
11
+ maxRowSpan: Map<UniqKey, number>;
12
+ updateMaxRowSpan: () => void;
13
+ };
14
+ export {};
@@ -1,13 +1,14 @@
1
1
  import { ShallowRef } from 'vue';
2
2
  import { ColKeyGen, MergeCellsParam, PrivateStkTableColumn, RowKeyGen, UniqKey } from './types';
3
3
 
4
- export declare function useMergeCells({ props, tableHeaderLast, rowKeyGen, colKeyGen, virtual_dataSourcePart, }: {
4
+ type Options = {
5
5
  props: any;
6
6
  tableHeaderLast: ShallowRef<PrivateStkTableColumn<any>[]>;
7
7
  rowKeyGen: RowKeyGen;
8
8
  colKeyGen: ColKeyGen;
9
9
  virtual_dataSourcePart: ShallowRef<any[]>;
10
- }): {
10
+ };
11
+ export declare function useMergeCells({ props, tableHeaderLast, rowKeyGen, colKeyGen, virtual_dataSourcePart, }: Options): {
11
12
  hiddenCellMap: import('vue').Ref<Record<UniqKey, Set<UniqKey>>, Record<UniqKey, Set<UniqKey>>>;
12
13
  mergeCellsWrapper: (row: MergeCellsParam<any>["row"], col: MergeCellsParam<any>["col"], rowIndex: MergeCellsParam<any>["rowIndex"], colIndex: MergeCellsParam<any>["colIndex"]) => {
13
14
  colspan?: number;
@@ -18,3 +19,4 @@ export declare function useMergeCells({ props, tableHeaderLast, rowKeyGen, colKe
18
19
  activeMergedCells: import('vue').Ref<Set<string> & Omit<Set<string>, keyof Set<any>>, Set<string> | (Set<string> & Omit<Set<string>, keyof Set<any>>)>;
19
20
  updateActiveMergedCells: (clear?: boolean) => void;
20
21
  };
22
+ export {};
@@ -9,6 +9,7 @@ type Option<DT extends Record<string, any>> = {
9
9
  tableHeaderLast: ShallowRef<PrivateStkTableColumn<DT>[]>;
10
10
  tableHeaders: ShallowRef<PrivateStkTableColumn<DT>[][]>;
11
11
  rowKeyGen: RowKeyGen;
12
+ maxRowSpan: Map<UniqKey, number>;
12
13
  };
13
14
  /** 暂存纵向虚拟滚动的数据 */
14
15
  export type VirtualScrollStore = {
@@ -45,11 +46,11 @@ export type VirtualScrollXStore = {
45
46
  scrollLeft: number;
46
47
  };
47
48
  /**
48
- * 虚拟滚动
49
+ * virtual scroll
49
50
  * @param param0
50
51
  * @returns
51
52
  */
52
- export declare function useVirtualScroll<DT extends Record<string, any>>({ props, tableContainerRef, trRef, dataSourceCopy, tableHeaderLast, tableHeaders, rowKeyGen, }: Option<DT>): {
53
+ export declare function useVirtualScroll<DT extends Record<string, any>>({ props, tableContainerRef, trRef, dataSourceCopy, tableHeaderLast, tableHeaders, rowKeyGen, maxRowSpan, }: Option<DT>): {
53
54
  virtualScroll: Ref<{
54
55
  containerHeight: number;
55
56
  pageSize: number;
@@ -853,6 +853,107 @@ function useKeyboardArrowScroll(targetElement, { props, scrollTo, virtualScroll,
853
853
  if (!isMouseOver) isMouseOver = true;
854
854
  }
855
855
  }
856
+ function useMaxRowSpan({ props, tableHeaderLast, rowKeyGen, dataSourceCopy }) {
857
+ const maxRowSpan = /* @__PURE__ */ new Map();
858
+ function updateMaxRowSpan() {
859
+ if (!props.virtual) {
860
+ if (maxRowSpan.size) maxRowSpan.clear();
861
+ return;
862
+ }
863
+ maxRowSpan.clear();
864
+ const data = dataSourceCopy.value;
865
+ const columns = tableHeaderLast.value;
866
+ const columnsWithMerge = columns.filter((col) => col.mergeCells);
867
+ if (!columnsWithMerge.length) return;
868
+ const dataLength = data.length;
869
+ const mergeColumnsLength = columnsWithMerge.length;
870
+ for (let rowIndex = 0; rowIndex < dataLength; rowIndex++) {
871
+ const row = data[rowIndex];
872
+ const rowKey = rowKeyGen(row);
873
+ let currentMax = maxRowSpan.get(rowKey) || 0;
874
+ for (let colIndex = 0; colIndex < mergeColumnsLength; colIndex++) {
875
+ const col = columnsWithMerge[colIndex];
876
+ const { rowspan = 1 } = col.mergeCells({ row, col, rowIndex, colIndex }) || {};
877
+ if (rowspan > 1 && rowspan > currentMax) {
878
+ currentMax = rowspan;
879
+ maxRowSpan.set(rowKey, currentMax);
880
+ }
881
+ }
882
+ }
883
+ }
884
+ return {
885
+ maxRowSpan,
886
+ updateMaxRowSpan
887
+ };
888
+ }
889
+ function useMergeCells({
890
+ props,
891
+ tableHeaderLast,
892
+ rowKeyGen,
893
+ colKeyGen,
894
+ virtual_dataSourcePart
895
+ }) {
896
+ const hiddenCellMap = ref({});
897
+ const hoverRowMap = ref({});
898
+ const hoverMergedCells = ref(/* @__PURE__ */ new Set());
899
+ const activeMergedCells = ref(/* @__PURE__ */ new Set());
900
+ watch([virtual_dataSourcePart, tableHeaderLast], () => {
901
+ hiddenCellMap.value = {};
902
+ hoverRowMap.value = {};
903
+ });
904
+ function hideCells(rowKey, startIndex, count, isSelfRow = false, mergeCellKey) {
905
+ for (let i = startIndex; i < startIndex + count; i++) {
906
+ if (!isSelfRow || i !== startIndex) {
907
+ const nextCol = tableHeaderLast.value[i];
908
+ if (!nextCol) break;
909
+ const nextColKey = colKeyGen.value(nextCol);
910
+ if (!hiddenCellMap.value[rowKey]) hiddenCellMap.value[rowKey] = /* @__PURE__ */ new Set();
911
+ hiddenCellMap.value[rowKey].add(nextColKey);
912
+ }
913
+ if (!hoverRowMap.value[rowKey]) hoverRowMap.value[rowKey] = /* @__PURE__ */ new Set();
914
+ hoverRowMap.value[rowKey].add(mergeCellKey);
915
+ }
916
+ }
917
+ function mergeCellsWrapper(row, col, rowIndex, colIndex) {
918
+ if (!col.mergeCells) return;
919
+ let { colspan, rowspan } = col.mergeCells({ row, col, rowIndex, colIndex }) || {};
920
+ colspan = colspan || 1;
921
+ rowspan = rowspan || 1;
922
+ if (colspan === 1 && rowspan === 1) return;
923
+ const rowKey = rowKeyGen(row);
924
+ const colKey = colKeyGen.value(col);
925
+ const curColIndex = tableHeaderLast.value.findIndex((item) => colKeyGen.value(item) === colKey);
926
+ const curRowIndex = virtual_dataSourcePart.value.findIndex((item) => rowKeyGen(item) === rowKey);
927
+ const mergedCellKey = pureCellKeyGen(rowKey, colKey);
928
+ if (curRowIndex === -1) return;
929
+ for (let i = curRowIndex; i < curRowIndex + rowspan; i++) {
930
+ const row2 = virtual_dataSourcePart.value[i];
931
+ if (!row2) break;
932
+ hideCells(rowKeyGen(row2), curColIndex, colspan, i === curRowIndex, mergedCellKey);
933
+ }
934
+ return { colspan, rowspan };
935
+ }
936
+ function updateHoverMergedCells(rowKey) {
937
+ const set = rowKey === void 0 ? null : hoverRowMap.value[rowKey];
938
+ hoverMergedCells.value = set || /* @__PURE__ */ new Set();
939
+ }
940
+ function updateActiveMergedCells(clear) {
941
+ if (!props.rowActive) return;
942
+ if (clear) {
943
+ activeMergedCells.value.clear();
944
+ } else {
945
+ activeMergedCells.value = new Set(hoverMergedCells.value);
946
+ }
947
+ }
948
+ return {
949
+ hiddenCellMap,
950
+ mergeCellsWrapper,
951
+ hoverMergedCells,
952
+ updateHoverMergedCells,
953
+ activeMergedCells,
954
+ updateActiveMergedCells
955
+ };
956
+ }
856
957
  function useRowExpand({ dataSourceCopy, rowKeyGen, emits }) {
857
958
  const expandedKey = "__EXPANDED__";
858
959
  function isExpanded(row, col) {
@@ -1260,7 +1361,8 @@ function useVirtualScroll({
1260
1361
  dataSourceCopy,
1261
1362
  tableHeaderLast,
1262
1363
  tableHeaders,
1263
- rowKeyGen
1364
+ rowKeyGen,
1365
+ maxRowSpan
1264
1366
  }) {
1265
1367
  const tableHeaderHeight = ref(props.headerRowHeight);
1266
1368
  const virtualScroll = ref({
@@ -1465,6 +1567,32 @@ function useVirtualScroll({
1465
1567
  startIndex = Math.floor(sTop / rowHeight);
1466
1568
  endIndex = startIndex + pageSize;
1467
1569
  }
1570
+ if (maxRowSpan.size) {
1571
+ let correctedStartIndex = startIndex;
1572
+ let correctedEndIndex = endIndex;
1573
+ for (let i = 0; i < startIndex; i++) {
1574
+ const row = dataSourceCopyTemp[i];
1575
+ if (!row) continue;
1576
+ const spanEndIndex = i + (maxRowSpan.get(rowKeyGen(row)) || 1);
1577
+ if (spanEndIndex > startIndex) {
1578
+ correctedStartIndex = i;
1579
+ if (spanEndIndex > endIndex) {
1580
+ correctedEndIndex = spanEndIndex;
1581
+ }
1582
+ break;
1583
+ }
1584
+ }
1585
+ for (let i = correctedStartIndex; i < endIndex; i++) {
1586
+ const row = dataSourceCopyTemp[i];
1587
+ if (!row) continue;
1588
+ const spanEndIndex = i + (maxRowSpan.get(rowKeyGen(row)) || 1);
1589
+ if (spanEndIndex > correctedEndIndex) {
1590
+ correctedEndIndex = Math.max(spanEndIndex, correctedEndIndex);
1591
+ }
1592
+ }
1593
+ startIndex = correctedStartIndex;
1594
+ endIndex = correctedEndIndex;
1595
+ }
1468
1596
  if (stripe && startIndex > 0 && startIndex % 2) {
1469
1597
  startIndex -= 1;
1470
1598
  if (autoRowHeight || hasExpandCol.value) {
@@ -1568,77 +1696,6 @@ function useVirtualScroll({
1568
1696
  clearAllAutoHeight
1569
1697
  };
1570
1698
  }
1571
- function useMergeCells({
1572
- props,
1573
- tableHeaderLast,
1574
- rowKeyGen,
1575
- colKeyGen,
1576
- virtual_dataSourcePart
1577
- }) {
1578
- const hiddenCellMap = ref({});
1579
- const hoverRowMap = ref({});
1580
- const hoverMergedCells = ref(/* @__PURE__ */ new Set());
1581
- const activeMergedCells = ref(/* @__PURE__ */ new Set());
1582
- watch([virtual_dataSourcePart, tableHeaderLast], () => {
1583
- hiddenCellMap.value = {};
1584
- hoverRowMap.value = {};
1585
- });
1586
- function hideCells(rowKey, startIndex, count, isSelfRow = false, mergeCellKey) {
1587
- for (let i = startIndex; i < startIndex + count; i++) {
1588
- if (!isSelfRow || i !== startIndex) {
1589
- const nextCol = tableHeaderLast.value[i];
1590
- if (!nextCol) break;
1591
- const nextColKey = colKeyGen.value(nextCol);
1592
- if (!hiddenCellMap.value[rowKey]) hiddenCellMap.value[rowKey] = /* @__PURE__ */ new Set();
1593
- hiddenCellMap.value[rowKey].add(nextColKey);
1594
- }
1595
- if (!hoverRowMap.value[rowKey]) hoverRowMap.value[rowKey] = /* @__PURE__ */ new Set();
1596
- hoverRowMap.value[rowKey].add(mergeCellKey);
1597
- }
1598
- }
1599
- function mergeCellsWrapper(row, col, rowIndex, colIndex) {
1600
- if (!col.mergeCells) return;
1601
- let { colspan, rowspan } = col.mergeCells({ row, col, rowIndex, colIndex }) || {};
1602
- colspan = colspan || 1;
1603
- rowspan = rowspan || 1;
1604
- if (colspan === 1 && rowspan === 1) return;
1605
- const rowKey = rowKeyGen(row);
1606
- const colKey = colKeyGen.value(col);
1607
- const dataSourceSlice = virtual_dataSourcePart.value.slice();
1608
- const curColIndex = tableHeaderLast.value.findIndex((item) => colKeyGen.value(item) === colKey);
1609
- const curRowIndex = dataSourceSlice.findIndex((item) => rowKeyGen(item) === rowKey);
1610
- const mergedCellKey = pureCellKeyGen(rowKey, colKey);
1611
- if (curRowIndex === -1) return;
1612
- for (let i = curRowIndex; i < curRowIndex + rowspan; i++) {
1613
- const row2 = dataSourceSlice[i];
1614
- if (!row2) break;
1615
- const rKey = rowKeyGen(row2);
1616
- const isSelfRow = i === curRowIndex;
1617
- hideCells(rKey, curColIndex, colspan, isSelfRow, mergedCellKey);
1618
- }
1619
- return { colspan, rowspan };
1620
- }
1621
- function updateHoverMergedCells(rowKey) {
1622
- const set = rowKey === void 0 ? null : hoverRowMap.value[rowKey];
1623
- hoverMergedCells.value = set || /* @__PURE__ */ new Set();
1624
- }
1625
- function updateActiveMergedCells(clear) {
1626
- if (!props.rowActive) return;
1627
- if (clear) {
1628
- activeMergedCells.value.clear();
1629
- } else {
1630
- activeMergedCells.value = new Set(hoverMergedCells.value);
1631
- }
1632
- }
1633
- return {
1634
- hiddenCellMap,
1635
- mergeCellsWrapper,
1636
- hoverMergedCells,
1637
- updateHoverMergedCells,
1638
- activeMergedCells,
1639
- updateActiveMergedCells
1640
- };
1641
- }
1642
1699
  const _hoisted_1 = ["data-col-key", "draggable", "rowspan", "colspan", "title", "onClick"];
1643
1700
  const _hoisted_2 = ["onMousedown"];
1644
1701
  const _hoisted_3 = { class: "table-header-title" };
@@ -1766,6 +1823,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1766
1823
  const { isSRBRActive } = useScrollRowByRow({ props, tableContainerRef });
1767
1824
  const { onThDragStart, onThDragOver, onThDrop, isHeaderDraggable } = useThDrag({ props, emits, colKeyGen });
1768
1825
  const { onTrDragStart, onTrDrop, onTrDragOver, onTrDragEnd, onTrDragEnter } = useTrDrag({ props, emits, dataSourceCopy });
1826
+ const { maxRowSpan, updateMaxRowSpan } = useMaxRowSpan({ props, tableHeaderLast, rowKeyGen, dataSourceCopy });
1769
1827
  const {
1770
1828
  virtualScroll,
1771
1829
  virtualScrollX,
@@ -1782,7 +1840,16 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1782
1840
  updateVirtualScrollX,
1783
1841
  setAutoHeight,
1784
1842
  clearAllAutoHeight
1785
- } = useVirtualScroll({ tableContainerRef, trRef, props, dataSourceCopy, tableHeaderLast, tableHeaders, rowKeyGen });
1843
+ } = useVirtualScroll({ tableContainerRef, trRef, props, dataSourceCopy, tableHeaderLast, tableHeaders, rowKeyGen, maxRowSpan });
1844
+ const {
1845
+ hiddenCellMap,
1846
+ //
1847
+ mergeCellsWrapper,
1848
+ hoverMergedCells,
1849
+ updateHoverMergedCells,
1850
+ activeMergedCells,
1851
+ updateActiveMergedCells
1852
+ } = useMergeCells({ props, tableHeaderLast, rowKeyGen, colKeyGen, virtual_dataSourcePart });
1786
1853
  const getFixedColPosition = useGetFixedColPosition({ colKeyGen, tableHeadersForCalc });
1787
1854
  const getFixedStyle = useFixedStyle({
1788
1855
  props,
@@ -1824,17 +1891,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1824
1891
  });
1825
1892
  const { toggleExpandRow, setRowExpand } = useRowExpand({ dataSourceCopy, rowKeyGen, emits });
1826
1893
  const { toggleTreeNode, setTreeExpand, flatTreeData } = useTree({ props, dataSourceCopy, rowKeyGen, emits });
1827
- const { hiddenCellMap, mergeCellsWrapper, hoverMergedCells, updateHoverMergedCells, activeMergedCells, updateActiveMergedCells } = useMergeCells({
1828
- props,
1829
- tableHeaderLast,
1830
- rowKeyGen,
1831
- colKeyGen,
1832
- virtual_dataSourcePart
1833
- });
1834
1894
  watch(
1835
1895
  () => props.columns,
1836
1896
  () => {
1837
1897
  dealColumns();
1898
+ updateMaxRowSpan();
1838
1899
  nextTick(() => {
1839
1900
  initVirtualScrollX();
1840
1901
  updateFixedShadow();
@@ -1877,6 +1938,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1877
1938
  needInitVirtualScrollY = true;
1878
1939
  }
1879
1940
  initDataSource(val);
1941
+ updateMaxRowSpan();
1880
1942
  if (needInitVirtualScrollY) {
1881
1943
  nextTick(() => initVirtualScrollY());
1882
1944
  }
@@ -1894,6 +1956,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1894
1956
  );
1895
1957
  dealColumns();
1896
1958
  initDataSource();
1959
+ updateMaxRowSpan();
1897
1960
  onMounted(() => {
1898
1961
  initVirtualScroll();
1899
1962
  updateFixedShadow();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stk-table-vue",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Simple realtime virtual table for vue3 and vue2.7",
5
5
  "main": "./lib/stk-table-vue.js",
6
6
  "types": "./lib/src/StkTable/index.d.ts",
@@ -293,13 +293,14 @@ import { useFixedStyle } from './useFixedStyle';
293
293
  import { useGetFixedColPosition } from './useGetFixedColPosition';
294
294
  import { useHighlight } from './useHighlight';
295
295
  import { useKeyboardArrowScroll } from './useKeyboardArrowScroll';
296
+ import { useMaxRowSpan } from './useMaxRowSpan';
297
+ import { useMergeCells } from './useMergeCells';
296
298
  import { useRowExpand } from './useRowExpand';
297
299
  import { useScrollRowByRow } from './useScrollRowByRow';
298
300
  import { useThDrag } from './useThDrag';
299
301
  import { useTrDrag } from './useTrDrag';
300
302
  import { useTree } from './useTree';
301
303
  import { useVirtualScroll } from './useVirtualScroll';
302
- import { useMergeCells } from './useMergeCells';
303
304
  import { createStkTableId, getCalculatedColWidth, getColWidth } from './utils/constRefUtils';
304
305
  import { howDeepTheHeader, isEmptyValue, tableSort, transformWidthToStr } from './utils/index';
305
306
 
@@ -687,7 +688,7 @@ const sortSwitchOrder: Order[] = [null, 'desc', 'asc'];
687
688
  * @eg
688
689
  * ```js
689
690
  * [
690
- * [{dataInex:'id',...}], // 第0行列配置
691
+ * [{dataIndex:'id',...}], // 第0行列配置
691
692
  * [], // 第一行列配置
692
693
  * //...
693
694
  * ]
@@ -761,6 +762,8 @@ const { onThDragStart, onThDragOver, onThDrop, isHeaderDraggable } = useThDrag({
761
762
 
762
763
  const { onTrDragStart, onTrDrop, onTrDragOver, onTrDragEnd, onTrDragEnter } = useTrDrag({ props, emits, dataSourceCopy });
763
764
 
765
+ const { maxRowSpan, updateMaxRowSpan } = useMaxRowSpan({ props, tableHeaderLast, rowKeyGen, dataSourceCopy });
766
+
764
767
  const {
765
768
  virtualScroll,
766
769
  virtualScrollX,
@@ -777,7 +780,16 @@ const {
777
780
  updateVirtualScrollX,
778
781
  setAutoHeight,
779
782
  clearAllAutoHeight,
780
- } = useVirtualScroll({ tableContainerRef, trRef, props, dataSourceCopy, tableHeaderLast, tableHeaders, rowKeyGen });
783
+ } = useVirtualScroll({ tableContainerRef, trRef, props, dataSourceCopy, tableHeaderLast, tableHeaders, rowKeyGen, maxRowSpan });
784
+
785
+ const {
786
+ hiddenCellMap, //
787
+ mergeCellsWrapper,
788
+ hoverMergedCells,
789
+ updateHoverMergedCells,
790
+ activeMergedCells,
791
+ updateActiveMergedCells,
792
+ } = useMergeCells({ props, tableHeaderLast, rowKeyGen, colKeyGen, virtual_dataSourcePart });
781
793
 
782
794
  const getFixedColPosition = useGetFixedColPosition({ colKeyGen, tableHeadersForCalc });
783
795
 
@@ -831,19 +843,12 @@ const { toggleExpandRow, setRowExpand } = useRowExpand({ dataSourceCopy, rowKeyG
831
843
 
832
844
  const { toggleTreeNode, setTreeExpand, flatTreeData } = useTree({ props, dataSourceCopy, rowKeyGen, emits });
833
845
 
834
- const { hiddenCellMap, mergeCellsWrapper, hoverMergedCells, updateHoverMergedCells, activeMergedCells, updateActiveMergedCells } = useMergeCells({
835
- props,
836
- tableHeaderLast,
837
- rowKeyGen,
838
- colKeyGen,
839
- virtual_dataSourcePart,
840
- });
841
-
842
846
  watch(
843
847
  () => props.columns,
844
848
  () => {
845
849
  dealColumns();
846
- // initVirtualScrollX 需要获取容器滚动宽度等。必须等渲染完成后再调用。因此使用nextTick。
850
+ updateMaxRowSpan();
851
+ // nextTick: initVirtualScrollX need get container width。
847
852
  nextTick(() => {
848
853
  initVirtualScrollX();
849
854
  updateFixedShadow();
@@ -885,12 +890,13 @@ watch(
885
890
  console.warn('invalid dataSource');
886
891
  return;
887
892
  }
888
- /** 是否需要更新ScrollY,这里由于watch newValue与oldValue 的长度一样,因此需要这样使用 */
893
+
889
894
  let needInitVirtualScrollY = false;
890
895
  if (dataSourceCopy.value.length !== val.length) {
891
896
  needInitVirtualScrollY = true;
892
897
  }
893
898
  initDataSource(val);
899
+ updateMaxRowSpan();
894
900
  // if data length is not change, not init virtual scroll
895
901
  if (needInitVirtualScrollY) {
896
902
  // wait for table render,initVirtualScrollY has get `dom` operation.
@@ -913,6 +919,7 @@ watch(
913
919
 
914
920
  dealColumns();
915
921
  initDataSource();
922
+ updateMaxRowSpan();
916
923
 
917
924
  onMounted(() => {
918
925
  initVirtualScroll();
@@ -0,0 +1,56 @@
1
+ import { ShallowRef } from "vue";
2
+ import { PrivateStkTableColumn, RowKeyGen, UniqKey } from "./types";
3
+
4
+ type Options = {
5
+ props:any,
6
+ tableHeaderLast: ShallowRef<PrivateStkTableColumn<any>[]>;
7
+ rowKeyGen: RowKeyGen;
8
+ dataSourceCopy: ShallowRef<any[]>;
9
+ }
10
+
11
+ export function useMaxRowSpan({ props, tableHeaderLast, rowKeyGen, dataSourceCopy }: Options) {
12
+ /** max rowspan of each row */
13
+ const maxRowSpan = new Map<UniqKey, number>();
14
+
15
+ /**
16
+ * Use dataSourceCopy and tableHeaderLast to calculate maxRowSpan
17
+ * @link {maxRowSpan}
18
+ */
19
+ function updateMaxRowSpan() {
20
+ if(!props.virtual) {
21
+ if(maxRowSpan.size) maxRowSpan.clear();
22
+ return;
23
+ }
24
+ maxRowSpan.clear();
25
+
26
+ const data = dataSourceCopy.value;
27
+ const columns = tableHeaderLast.value;
28
+
29
+ const columnsWithMerge = columns.filter(col => col.mergeCells);
30
+ if (!columnsWithMerge.length) return;
31
+
32
+ const dataLength = data.length;
33
+ const mergeColumnsLength = columnsWithMerge.length;
34
+
35
+ for (let rowIndex = 0; rowIndex < dataLength; rowIndex++) {
36
+ const row = data[rowIndex];
37
+ const rowKey = rowKeyGen(row);
38
+ let currentMax = maxRowSpan.get(rowKey) || 0;
39
+
40
+ for (let colIndex = 0; colIndex < mergeColumnsLength; colIndex++) {
41
+ const col = columnsWithMerge[colIndex];
42
+ const { rowspan = 1 } = col.mergeCells!({ row, col, rowIndex, colIndex }) || {};
43
+
44
+ if (rowspan > 1 && rowspan > currentMax) {
45
+ currentMax = rowspan;
46
+ maxRowSpan.set(rowKey, currentMax);
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ return {
53
+ maxRowSpan,
54
+ updateMaxRowSpan
55
+ }
56
+ }
@@ -1,20 +1,20 @@
1
1
  import { ref, ShallowRef, watch } from 'vue';
2
2
  import { ColKeyGen, MergeCellsParam, PrivateStkTableColumn, RowKeyGen, UniqKey } from './types';
3
3
  import { pureCellKeyGen } from './utils';
4
-
4
+ type Options = {
5
+ props: any;
6
+ tableHeaderLast: ShallowRef<PrivateStkTableColumn<any>[]>;
7
+ rowKeyGen: RowKeyGen;
8
+ colKeyGen: ColKeyGen;
9
+ virtual_dataSourcePart: ShallowRef<any[]>;
10
+ }
5
11
  export function useMergeCells({
6
12
  props,
7
13
  tableHeaderLast,
8
14
  rowKeyGen,
9
15
  colKeyGen,
10
16
  virtual_dataSourcePart,
11
- }: {
12
- props: any;
13
- tableHeaderLast: ShallowRef<PrivateStkTableColumn<any>[]>;
14
- rowKeyGen: RowKeyGen;
15
- colKeyGen: ColKeyGen;
16
- virtual_dataSourcePart: ShallowRef<any[]>;
17
- }) {
17
+ }:Options ) {
18
18
  /**
19
19
  * which cell need be hidden
20
20
  * - key: rowKey
@@ -33,16 +33,19 @@ export function useMergeCells({
33
33
  /** click current row , which rowspan cells should be highlight */
34
34
  const activeMergedCells = ref(new Set<string>());
35
35
 
36
+
36
37
  watch([virtual_dataSourcePart, tableHeaderLast], () => {
37
38
  hiddenCellMap.value = {};
38
39
  hoverRowMap.value = {};
39
40
  });
40
41
 
41
- /** 抽象隐藏单元格的逻辑 */
42
+ /**
43
+ * abstract the logic of hiding cells
44
+ */
42
45
  function hideCells(rowKey: UniqKey, startIndex: number, count: number, isSelfRow = false, mergeCellKey: string) {
43
46
  for (let i = startIndex; i < startIndex + count; i++) {
44
47
  if (!isSelfRow || i !== startIndex) {
45
- // 自己不需要隐藏
48
+ // self row does not need to be hidden
46
49
  const nextCol = tableHeaderLast.value[i];
47
50
  if (!nextCol) break;
48
51
  const nextColKey = colKeyGen.value(nextCol);
@@ -81,20 +84,18 @@ export function useMergeCells({
81
84
  if (colspan === 1 && rowspan === 1) return;
82
85
 
83
86
  const rowKey = rowKeyGen(row);
87
+
84
88
  const colKey = colKeyGen.value(col);
85
- const dataSourceSlice = virtual_dataSourcePart.value.slice();
86
89
  const curColIndex = tableHeaderLast.value.findIndex(item => colKeyGen.value(item) === colKey);
87
- const curRowIndex = dataSourceSlice.findIndex(item => rowKeyGen(item) === rowKey);
90
+ const curRowIndex = virtual_dataSourcePart.value.findIndex(item => rowKeyGen(item) === rowKey);
88
91
  const mergedCellKey = pureCellKeyGen(rowKey, colKey);
89
92
 
90
93
  if (curRowIndex === -1) return;
91
-
94
+
92
95
  for (let i = curRowIndex; i < curRowIndex + rowspan; i++) {
93
- const row = dataSourceSlice[i];
96
+ const row = virtual_dataSourcePart.value[i];
94
97
  if (!row) break;
95
- const rKey = rowKeyGen(row);
96
- const isSelfRow = i === curRowIndex;
97
- hideCells(rKey, curColIndex, colspan, isSelfRow, mergedCellKey);
98
+ hideCells(rowKeyGen(row), curColIndex, colspan, i === curRowIndex, mergedCellKey);
98
99
  }
99
100
 
100
101
  return { colspan, rowspan };
@@ -11,6 +11,7 @@ type Option<DT extends Record<string, any>> = {
11
11
  tableHeaderLast: ShallowRef<PrivateStkTableColumn<DT>[]>;
12
12
  tableHeaders: ShallowRef<PrivateStkTableColumn<DT>[][]>;
13
13
  rowKeyGen: RowKeyGen;
14
+ maxRowSpan: Map<UniqKey, number>;
14
15
  };
15
16
 
16
17
  /** 暂存纵向虚拟滚动的数据 */
@@ -52,7 +53,7 @@ export type VirtualScrollXStore = {
52
53
  const VUE2_SCROLL_TIMEOUT_MS = 200;
53
54
 
54
55
  /**
55
- * 虚拟滚动
56
+ * virtual scroll
56
57
  * @param param0
57
58
  * @returns
58
59
  */
@@ -64,8 +65,8 @@ export function useVirtualScroll<DT extends Record<string, any>>({
64
65
  tableHeaderLast,
65
66
  tableHeaders,
66
67
  rowKeyGen,
68
+ maxRowSpan,
67
69
  }: Option<DT>) {
68
- /** 表头高度 */
69
70
  const tableHeaderHeight = ref(props.headerRowHeight);
70
71
 
71
72
  const virtualScroll = ref<VirtualScrollStore>({
@@ -279,7 +280,6 @@ export function useVirtualScroll<DT extends Record<string, any>>({
279
280
  // 先更新滚动条位置记录,其他地方有依赖。(stripe 时ArrowUp/Down滚动依赖)
280
281
  virtualScroll.value.scrollTop = sTop;
281
282
 
282
- // 非虚拟滚动不往下执行
283
283
  if (!virtual_on.value) {
284
284
  return;
285
285
  }
@@ -325,6 +325,41 @@ export function useVirtualScroll<DT extends Record<string, any>>({
325
325
  endIndex = startIndex + pageSize;
326
326
  }
327
327
 
328
+ if (maxRowSpan.size) {
329
+ // fix startIndex:查找是否有合并行跨越当前startIndex
330
+ let correctedStartIndex = startIndex;
331
+ let correctedEndIndex = endIndex;
332
+
333
+ for (let i = 0; i < startIndex; i++) {
334
+ const row = dataSourceCopyTemp[i];
335
+ if (!row) continue;
336
+ const spanEndIndex = i + (maxRowSpan.get(rowKeyGen(row)) || 1);
337
+ if (spanEndIndex > startIndex) {
338
+ // 找到跨越startIndex的合并行,将startIndex修正为合并行的起始索引
339
+ correctedStartIndex = i;
340
+ if (spanEndIndex > endIndex) {
341
+ // 合并行跨越了整个可视区
342
+ correctedEndIndex = spanEndIndex;
343
+ }
344
+ break;
345
+ }
346
+ }
347
+
348
+ // fix endIndex:查找是否有合并行跨越当前endIndex
349
+ for (let i = correctedStartIndex; i < endIndex; i++) {
350
+ const row = dataSourceCopyTemp[i];
351
+ if (!row) continue;
352
+ const spanEndIndex = i + (maxRowSpan.get(rowKeyGen(row)) || 1);
353
+ if (spanEndIndex > correctedEndIndex) {
354
+ // 找到跨越endIndex的合并行,将endIndex修正为合并行的结束索引
355
+ correctedEndIndex = Math.max(spanEndIndex, correctedEndIndex);
356
+ }
357
+ }
358
+
359
+ startIndex = correctedStartIndex;
360
+ endIndex = correctedEndIndex;
361
+ }
362
+
328
363
  if (stripe && startIndex > 0 && startIndex % 2) {
329
364
  // 斑马纹情况下,每滚动偶数行才加载。防止斑马纹错位。
330
365
  startIndex -= 1; // 奇数-1变成偶数
@@ -338,7 +373,7 @@ export function useVirtualScroll<DT extends Record<string, any>>({
338
373
  endIndex = Math.min(endIndex, dataLength);
339
374
 
340
375
  if (startIndex >= endIndex) {
341
- // 兜底,不一定会执行到这里
376
+ // fallback
342
377
  startIndex = endIndex - pageSize;
343
378
  }
344
379
 
@@ -351,20 +386,20 @@ export function useVirtualScroll<DT extends Record<string, any>>({
351
386
  offsetTop = autoRowHeightTop;
352
387
  } else {
353
388
  if (oldStartIndex === startIndex && oldEndIndex === endIndex) {
354
- // 没有变化,不需要更新
389
+ // Not change: not update
355
390
  return;
356
391
  }
357
392
  offsetTop = startIndex * rowHeight;
358
393
  }
359
394
 
360
395
  /**
361
- * 一次滚动大于一页时表示滚动过快,回退优化
396
+ * en: If scroll faster than one page, roll back
362
397
  */
363
398
  if (!optimizeVue2Scroll || sTop <= scrollTop || Math.abs(oldStartIndex - startIndex) >= pageSize) {
364
- // 向上滚动
399
+ // scroll up
365
400
  Object.assign(virtualScroll.value, { startIndex, endIndex, offsetTop });
366
401
  } else {
367
- // vue2向下滚动优化
402
+ // vue2 scroll down optimize
368
403
  virtualScroll.value.endIndex = endIndex;
369
404
  vue2ScrollYTimeout = window.setTimeout(() => {
370
405
  Object.assign(virtualScroll.value, { startIndex, offsetTop });
@@ -374,7 +409,9 @@ export function useVirtualScroll<DT extends Record<string, any>>({
374
409
 
375
410
  let vue2ScrollXTimeout: null | number = null;
376
411
 
377
- /** 通过横向滚动条位置,计算横向虚拟滚动的参数 */
412
+ /**
413
+ * Calculate virtual scroll parameters based on horizontal scroll bar position
414
+ */
378
415
  function updateVirtualScrollX(sLeft = 0) {
379
416
  if (!props.virtualX) return;
380
417
  const tableHeaderLastValue = tableHeaderLast.value;
@@ -384,7 +421,6 @@ export function useVirtualScroll<DT extends Record<string, any>>({
384
421
  const { scrollLeft, containerWidth } = virtualScrollX.value;
385
422
  let startIndex = 0;
386
423
  let offsetLeft = 0;
387
- /** 列宽累加 */
388
424
  let colWidthSum = 0;
389
425
  /** 固定左侧列宽 */
390
426
  let leftColWidthSum = 0;