stk-table-vue 1.2.4 → 1.2.6
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 +194 -194
- package/lib/{Dropdown-D_9cB0rD.js → Dropdown-BAANgg_5.js} +2 -2
- package/lib/{StkTable-CVcmlZi9.js → StkTable-CGcxwpdb.js} +22 -6
- package/lib/src/StkTable/StkTable.vue.d.ts +18 -4
- package/lib/src/StkTable/types/index.d.ts +4 -1
- package/lib/src/StkTable/useVirtualScroll.d.ts +6 -1
- package/lib/stk-table-vue.js +3 -3
- package/lib/style.css +1 -1
- package/package.json +93 -93
- package/src/StkTable/StkTable.vue +2362 -2338
- package/src/StkTable/components/DragHandle.vue +9 -9
- package/src/StkTable/components/SortIcon.vue +6 -6
- package/src/StkTable/components/TreeNodeCell.vue +19 -19
- package/src/StkTable/components/TriangleIcon.vue +3 -3
- package/src/StkTable/const.ts +50 -50
- package/src/StkTable/custom-cells/ChangeCell/ChangeCell.less +34 -34
- package/src/StkTable/custom-cells/ChangeCell/createChangeCell.ts +90 -90
- package/src/StkTable/custom-cells/ChangeCell/index.ts +4 -4
- package/src/StkTable/custom-cells/CheckboxCell/CheckboxCell.less +14 -14
- package/src/StkTable/custom-cells/CheckboxCell/CheckboxCell.vue +56 -56
- package/src/StkTable/custom-cells/CheckboxCell/createCheckboxCell.ts +146 -146
- package/src/StkTable/custom-cells/CheckboxCell/index.ts +5 -5
- package/src/StkTable/custom-cells/FilterCell/Dropdown/index.ts +16 -16
- package/src/StkTable/custom-cells/FilterCell/Dropdown/index.vue +186 -186
- package/src/StkTable/custom-cells/FilterCell/Filter.less +118 -118
- package/src/StkTable/custom-cells/FilterCell/Filter.vue +66 -66
- package/src/StkTable/custom-cells/FilterCell/createFilterCell.ts +120 -120
- package/src/StkTable/custom-cells/FilterCell/index.ts +6 -6
- package/src/StkTable/custom-cells/FilterCell/types.ts +43 -43
- package/src/StkTable/custom-cells/NumberCell/createNumberCell.ts +43 -43
- package/src/StkTable/custom-cells/NumberCell/index.ts +2 -2
- package/src/StkTable/custom-cells/utils/formatNumber.ts +137 -137
- package/src/StkTable/features/const.ts +1 -1
- package/src/StkTable/features/index.ts +1 -1
- package/src/StkTable/features/useAreaSelection.ts +1240 -1239
- package/src/StkTable/index.ts +20 -20
- package/src/StkTable/mergeCellsCache.ts +69 -69
- package/src/StkTable/registerFeature.ts +40 -40
- package/src/StkTable/style.less +791 -791
- package/src/StkTable/types/highlightDimOptions.ts +28 -28
- package/src/StkTable/types/index.ts +503 -500
- package/src/StkTable/useAutoResize.ts +87 -87
- package/src/StkTable/useColResize.ts +202 -202
- package/src/StkTable/useFixedCol.ts +168 -168
- package/src/StkTable/useFixedStyle.ts +75 -75
- package/src/StkTable/useGetFixedColPosition.ts +63 -63
- package/src/StkTable/useHighlight.ts +270 -270
- package/src/StkTable/useIndexResolver.ts +29 -29
- package/src/StkTable/useKeyboardArrowScroll.ts +114 -114
- package/src/StkTable/useMaxRowSpan.ts +60 -60
- package/src/StkTable/useMergeCells.ts +541 -541
- package/src/StkTable/useRowExpand.ts +81 -81
- package/src/StkTable/useScrollRowByRow.ts +96 -96
- package/src/StkTable/useScrollbar.ts +177 -177
- package/src/StkTable/useSorter.ts +267 -267
- package/src/StkTable/useTableColumns.ts +142 -142
- package/src/StkTable/useThDrag.ts +94 -94
- package/src/StkTable/useTrDrag.ts +100 -100
- package/src/StkTable/useTree.ts +253 -253
- package/src/StkTable/useVirtualScroll.ts +1032 -1025
- package/src/StkTable/useWheeling.ts +23 -23
- package/src/StkTable/utils/constRefUtils.ts +29 -29
- package/src/StkTable/utils/index.ts +331 -331
- package/src/StkTable/utils/rowHeightFenwickTree.ts +74 -74
- package/src/StkTable/utils/useTriggerRef.ts +33 -33
- package/src/VirtualTree.vue +622 -622
- package/src/VirtualTreeSelect.vue +367 -367
- package/src/vite-env.d.ts +10 -10
|
@@ -1,2338 +1,2362 @@
|
|
|
1
|
-
<!-- eslint-disable vue/attribute-hyphenation -->
|
|
2
|
-
<template>
|
|
3
|
-
<div
|
|
4
|
-
ref="tableContainerRef"
|
|
5
|
-
class="stk-table"
|
|
6
|
-
:class="{
|
|
7
|
-
virtual,
|
|
8
|
-
'virtual-x': virtualX,
|
|
9
|
-
'vt-on': virtual_on,
|
|
10
|
-
light: theme === 'light',
|
|
11
|
-
dark: theme === 'dark',
|
|
12
|
-
headless,
|
|
13
|
-
'is-col-resizing': isColResizing,
|
|
14
|
-
'col-resizable': props.colResizable,
|
|
15
|
-
bordered: props.bordered,
|
|
16
|
-
[`bordered-${props.bordered}`]: typeof props.bordered === 'string',
|
|
17
|
-
stripe: props.stripe,
|
|
18
|
-
'cell-hover': props.cellHover,
|
|
19
|
-
'cell-active': props.cellActive,
|
|
20
|
-
'row-hover': props.rowHover,
|
|
21
|
-
'row-active': rowActiveProp.enabled,
|
|
22
|
-
'text-overflow': props.showOverflow,
|
|
23
|
-
'header-text-overflow': props.showHeaderOverflow,
|
|
24
|
-
'fixed-relative-mode': isRelativeMode,
|
|
25
|
-
'auto-row-height': props.autoRowHeight,
|
|
26
|
-
'scroll-row-by-row': isSRBRActive,
|
|
27
|
-
'scrollbar-on': scrollbarOptions.enabled,
|
|
28
|
-
'area-selection': areaSelectionConfig.enabled,
|
|
29
|
-
'is-area-selecting': isAreaSelecting,
|
|
30
|
-
'exp-scroll-y': isExperimentalScrollY,
|
|
31
|
-
}"
|
|
32
|
-
:tabindex="areaSelectionConfig.enabled ? 0 : void 0"
|
|
33
|
-
:style="{
|
|
34
|
-
'--row-height': props.autoRowHeight ? void 0 : virtualScroll.rowHeight + 'px',
|
|
35
|
-
'--header-row-height': props.headerRowHeight + 'px',
|
|
36
|
-
'--footer-row-height': props.footerRowHeight + 'px',
|
|
37
|
-
'--highlight-duration': props.highlightConfig.duration && props.highlightConfig.duration + 's',
|
|
38
|
-
'--highlight-timing-function': highlightSteps ? `steps(${highlightSteps})` : void 0,
|
|
39
|
-
'--sb-width': `${scrollbarOptions.width}px`,
|
|
40
|
-
'--sb-height': `${scrollbarOptions.height}px`,
|
|
41
|
-
}"
|
|
42
|
-
@scroll="onTableScroll"
|
|
43
|
-
@wheel="onTableWheel"
|
|
44
|
-
>
|
|
45
|
-
<div v-if="!isExperimentalScrollY && SRBRTotalHeight" class="row-by-row-table-height" :style="`height: ${SRBRTotalHeight}px`"></div>
|
|
46
|
-
|
|
47
|
-
<div v-if="colResizable" ref="colResizeIndicatorRef" class="column-resize-indicator"></div>
|
|
48
|
-
|
|
49
|
-
<div class="stk-table-scroll-container">
|
|
50
|
-
<table
|
|
51
|
-
class="stk-table-main"
|
|
52
|
-
:style="{ width, minWidth, maxWidth }"
|
|
53
|
-
:class="{
|
|
54
|
-
'fixed-mode': props.fixedMode,
|
|
55
|
-
}"
|
|
56
|
-
@dragover="onTrDragOver"
|
|
57
|
-
@dragenter="onTrDragEnter"
|
|
58
|
-
@dragend="onTrDragEnd"
|
|
59
|
-
@click="onRowClick"
|
|
60
|
-
@dblclick="onRowDblclick"
|
|
61
|
-
@contextmenu="onRowMenu"
|
|
62
|
-
@mouseover="onTrMouseOver"
|
|
63
|
-
>
|
|
64
|
-
<!-- table-layout:fixed 下浏览器仅依据首行/colgroup 决定列宽,多级表头时子列宽度位于非首行会被忽略。
|
|
65
|
-
故固定模式下通过 colgroup 显式声明每个叶子列宽度,保证子列 width 生效。 -->
|
|
66
|
-
<colgroup v-if="props.fixedMode && !virtualX_on">
|
|
67
|
-
<col v-for="col in tableHeaderLast" :key="colKeyGen(col)" :style="getColGroupColStyle(col)" />
|
|
68
|
-
</colgroup>
|
|
69
|
-
<thead v-if="!headless">
|
|
70
|
-
<tr
|
|
71
|
-
v-for="(row, rowIndex) in virtualX_on ? virtualX_tableHeaders : tableHeaders"
|
|
72
|
-
:key="rowIndex"
|
|
73
|
-
@contextmenu="onHeaderMenu($event)"
|
|
74
|
-
>
|
|
75
|
-
<th
|
|
76
|
-
v-if="virtualX_on"
|
|
77
|
-
class="vt-x-left"
|
|
78
|
-
:style="`min-width:${theadVirtualX.offsetLeft}px;width:${theadVirtualX.offsetLeft}px`"
|
|
79
|
-
></th>
|
|
80
|
-
<th
|
|
81
|
-
v-for="(col, colIndex) in row"
|
|
82
|
-
:key="colKeyGen(col)"
|
|
83
|
-
v-bind="getTHProps(col)"
|
|
84
|
-
@click="e => onHeaderCellClick(e, col)"
|
|
85
|
-
@dragstart="onThDragStart"
|
|
86
|
-
@drop="onThDrop"
|
|
87
|
-
@dragover="onThDragOver"
|
|
88
|
-
>
|
|
89
|
-
<div
|
|
90
|
-
v-if="colResizeOn(col) && colIndex > 0"
|
|
91
|
-
class="table-header-resizer left"
|
|
92
|
-
@mousedown="onThResizeMouseDown($event, col, true)"
|
|
93
|
-
></div>
|
|
94
|
-
<div class="table-header-cell-wrapper" :style="col.__R_SP__ ? `--row-span:${col.__R_SP__}` : null">
|
|
95
|
-
<component
|
|
96
|
-
:is="col.customHeaderCell"
|
|
97
|
-
v-if="col.customHeaderCell"
|
|
98
|
-
:col="col"
|
|
99
|
-
:colIndex="colIndex"
|
|
100
|
-
:rowIndex="rowIndex"
|
|
101
|
-
/>
|
|
102
|
-
<template v-else>
|
|
103
|
-
<slot name="tableHeader" :col="col">
|
|
104
|
-
<span class="table-header-title">{{ col.title }}</span>
|
|
105
|
-
</slot>
|
|
106
|
-
</template>
|
|
107
|
-
<SortIcon v-if="col.sorter" class="table-header-sorter" />
|
|
108
|
-
</div>
|
|
109
|
-
<div v-if="colResizeOn(col)" class="table-header-resizer right" @mousedown="onThResizeMouseDown($event, col)"></div>
|
|
110
|
-
</th>
|
|
111
|
-
<th v-if="virtualX_on" class="vt-x-right" :style="`min-width:${virtualX_offsetRight}px;width:${virtualX_offsetRight}px`"></th>
|
|
112
|
-
</tr>
|
|
113
|
-
</thead>
|
|
114
|
-
|
|
115
|
-
<component
|
|
116
|
-
:is="footerTagName"
|
|
117
|
-
v-if="footerData && footerData.length > 0"
|
|
118
|
-
class="stk-footer"
|
|
119
|
-
:style="isFooterTop ? `top:${tableHeaderHeight}px` : ''"
|
|
120
|
-
>
|
|
121
|
-
<tr v-for="(footRow, footRowIndex) in footerData" :key="footRowIndex">
|
|
122
|
-
<td
|
|
123
|
-
v-if="virtualX_on"
|
|
124
|
-
class="vt-x-left"
|
|
125
|
-
:style="`min-width:${theadVirtualX.offsetLeft}px;width:${theadVirtualX.offsetLeft}px`"
|
|
126
|
-
></td>
|
|
127
|
-
<template v-for="(col, _colIdx) in virtualX_columnPart" :key="col.__VT_C_SP__ ? `spacer-${_colIdx}` : colKeyGen(col)">
|
|
128
|
-
<td v-if="col.__VT_C_SP__" class="vt-x-spacer" :colspan="col.__VT_C_SP__"></td>
|
|
129
|
-
<td v-else v-bind="getTFProps(col)">
|
|
130
|
-
<component
|
|
131
|
-
:is="col.customFooterCell"
|
|
132
|
-
v-if="col.customFooterCell"
|
|
133
|
-
class="table-cell-wrapper"
|
|
134
|
-
tabindex="-1"
|
|
135
|
-
:col="col"
|
|
136
|
-
:row="footRow"
|
|
137
|
-
:rowIndex="footRowIndex"
|
|
138
|
-
:cellValue="footRow[col.dataIndex]"
|
|
139
|
-
/>
|
|
140
|
-
<div class="table-cell-wrapper" tabindex="-1" :title="footRow[col.dataIndex] || ''">
|
|
141
|
-
<span v-if="footRow[col.dataIndex] != null">{{ footRow[col.dataIndex] }}</span>
|
|
142
|
-
</div>
|
|
143
|
-
</td>
|
|
144
|
-
</template>
|
|
145
|
-
<td v-if="virtualX_on" class="vt-x-right" :style="`min-width:${virtualX_offsetRight}px;width:${virtualX_offsetRight}px`"></td>
|
|
146
|
-
</tr>
|
|
147
|
-
</component>
|
|
148
|
-
|
|
149
|
-
<tbody
|
|
150
|
-
class="stk-tbody-main"
|
|
151
|
-
:style="isExperimentalScrollY ? `transform:translateY(${virtualScroll.translateY}px)` : ''"
|
|
152
|
-
@click="onCellClick"
|
|
153
|
-
@mousedown="onCellMouseDown"
|
|
154
|
-
@mouseover="onCellMouseOver"
|
|
155
|
-
@mouseout="onTbodyMouseOut"
|
|
156
|
-
@drop="onBodyDrop"
|
|
157
|
-
>
|
|
158
|
-
<tr v-if="!isExperimentalScrollY && virtual_on && !isSRBRActive" :style="paddingTopStyle" class="padding-top-tr">
|
|
159
|
-
<template v-if="fixedMode && headless">
|
|
160
|
-
<td
|
|
161
|
-
v-if="virtualX_on"
|
|
162
|
-
class="vt-x-left"
|
|
163
|
-
:style="`min-width:${theadVirtualX.offsetLeft}px;width:${theadVirtualX.offsetLeft}px`"
|
|
164
|
-
></td>
|
|
165
|
-
<template v-for="(col, _colIdx) in virtualX_columnPart" :key="col.__VT_C_SP__ ? `spacer-${_colIdx}` : colKeyGen(col)">
|
|
166
|
-
<td v-if="col.__VT_C_SP__" class="vt-x-spacer" :colspan="col.__VT_C_SP__"></td>
|
|
167
|
-
<td v-else :style="cellStyleMap[TagType.TD].get(colKeyGen(col))"></td>
|
|
168
|
-
</template>
|
|
169
|
-
<td
|
|
170
|
-
v-if="virtualX_on"
|
|
171
|
-
class="vt-x-right"
|
|
172
|
-
:style="`min-width:${virtualX_offsetRight}px;width:${virtualX_offsetRight}px`"
|
|
173
|
-
></td>
|
|
174
|
-
</template>
|
|
175
|
-
</tr>
|
|
176
|
-
<!-- tbody 渲染列表(异构):
|
|
177
|
-
- row: 数据行(视口行 + 视口上方保留的锚点行/孤立空行)
|
|
178
|
-
- above-ph: 视口上方连续空行(无 td)合并的占位段
|
|
179
|
-
- below-ph: 视口下方行(rowspan 修正产生的 viewportEndIndex ~ endIndex 区域)
|
|
180
|
-
按跨界 rowspan 结束行切分的占位段
|
|
181
|
-
占位 tr 以 height: calc(var(--row-height) * 行数) 保持总高度,大 rowspan 场景显著减少 DOM 节点数 -->
|
|
182
|
-
<!-- key 必须在 template 上:v-for 模板含多个条件分支时子节点 key 不会提升到 Fragment,
|
|
183
|
-
会导致 Fragment 无 key 走 unkeyed diff,滚动时整行重建 -->
|
|
184
|
-
<template v-for="item in bodyRenderItems" :key="item.key">
|
|
185
|
-
<tr v-if="item.type === 'row'" ref="trRef" v-bind="getTRProps(item.row, item.rowIndex)">
|
|
186
|
-
<td v-if="item.row && item.row.__EXP_R__" :colspan="expandRowColspan">
|
|
187
|
-
<div class="table-cell-wrapper" tabindex="-1">
|
|
188
|
-
<slot name="expand" :row="item.row.__EXP_R__" :col="item.row.__EXP_C__">
|
|
189
|
-
{{ (item.row.__EXP_R__ && item.row.__EXP_C__ && item.row.__EXP_R__[item.row.__EXP_C__.dataIndex]) || '' }}
|
|
190
|
-
</slot>
|
|
191
|
-
</div>
|
|
192
|
-
</td>
|
|
193
|
-
<template v-else>
|
|
194
|
-
<td v-if="virtualX_on" class="vt-x-left"></td>
|
|
195
|
-
<template
|
|
196
|
-
v-for="(col, _colIdx) in getBodyColumns(item.row, item.rowIndex)"
|
|
197
|
-
:key="col.__VT_C_SP__ ? `spacer-${_colIdx}` : col.__VT_PH__ ? `ph-${_colIdx}` : colKeyGen(col)"
|
|
198
|
-
>
|
|
199
|
-
<td v-if="col.__VT_C_SP__" class="vt-x-spacer" :colspan="col.__VT_C_SP__"></td>
|
|
200
|
-
<td v-else-if="col.__VT_PH__" class="vt-above-viewport-ph" :colspan="col.__VT_PH__"></td>
|
|
201
|
-
<td
|
|
202
|
-
v-else-if="!shouldHideCell(item.row, col)"
|
|
203
|
-
v-bind="getTDProps(item.row, col, item.rowIndex, (col as PrivateStkTableColumn<DT>).__LF_S__ ?? 0)"
|
|
204
|
-
>
|
|
205
|
-
<component
|
|
206
|
-
:is="col.customCell"
|
|
207
|
-
v-if="col.customCell"
|
|
208
|
-
class="table-cell-wrapper"
|
|
209
|
-
tabindex="-1"
|
|
210
|
-
:col="col"
|
|
211
|
-
:row="item.row"
|
|
212
|
-
:rowIndex="getAbsoluteRowIndex(item.rowIndex)"
|
|
213
|
-
:colIndex="(col as PrivateStkTableColumn<DT>).__LF_S__ ?? 0"
|
|
214
|
-
:cellValue="item.row && item.row[col.dataIndex]"
|
|
215
|
-
:expanded="item.row && item.row.__EXP__"
|
|
216
|
-
:tree-expanded="item.row && item.row.__T_EXP__"
|
|
217
|
-
>
|
|
218
|
-
<template #stkFoldIcon>
|
|
219
|
-
<TriangleIcon @click="triangleClick($event, item.row, col)"></TriangleIcon>
|
|
220
|
-
</template>
|
|
221
|
-
<template #stkDragIcon>
|
|
222
|
-
<DragHandle @dragstart="onTrDragStart($event, getAbsoluteRowIndex(item.rowIndex))" />
|
|
223
|
-
</template>
|
|
224
|
-
</component>
|
|
225
|
-
<div v-else-if="!col.type" class="table-cell-wrapper" tabindex="-1" :title="item.row[col.dataIndex] || ''">
|
|
226
|
-
{{
|
|
227
|
-
(item.row && item.row[col.dataIndex]) != null
|
|
228
|
-
? item.row && item.row[col.dataIndex]
|
|
229
|
-
: getEmptyCellText(col, item.row)
|
|
230
|
-
}}
|
|
231
|
-
</div>
|
|
232
|
-
<div v-else-if="col.type === 'seq'" class="table-cell-wrapper" tabindex="-1">
|
|
233
|
-
{{ (props.seqConfig.startIndex || 0) + getAbsoluteRowIndex(item.rowIndex) + 1 }}
|
|
234
|
-
</div>
|
|
235
|
-
<TreeNodeCell
|
|
236
|
-
v-else-if="col.type === 'tree-node'"
|
|
237
|
-
class="table-cell-wrapper"
|
|
238
|
-
tabindex="-1"
|
|
239
|
-
:col="col"
|
|
240
|
-
:row="item.row"
|
|
241
|
-
></TreeNodeCell>
|
|
242
|
-
<div v-else class="table-cell-wrapper" tabindex="-1" :title="item.row[col.dataIndex] || ''">
|
|
243
|
-
<DragHandle
|
|
244
|
-
v-if="col.type === 'dragRow'"
|
|
245
|
-
@dragstart="onTrDragStart($event, getAbsoluteRowIndex(item.rowIndex))"
|
|
246
|
-
/>
|
|
247
|
-
<TriangleIcon v-else-if="col.type === 'expand'" />
|
|
248
|
-
<span v-if="item.row[col.dataIndex] != null">{{ item.row[col.dataIndex] }}</span>
|
|
249
|
-
</div>
|
|
250
|
-
</td>
|
|
251
|
-
</template>
|
|
252
|
-
<td v-if="virtualX_on" class="vt-x-right"></td>
|
|
253
|
-
</template>
|
|
254
|
-
</tr>
|
|
255
|
-
<tr
|
|
256
|
-
v-else-if="item.type === 'above-ph'"
|
|
257
|
-
class="vt-above-viewport-ph-row"
|
|
258
|
-
:data-above-count="item.count"
|
|
259
|
-
:style="`height: calc(var(--row-height) * ${item.count})`"
|
|
260
|
-
></tr>
|
|
261
|
-
<tr
|
|
262
|
-
v-else
|
|
263
|
-
class="vt-below-viewport-ph"
|
|
264
|
-
:data-below-count="item.count"
|
|
265
|
-
:style="`height: calc(var(--row-height) * ${item.count})`"
|
|
266
|
-
></tr>
|
|
267
|
-
</template>
|
|
268
|
-
<template v-if="!isExperimentalScrollY">
|
|
269
|
-
<tr v-if="virtual_on && !isSRBRActive" :style="offsetBottomStyle"></tr>
|
|
270
|
-
<tr v-if="SRBRBottomHeight" :style="SRBRBottomStyle"></tr>
|
|
271
|
-
</template>
|
|
272
|
-
</tbody>
|
|
273
|
-
</table>
|
|
274
|
-
<div
|
|
275
|
-
v-if="scrollbarOptions.enabled && showScrollbar.y"
|
|
276
|
-
class="stk-sb-thumb vertical"
|
|
277
|
-
:style="`height:${scrollbar.h}px;transform:translateY(${scrollbar.t}px)`"
|
|
278
|
-
@mousedown="onVerticalScrollbarMouseDown"
|
|
279
|
-
@touchstart.passive="onVerticalScrollbarMouseDown"
|
|
280
|
-
></div>
|
|
281
|
-
</div>
|
|
282
|
-
<div v-if="(!dataSourceCopy || !dataSourceCopy.length) && showNoData" class="stk-table-no-data" :class="{ 'no-data-full': noDataFull }">
|
|
283
|
-
<slot name="empty">暂无数据</slot>
|
|
284
|
-
</div>
|
|
285
|
-
<slot name="customBottom"></slot>
|
|
286
|
-
<div
|
|
287
|
-
v-if="scrollbarOptions.enabled && showScrollbar.x"
|
|
288
|
-
class="stk-sb-thumb horizontal"
|
|
289
|
-
:style="`width:${scrollbar.w}px;transform:translateX(${scrollbar.l}px)`"
|
|
290
|
-
@mousedown="onHorizontalScrollbarMouseDown"
|
|
291
|
-
@touchstart.passive="onHorizontalScrollbarMouseDown"
|
|
292
|
-
></div>
|
|
293
|
-
</div>
|
|
294
|
-
</template>
|
|
295
|
-
|
|
296
|
-
<!-- Vue 2.7 compatible: add component name -->
|
|
297
|
-
<script lang="ts">
|
|
298
|
-
export default {
|
|
299
|
-
name: 'StkTable',
|
|
300
|
-
};
|
|
301
|
-
</script>
|
|
302
|
-
|
|
303
|
-
<script setup lang="ts">
|
|
304
|
-
/**
|
|
305
|
-
* StkTable 主组件(Vue 3 / Vue 2.7)
|
|
306
|
-
*
|
|
307
|
-
* 这是一个超大的核心组件文件,约 1800+ 行。理解它时请按以下「分区地图」定位:
|
|
308
|
-
*
|
|
309
|
-
* 1. 顶部(template 261 行前)
|
|
310
|
-
* 渲染结构:滚动容器 + 原生 <table>。包括 colgroup(固定模式列宽)、thead(多级表头、
|
|
311
|
-
* sticky 固定)、tbody(虚拟滚动 tr / 空 td 定位)、tfoot(汇总行)、自定义滚动条容器。
|
|
312
|
-
* 通过 CSS 类(如 virtual / virtual-x / area-selection / theme 等)联动对应 hook 状态。
|
|
313
|
-
*
|
|
314
|
-
* 2. script setup(270 行起)
|
|
315
|
-
* - props 定义:全部配置项,即公共 API(对应 types/index.ts 与文档 table-props.md)。
|
|
316
|
-
* - 大量 useXxx hooks 的组装:本文件只做「装配」,具体逻辑在对应 hook 中。
|
|
317
|
-
*
|
|
318
|
-
* 3. 核心 hook 与职责(均在 src/StkTable/use*.ts):
|
|
319
|
-
* - useVirtualScroll XY 轴虚拟滚动(可视区裁剪、占位 tr/th 定位、scroll 计算)
|
|
320
|
-
* - useTableColumns 列配置归一化 / 多级表头展开为叶子列
|
|
321
|
-
* - useFixedCol/useFixedStyle/getFixedColPosition 固定列 sticky/relative 定位
|
|
322
|
-
* - useSorter/useTableColumns.sort 排序(本地/远端、多列)
|
|
323
|
-
* - useColResize/useThDrag/useTrDrag 列宽调整 / 表头拖拽 / 行拖拽
|
|
324
|
-
* - useMergeCells/useMaxRowSpan 单元格合并与最大行跨
|
|
325
|
-
* - useRowExpand/useTree 展开行 / 树形
|
|
326
|
-
* - useHighlight 单元格/行高亮(Web Animations API 或 css)
|
|
327
|
-
* - useAreaSelection (features/) 区域选取(Excel 式键盘操作)
|
|
328
|
-
* - useScrollbar/useScrollRowByRow/useWheeling 自定义滚动条 / 按行滚动 / 滚轮
|
|
329
|
-
* - useAutoResize 容器尺寸自动适配
|
|
330
|
-
*
|
|
331
|
-
* 4. expose:对外暴露的实例方法(对应文档 expose.md),供 ref 调用。
|
|
332
|
-
*
|
|
333
|
-
* 注意:此文件与 vue2.7 兼容,props 不可抽离到独立文件(见下方注释),修改时注意不要破坏兼容性。
|
|
334
|
-
*
|
|
335
|
-
* @author japlus
|
|
336
|
-
*/
|
|
337
|
-
import { computed, nextTick, onMounted, provide, ref, shallowRef, toRaw, toRef, watch } from 'vue';
|
|
338
|
-
import DragHandle from './components/DragHandle.vue';
|
|
339
|
-
import SortIcon from './components/SortIcon.vue';
|
|
340
|
-
import TreeNodeCell from './components/TreeNodeCell.vue';
|
|
341
|
-
import TriangleIcon from './components/TriangleIcon.vue';
|
|
342
|
-
import {
|
|
343
|
-
CELL_KEY_SEPARATE,
|
|
344
|
-
DEFAULT_ROW_ACTIVE_CONFIG,
|
|
345
|
-
DEFAULT_ROW_HEIGHT,
|
|
346
|
-
DEFAULT_SMOOTH_SCROLL,
|
|
347
|
-
DEFAULT_SORT_CONFIG,
|
|
348
|
-
IS_LEGACY_MODE,
|
|
349
|
-
} from './const';
|
|
350
|
-
import type { FilterStatus } from './custom-cells/FilterCell/types';
|
|
351
|
-
import { useAreaSelectionName } from './features';
|
|
352
|
-
import { ON_DEMAND_FEATURE } from './registerFeature';
|
|
353
|
-
import {
|
|
354
|
-
AreaSelectionConfig,
|
|
355
|
-
AreaSelectionRange,
|
|
356
|
-
AutoRowHeightConfig,
|
|
357
|
-
ColResizableConfig,
|
|
358
|
-
DragRowConfig,
|
|
359
|
-
ExpandConfig,
|
|
360
|
-
ExperimentalConfig,
|
|
361
|
-
FooterConfig,
|
|
362
|
-
HeaderDragConfig,
|
|
363
|
-
HighlightConfig,
|
|
364
|
-
Order,
|
|
365
|
-
PrivateRowDT,
|
|
366
|
-
PrivateStkTableColumn,
|
|
367
|
-
RowActiveOption,
|
|
368
|
-
ScrollAxisTarget,
|
|
369
|
-
ScrollToFn,
|
|
370
|
-
ScrollToOptions,
|
|
371
|
-
SeqConfig,
|
|
372
|
-
SortConfig,
|
|
373
|
-
StkTableColumn,
|
|
374
|
-
TagType,
|
|
375
|
-
TreeConfig,
|
|
376
|
-
UniqKey,
|
|
377
|
-
UniqKeyProp,
|
|
378
|
-
} from './types/index';
|
|
379
|
-
import { useAutoResize } from './useAutoResize';
|
|
380
|
-
import { useColResize } from './useColResize';
|
|
381
|
-
import { useFixedCol } from './useFixedCol';
|
|
382
|
-
import { useFixedStyle } from './useFixedStyle';
|
|
383
|
-
import { useGetFixedColPosition } from './useGetFixedColPosition';
|
|
384
|
-
import { useHighlight } from './useHighlight';
|
|
385
|
-
import { useKeyboardArrowScroll } from './useKeyboardArrowScroll';
|
|
386
|
-
import { useMaxRowSpan } from './useMaxRowSpan';
|
|
387
|
-
import { createMergeCellsCache } from './mergeCellsCache';
|
|
388
|
-
import { useMergeCells } from './useMergeCells';
|
|
389
|
-
import { useRowExpand } from './useRowExpand';
|
|
390
|
-
import { useScrollbar, type ScrollbarOptions } from './useScrollbar';
|
|
391
|
-
import { useScrollRowByRow } from './useScrollRowByRow';
|
|
392
|
-
import { useSorter } from './useSorter';
|
|
393
|
-
import { useTableColumns } from './useTableColumns';
|
|
394
|
-
import { useThDrag } from './useThDrag';
|
|
395
|
-
import { useTrDrag } from './useTrDrag';
|
|
396
|
-
import { useTree } from './useTree';
|
|
397
|
-
// import { useIndexResolver } from './useIndexResolver';
|
|
398
|
-
import { useVirtualScroll } from './useVirtualScroll';
|
|
399
|
-
import { useWheeling } from './useWheeling';
|
|
400
|
-
import { createStkTableId, getCalculatedColWidth } from './utils/constRefUtils';
|
|
401
|
-
import { getClosestColKey, getClosestTd, getClosestTr, getClosestTrIndex, rafThrottle, transformWidthToStr } from './utils/index';
|
|
402
|
-
|
|
403
|
-
/** Generic stands for DataType */
|
|
404
|
-
type DT = any & PrivateRowDT;
|
|
405
|
-
|
|
406
|
-
/** generate table instance id */
|
|
407
|
-
const stkTableId = createStkTableId();
|
|
408
|
-
|
|
409
|
-
// Vue 2.7 compatible: use export default instead of defineOptions
|
|
410
|
-
// defineOptions({ name: 'StkTable' });
|
|
411
|
-
/**
|
|
412
|
-
* props cannot be placed in a separate file. It will cause compilation errors with vue 2.7 compiler.
|
|
413
|
-
*/
|
|
414
|
-
const props = withDefaults(
|
|
415
|
-
defineProps<{
|
|
416
|
-
/** 表格宽度*/
|
|
417
|
-
width?: string;
|
|
418
|
-
/** 最小表格宽度 @deprecated*/
|
|
419
|
-
minWidth?: string;
|
|
420
|
-
/** 表格最大宽度 @deprecated*/
|
|
421
|
-
maxWidth?: string;
|
|
422
|
-
/** 斑马线条纹 */
|
|
423
|
-
stripe?: boolean;
|
|
424
|
-
/** 是否使用 table-layout:fixed(低版本浏览器需要设置table) */
|
|
425
|
-
fixedMode?: boolean;
|
|
426
|
-
/** 是否隐藏表头 */
|
|
427
|
-
headless?: boolean;
|
|
428
|
-
/** 主题,亮、暗 */
|
|
429
|
-
theme?: 'light' | 'dark';
|
|
430
|
-
/**
|
|
431
|
-
* 行高
|
|
432
|
-
* - `props.autoRowHeight` 为 `true` 时,将表示为期望行高,用于计算。不再影响实际行高。
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
*
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*/
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
*
|
|
546
|
-
* -
|
|
547
|
-
*
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
*
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
*
|
|
559
|
-
* -
|
|
560
|
-
*/
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
*
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
*
|
|
640
|
-
*
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
*
|
|
718
|
-
*
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
*
|
|
766
|
-
*
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const
|
|
814
|
-
/**
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
/**
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
});
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
return
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
return
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
return (
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
});
|
|
904
|
-
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
props
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
});
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
)
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
nextTick(
|
|
1214
|
-
|
|
1215
|
-
);
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
() =>
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
watch(
|
|
1239
|
-
() => props.
|
|
1240
|
-
() =>
|
|
1241
|
-
);
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
});
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
)
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
if (!
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
let
|
|
1352
|
-
if (
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
if
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
/**
|
|
1372
|
-
function
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
const
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
*
|
|
1462
|
-
*
|
|
1463
|
-
*
|
|
1464
|
-
*
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
'
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
const
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
classList.push('
|
|
1631
|
-
} else if (
|
|
1632
|
-
classList.push('
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
} else {
|
|
1658
|
-
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
const
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
if (
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
const
|
|
1718
|
-
const
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
const
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
const {
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
e.preventDefault();
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
const
|
|
1862
|
-
const
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
*/
|
|
1920
|
-
function
|
|
1921
|
-
const
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
/**
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
*/
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
function
|
|
2052
|
-
if (index < 0 || index >=
|
|
2053
|
-
return
|
|
2054
|
-
}
|
|
2055
|
-
function
|
|
2056
|
-
const index =
|
|
2057
|
-
return index === -1 ? null :
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
/**
|
|
2061
|
-
function
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
if (
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
const
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
*
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
*
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
*
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
*
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
*
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
*
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
*
|
|
2237
|
-
*
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
*
|
|
2244
|
-
*
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
*
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
*
|
|
2261
|
-
*
|
|
2262
|
-
*
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
*
|
|
2268
|
-
*
|
|
2269
|
-
*
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
*
|
|
2276
|
-
*
|
|
2277
|
-
*
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
*
|
|
2283
|
-
*
|
|
2284
|
-
*
|
|
2285
|
-
*
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
*
|
|
2291
|
-
*
|
|
2292
|
-
*
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
*
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
*
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
*
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
*
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
*
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
*
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
}
|
|
2338
|
-
|
|
1
|
+
<!-- eslint-disable vue/attribute-hyphenation -->
|
|
2
|
+
<template>
|
|
3
|
+
<div
|
|
4
|
+
ref="tableContainerRef"
|
|
5
|
+
class="stk-table"
|
|
6
|
+
:class="{
|
|
7
|
+
virtual,
|
|
8
|
+
'virtual-x': virtualX,
|
|
9
|
+
'vt-on': virtual_on,
|
|
10
|
+
light: theme === 'light',
|
|
11
|
+
dark: theme === 'dark',
|
|
12
|
+
headless,
|
|
13
|
+
'is-col-resizing': isColResizing,
|
|
14
|
+
'col-resizable': props.colResizable,
|
|
15
|
+
bordered: props.bordered,
|
|
16
|
+
[`bordered-${props.bordered}`]: typeof props.bordered === 'string',
|
|
17
|
+
stripe: props.stripe,
|
|
18
|
+
'cell-hover': props.cellHover,
|
|
19
|
+
'cell-active': props.cellActive,
|
|
20
|
+
'row-hover': props.rowHover,
|
|
21
|
+
'row-active': rowActiveProp.enabled,
|
|
22
|
+
'text-overflow': props.showOverflow,
|
|
23
|
+
'header-text-overflow': props.showHeaderOverflow,
|
|
24
|
+
'fixed-relative-mode': isRelativeMode,
|
|
25
|
+
'auto-row-height': props.autoRowHeight,
|
|
26
|
+
'scroll-row-by-row': isSRBRActive,
|
|
27
|
+
'scrollbar-on': scrollbarOptions.enabled,
|
|
28
|
+
'area-selection': areaSelectionConfig.enabled,
|
|
29
|
+
'is-area-selecting': isAreaSelecting,
|
|
30
|
+
'exp-scroll-y': isExperimentalScrollY,
|
|
31
|
+
}"
|
|
32
|
+
:tabindex="areaSelectionConfig.enabled ? 0 : void 0"
|
|
33
|
+
:style="{
|
|
34
|
+
'--row-height': props.autoRowHeight ? void 0 : virtualScroll.rowHeight + 'px',
|
|
35
|
+
'--header-row-height': props.headerRowHeight + 'px',
|
|
36
|
+
'--footer-row-height': props.footerRowHeight + 'px',
|
|
37
|
+
'--highlight-duration': props.highlightConfig.duration && props.highlightConfig.duration + 's',
|
|
38
|
+
'--highlight-timing-function': highlightSteps ? `steps(${highlightSteps})` : void 0,
|
|
39
|
+
'--sb-width': `${scrollbarOptions.width}px`,
|
|
40
|
+
'--sb-height': `${scrollbarOptions.height}px`,
|
|
41
|
+
}"
|
|
42
|
+
@scroll="onTableScroll"
|
|
43
|
+
@wheel="onTableWheel"
|
|
44
|
+
>
|
|
45
|
+
<div v-if="!isExperimentalScrollY && SRBRTotalHeight" class="row-by-row-table-height" :style="`height: ${SRBRTotalHeight}px`"></div>
|
|
46
|
+
|
|
47
|
+
<div v-if="colResizable" ref="colResizeIndicatorRef" class="column-resize-indicator"></div>
|
|
48
|
+
|
|
49
|
+
<div class="stk-table-scroll-container">
|
|
50
|
+
<table
|
|
51
|
+
class="stk-table-main"
|
|
52
|
+
:style="{ width, minWidth, maxWidth }"
|
|
53
|
+
:class="{
|
|
54
|
+
'fixed-mode': props.fixedMode,
|
|
55
|
+
}"
|
|
56
|
+
@dragover="onTrDragOver"
|
|
57
|
+
@dragenter="onTrDragEnter"
|
|
58
|
+
@dragend="onTrDragEnd"
|
|
59
|
+
@click="onRowClick"
|
|
60
|
+
@dblclick="onRowDblclick"
|
|
61
|
+
@contextmenu="onRowMenu"
|
|
62
|
+
@mouseover="onTrMouseOver"
|
|
63
|
+
>
|
|
64
|
+
<!-- table-layout:fixed 下浏览器仅依据首行/colgroup 决定列宽,多级表头时子列宽度位于非首行会被忽略。
|
|
65
|
+
故固定模式下通过 colgroup 显式声明每个叶子列宽度,保证子列 width 生效。 -->
|
|
66
|
+
<colgroup v-if="props.fixedMode && !virtualX_on">
|
|
67
|
+
<col v-for="col in tableHeaderLast" :key="colKeyGen(col)" :style="getColGroupColStyle(col)" />
|
|
68
|
+
</colgroup>
|
|
69
|
+
<thead v-if="!headless">
|
|
70
|
+
<tr
|
|
71
|
+
v-for="(row, rowIndex) in virtualX_on ? virtualX_tableHeaders : tableHeaders"
|
|
72
|
+
:key="rowIndex"
|
|
73
|
+
@contextmenu="onHeaderMenu($event)"
|
|
74
|
+
>
|
|
75
|
+
<th
|
|
76
|
+
v-if="virtualX_on"
|
|
77
|
+
class="vt-x-left"
|
|
78
|
+
:style="`min-width:${theadVirtualX.offsetLeft}px;width:${theadVirtualX.offsetLeft}px`"
|
|
79
|
+
></th>
|
|
80
|
+
<th
|
|
81
|
+
v-for="(col, colIndex) in row"
|
|
82
|
+
:key="colKeyGen(col)"
|
|
83
|
+
v-bind="getTHProps(col)"
|
|
84
|
+
@click="e => onHeaderCellClick(e, col)"
|
|
85
|
+
@dragstart="onThDragStart"
|
|
86
|
+
@drop="onThDrop"
|
|
87
|
+
@dragover="onThDragOver"
|
|
88
|
+
>
|
|
89
|
+
<div
|
|
90
|
+
v-if="colResizeOn(col) && colIndex > 0"
|
|
91
|
+
class="table-header-resizer left"
|
|
92
|
+
@mousedown="onThResizeMouseDown($event, col, true)"
|
|
93
|
+
></div>
|
|
94
|
+
<div class="table-header-cell-wrapper" :style="col.__R_SP__ ? `--row-span:${col.__R_SP__}` : null">
|
|
95
|
+
<component
|
|
96
|
+
:is="col.customHeaderCell"
|
|
97
|
+
v-if="col.customHeaderCell"
|
|
98
|
+
:col="col"
|
|
99
|
+
:colIndex="colIndex"
|
|
100
|
+
:rowIndex="rowIndex"
|
|
101
|
+
/>
|
|
102
|
+
<template v-else>
|
|
103
|
+
<slot name="tableHeader" :col="col">
|
|
104
|
+
<span class="table-header-title">{{ col.title }}</span>
|
|
105
|
+
</slot>
|
|
106
|
+
</template>
|
|
107
|
+
<SortIcon v-if="col.sorter" class="table-header-sorter" />
|
|
108
|
+
</div>
|
|
109
|
+
<div v-if="colResizeOn(col)" class="table-header-resizer right" @mousedown="onThResizeMouseDown($event, col)"></div>
|
|
110
|
+
</th>
|
|
111
|
+
<th v-if="virtualX_on" class="vt-x-right" :style="`min-width:${virtualX_offsetRight}px;width:${virtualX_offsetRight}px`"></th>
|
|
112
|
+
</tr>
|
|
113
|
+
</thead>
|
|
114
|
+
|
|
115
|
+
<component
|
|
116
|
+
:is="footerTagName"
|
|
117
|
+
v-if="footerData && footerData.length > 0"
|
|
118
|
+
class="stk-footer"
|
|
119
|
+
:style="isFooterTop ? `top:${tableHeaderHeight}px` : ''"
|
|
120
|
+
>
|
|
121
|
+
<tr v-for="(footRow, footRowIndex) in footerData" :key="footRowIndex">
|
|
122
|
+
<td
|
|
123
|
+
v-if="virtualX_on"
|
|
124
|
+
class="vt-x-left"
|
|
125
|
+
:style="`min-width:${theadVirtualX.offsetLeft}px;width:${theadVirtualX.offsetLeft}px`"
|
|
126
|
+
></td>
|
|
127
|
+
<template v-for="(col, _colIdx) in virtualX_columnPart" :key="col.__VT_C_SP__ ? `spacer-${_colIdx}` : colKeyGen(col)">
|
|
128
|
+
<td v-if="col.__VT_C_SP__" class="vt-x-spacer" :colspan="col.__VT_C_SP__"></td>
|
|
129
|
+
<td v-else v-bind="getTFProps(col)">
|
|
130
|
+
<component
|
|
131
|
+
:is="col.customFooterCell"
|
|
132
|
+
v-if="col.customFooterCell"
|
|
133
|
+
class="table-cell-wrapper"
|
|
134
|
+
tabindex="-1"
|
|
135
|
+
:col="col"
|
|
136
|
+
:row="footRow"
|
|
137
|
+
:rowIndex="footRowIndex"
|
|
138
|
+
:cellValue="footRow[col.dataIndex]"
|
|
139
|
+
/>
|
|
140
|
+
<div class="table-cell-wrapper" tabindex="-1" :title="footRow[col.dataIndex] || ''">
|
|
141
|
+
<span v-if="footRow[col.dataIndex] != null">{{ footRow[col.dataIndex] }}</span>
|
|
142
|
+
</div>
|
|
143
|
+
</td>
|
|
144
|
+
</template>
|
|
145
|
+
<td v-if="virtualX_on" class="vt-x-right" :style="`min-width:${virtualX_offsetRight}px;width:${virtualX_offsetRight}px`"></td>
|
|
146
|
+
</tr>
|
|
147
|
+
</component>
|
|
148
|
+
|
|
149
|
+
<tbody
|
|
150
|
+
class="stk-tbody-main"
|
|
151
|
+
:style="isExperimentalScrollY ? `transform:translateY(${virtualScroll.translateY}px)` : ''"
|
|
152
|
+
@click="onCellClick"
|
|
153
|
+
@mousedown="onCellMouseDown"
|
|
154
|
+
@mouseover="onCellMouseOver"
|
|
155
|
+
@mouseout="onTbodyMouseOut"
|
|
156
|
+
@drop="onBodyDrop"
|
|
157
|
+
>
|
|
158
|
+
<tr v-if="!isExperimentalScrollY && virtual_on && !isSRBRActive" :style="paddingTopStyle" class="padding-top-tr">
|
|
159
|
+
<template v-if="fixedMode && headless">
|
|
160
|
+
<td
|
|
161
|
+
v-if="virtualX_on"
|
|
162
|
+
class="vt-x-left"
|
|
163
|
+
:style="`min-width:${theadVirtualX.offsetLeft}px;width:${theadVirtualX.offsetLeft}px`"
|
|
164
|
+
></td>
|
|
165
|
+
<template v-for="(col, _colIdx) in virtualX_columnPart" :key="col.__VT_C_SP__ ? `spacer-${_colIdx}` : colKeyGen(col)">
|
|
166
|
+
<td v-if="col.__VT_C_SP__" class="vt-x-spacer" :colspan="col.__VT_C_SP__"></td>
|
|
167
|
+
<td v-else :style="cellStyleMap[TagType.TD].get(colKeyGen(col))"></td>
|
|
168
|
+
</template>
|
|
169
|
+
<td
|
|
170
|
+
v-if="virtualX_on"
|
|
171
|
+
class="vt-x-right"
|
|
172
|
+
:style="`min-width:${virtualX_offsetRight}px;width:${virtualX_offsetRight}px`"
|
|
173
|
+
></td>
|
|
174
|
+
</template>
|
|
175
|
+
</tr>
|
|
176
|
+
<!-- tbody 渲染列表(异构):
|
|
177
|
+
- row: 数据行(视口行 + 视口上方保留的锚点行/孤立空行)
|
|
178
|
+
- above-ph: 视口上方连续空行(无 td)合并的占位段
|
|
179
|
+
- below-ph: 视口下方行(rowspan 修正产生的 viewportEndIndex ~ endIndex 区域)
|
|
180
|
+
按跨界 rowspan 结束行切分的占位段
|
|
181
|
+
占位 tr 以 height: calc(var(--row-height) * 行数) 保持总高度,大 rowspan 场景显著减少 DOM 节点数 -->
|
|
182
|
+
<!-- key 必须在 template 上:v-for 模板含多个条件分支时子节点 key 不会提升到 Fragment,
|
|
183
|
+
会导致 Fragment 无 key 走 unkeyed diff,滚动时整行重建 -->
|
|
184
|
+
<template v-for="item in bodyRenderItems" :key="item.key">
|
|
185
|
+
<tr v-if="item.type === 'row'" ref="trRef" v-bind="getTRProps(item.row, item.rowIndex)">
|
|
186
|
+
<td v-if="item.row && item.row.__EXP_R__" :colspan="expandRowColspan">
|
|
187
|
+
<div class="table-cell-wrapper" tabindex="-1">
|
|
188
|
+
<slot name="expand" :row="item.row.__EXP_R__" :col="item.row.__EXP_C__">
|
|
189
|
+
{{ (item.row.__EXP_R__ && item.row.__EXP_C__ && item.row.__EXP_R__[item.row.__EXP_C__.dataIndex]) || '' }}
|
|
190
|
+
</slot>
|
|
191
|
+
</div>
|
|
192
|
+
</td>
|
|
193
|
+
<template v-else>
|
|
194
|
+
<td v-if="virtualX_on" class="vt-x-left"></td>
|
|
195
|
+
<template
|
|
196
|
+
v-for="(col, _colIdx) in getBodyColumns(item.row, item.rowIndex)"
|
|
197
|
+
:key="col.__VT_C_SP__ ? `spacer-${_colIdx}` : col.__VT_PH__ ? `ph-${_colIdx}` : colKeyGen(col)"
|
|
198
|
+
>
|
|
199
|
+
<td v-if="col.__VT_C_SP__" class="vt-x-spacer" :colspan="col.__VT_C_SP__"></td>
|
|
200
|
+
<td v-else-if="col.__VT_PH__" class="vt-above-viewport-ph" :colspan="col.__VT_PH__"></td>
|
|
201
|
+
<td
|
|
202
|
+
v-else-if="!shouldHideCell(item.row, col)"
|
|
203
|
+
v-bind="getTDProps(item.row, col, item.rowIndex, (col as PrivateStkTableColumn<DT>).__LF_S__ ?? 0)"
|
|
204
|
+
>
|
|
205
|
+
<component
|
|
206
|
+
:is="col.customCell"
|
|
207
|
+
v-if="col.customCell"
|
|
208
|
+
class="table-cell-wrapper"
|
|
209
|
+
tabindex="-1"
|
|
210
|
+
:col="col"
|
|
211
|
+
:row="item.row"
|
|
212
|
+
:rowIndex="getAbsoluteRowIndex(item.rowIndex)"
|
|
213
|
+
:colIndex="(col as PrivateStkTableColumn<DT>).__LF_S__ ?? 0"
|
|
214
|
+
:cellValue="item.row && item.row[col.dataIndex]"
|
|
215
|
+
:expanded="item.row && item.row.__EXP__"
|
|
216
|
+
:tree-expanded="item.row && item.row.__T_EXP__"
|
|
217
|
+
>
|
|
218
|
+
<template #stkFoldIcon>
|
|
219
|
+
<TriangleIcon @click="triangleClick($event, item.row, col)"></TriangleIcon>
|
|
220
|
+
</template>
|
|
221
|
+
<template #stkDragIcon>
|
|
222
|
+
<DragHandle @dragstart="onTrDragStart($event, getAbsoluteRowIndex(item.rowIndex))" />
|
|
223
|
+
</template>
|
|
224
|
+
</component>
|
|
225
|
+
<div v-else-if="!col.type" class="table-cell-wrapper" tabindex="-1" :title="item.row[col.dataIndex] || ''">
|
|
226
|
+
{{
|
|
227
|
+
(item.row && item.row[col.dataIndex]) != null
|
|
228
|
+
? item.row && item.row[col.dataIndex]
|
|
229
|
+
: getEmptyCellText(col, item.row)
|
|
230
|
+
}}
|
|
231
|
+
</div>
|
|
232
|
+
<div v-else-if="col.type === 'seq'" class="table-cell-wrapper" tabindex="-1">
|
|
233
|
+
{{ (props.seqConfig.startIndex || 0) + getAbsoluteRowIndex(item.rowIndex) + 1 }}
|
|
234
|
+
</div>
|
|
235
|
+
<TreeNodeCell
|
|
236
|
+
v-else-if="col.type === 'tree-node'"
|
|
237
|
+
class="table-cell-wrapper"
|
|
238
|
+
tabindex="-1"
|
|
239
|
+
:col="col"
|
|
240
|
+
:row="item.row"
|
|
241
|
+
></TreeNodeCell>
|
|
242
|
+
<div v-else class="table-cell-wrapper" tabindex="-1" :title="item.row[col.dataIndex] || ''">
|
|
243
|
+
<DragHandle
|
|
244
|
+
v-if="col.type === 'dragRow'"
|
|
245
|
+
@dragstart="onTrDragStart($event, getAbsoluteRowIndex(item.rowIndex))"
|
|
246
|
+
/>
|
|
247
|
+
<TriangleIcon v-else-if="col.type === 'expand'" />
|
|
248
|
+
<span v-if="item.row[col.dataIndex] != null">{{ item.row[col.dataIndex] }}</span>
|
|
249
|
+
</div>
|
|
250
|
+
</td>
|
|
251
|
+
</template>
|
|
252
|
+
<td v-if="virtualX_on" class="vt-x-right"></td>
|
|
253
|
+
</template>
|
|
254
|
+
</tr>
|
|
255
|
+
<tr
|
|
256
|
+
v-else-if="item.type === 'above-ph'"
|
|
257
|
+
class="vt-above-viewport-ph-row"
|
|
258
|
+
:data-above-count="item.count"
|
|
259
|
+
:style="`height: calc(var(--row-height) * ${item.count})`"
|
|
260
|
+
></tr>
|
|
261
|
+
<tr
|
|
262
|
+
v-else
|
|
263
|
+
class="vt-below-viewport-ph"
|
|
264
|
+
:data-below-count="item.count"
|
|
265
|
+
:style="`height: calc(var(--row-height) * ${item.count})`"
|
|
266
|
+
></tr>
|
|
267
|
+
</template>
|
|
268
|
+
<template v-if="!isExperimentalScrollY">
|
|
269
|
+
<tr v-if="virtual_on && !isSRBRActive" :style="offsetBottomStyle"></tr>
|
|
270
|
+
<tr v-if="SRBRBottomHeight" :style="SRBRBottomStyle"></tr>
|
|
271
|
+
</template>
|
|
272
|
+
</tbody>
|
|
273
|
+
</table>
|
|
274
|
+
<div
|
|
275
|
+
v-if="scrollbarOptions.enabled && showScrollbar.y"
|
|
276
|
+
class="stk-sb-thumb vertical"
|
|
277
|
+
:style="`height:${scrollbar.h}px;transform:translateY(${scrollbar.t}px)`"
|
|
278
|
+
@mousedown="onVerticalScrollbarMouseDown"
|
|
279
|
+
@touchstart.passive="onVerticalScrollbarMouseDown"
|
|
280
|
+
></div>
|
|
281
|
+
</div>
|
|
282
|
+
<div v-if="(!dataSourceCopy || !dataSourceCopy.length) && showNoData" class="stk-table-no-data" :class="{ 'no-data-full': noDataFull }">
|
|
283
|
+
<slot name="empty">暂无数据</slot>
|
|
284
|
+
</div>
|
|
285
|
+
<slot name="customBottom"></slot>
|
|
286
|
+
<div
|
|
287
|
+
v-if="scrollbarOptions.enabled && showScrollbar.x"
|
|
288
|
+
class="stk-sb-thumb horizontal"
|
|
289
|
+
:style="`width:${scrollbar.w}px;transform:translateX(${scrollbar.l}px)`"
|
|
290
|
+
@mousedown="onHorizontalScrollbarMouseDown"
|
|
291
|
+
@touchstart.passive="onHorizontalScrollbarMouseDown"
|
|
292
|
+
></div>
|
|
293
|
+
</div>
|
|
294
|
+
</template>
|
|
295
|
+
|
|
296
|
+
<!-- Vue 2.7 compatible: add component name -->
|
|
297
|
+
<script lang="ts">
|
|
298
|
+
export default {
|
|
299
|
+
name: 'StkTable',
|
|
300
|
+
};
|
|
301
|
+
</script>
|
|
302
|
+
|
|
303
|
+
<script setup lang="ts">
|
|
304
|
+
/**
|
|
305
|
+
* StkTable 主组件(Vue 3 / Vue 2.7)
|
|
306
|
+
*
|
|
307
|
+
* 这是一个超大的核心组件文件,约 1800+ 行。理解它时请按以下「分区地图」定位:
|
|
308
|
+
*
|
|
309
|
+
* 1. 顶部(template 261 行前)
|
|
310
|
+
* 渲染结构:滚动容器 + 原生 <table>。包括 colgroup(固定模式列宽)、thead(多级表头、
|
|
311
|
+
* sticky 固定)、tbody(虚拟滚动 tr / 空 td 定位)、tfoot(汇总行)、自定义滚动条容器。
|
|
312
|
+
* 通过 CSS 类(如 virtual / virtual-x / area-selection / theme 等)联动对应 hook 状态。
|
|
313
|
+
*
|
|
314
|
+
* 2. script setup(270 行起)
|
|
315
|
+
* - props 定义:全部配置项,即公共 API(对应 types/index.ts 与文档 table-props.md)。
|
|
316
|
+
* - 大量 useXxx hooks 的组装:本文件只做「装配」,具体逻辑在对应 hook 中。
|
|
317
|
+
*
|
|
318
|
+
* 3. 核心 hook 与职责(均在 src/StkTable/use*.ts):
|
|
319
|
+
* - useVirtualScroll XY 轴虚拟滚动(可视区裁剪、占位 tr/th 定位、scroll 计算)
|
|
320
|
+
* - useTableColumns 列配置归一化 / 多级表头展开为叶子列
|
|
321
|
+
* - useFixedCol/useFixedStyle/getFixedColPosition 固定列 sticky/relative 定位
|
|
322
|
+
* - useSorter/useTableColumns.sort 排序(本地/远端、多列)
|
|
323
|
+
* - useColResize/useThDrag/useTrDrag 列宽调整 / 表头拖拽 / 行拖拽
|
|
324
|
+
* - useMergeCells/useMaxRowSpan 单元格合并与最大行跨
|
|
325
|
+
* - useRowExpand/useTree 展开行 / 树形
|
|
326
|
+
* - useHighlight 单元格/行高亮(Web Animations API 或 css)
|
|
327
|
+
* - useAreaSelection (features/) 区域选取(Excel 式键盘操作)
|
|
328
|
+
* - useScrollbar/useScrollRowByRow/useWheeling 自定义滚动条 / 按行滚动 / 滚轮
|
|
329
|
+
* - useAutoResize 容器尺寸自动适配
|
|
330
|
+
*
|
|
331
|
+
* 4. expose:对外暴露的实例方法(对应文档 expose.md),供 ref 调用。
|
|
332
|
+
*
|
|
333
|
+
* 注意:此文件与 vue2.7 兼容,props 不可抽离到独立文件(见下方注释),修改时注意不要破坏兼容性。
|
|
334
|
+
*
|
|
335
|
+
* @author japlus
|
|
336
|
+
*/
|
|
337
|
+
import { computed, nextTick, onMounted, provide, ref, shallowRef, toRaw, toRef, watch } from 'vue';
|
|
338
|
+
import DragHandle from './components/DragHandle.vue';
|
|
339
|
+
import SortIcon from './components/SortIcon.vue';
|
|
340
|
+
import TreeNodeCell from './components/TreeNodeCell.vue';
|
|
341
|
+
import TriangleIcon from './components/TriangleIcon.vue';
|
|
342
|
+
import {
|
|
343
|
+
CELL_KEY_SEPARATE,
|
|
344
|
+
DEFAULT_ROW_ACTIVE_CONFIG,
|
|
345
|
+
DEFAULT_ROW_HEIGHT,
|
|
346
|
+
DEFAULT_SMOOTH_SCROLL,
|
|
347
|
+
DEFAULT_SORT_CONFIG,
|
|
348
|
+
IS_LEGACY_MODE,
|
|
349
|
+
} from './const';
|
|
350
|
+
import type { FilterStatus } from './custom-cells/FilterCell/types';
|
|
351
|
+
import { useAreaSelectionName } from './features';
|
|
352
|
+
import { ON_DEMAND_FEATURE } from './registerFeature';
|
|
353
|
+
import {
|
|
354
|
+
AreaSelectionConfig,
|
|
355
|
+
AreaSelectionRange,
|
|
356
|
+
AutoRowHeightConfig,
|
|
357
|
+
ColResizableConfig,
|
|
358
|
+
DragRowConfig,
|
|
359
|
+
ExpandConfig,
|
|
360
|
+
ExperimentalConfig,
|
|
361
|
+
FooterConfig,
|
|
362
|
+
HeaderDragConfig,
|
|
363
|
+
HighlightConfig,
|
|
364
|
+
Order,
|
|
365
|
+
PrivateRowDT,
|
|
366
|
+
PrivateStkTableColumn,
|
|
367
|
+
RowActiveOption,
|
|
368
|
+
ScrollAxisTarget,
|
|
369
|
+
ScrollToFn,
|
|
370
|
+
ScrollToOptions,
|
|
371
|
+
SeqConfig,
|
|
372
|
+
SortConfig,
|
|
373
|
+
StkTableColumn,
|
|
374
|
+
TagType,
|
|
375
|
+
TreeConfig,
|
|
376
|
+
UniqKey,
|
|
377
|
+
UniqKeyProp,
|
|
378
|
+
} from './types/index';
|
|
379
|
+
import { useAutoResize } from './useAutoResize';
|
|
380
|
+
import { useColResize } from './useColResize';
|
|
381
|
+
import { useFixedCol } from './useFixedCol';
|
|
382
|
+
import { useFixedStyle } from './useFixedStyle';
|
|
383
|
+
import { useGetFixedColPosition } from './useGetFixedColPosition';
|
|
384
|
+
import { useHighlight } from './useHighlight';
|
|
385
|
+
import { useKeyboardArrowScroll } from './useKeyboardArrowScroll';
|
|
386
|
+
import { useMaxRowSpan } from './useMaxRowSpan';
|
|
387
|
+
import { createMergeCellsCache } from './mergeCellsCache';
|
|
388
|
+
import { useMergeCells } from './useMergeCells';
|
|
389
|
+
import { useRowExpand } from './useRowExpand';
|
|
390
|
+
import { useScrollbar, type ScrollbarOptions } from './useScrollbar';
|
|
391
|
+
import { useScrollRowByRow } from './useScrollRowByRow';
|
|
392
|
+
import { useSorter } from './useSorter';
|
|
393
|
+
import { useTableColumns } from './useTableColumns';
|
|
394
|
+
import { useThDrag } from './useThDrag';
|
|
395
|
+
import { useTrDrag } from './useTrDrag';
|
|
396
|
+
import { useTree } from './useTree';
|
|
397
|
+
// import { useIndexResolver } from './useIndexResolver';
|
|
398
|
+
import { useVirtualScroll } from './useVirtualScroll';
|
|
399
|
+
import { useWheeling } from './useWheeling';
|
|
400
|
+
import { createStkTableId, getCalculatedColWidth } from './utils/constRefUtils';
|
|
401
|
+
import { getClosestColKey, getClosestTd, getClosestTr, getClosestTrIndex, rafThrottle, transformWidthToStr } from './utils/index';
|
|
402
|
+
|
|
403
|
+
/** Generic stands for DataType */
|
|
404
|
+
type DT = any & PrivateRowDT;
|
|
405
|
+
|
|
406
|
+
/** generate table instance id */
|
|
407
|
+
const stkTableId = createStkTableId();
|
|
408
|
+
|
|
409
|
+
// Vue 2.7 compatible: use export default instead of defineOptions
|
|
410
|
+
// defineOptions({ name: 'StkTable' });
|
|
411
|
+
/**
|
|
412
|
+
* props cannot be placed in a separate file. It will cause compilation errors with vue 2.7 compiler.
|
|
413
|
+
*/
|
|
414
|
+
const props = withDefaults(
|
|
415
|
+
defineProps<{
|
|
416
|
+
/** 表格宽度*/
|
|
417
|
+
width?: string;
|
|
418
|
+
/** 最小表格宽度 @deprecated*/
|
|
419
|
+
minWidth?: string;
|
|
420
|
+
/** 表格最大宽度 @deprecated*/
|
|
421
|
+
maxWidth?: string;
|
|
422
|
+
/** 斑马线条纹 */
|
|
423
|
+
stripe?: boolean;
|
|
424
|
+
/** 是否使用 table-layout:fixed(低版本浏览器需要设置table) */
|
|
425
|
+
fixedMode?: boolean;
|
|
426
|
+
/** 是否隐藏表头 */
|
|
427
|
+
headless?: boolean;
|
|
428
|
+
/** 主题,亮、暗 */
|
|
429
|
+
theme?: 'light' | 'dark';
|
|
430
|
+
/**
|
|
431
|
+
* 行高
|
|
432
|
+
* - `props.autoRowHeight` 为 `true` 时,将表示为期望行高,用于计算。不再影响实际行高。
|
|
433
|
+
* - 支持动态修改,修改后自动重算可视区行数与滚动高度,并与 `--row-height` 保持同步。
|
|
434
|
+
*/
|
|
435
|
+
rowHeight?: number;
|
|
436
|
+
/**
|
|
437
|
+
* 是否可变行高
|
|
438
|
+
* - 设置为 `true` 时, `props.rowHeight` 将表示为期望行高,用于计算。不再影响实际行高。
|
|
439
|
+
*/
|
|
440
|
+
autoRowHeight?: boolean | AutoRowHeightConfig<DT>;
|
|
441
|
+
/** 是否高亮鼠标悬浮的行 */
|
|
442
|
+
rowHover?: boolean;
|
|
443
|
+
/** 是否高亮选中的行 */
|
|
444
|
+
rowActive?: boolean | RowActiveOption<DT>;
|
|
445
|
+
/**
|
|
446
|
+
* @deprecated
|
|
447
|
+
*/
|
|
448
|
+
rowCurrentRevokable?: boolean;
|
|
449
|
+
/**
|
|
450
|
+
* 表头行高。default = rowHeight
|
|
451
|
+
* - 支持动态修改,表头占用的表体行数(pageSize)随之重算。
|
|
452
|
+
*/
|
|
453
|
+
headerRowHeight?: number | string;
|
|
454
|
+
/**
|
|
455
|
+
* 表尾行高。default = rowHeight
|
|
456
|
+
* - 支持动态修改。表尾行悬浮于滚动区底部,不计入虚拟滚动的总高度。
|
|
457
|
+
*/
|
|
458
|
+
footerRowHeight?: number | string;
|
|
459
|
+
/** 虚拟滚动 */
|
|
460
|
+
virtual?: boolean;
|
|
461
|
+
/** x轴虚拟滚动(必须设置列宽)*/
|
|
462
|
+
virtualX?: boolean;
|
|
463
|
+
/** 表格列配置 */
|
|
464
|
+
columns?: StkTableColumn<DT>[];
|
|
465
|
+
/** 表格数据源 */
|
|
466
|
+
dataSource?: DT[];
|
|
467
|
+
/** 行唯一键 (行唯一值不能为undefined) */
|
|
468
|
+
rowKey?: UniqKeyProp;
|
|
469
|
+
/** 列唯一键 */
|
|
470
|
+
colKey?: UniqKeyProp;
|
|
471
|
+
/** 空值展示文字 */
|
|
472
|
+
emptyCellText?: string | ((option: { row: DT; col: StkTableColumn<DT> }) => string);
|
|
473
|
+
/** 暂无数据兜底高度是否撑满 */
|
|
474
|
+
noDataFull?: boolean;
|
|
475
|
+
/** 是否展示暂无数据 */
|
|
476
|
+
showNoData?: boolean;
|
|
477
|
+
/** 是否服务端排序,true则不排序数据 */
|
|
478
|
+
sortRemote?: boolean;
|
|
479
|
+
/** 表头是否溢出展示... */
|
|
480
|
+
showHeaderOverflow?: boolean;
|
|
481
|
+
/** 表体溢出是否展示... */
|
|
482
|
+
showOverflow?: boolean;
|
|
483
|
+
/** 是否增加行hover class $*$ rename*/
|
|
484
|
+
showTrHoverClass?: boolean;
|
|
485
|
+
/** 是否高亮鼠标悬浮的单元格 */
|
|
486
|
+
cellHover?: boolean;
|
|
487
|
+
/** 是否高亮选中的单元格 */
|
|
488
|
+
cellActive?: boolean;
|
|
489
|
+
/** 单元格再次点击否可以取消选中 (cellActive=true)*/
|
|
490
|
+
selectedCellRevokable?: boolean;
|
|
491
|
+
/** 是否启用单元格范围选中(拖拽选区) */
|
|
492
|
+
areaSelection?: boolean | AreaSelectionConfig;
|
|
493
|
+
/** 表头是否可拖动。支持回调函数。 */
|
|
494
|
+
headerDrag?: boolean | HeaderDragConfig<DT>;
|
|
495
|
+
/**
|
|
496
|
+
* 给行附加className<br>
|
|
497
|
+
* FIXME: 是否需要优化,因为不传此prop会使表格行一直执行空函数,是否有影响
|
|
498
|
+
*/
|
|
499
|
+
rowClassName?: (row: DT, i: number) => string | undefined;
|
|
500
|
+
/**
|
|
501
|
+
* 列宽是否可拖动(需要设置v-model:columns)<br>
|
|
502
|
+
* **不要设置**列minWidth,**必须**设置width<br>
|
|
503
|
+
* 列宽拖动时,每一列都必须要有width,且minWidth/maxWidth不生效。table width会变为"fit-content"。
|
|
504
|
+
* - 会自动更新props.columns中的with属性
|
|
505
|
+
*/
|
|
506
|
+
colResizable?: boolean | ColResizableConfig<DT>;
|
|
507
|
+
/** 可拖动至最小的列宽 */
|
|
508
|
+
colMinWidth?: number;
|
|
509
|
+
/**
|
|
510
|
+
* 单元格分割线。
|
|
511
|
+
* 默认横竖都有
|
|
512
|
+
* "h" - 仅展示横线
|
|
513
|
+
* "v" - 仅展示竖线
|
|
514
|
+
* "body-v" - 仅表体展示竖线
|
|
515
|
+
*/
|
|
516
|
+
bordered?: boolean | 'h' | 'v' | 'body-v' | 'body-h';
|
|
517
|
+
/**
|
|
518
|
+
* 自动重新计算虚拟滚动高度宽度。默认true
|
|
519
|
+
* [非响应式]
|
|
520
|
+
* 传入方法表示resize后的回调
|
|
521
|
+
*/
|
|
522
|
+
autoResize?: boolean | (() => void);
|
|
523
|
+
/** 是否展示固定列阴影。为节省性能,默认false。 */
|
|
524
|
+
fixedColShadow?: boolean;
|
|
525
|
+
/** 优化vue2 滚动 */
|
|
526
|
+
optimizeVue2Scroll?: boolean;
|
|
527
|
+
/** 排序配置 */
|
|
528
|
+
sortConfig?: SortConfig<DT>;
|
|
529
|
+
/** 隐藏头部title。可传入colKey数组 */
|
|
530
|
+
hideHeaderTitle?: boolean | string[];
|
|
531
|
+
/** 高亮配置 */
|
|
532
|
+
highlightConfig?: HighlightConfig;
|
|
533
|
+
/** 序号列配置 */
|
|
534
|
+
seqConfig?: SeqConfig;
|
|
535
|
+
/** 展开行配置 */
|
|
536
|
+
expandConfig?: ExpandConfig;
|
|
537
|
+
/** 行拖动配置 */
|
|
538
|
+
dragRowConfig?: DragRowConfig;
|
|
539
|
+
/** 树形配置 */
|
|
540
|
+
treeConfig?: TreeConfig;
|
|
541
|
+
/**
|
|
542
|
+
* 固定头,固定列实现方式。(非响应式)
|
|
543
|
+
*
|
|
544
|
+
* relative:固定列只会放在props.columns的两侧。
|
|
545
|
+
* - 如果列宽会变动则谨慎使用。
|
|
546
|
+
* - 多级表头固定列慎用
|
|
547
|
+
*
|
|
548
|
+
* 低版本浏览器强制为'relative',
|
|
549
|
+
*/
|
|
550
|
+
cellFixedMode?: 'sticky' | 'relative';
|
|
551
|
+
/**
|
|
552
|
+
* 是否平滑滚动。default: chrome < 85 ? true : false
|
|
553
|
+
* - false: 使用 onwheel 滚动。为了防止滚动过快导致白屏。
|
|
554
|
+
* - true: 不使用 onwheel 滚动。鼠标滚轮滚动时更加平滑。滚动过快时会白屏。
|
|
555
|
+
*/
|
|
556
|
+
smoothScroll?: boolean;
|
|
557
|
+
/**
|
|
558
|
+
* 按整数行纵向滚动
|
|
559
|
+
* - scrollbar:仅拖动滚动条生效
|
|
560
|
+
*/
|
|
561
|
+
scrollRowByRow?: boolean | 'scrollbar';
|
|
562
|
+
/**
|
|
563
|
+
* 自定义滚动条配置
|
|
564
|
+
* - false: 禁用自定义滚动条
|
|
565
|
+
* - true: 启用默认配置的自定义滚动条
|
|
566
|
+
* - ScrollbarOptions: 启用并配置自定义滚动条
|
|
567
|
+
*/
|
|
568
|
+
scrollbar?: boolean | ScrollbarOptions;
|
|
569
|
+
/**
|
|
570
|
+
* 实验性功能配置
|
|
571
|
+
*/
|
|
572
|
+
experimental?: ExperimentalConfig;
|
|
573
|
+
/** 表格底部合计行数据 */
|
|
574
|
+
footerData?: DT[];
|
|
575
|
+
/** 表格底部配置 */
|
|
576
|
+
footerConfig?: FooterConfig;
|
|
577
|
+
}>(),
|
|
578
|
+
{
|
|
579
|
+
width: '',
|
|
580
|
+
fixedMode: false,
|
|
581
|
+
stripe: false,
|
|
582
|
+
minWidth: '',
|
|
583
|
+
maxWidth: '',
|
|
584
|
+
headless: false,
|
|
585
|
+
theme: 'light',
|
|
586
|
+
rowHeight: DEFAULT_ROW_HEIGHT,
|
|
587
|
+
autoRowHeight: () => false,
|
|
588
|
+
footerData: () => [],
|
|
589
|
+
rowHover: true,
|
|
590
|
+
rowActive: () => DEFAULT_ROW_ACTIVE_CONFIG,
|
|
591
|
+
rowCurrentRevokable: true,
|
|
592
|
+
headerRowHeight: DEFAULT_ROW_HEIGHT,
|
|
593
|
+
footerRowHeight: DEFAULT_ROW_HEIGHT,
|
|
594
|
+
virtual: false,
|
|
595
|
+
virtualX: false,
|
|
596
|
+
columns: () => [],
|
|
597
|
+
dataSource: () => [],
|
|
598
|
+
rowKey: '',
|
|
599
|
+
colKey: void 0,
|
|
600
|
+
emptyCellText: '--',
|
|
601
|
+
noDataFull: false,
|
|
602
|
+
showNoData: true,
|
|
603
|
+
sortRemote: false,
|
|
604
|
+
showHeaderOverflow: false,
|
|
605
|
+
showOverflow: false,
|
|
606
|
+
showTrHoverClass: false,
|
|
607
|
+
cellHover: false,
|
|
608
|
+
cellActive: false,
|
|
609
|
+
selectedCellRevokable: true,
|
|
610
|
+
areaSelection: false,
|
|
611
|
+
headerDrag: () => false,
|
|
612
|
+
rowClassName: () => '',
|
|
613
|
+
colResizable: () => false,
|
|
614
|
+
colMinWidth: 10,
|
|
615
|
+
bordered: true,
|
|
616
|
+
autoResize: true,
|
|
617
|
+
fixedColShadow: false,
|
|
618
|
+
optimizeVue2Scroll: false,
|
|
619
|
+
sortConfig: () => DEFAULT_SORT_CONFIG,
|
|
620
|
+
hideHeaderTitle: false,
|
|
621
|
+
highlightConfig: () => ({}),
|
|
622
|
+
seqConfig: () => ({}),
|
|
623
|
+
expandConfig: () => ({}),
|
|
624
|
+
dragRowConfig: () => ({}),
|
|
625
|
+
treeConfig: () => ({}),
|
|
626
|
+
cellFixedMode: 'sticky',
|
|
627
|
+
smoothScroll: DEFAULT_SMOOTH_SCROLL,
|
|
628
|
+
scrollRowByRow: false,
|
|
629
|
+
scrollbar: false,
|
|
630
|
+
experimental: () => ({}),
|
|
631
|
+
footerConfig: () => ({ position: 'bottom' }),
|
|
632
|
+
},
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
provide('stkTheme', toRef(props, 'theme'));
|
|
636
|
+
|
|
637
|
+
const emits = defineEmits<{
|
|
638
|
+
/**
|
|
639
|
+
* 排序变更触发。defaultSort.dataIndex 找不到时,col 将返回null。
|
|
640
|
+
*
|
|
641
|
+
* ```(col: StkTableColumn<DT> | null, order: Order, data: DT[], sortConfig: SortConfig<DT>)```
|
|
642
|
+
*/
|
|
643
|
+
(e: 'sort-change', col: StkTableColumn<DT> | null, order: Order, data: DT[], sortConfig: SortConfig<DT>): void;
|
|
644
|
+
/**
|
|
645
|
+
* 一行点击事件
|
|
646
|
+
*
|
|
647
|
+
* ```(ev: MouseEvent, row: DT, data: { rowIndex: number })```
|
|
648
|
+
*/
|
|
649
|
+
(e: 'row-click', ev: MouseEvent, row: DT, data: { rowIndex: number }): void;
|
|
650
|
+
/**
|
|
651
|
+
* 选中一行触发。ev返回null表示不是点击事件触发的
|
|
652
|
+
*
|
|
653
|
+
* ```(ev: MouseEvent | null, row: DT | undefined, data: { select: boolean} })```
|
|
654
|
+
*/
|
|
655
|
+
(e: 'current-change', ev: MouseEvent | null, row: DT | undefined, data: { select: boolean }): void;
|
|
656
|
+
/**
|
|
657
|
+
* 选中单元格触发。ev返回null表示不是点击事件触发的
|
|
658
|
+
*
|
|
659
|
+
* ```(ev: MouseEvent | null, data: { select: boolean; row: DT | undefined; col: StkTableColumn<DT> | null })```
|
|
660
|
+
*/
|
|
661
|
+
(e: 'cell-selected', ev: MouseEvent | null, data: { select: boolean; row: DT | undefined; col: StkTableColumn<DT> | undefined }): void;
|
|
662
|
+
/**
|
|
663
|
+
* 行双击事件
|
|
664
|
+
*
|
|
665
|
+
* ```(ev: MouseEvent, row: DT, data: { rowIndex: number })```
|
|
666
|
+
*/
|
|
667
|
+
(e: 'row-dblclick', ev: MouseEvent, row: DT, data: { rowIndex: number }): void;
|
|
668
|
+
/**
|
|
669
|
+
* 表头右键事件
|
|
670
|
+
*
|
|
671
|
+
* ```(ev: MouseEvent)```
|
|
672
|
+
*/
|
|
673
|
+
(e: 'header-row-menu', ev: MouseEvent): void;
|
|
674
|
+
/**
|
|
675
|
+
* 表体行右键点击事件
|
|
676
|
+
*
|
|
677
|
+
* ```(ev: MouseEvent, row: DT, data: { rowIndex: number })```
|
|
678
|
+
*/
|
|
679
|
+
(e: 'row-menu', ev: MouseEvent, row: DT, data: { rowIndex: number }): void;
|
|
680
|
+
/**
|
|
681
|
+
* 单元格点击事件
|
|
682
|
+
*
|
|
683
|
+
* ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>, data: { rowIndex: number })```
|
|
684
|
+
*/
|
|
685
|
+
(e: 'cell-click', ev: MouseEvent, row: DT, col: StkTableColumn<DT>, data: { rowIndex: number }): void;
|
|
686
|
+
/**
|
|
687
|
+
* 单元格鼠标进入事件
|
|
688
|
+
*
|
|
689
|
+
* ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>)```
|
|
690
|
+
*/
|
|
691
|
+
(e: 'cell-mouseenter', ev: MouseEvent, row: DT, col: StkTableColumn<DT>): void;
|
|
692
|
+
/**
|
|
693
|
+
* 单元格鼠标移出事件
|
|
694
|
+
*
|
|
695
|
+
* ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>)```
|
|
696
|
+
*/
|
|
697
|
+
(e: 'cell-mouseleave', ev: MouseEvent, row: DT, col: StkTableColumn<DT>): void;
|
|
698
|
+
/**
|
|
699
|
+
* 单元格悬浮事件
|
|
700
|
+
*
|
|
701
|
+
* ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>)```
|
|
702
|
+
*/
|
|
703
|
+
(e: 'cell-mouseover', ev: MouseEvent, row: DT, col: StkTableColumn<DT>): void;
|
|
704
|
+
/**
|
|
705
|
+
* 单元格鼠标按下事件
|
|
706
|
+
*
|
|
707
|
+
* ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>, data: { rowIndex: number })```
|
|
708
|
+
*/
|
|
709
|
+
(e: 'cell-mousedown', ev: MouseEvent, row: DT, col: StkTableColumn<DT>, data: { rowIndex: number }): void;
|
|
710
|
+
/**
|
|
711
|
+
* 表头单元格点击事件
|
|
712
|
+
*
|
|
713
|
+
* ```(ev: MouseEvent, col: StkTableColumn<DT>)```
|
|
714
|
+
*/
|
|
715
|
+
(e: 'header-cell-click', ev: MouseEvent, col: StkTableColumn<DT>): void;
|
|
716
|
+
/**
|
|
717
|
+
* 表格滚动事件
|
|
718
|
+
*
|
|
719
|
+
* ```(ev: Event, data: { startIndex: number; endIndex: number })```
|
|
720
|
+
*/
|
|
721
|
+
(e: 'scroll', ev: Event, data: { startIndex: number; endIndex: number }): void;
|
|
722
|
+
/**
|
|
723
|
+
* 表格横向滚动事件
|
|
724
|
+
*
|
|
725
|
+
* ```(ev: Event)```
|
|
726
|
+
*/
|
|
727
|
+
(e: 'scroll-x', ev: Event): void;
|
|
728
|
+
/**
|
|
729
|
+
* 表头列拖动事件
|
|
730
|
+
*
|
|
731
|
+
* ```(dragStartKey: string, targetColKey: string)```
|
|
732
|
+
*/
|
|
733
|
+
(e: 'col-order-change', dragStartKey: string, targetColKey: string): void;
|
|
734
|
+
/**
|
|
735
|
+
* 表头列拖动开始
|
|
736
|
+
*
|
|
737
|
+
* ```(dragStartKey: string)```
|
|
738
|
+
*/
|
|
739
|
+
(e: 'th-drag-start', dragStartKey: string): void;
|
|
740
|
+
/**
|
|
741
|
+
* 表头列拖动drop
|
|
742
|
+
*
|
|
743
|
+
* ```(targetColKey: string)```
|
|
744
|
+
*/
|
|
745
|
+
(e: 'th-drop', targetColKey: string): void;
|
|
746
|
+
/**
|
|
747
|
+
* 行拖动事件
|
|
748
|
+
*
|
|
749
|
+
* ```(dragStartKey: string, targetRowKey: string)```
|
|
750
|
+
*/
|
|
751
|
+
(e: 'row-order-change', dragStartKey: string, targetRowKey: string): void;
|
|
752
|
+
/**
|
|
753
|
+
* 列宽变动时触发
|
|
754
|
+
*
|
|
755
|
+
* ```(col: StkTableColumn<DT>)```
|
|
756
|
+
*/
|
|
757
|
+
(e: 'col-resize', col: StkTableColumn<DT>): void;
|
|
758
|
+
/**
|
|
759
|
+
* 展开行触发
|
|
760
|
+
*
|
|
761
|
+
* ```( data: { expanded: boolean; row: DT; col: StkTableColumn<DT> })```
|
|
762
|
+
*/
|
|
763
|
+
(e: 'toggle-row-expand', data: { expanded: boolean; row: DT; col: StkTableColumn<DT> | null }): void;
|
|
764
|
+
/**
|
|
765
|
+
* 点击展开树行触发
|
|
766
|
+
*
|
|
767
|
+
* ```( data: { expanded: boolean; row: DT; col: StkTableColumn<DT> })```
|
|
768
|
+
*/
|
|
769
|
+
(e: 'toggle-tree-expand', data: { expanded: boolean; row: DT; col: StkTableColumn<DT> | null }): void;
|
|
770
|
+
/**
|
|
771
|
+
* 单元格选区变更事件
|
|
772
|
+
*
|
|
773
|
+
* ```(ranges: AreaSelectionRange[])```
|
|
774
|
+
*/
|
|
775
|
+
(e: 'area-selection-change', ranges: AreaSelectionRange[]): void;
|
|
776
|
+
/**
|
|
777
|
+
* 筛选变更触发(Beta)
|
|
778
|
+
*
|
|
779
|
+
* ```(status: Record<UniqKey, FilterStatus>)```
|
|
780
|
+
*/
|
|
781
|
+
(e: 'filter-change', status: Record<UniqKey, FilterStatus>): void;
|
|
782
|
+
/**
|
|
783
|
+
* v-model:columns col resize 时更新宽度
|
|
784
|
+
*/
|
|
785
|
+
(e: 'update:columns', cols: StkTableColumn<DT>[]): void;
|
|
786
|
+
}>();
|
|
787
|
+
|
|
788
|
+
// 仅支持vue3.3+
|
|
789
|
+
// const slots = defineSlots<{
|
|
790
|
+
// /** 表头插槽 */
|
|
791
|
+
// tableHeader(props: { col: StkTableColumn<DT> }): void;
|
|
792
|
+
// /** 空状态插槽 */
|
|
793
|
+
// empty(): void;
|
|
794
|
+
// }>();
|
|
795
|
+
|
|
796
|
+
const tableContainerRef = ref<HTMLDivElement>();
|
|
797
|
+
const colResizeIndicatorRef = ref<HTMLDivElement>();
|
|
798
|
+
const trRef = ref<HTMLTableRowElement[]>();
|
|
799
|
+
|
|
800
|
+
/** 是否使用 relative 固定头和列 */
|
|
801
|
+
const isRelativeMode = ref(IS_LEGACY_MODE ? true : props.cellFixedMode === 'relative');
|
|
802
|
+
|
|
803
|
+
/** 表格底部是否吸附在顶部 */
|
|
804
|
+
const isFooterTop = computed(() => props.footerConfig?.position === 'top');
|
|
805
|
+
|
|
806
|
+
/** 表格底部标签名:顶部吸附用 tbody,底部吸附用 tfoot */
|
|
807
|
+
const footerTagName = computed(() => (isFooterTop.value ? 'tbody' : 'tfoot'));
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* 当前选中的一行
|
|
811
|
+
* - shallowRef: 使 currentRow.value === row 地址相同。防止rowKeyGen 的WeakMap key不一致。
|
|
812
|
+
*/
|
|
813
|
+
const currentRow = shallowRef<DT>();
|
|
814
|
+
/**
|
|
815
|
+
* 保存当前选中行的key<br>
|
|
816
|
+
* 原因:vue3 不用ref包dataSource时,row为原始对象,与currentItem(Ref)相比会不相等。
|
|
817
|
+
*/
|
|
818
|
+
const currentRowKey = ref<UniqKey | undefined>();
|
|
819
|
+
/** 当前选中的单元格key */
|
|
820
|
+
const currentSelectedCellKey = ref<string | undefined>();
|
|
821
|
+
/** 当前hover行 */
|
|
822
|
+
let currentHoverRow: DT | null = null;
|
|
823
|
+
/** 当前hover的行的key */
|
|
824
|
+
const currentHoverRowKey = ref<UniqKey | null>(null);
|
|
825
|
+
/** 当前hover的列的key */
|
|
826
|
+
// const currentColHoverKey = ref(null);
|
|
827
|
+
|
|
828
|
+
const [tableHeaders, tableHeadersForCalc, dealColumns] = useTableColumns<DT>(props.virtualX, isRelativeMode);
|
|
829
|
+
|
|
830
|
+
const filterStatus = ref<Record<UniqKey, FilterStatus>>({});
|
|
831
|
+
|
|
832
|
+
/** 最后一行的tableHeaders.内容是 props.columns 的引用集合 */
|
|
833
|
+
const tableHeaderLast = computed(() => tableHeadersForCalc.value.slice(-1)[0] || []);
|
|
834
|
+
|
|
835
|
+
const isTreeData = computed(() => {
|
|
836
|
+
return props.columns.some(col => col.type === 'tree-node');
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
const rowActiveProp = computed<Required<RowActiveOption<DT>>>(() => {
|
|
840
|
+
const { rowActive } = props;
|
|
841
|
+
if (typeof rowActive === 'boolean') {
|
|
842
|
+
return {
|
|
843
|
+
...DEFAULT_ROW_ACTIVE_CONFIG,
|
|
844
|
+
enabled: rowActive ?? true,
|
|
845
|
+
revokable: Boolean(props.rowCurrentRevokable),
|
|
846
|
+
};
|
|
847
|
+
} else {
|
|
848
|
+
return { ...DEFAULT_ROW_ACTIVE_CONFIG, ...rowActive };
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
const dataSourceCopy = shallowRef<DT[]>([]);
|
|
853
|
+
|
|
854
|
+
const rowKeyGenComputed = computed(() => {
|
|
855
|
+
const { rowKey } = props;
|
|
856
|
+
if (typeof rowKey === 'function') {
|
|
857
|
+
return (row: DT) => (rowKey as (row: DT) => string)(row);
|
|
858
|
+
} else {
|
|
859
|
+
return (row: DT) => row[rowKey];
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
const colKeyGen = computed<(col: StkTableColumn<DT>) => string>(() => {
|
|
864
|
+
const { colKey } = props;
|
|
865
|
+
if (colKey === void 0) {
|
|
866
|
+
return col => col.key || col.dataIndex;
|
|
867
|
+
} else if (typeof colKey === 'function') {
|
|
868
|
+
return col => (colKey as (col: StkTableColumn<DT>) => string)(col);
|
|
869
|
+
} else {
|
|
870
|
+
return col => (col as any)[colKey];
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
const getEmptyCellText = computed(() => {
|
|
875
|
+
const { emptyCellText } = props;
|
|
876
|
+
if (typeof emptyCellText === 'string') {
|
|
877
|
+
return () => emptyCellText;
|
|
878
|
+
} else {
|
|
879
|
+
return (col: StkTableColumn<DT>, row: DT) => emptyCellText({ row, col });
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
/** scroll-row-by-row total-height */
|
|
884
|
+
const SRBRTotalHeight = computed(() => {
|
|
885
|
+
if (!isSRBRActive.value || !props.virtual) return 0;
|
|
886
|
+
return (
|
|
887
|
+
dataSourceCopy.value.length * virtualScroll.value.rowHeight + tableHeaderHeight.value //+
|
|
888
|
+
);
|
|
889
|
+
});
|
|
890
|
+
const SRBRBottomHeight = computed(() => {
|
|
891
|
+
if (!isSRBRActive.value || !props.virtual) return 0;
|
|
892
|
+
const { containerHeight, rowHeight } = virtualScroll.value;
|
|
893
|
+
return (containerHeight - tableHeaderHeight.value) % rowHeight;
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
const scrollbarOptions = computed(() => ({
|
|
897
|
+
enabled: true,
|
|
898
|
+
minHeight: 20,
|
|
899
|
+
minWidth: 20,
|
|
900
|
+
width: 8,
|
|
901
|
+
height: 8,
|
|
902
|
+
...(typeof props.scrollbar === 'boolean' ? { enabled: props.scrollbar } : props.scrollbar),
|
|
903
|
+
}));
|
|
904
|
+
|
|
905
|
+
const isExperimentalScrollY = computed(() => {
|
|
906
|
+
if (scrollbarOptions.value?.enabled && props.scrollRowByRow) {
|
|
907
|
+
return true;
|
|
908
|
+
}
|
|
909
|
+
return props.experimental?.scrollY;
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
const rowKeyGenCache = new WeakMap();
|
|
913
|
+
|
|
914
|
+
// 使用 useSorter hook 管理排序逻辑
|
|
915
|
+
const [sortStates, sortCol, onColumnSort, setSorter, resetSorter, getSortColumns, dealDefaultSorter, getColumnSortState, sortData] = useSorter<DT>(
|
|
916
|
+
props,
|
|
917
|
+
emits,
|
|
918
|
+
colKeyGen,
|
|
919
|
+
tableHeaderLast,
|
|
920
|
+
dataSourceCopy,
|
|
921
|
+
initDataSource,
|
|
922
|
+
onDataSourceChange,
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
const [isSRBRActive] = useScrollRowByRow(props, tableContainerRef);
|
|
926
|
+
|
|
927
|
+
const [onThDragStart, onThDragOver, onThDrop, isHeaderDraggable] = useThDrag(props, emits, colKeyGen);
|
|
928
|
+
|
|
929
|
+
const [onTrDragStart, onTrDragEnter, onTrDragOver, onTrDrop, onTrDragEnd] = useTrDrag(props, emits, dataSourceCopy);
|
|
930
|
+
|
|
931
|
+
const [maxRowSpan, updateMaxRowSpan, getMaxRowSpanValue] = useMaxRowSpan(props, tableHeaderLast, rowKeyGen, dataSourceCopy);
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* mergeCells 结果共享缓存:useVirtualScroll(可视列范围修正)与 useMergeCells
|
|
935
|
+
* (hidden map 构建 / 视口上方占位 / 渲染)共用,同一单元格只调用一次用户回调,
|
|
936
|
+
* 且可跨滚动帧复用(数据/列变化时才清空)。
|
|
937
|
+
*/
|
|
938
|
+
const mergeCellsCache = createMergeCellsCache();
|
|
939
|
+
|
|
940
|
+
const [
|
|
941
|
+
virtualScroll,
|
|
942
|
+
virtualScrollX,
|
|
943
|
+
virtual_on,
|
|
944
|
+
virtual_dataSourcePart,
|
|
945
|
+
virtual_offsetBottom,
|
|
946
|
+
virtualX_on,
|
|
947
|
+
virtualX_offsetRight,
|
|
948
|
+
tableHeaderHeight,
|
|
949
|
+
initVirtualScroll,
|
|
950
|
+
initVirtualScrollY,
|
|
951
|
+
initVirtualScrollX,
|
|
952
|
+
updateVirtualScrollY,
|
|
953
|
+
updateVirtualScrollX,
|
|
954
|
+
setAutoHeight,
|
|
955
|
+
clearAllAutoHeight,
|
|
956
|
+
getRowsHeight,
|
|
957
|
+
clearColWidthCache,
|
|
958
|
+
virtualX_tableHeaders,
|
|
959
|
+
expandRowColspan,
|
|
960
|
+
theadVirtualX,
|
|
961
|
+
virtualX_columnPart,
|
|
962
|
+
virtualX_expandColSegments,
|
|
963
|
+
// 内部诊断用(测试通过 setupState 访问),不加入 defineExpose
|
|
964
|
+
getRowHeightCacheInfo,
|
|
965
|
+
] = useVirtualScroll(
|
|
966
|
+
props,
|
|
967
|
+
tableContainerRef,
|
|
968
|
+
trRef,
|
|
969
|
+
dataSourceCopy,
|
|
970
|
+
tableHeaderLast,
|
|
971
|
+
tableHeaders,
|
|
972
|
+
rowKeyGen,
|
|
973
|
+
maxRowSpan,
|
|
974
|
+
getMaxRowSpanValue,
|
|
975
|
+
scrollbarOptions,
|
|
976
|
+
isExperimentalScrollY,
|
|
977
|
+
isRelativeMode,
|
|
978
|
+
mergeCellsCache,
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
/** requestAnimationFrame throttled version of updateVirtualScrollY for smoother wheel scrolling */
|
|
982
|
+
const rafUpdateVirtualScrollYForWheel = rafThrottle(updateVirtualScrollY);
|
|
983
|
+
|
|
984
|
+
const [scrollbar, showScrollbar, onVerticalScrollbarMouseDown, onHorizontalScrollbarMouseDown, updateCustomScrollbar] = useScrollbar(
|
|
985
|
+
props,
|
|
986
|
+
tableContainerRef,
|
|
987
|
+
virtualScroll,
|
|
988
|
+
virtualScrollX,
|
|
989
|
+
updateVirtualScrollY,
|
|
990
|
+
scrollbarOptions,
|
|
991
|
+
isExperimentalScrollY,
|
|
992
|
+
);
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* 是否允许把虚拟窗口内的连续空行合并为占位 tr。
|
|
996
|
+
* autoRowHeight 时容器不定义 --row-height 且行高不均匀,不能合并。
|
|
997
|
+
*/
|
|
998
|
+
const canMergeEmptyRows = computed(() => virtual_on.value && !props.autoRowHeight);
|
|
999
|
+
|
|
1000
|
+
/** 视口上方行数(rowspan/stripe 修正区域)。不允许合并时为 0(上方行走常规行渲染) */
|
|
1001
|
+
const aboveViewportRowCount = computed(() => {
|
|
1002
|
+
if (!canMergeEmptyRows.value) return 0;
|
|
1003
|
+
const part = virtual_dataSourcePart.value;
|
|
1004
|
+
const { startIndex, viewportStartIndex } = virtualScroll.value;
|
|
1005
|
+
return Math.min(part.length, Math.max(0, viewportStartIndex - startIndex));
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* 视口下方可合并的占位行数。
|
|
1010
|
+
* rowspan 修正会使 endIndex 超出 viewportEndIndex,二者之间的行无可见内容
|
|
1011
|
+
* (跨视口的合并单元格由视口内锚点覆盖),此前逐行渲染为空 tr。
|
|
1012
|
+
* 这里将其合并为占位 tr(按跨界 rowspan 结束行切段,见 belowPhSegments),
|
|
1013
|
+
* 用 `height: calc(var(--row-height) * N)` 保持总高度,
|
|
1014
|
+
* 大 rowspan 场景下可显著减少 DOM 节点数。
|
|
1015
|
+
* 行高不均匀时(autoRowHeight / 该区域存在展开行)不能按统一行高合并,返回 0。
|
|
1016
|
+
*/
|
|
1017
|
+
const belowViewportRowCount = computed(() => {
|
|
1018
|
+
if (!canMergeEmptyRows.value) return 0;
|
|
1019
|
+
const part = virtual_dataSourcePart.value;
|
|
1020
|
+
const { startIndex, viewportEndIndex } = virtualScroll.value;
|
|
1021
|
+
// 视口行数(含 rowspan/stripe 修正的越界部分),钳制到实际数据长度
|
|
1022
|
+
const viewportCount = Math.min(part.length, Math.max(0, viewportEndIndex - startIndex + 1));
|
|
1023
|
+
const count = part.length - viewportCount;
|
|
1024
|
+
if (count <= 0) return 0;
|
|
1025
|
+
// 展开行有独立行高(--row-height 被行内覆写),不能参与统一行高合并
|
|
1026
|
+
for (let i = viewportCount; i < part.length; i++) {
|
|
1027
|
+
if (part[i]?.__EXP_R__) return 0;
|
|
1028
|
+
}
|
|
1029
|
+
return count;
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
const [
|
|
1033
|
+
hiddenCellMap,
|
|
1034
|
+
mergeCellsWrapper,
|
|
1035
|
+
hoverMergedCells,
|
|
1036
|
+
updateHoverMergedCells,
|
|
1037
|
+
activeMergedCells,
|
|
1038
|
+
updateActiveMergedCells,
|
|
1039
|
+
aboveViewportColumnMap,
|
|
1040
|
+
aboveEmptyBlocks,
|
|
1041
|
+
belowPhSegments,
|
|
1042
|
+
] = useMergeCells(
|
|
1043
|
+
rowActiveProp,
|
|
1044
|
+
tableHeaderLast,
|
|
1045
|
+
rowKeyGen,
|
|
1046
|
+
colKeyGen,
|
|
1047
|
+
virtual_dataSourcePart,
|
|
1048
|
+
virtualScroll,
|
|
1049
|
+
virtualX_columnPart,
|
|
1050
|
+
dataSourceCopy,
|
|
1051
|
+
mergeCellsCache,
|
|
1052
|
+
canMergeEmptyRows,
|
|
1053
|
+
belowViewportRowCount,
|
|
1054
|
+
);
|
|
1055
|
+
|
|
1056
|
+
const getFixedColPosition = useGetFixedColPosition(tableHeadersForCalc, colKeyGen);
|
|
1057
|
+
|
|
1058
|
+
const getFixedStyle = useFixedStyle<DT>(props, isRelativeMode, getFixedColPosition, virtualScroll, virtualScrollX, virtualX_on, virtualX_offsetRight);
|
|
1059
|
+
|
|
1060
|
+
const [highlightSteps, setHighlightDimRow, setHighlightDimCell] = useHighlight(props, stkTableId, tableContainerRef);
|
|
1061
|
+
|
|
1062
|
+
function getRowIndex(row: DT): number {
|
|
1063
|
+
const targetKey = rowKeyGen(row);
|
|
1064
|
+
return dataSourceCopy.value.findIndex(item => rowKeyGen(item) === targetKey);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function getColumnIndex(column: PrivateStkTableColumn<DT>): number {
|
|
1068
|
+
const targetKey = colKeyGen.value(column);
|
|
1069
|
+
return tableHeaderLast.value.findIndex(item => colKeyGen.value(item) === targetKey);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const {
|
|
1073
|
+
config: areaSelectionConfig,
|
|
1074
|
+
isSelecting: isAreaSelecting,
|
|
1075
|
+
onMD: onSelectionMouseDown,
|
|
1076
|
+
// getClass: getAreaSelectionClasses,
|
|
1077
|
+
// getRowClass: getAreaSelectionRowClass,
|
|
1078
|
+
get: getSelectedArea,
|
|
1079
|
+
set: setAreaSelection,
|
|
1080
|
+
clear: clearSelectedArea,
|
|
1081
|
+
copy: copySelectedArea,
|
|
1082
|
+
} = ON_DEMAND_FEATURE[useAreaSelectionName](
|
|
1083
|
+
props,
|
|
1084
|
+
emits,
|
|
1085
|
+
tableContainerRef,
|
|
1086
|
+
dataSourceCopy,
|
|
1087
|
+
tableHeaderLast,
|
|
1088
|
+
colKeyGen,
|
|
1089
|
+
cellKeyGen,
|
|
1090
|
+
scrollTo,
|
|
1091
|
+
virtualScroll,
|
|
1092
|
+
virtualScrollX,
|
|
1093
|
+
getRowIndex,
|
|
1094
|
+
getColumnIndex,
|
|
1095
|
+
);
|
|
1096
|
+
|
|
1097
|
+
/** 键盘箭头滚动 */
|
|
1098
|
+
useKeyboardArrowScroll(tableContainerRef, props, scrollTo, virtualScroll, virtualScrollX, tableHeaders, virtual_on, areaSelectionConfig);
|
|
1099
|
+
|
|
1100
|
+
/** 固定列处理 */
|
|
1101
|
+
const [fixedCols, fixedColClassMap, updateFixedShadow] = useFixedCol(
|
|
1102
|
+
props,
|
|
1103
|
+
colKeyGen,
|
|
1104
|
+
getFixedColPosition,
|
|
1105
|
+
tableHeaders,
|
|
1106
|
+
tableHeadersForCalc,
|
|
1107
|
+
tableContainerRef,
|
|
1108
|
+
);
|
|
1109
|
+
|
|
1110
|
+
if (props.autoResize) {
|
|
1111
|
+
useAutoResize(
|
|
1112
|
+
tableContainerRef,
|
|
1113
|
+
() => {
|
|
1114
|
+
initVirtualScroll();
|
|
1115
|
+
// 容器宽度变化后,需重新计算固定列状态
|
|
1116
|
+
updateFixedShadow();
|
|
1117
|
+
// initVirtualScroll 只是把新的 containerWidth/Height 写回 store,
|
|
1118
|
+
// 自定义滚动条 thumb 长度依赖 store 值,须在其后重算,否则 resize 后横向滚动条长度不更新
|
|
1119
|
+
updateCustomScrollbar();
|
|
1120
|
+
},
|
|
1121
|
+
props,
|
|
1122
|
+
200,
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const [colResizeOn, isColResizing, onThResizeMouseDown] = useColResize(
|
|
1127
|
+
props,
|
|
1128
|
+
emits,
|
|
1129
|
+
tableContainerRef,
|
|
1130
|
+
tableHeaderLast,
|
|
1131
|
+
colResizeIndicatorRef,
|
|
1132
|
+
colKeyGen,
|
|
1133
|
+
fixedCols,
|
|
1134
|
+
clearColWidthCache,
|
|
1135
|
+
);
|
|
1136
|
+
|
|
1137
|
+
const [toggleExpandRow, setRowExpand] = useRowExpand(emits, dataSourceCopy, rowKeyGen, onDataSourceChange);
|
|
1138
|
+
|
|
1139
|
+
const [toggleTreeNode, setTreeExpand, flatTreeData] = useTree(props, dataSourceCopy, rowKeyGen, emits, onDataSourceChange);
|
|
1140
|
+
|
|
1141
|
+
/** style cache */
|
|
1142
|
+
const paddingTopStyle = computed(() => `height:${virtualScroll.value.offsetTop}px`);
|
|
1143
|
+
const offsetBottomStyle = computed(() => `height:${virtual_offsetBottom.value}px`);
|
|
1144
|
+
const SRBRBottomStyle = computed(() => `height:${SRBRBottomHeight.value}px`);
|
|
1145
|
+
|
|
1146
|
+
/** tbody 渲染项:数据行 | 视口上方合并占位段 | 视口下方合并占位段 */
|
|
1147
|
+
type BodyRenderItem =
|
|
1148
|
+
| { type: 'row'; row: DT; rowIndex: number; key: UniqKey }
|
|
1149
|
+
| { type: 'above-ph'; count: number; key: string }
|
|
1150
|
+
| { type: 'below-ph'; count: number; key: string };
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* 视口上方区域(startIndex..viewportStartIndex-1)的有序渲染段。
|
|
1154
|
+
* 含 must-render 单元格(rowspan 跨入视口)的锚点行保留独立 tr;
|
|
1155
|
+
* 连续「无 td」的空行(aboveEmptyBlocks,>= 2 行)合并为单个占位段,
|
|
1156
|
+
* 以 height: calc(var(--row-height) * N) 保持总高度。
|
|
1157
|
+
*
|
|
1158
|
+
* 注意:占位段在 DOM 中只算 1 行,跨越它的 rowspan 属性已在
|
|
1159
|
+
* mergeCellsWrapper 中按合并行数修正,保证单元格不会多跨行。
|
|
1160
|
+
* 不允许合并时返回空数组。
|
|
1161
|
+
*/
|
|
1162
|
+
const aboveRenderParts = computed<BodyRenderItem[]>(() => {
|
|
1163
|
+
const aboveCount = aboveViewportRowCount.value;
|
|
1164
|
+
if (aboveCount <= 0) return [];
|
|
1165
|
+
const part = virtual_dataSourcePart.value;
|
|
1166
|
+
const blocks = aboveEmptyBlocks.value;
|
|
1167
|
+
const { startIndex } = virtualScroll.value;
|
|
1168
|
+
|
|
1169
|
+
const items: BodyRenderItem[] = [];
|
|
1170
|
+
let i = 0;
|
|
1171
|
+
let blockIdx = 0;
|
|
1172
|
+
while (i < aboveCount) {
|
|
1173
|
+
const block = blocks[blockIdx];
|
|
1174
|
+
if (block && block.start === startIndex + i) {
|
|
1175
|
+
items.push({ type: 'above-ph', count: block.count, key: `vt-above-ph-${i}` });
|
|
1176
|
+
i += block.count;
|
|
1177
|
+
blockIdx++;
|
|
1178
|
+
} else {
|
|
1179
|
+
const row = part[i];
|
|
1180
|
+
items.push({ type: 'row', row, rowIndex: i, key: rowKeyGen(row) });
|
|
1181
|
+
i++;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return items;
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
/**
|
|
1188
|
+
* tbody 渲染列表(异构):视口上方段 + 视口行 + 视口下方占位段。
|
|
1189
|
+
* 不允许合并时退化为全量数据行列表。
|
|
1190
|
+
*/
|
|
1191
|
+
const bodyRenderItems = computed<BodyRenderItem[]>(() => {
|
|
1192
|
+
const part = virtual_dataSourcePart.value;
|
|
1193
|
+
const items = aboveRenderParts.value.slice();
|
|
1194
|
+
const rowEnd = part.length - belowViewportRowCount.value;
|
|
1195
|
+
for (let i = aboveViewportRowCount.value; i < rowEnd; i++) {
|
|
1196
|
+
const row = part[i];
|
|
1197
|
+
items.push({ type: 'row', row, rowIndex: i, key: rowKeyGen(row) });
|
|
1198
|
+
}
|
|
1199
|
+
// 下方占位按跨界 rowspan 结束行分段输出,保证不同结束行的单元格止于不同 tr
|
|
1200
|
+
const belowSegs = belowPhSegments.value;
|
|
1201
|
+
for (let i = 0; i < belowSegs.length; i++) {
|
|
1202
|
+
items.push({ type: 'below-ph', count: belowSegs[i].count, key: `vt-below-ph-${i}` });
|
|
1203
|
+
}
|
|
1204
|
+
return items;
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
watch(
|
|
1208
|
+
() => props.columns,
|
|
1209
|
+
() => {
|
|
1210
|
+
handleDealColumns();
|
|
1211
|
+
updateMaxRowSpan();
|
|
1212
|
+
// nextTick: initVirtualScrollX need get container width。
|
|
1213
|
+
nextTick(() => {
|
|
1214
|
+
initVirtualScrollX();
|
|
1215
|
+
updateFixedShadow();
|
|
1216
|
+
updateCustomScrollbar();
|
|
1217
|
+
});
|
|
1218
|
+
},
|
|
1219
|
+
);
|
|
1220
|
+
watch(
|
|
1221
|
+
() => props.virtual,
|
|
1222
|
+
() => {
|
|
1223
|
+
nextTick(initVirtualScrollY);
|
|
1224
|
+
},
|
|
1225
|
+
);
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* 行高类配置变化后重算虚拟滚动几何。
|
|
1229
|
+
* 合并为一个 watcher:同一 tick 内多项同时变化只重算一次。
|
|
1230
|
+
* 必须无参调用 initVirtualScrollY(其首参是虚拟滚动容器高度,不能把行高值传入),
|
|
1231
|
+
* 且需 nextTick 等新的 --row-height / 表头行高等样式应用到 DOM 后再测量。
|
|
1232
|
+
*
|
|
1233
|
+
* 监听值拼接为单个原始值(字符串)而非返回数组:getter 返回数组时每次求值都是新数组,
|
|
1234
|
+
* 身份比较必然变化,父组件传入内联对象字面量(如 :expand-config="{ height: 40 }",官方示例即如此写法)
|
|
1235
|
+
* 会让本 watcher 在父组件每次重渲染时误触发;展开行/变高大表下误触发一次会连带行高树 O(n) 重建
|
|
1236
|
+
* (实测 50K 行每次父重渲染多付 ~10ms)。拼接为原始值后仅在值真正变化时触发,且兼容 Vue 2.7。
|
|
1237
|
+
*/
|
|
1238
|
+
watch(
|
|
1239
|
+
() => `${props.rowHeight}|${props.headerRowHeight}|${props.footerRowHeight}|${props.expandConfig?.height}`,
|
|
1240
|
+
() => nextTick(initVirtualScrollY),
|
|
1241
|
+
);
|
|
1242
|
+
|
|
1243
|
+
watch(
|
|
1244
|
+
() => props.virtualX,
|
|
1245
|
+
() => {
|
|
1246
|
+
handleDealColumns();
|
|
1247
|
+
// initVirtualScrollX 需要获取容器滚动宽度等。必须等渲染完成后再调用。因此使用nextTick。
|
|
1248
|
+
nextTick(() => {
|
|
1249
|
+
initVirtualScrollX();
|
|
1250
|
+
updateFixedShadow();
|
|
1251
|
+
});
|
|
1252
|
+
},
|
|
1253
|
+
);
|
|
1254
|
+
|
|
1255
|
+
watch(
|
|
1256
|
+
() => props.dataSource,
|
|
1257
|
+
val => {
|
|
1258
|
+
updateDataSource(val);
|
|
1259
|
+
},
|
|
1260
|
+
);
|
|
1261
|
+
|
|
1262
|
+
watch(
|
|
1263
|
+
() => props.fixedColShadow,
|
|
1264
|
+
() => updateFixedShadow(),
|
|
1265
|
+
);
|
|
1266
|
+
|
|
1267
|
+
handleDealColumns();
|
|
1268
|
+
initDataSource();
|
|
1269
|
+
updateMaxRowSpan();
|
|
1270
|
+
|
|
1271
|
+
onMounted(() => {
|
|
1272
|
+
initVirtualScroll();
|
|
1273
|
+
updateFixedShadow();
|
|
1274
|
+
dealDefaultSorter();
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
async function onDataSourceChange() {
|
|
1278
|
+
// 数据顺序/行数变化后重算 rowspan 映射(排序后 mergeCells 的 rowIndex 会变化)
|
|
1279
|
+
updateMaxRowSpan();
|
|
1280
|
+
await nextTick();
|
|
1281
|
+
initVirtualScrollY();
|
|
1282
|
+
updateCustomScrollbar();
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function initDataSource(v = props.dataSource, option?: { forceSort?: boolean }) {
|
|
1286
|
+
let dataSourceTemp = v.slice(); // shallow copy
|
|
1287
|
+
|
|
1288
|
+
// 排序(tableSort 内部会根据 sortChildren 自动处理树形递归排序)
|
|
1289
|
+
if (!props.sortRemote || option?.forceSort) {
|
|
1290
|
+
dataSourceTemp = sortData(dataSourceTemp);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
if (isTreeData.value) {
|
|
1294
|
+
dataSourceTemp = flatTreeData(dataSourceTemp);
|
|
1295
|
+
}
|
|
1296
|
+
dataSourceTemp = filterDataSource(dataSourceTemp);
|
|
1297
|
+
dataSourceCopy.value = dataSourceTemp;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
function setFilter(
|
|
1301
|
+
status: Record<UniqKey, FilterStatus> | null,
|
|
1302
|
+
option?: {
|
|
1303
|
+
remote?: boolean;
|
|
1304
|
+
silent?: boolean;
|
|
1305
|
+
},
|
|
1306
|
+
) {
|
|
1307
|
+
status = status || {};
|
|
1308
|
+
filterStatus.value = status;
|
|
1309
|
+
if (!option?.remote) {
|
|
1310
|
+
initDataSource();
|
|
1311
|
+
// #80:筛选改变 dataSourceCopy 后重算虚拟滚动窗口,
|
|
1312
|
+
// 否则残留 startIndex/endIndex/scrollHeight 会导致只渲染 1 行或空白视口
|
|
1313
|
+
onDataSourceChange();
|
|
1314
|
+
}
|
|
1315
|
+
if (!option?.silent) {
|
|
1316
|
+
emits('filter-change', status);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
function filterDataSource(dataSource: DT[]) {
|
|
1321
|
+
const filterKeys = Object.keys(filterStatus.value);
|
|
1322
|
+
if (!filterKeys?.length) return dataSource;
|
|
1323
|
+
let result = dataSource;
|
|
1324
|
+
for (const key of filterKeys) {
|
|
1325
|
+
const { value, filter } = filterStatus.value[key];
|
|
1326
|
+
if (!value?.length) continue;
|
|
1327
|
+
result = result.filter(row => {
|
|
1328
|
+
const cellValue = row[key];
|
|
1329
|
+
if (filter) {
|
|
1330
|
+
return filter({ row, cellValue, filterValues: value });
|
|
1331
|
+
}
|
|
1332
|
+
return value.some(v => cellValue == v);
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
return result;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* Wrapper for dealColumns to pass props.columns
|
|
1340
|
+
*/
|
|
1341
|
+
function handleDealColumns() {
|
|
1342
|
+
dealColumns(props.columns);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
function updateDataSource(val: DT[]) {
|
|
1346
|
+
if (!Array.isArray(val)) {
|
|
1347
|
+
console.warn('invalid dataSource');
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
let needInitVirtualScrollY = false;
|
|
1352
|
+
if (/* slots.customBottom || */ dataSourceCopy.value.length !== val.length) {
|
|
1353
|
+
needInitVirtualScrollY = true;
|
|
1354
|
+
}
|
|
1355
|
+
initDataSource(val);
|
|
1356
|
+
updateMaxRowSpan();
|
|
1357
|
+
|
|
1358
|
+
// #47
|
|
1359
|
+
if (!val.length) {
|
|
1360
|
+
clearSelectedArea();
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// if data length is not change, not init virtual scroll
|
|
1364
|
+
if (needInitVirtualScrollY) {
|
|
1365
|
+
// wait for table render,initVirtualScrollY has get `dom` operation.
|
|
1366
|
+
nextTick(() => initVirtualScrollY());
|
|
1367
|
+
}
|
|
1368
|
+
nextTick(updateCustomScrollbar);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/** tr key */
|
|
1372
|
+
function rowKeyGen(row: DT | null | undefined) {
|
|
1373
|
+
if (!row) return row;
|
|
1374
|
+
|
|
1375
|
+
let key = rowKeyGenCache.get(row);
|
|
1376
|
+
if (key !== undefined) return key;
|
|
1377
|
+
|
|
1378
|
+
// Check for cached key in row object
|
|
1379
|
+
const cachedRowKey = (row as PrivateRowDT).__R_K__;
|
|
1380
|
+
if (cachedRowKey !== undefined) {
|
|
1381
|
+
rowKeyGenCache.set(row, cachedRowKey);
|
|
1382
|
+
return cachedRowKey;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
key = rowKeyGenComputed.value(row);
|
|
1386
|
+
|
|
1387
|
+
if (key === void 0) {
|
|
1388
|
+
// key为undefined时,不应该高亮行。因此重新生成key
|
|
1389
|
+
key = Math.random().toString(36).slice(2);
|
|
1390
|
+
}
|
|
1391
|
+
rowKeyGenCache.set(row, key);
|
|
1392
|
+
return key;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/** td key */
|
|
1396
|
+
function cellKeyGen(row: DT | null | undefined, col: StkTableColumn<DT>) {
|
|
1397
|
+
return rowKeyGen(row) + CELL_KEY_SEPARATE + colKeyGen.value(col);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
const cellStyleMap = computed(() => {
|
|
1401
|
+
const thMap = new Map();
|
|
1402
|
+
const tdMap = new Map();
|
|
1403
|
+
const tfMap = new Map();
|
|
1404
|
+
const { virtualX } = props;
|
|
1405
|
+
const headers = tableHeaders.value;
|
|
1406
|
+
const colKeyGenValue = colKeyGen.value;
|
|
1407
|
+
|
|
1408
|
+
for (let depth = 0, depthLen = headers.length; depth < depthLen; depth++) {
|
|
1409
|
+
const cols = headers[depth];
|
|
1410
|
+
for (let i = 0, colsLen = cols.length; i < colsLen; i++) {
|
|
1411
|
+
const col = cols[i];
|
|
1412
|
+
const width = virtualX ? getCalculatedColWidth(col) + 'px' : transformWidthToStr(col.width);
|
|
1413
|
+
const minWidthStr = transformWidthToStr(col.minWidth);
|
|
1414
|
+
const maxWidthStr = transformWidthToStr(col.maxWidth);
|
|
1415
|
+
// en: Use string instead of object to reduce patchStyle overhead
|
|
1416
|
+
let styleStr = '';
|
|
1417
|
+
if (width) styleStr += `--cw:${width}`;
|
|
1418
|
+
if (minWidthStr) styleStr += `;min-width:${minWidthStr}`;
|
|
1419
|
+
if (maxWidthStr) styleStr += `;max-width:${maxWidthStr}`;
|
|
1420
|
+
const colKey = colKeyGenValue(col);
|
|
1421
|
+
thMap.set(colKey, styleStr + ';' + getFixedStyle(TagType.TH, col, depth));
|
|
1422
|
+
tdMap.set(colKey, styleStr + ';' + getFixedStyle(TagType.TD, col, depth));
|
|
1423
|
+
tfMap.set(colKey, 'position:sticky;' + styleStr + ';' + getFixedStyle(TagType.TF, col, depth));
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
return {
|
|
1427
|
+
[TagType.TH]: thMap,
|
|
1428
|
+
[TagType.TD]: tdMap,
|
|
1429
|
+
[TagType.TF]: tfMap,
|
|
1430
|
+
};
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* fixed 模式下 colgroup 中单个 col 的样式。
|
|
1435
|
+
* 仅取叶子列的 width(与 cellStyleMap 中 --cw 保持一致);未设置 width 的列不声明宽度,
|
|
1436
|
+
* 由 table-layout:fixed 将剩余空间平分,符合“一列固定、其余列平分”的预期。
|
|
1437
|
+
*/
|
|
1438
|
+
function getColGroupColStyle(col: PrivateStkTableColumn<DT>) {
|
|
1439
|
+
const width = transformWidthToStr(col.width);
|
|
1440
|
+
return width ? `width:${width}` : null;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
function getAbsoluteRowIndex(rowIndex: number) {
|
|
1444
|
+
return rowIndex + virtualScroll.value.startIndex;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
function shouldHideCell(row: PrivateRowDT | null | undefined, col: StkTableColumn<PrivateRowDT>): boolean | undefined {
|
|
1448
|
+
if (!hiddenCellMap.value || !row) return;
|
|
1449
|
+
return hiddenCellMap.value[rowKeyGen(row)]?.has(colKeyGen.value(col));
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/**
|
|
1453
|
+
* Get the column list for a body row.
|
|
1454
|
+
* - Above-viewport rows: returns a modified list with placeholder entries (__VT_PH__) to reduce DOM nodes.
|
|
1455
|
+
* Rows without any must-render cell return [] and consecutive such rows are merged into a single
|
|
1456
|
+
* placeholder tr (see aboveRenderParts).
|
|
1457
|
+
* - Below-viewport rows: normally merged into placeholder tr segments split at crossing rowspan
|
|
1458
|
+
* ends (see belowPhSegments); when still rendered individually (fallback), returns []
|
|
1459
|
+
* (tr already has height via CSS, no td needed).
|
|
1460
|
+
*
|
|
1461
|
+
* 仅在 canMergeEmptyRows(定高虚拟列表)时启用上述剔除:空 tr 依赖 CSS `height: var(--row-height)`
|
|
1462
|
+
* 保持高度,autoRowHeight 模式行高靠 td 内容撑开(容器也不定义 --row-height),
|
|
1463
|
+
* 剔除 td 会让视口上方行(stripe/rowspan 修正保留的行)塌陷为 0 高,且塌陷高度会被
|
|
1464
|
+
* DOM 测量写入行高缓存,导致行高无法正常撑开。
|
|
1465
|
+
*/
|
|
1466
|
+
function getBodyColumns(row: PrivateRowDT | null | undefined, rowIndex: number): PrivateStkTableColumn<PrivateRowDT>[] {
|
|
1467
|
+
if (!row) return virtualX_columnPart.value;
|
|
1468
|
+
if (canMergeEmptyRows.value) {
|
|
1469
|
+
const { startIndex, viewportStartIndex, viewportEndIndex } = virtualScroll.value;
|
|
1470
|
+
const aboveCount = viewportStartIndex - startIndex;
|
|
1471
|
+
if (aboveCount > 0 && rowIndex < aboveCount) {
|
|
1472
|
+
return (aboveViewportColumnMap.value.get(rowKeyGen(row)) || virtualX_columnPart.value) as PrivateStkTableColumn<PrivateRowDT>[];
|
|
1473
|
+
}
|
|
1474
|
+
// Below-viewport rows: no td needed
|
|
1475
|
+
if (startIndex + rowIndex > viewportEndIndex) {
|
|
1476
|
+
return [];
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
const segments = virtualX_expandColSegments.value;
|
|
1480
|
+
if (!segments) return virtualX_columnPart.value;
|
|
1481
|
+
return buildExpandColumns(row, rowIndex, segments) as PrivateStkTableColumn<PrivateRowDT>[];
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/**
|
|
1485
|
+
* 超长 colspan 优化:收缩修正扩展区域(virtualX_expandColSegments)内的行渲染列列表。
|
|
1486
|
+
* - 左扩展区:colspan/rowspan 锚点单元格保留真实渲染(否则行列占位断裂);
|
|
1487
|
+
* 被覆盖单元格照旧不渲染(槽位由跨越它的单元格占用);其余连续普通单元格
|
|
1488
|
+
* 合并为单个 colspan 占位 td(fixed 布局下恰好占用相同列槽位,保持对齐);
|
|
1489
|
+
* - 右扩展区:完全不渲染(均在可视视口外,不影响可视单元格对齐)。
|
|
1490
|
+
*/
|
|
1491
|
+
function buildExpandColumns(
|
|
1492
|
+
row: PrivateRowDT,
|
|
1493
|
+
rowIndex: number,
|
|
1494
|
+
segments: {
|
|
1495
|
+
prefix: PrivateStkTableColumn<PrivateRowDT>[];
|
|
1496
|
+
leftExpand: PrivateStkTableColumn<PrivateRowDT>[];
|
|
1497
|
+
viewport: PrivateStkTableColumn<PrivateRowDT>[];
|
|
1498
|
+
suffix: PrivateStkTableColumn<PrivateRowDT>[];
|
|
1499
|
+
leftExpandStart: number;
|
|
1500
|
+
},
|
|
1501
|
+
) {
|
|
1502
|
+
const { prefix, leftExpand, viewport, suffix, leftExpandStart } = segments;
|
|
1503
|
+
const result: (PrivateStkTableColumn<PrivateRowDT> | { __VT_PH__: number })[] = prefix.slice();
|
|
1504
|
+
|
|
1505
|
+
if (leftExpand.length) {
|
|
1506
|
+
const hiddenSet = hiddenCellMap.value ? hiddenCellMap.value[rowKeyGen(row)] : void 0;
|
|
1507
|
+
const colKeyGenValue = colKeyGen.value;
|
|
1508
|
+
const absRowIndex = (virtual_on.value ? virtualScroll.value.startIndex : 0) + rowIndex;
|
|
1509
|
+
let run = 0;
|
|
1510
|
+
for (let i = 0; i < leftExpand.length; i++) {
|
|
1511
|
+
const col = leftExpand[i];
|
|
1512
|
+
// 被覆盖单元格:不渲染 td(槽位由上方/左侧的跨越单元格占用)
|
|
1513
|
+
if (hiddenSet && hiddenSet.has(colKeyGenValue(col))) continue;
|
|
1514
|
+
// 锚点单元格(colspan/rowspan > 1)必须真实渲染,不能占位替代
|
|
1515
|
+
if (col.mergeCells) {
|
|
1516
|
+
const { colspan, rowspan } = mergeCellsCache.getMergeCellsResult(row, col, absRowIndex, col.__LF_S__ ?? leftExpandStart + i);
|
|
1517
|
+
if (colspan > 1 || rowspan > 1) {
|
|
1518
|
+
if (run > 0) {
|
|
1519
|
+
result.push({ __VT_PH__: run } as PrivateStkTableColumn<PrivateRowDT>);
|
|
1520
|
+
run = 0;
|
|
1521
|
+
}
|
|
1522
|
+
result.push(col);
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
run++;
|
|
1527
|
+
}
|
|
1528
|
+
if (run > 0) result.push({ __VT_PH__: run } as PrivateStkTableColumn<PrivateRowDT>);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// 右扩展区不渲染,直接拼接可视区与右侧固定列
|
|
1532
|
+
result.push(...viewport, ...suffix);
|
|
1533
|
+
return result;
|
|
1534
|
+
}
|
|
1535
|
+
/** th title */
|
|
1536
|
+
function getHeaderTitle(col: StkTableColumn<DT>): string {
|
|
1537
|
+
const colKey = colKeyGen.value(col);
|
|
1538
|
+
// hide title
|
|
1539
|
+
if (props.hideHeaderTitle === true || (Array.isArray(props.hideHeaderTitle) && props.hideHeaderTitle.includes(colKey))) {
|
|
1540
|
+
return '';
|
|
1541
|
+
}
|
|
1542
|
+
return col.title || '';
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
function getTRProps(row: PrivateRowDT | null | undefined, index: number) {
|
|
1546
|
+
const rowIndex = getAbsoluteRowIndex(index);
|
|
1547
|
+
const rowKey = rowKeyGen(row);
|
|
1548
|
+
|
|
1549
|
+
const classList = [props.rowClassName(row, rowIndex), row?.__EXP__ ? 'expanded' : '', row?.__EXP_R__ ? 'expanded-row' : ''];
|
|
1550
|
+
// area selection row highlight
|
|
1551
|
+
// if (areaSelectionConfig.value.enabled) {
|
|
1552
|
+
// classList.push(...getAreaSelectionRowClass(rowIndex));
|
|
1553
|
+
// }
|
|
1554
|
+
if (currentRowKey.value === rowKey || row === currentRow.value) {
|
|
1555
|
+
classList.push('active');
|
|
1556
|
+
}
|
|
1557
|
+
if (props.showTrHoverClass && (rowKey === currentHoverRowKey.value || row === currentHoverRow)) {
|
|
1558
|
+
classList.push('hover');
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
const result: Record<string, number | string | null> = {
|
|
1562
|
+
id: stkTableId + '-' + rowKey,
|
|
1563
|
+
'data-row-key': rowKey,
|
|
1564
|
+
'data-row-i': rowIndex,
|
|
1565
|
+
class: classList.filter(Boolean).join(' '),
|
|
1566
|
+
style: null,
|
|
1567
|
+
};
|
|
1568
|
+
|
|
1569
|
+
const needRowHeight = row?.__EXP_R__ && props.virtual && props.expandConfig?.height;
|
|
1570
|
+
|
|
1571
|
+
if (needRowHeight) {
|
|
1572
|
+
result.style = `--row-height: ${props.expandConfig?.height}px`;
|
|
1573
|
+
}
|
|
1574
|
+
return result;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
function getTHProps(col: PrivateStkTableColumn<DT>) {
|
|
1578
|
+
const colKey = colKeyGen.value(col);
|
|
1579
|
+
const sortState = getColumnSortState(colKey);
|
|
1580
|
+
const isSorted = !!sortState && sortState.order !== null;
|
|
1581
|
+
|
|
1582
|
+
return {
|
|
1583
|
+
'data-col-key': colKey,
|
|
1584
|
+
draggable: Boolean(isHeaderDraggable(col)),
|
|
1585
|
+
rowspan: col.__R_SP__,
|
|
1586
|
+
colspan: col.__C_SP__,
|
|
1587
|
+
style: cellStyleMap.value[TagType.TH].get(colKey),
|
|
1588
|
+
title: getHeaderTitle(col),
|
|
1589
|
+
// class 用预拼接字符串(而非数组),降低每格 vnode diff 与 GC 开销
|
|
1590
|
+
class: [
|
|
1591
|
+
col.sorter ? 'sortable' : '',
|
|
1592
|
+
isSorted ? 'sorter-' + sortState?.order : '',
|
|
1593
|
+
col.headerClassName,
|
|
1594
|
+
fixedColClassMap.value.get(colKey),
|
|
1595
|
+
col.headerAlign === 'left' ? 'text-l' : col.headerAlign === 'right' ? 'text-r' : col.headerAlign === 'center' ? 'text-c' : '',
|
|
1596
|
+
]
|
|
1597
|
+
.filter(Boolean)
|
|
1598
|
+
.join(' '),
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
function getTFProps(col: StkTableColumn<DT>) {
|
|
1603
|
+
const colKey = colKeyGen.value(col);
|
|
1604
|
+
return {
|
|
1605
|
+
'data-col-key': colKey,
|
|
1606
|
+
style: cellStyleMap.value[TagType.TF].get(colKey),
|
|
1607
|
+
class: [
|
|
1608
|
+
col.className,
|
|
1609
|
+
fixedColClassMap.value.get(colKey),
|
|
1610
|
+
col.type === 'seq' ? 'seq-column' : '',
|
|
1611
|
+
col.align === 'center' ? 'text-c' : col.align === 'right' ? 'text-r' : '',
|
|
1612
|
+
]
|
|
1613
|
+
.filter(Boolean)
|
|
1614
|
+
.join(' '),
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
function getTDProps(row: PrivateRowDT | null | undefined, col: StkTableColumn<PrivateRowDT>, rowIndex: number, colIndex: number) {
|
|
1619
|
+
const colKey = colKeyGen.value(col);
|
|
1620
|
+
if (!row) {
|
|
1621
|
+
return {
|
|
1622
|
+
style: cellStyleMap.value[TagType.TD].get(colKey),
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
const cellKey = cellKeyGen(row, col);
|
|
1627
|
+
const classList = [col.className, fixedColClassMap.value.get(colKey)];
|
|
1628
|
+
|
|
1629
|
+
if (col.align === 'center') {
|
|
1630
|
+
classList.push('text-c');
|
|
1631
|
+
} else if (col.align === 'right') {
|
|
1632
|
+
classList.push('text-r');
|
|
1633
|
+
}
|
|
1634
|
+
if (col.mergeCells) {
|
|
1635
|
+
if (hoverMergedCells.value.has(cellKey)) {
|
|
1636
|
+
classList.push('cell-hover');
|
|
1637
|
+
}
|
|
1638
|
+
if (activeMergedCells.value.has(cellKey)) {
|
|
1639
|
+
classList.push('cell-active');
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
if (props.cellActive && currentSelectedCellKey.value === cellKey) {
|
|
1644
|
+
classList.push('active');
|
|
1645
|
+
}
|
|
1646
|
+
// // area selection style
|
|
1647
|
+
// if (areaSelectionConfig.value.enabled) {
|
|
1648
|
+
// const absRowIndex = getAbsoluteRowIndex(rowIndex);
|
|
1649
|
+
// classList.push(...getAreaSelectionClasses(cellKey, absRowIndex, colKey));
|
|
1650
|
+
// }
|
|
1651
|
+
if (col.type === 'seq') {
|
|
1652
|
+
classList.push('seq-column');
|
|
1653
|
+
} else if (col.type === 'expand' && (row.__EXP__ ? colKeyGen.value(row.__EXP__) === colKey : false)) {
|
|
1654
|
+
classList.push('expanded');
|
|
1655
|
+
} else if (row.__T_EXP__ && col.type === 'tree-node') {
|
|
1656
|
+
classList.push('tree-expanded');
|
|
1657
|
+
} else if (col.type === 'dragRow') {
|
|
1658
|
+
classList.push('drag-row-cell');
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
return {
|
|
1662
|
+
'data-col-key': colKey,
|
|
1663
|
+
style: cellStyleMap.value[TagType.TD].get(colKey),
|
|
1664
|
+
class: classList.filter(Boolean).join(' '),
|
|
1665
|
+
...mergeCellsWrapper(row, col, rowIndex, (col as PrivateStkTableColumn<DT>).__LF_S__ ?? 0),
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
function onRowClick(e: MouseEvent) {
|
|
1670
|
+
const rowIndex = getClosestTrIndex(e.target as HTMLElement);
|
|
1671
|
+
const row = dataSourceCopy.value[rowIndex];
|
|
1672
|
+
if (!row) return;
|
|
1673
|
+
emits('row-click', e, row, { rowIndex });
|
|
1674
|
+
if (rowActiveProp.value.disabled?.(row)) return;
|
|
1675
|
+
const isCurrentRow = props.rowKey ? currentRowKey.value === rowKeyGen(row) : currentRow.value === row;
|
|
1676
|
+
if (isCurrentRow) {
|
|
1677
|
+
if (!rowActiveProp.value.revokable) {
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
setCurrentRow(void 0, { silent: true });
|
|
1681
|
+
} else {
|
|
1682
|
+
setCurrentRow(row, { silent: true });
|
|
1683
|
+
}
|
|
1684
|
+
emits('current-change', e, row, { select: !isCurrentRow });
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
function onRowDblclick(e: MouseEvent) {
|
|
1688
|
+
const rowIndex = getClosestTrIndex(e.target as HTMLElement);
|
|
1689
|
+
const row = dataSourceCopy.value[rowIndex];
|
|
1690
|
+
if (!row) return;
|
|
1691
|
+
emits('row-dblclick', e, row, { rowIndex });
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
function onHeaderMenu(e: MouseEvent) {
|
|
1695
|
+
emits('header-row-menu', e);
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
function onRowMenu(e: MouseEvent) {
|
|
1699
|
+
const rowIndex = getClosestTrIndex(e.target as HTMLElement);
|
|
1700
|
+
const row = dataSourceCopy.value[rowIndex];
|
|
1701
|
+
if (!row) return;
|
|
1702
|
+
emits('row-menu', e, row, { rowIndex });
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
function triangleClick(e: MouseEvent, row: DT, col: StkTableColumn<DT>) {
|
|
1706
|
+
if (col.type === 'expand') {
|
|
1707
|
+
toggleExpandRow(row, col);
|
|
1708
|
+
} else if (col.type === 'tree-node') {
|
|
1709
|
+
toggleTreeNode(row, col);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
function onCellClick(e: MouseEvent) {
|
|
1714
|
+
const rowIndex = getClosestTrIndex(e.target as HTMLElement);
|
|
1715
|
+
const row = dataSourceCopy.value[rowIndex];
|
|
1716
|
+
if (!row) return;
|
|
1717
|
+
const colKey = getClosestColKey(e.target as HTMLElement);
|
|
1718
|
+
const col = tableHeaderLast.value.find(item => colKeyGen.value(item) === colKey);
|
|
1719
|
+
if (!col) return;
|
|
1720
|
+
// Delegated triangle/fold icon click
|
|
1721
|
+
if ((e.target as HTMLElement)?.closest('.stk-fold-icon')) {
|
|
1722
|
+
triangleClick(e, row, col);
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
if (props.cellActive) {
|
|
1726
|
+
const cellKey = cellKeyGen(row, col);
|
|
1727
|
+
const result = { row, col, select: false, rowIndex };
|
|
1728
|
+
if (props.selectedCellRevokable && currentSelectedCellKey.value === cellKey) {
|
|
1729
|
+
currentSelectedCellKey.value = void 0;
|
|
1730
|
+
} else {
|
|
1731
|
+
currentSelectedCellKey.value = cellKey;
|
|
1732
|
+
result.select = true;
|
|
1733
|
+
}
|
|
1734
|
+
emits('cell-selected', e, result);
|
|
1735
|
+
}
|
|
1736
|
+
emits('cell-click', e, row, col, { rowIndex });
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
function getCellEventData(e: MouseEvent) {
|
|
1740
|
+
const rowIndex = getClosestTrIndex(e.target as HTMLElement) || 0;
|
|
1741
|
+
const row = dataSourceCopy.value[rowIndex];
|
|
1742
|
+
const colKey = getClosestColKey(e.target as HTMLElement);
|
|
1743
|
+
const col = tableHeaderLast.value.find(item => colKeyGen.value(item) === colKey) as any;
|
|
1744
|
+
return { row, col, rowIndex };
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
/** th click */
|
|
1748
|
+
function onHeaderCellClick(e: MouseEvent, col: StkTableColumn<DT>) {
|
|
1749
|
+
onColumnSort(col);
|
|
1750
|
+
emits('header-cell-click', e, col);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
/**
|
|
1754
|
+
* Delegated mouseover on tbody: emits cell-mouseover, and simulates cell-mouseenter
|
|
1755
|
+
* by checking if relatedTarget is outside the current td.
|
|
1756
|
+
*/
|
|
1757
|
+
function onCellMouseOver(e: MouseEvent) {
|
|
1758
|
+
const td = getClosestTd(e.target as HTMLElement);
|
|
1759
|
+
if (!td) return;
|
|
1760
|
+
const { row, col } = getCellEventData(e);
|
|
1761
|
+
emits('cell-mouseover', e, row, col);
|
|
1762
|
+
// Simulate cell-mouseenter: relatedTarget is outside this td
|
|
1763
|
+
const related = e.relatedTarget as Node | null;
|
|
1764
|
+
if (!related || !td.contains(related)) {
|
|
1765
|
+
emits('cell-mouseenter', e, row, col);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
/**
|
|
1770
|
+
* Delegated mouseout on tbody: handles both cell-mouseleave and tr-mouseleave.
|
|
1771
|
+
* - cell-mouseleave: relatedTarget is outside the current td.
|
|
1772
|
+
* - tr-mouseleave: relatedTarget is outside the current tr (simulates the old onTrMouseLeave).
|
|
1773
|
+
*/
|
|
1774
|
+
function onTbodyMouseOut(e: MouseEvent) {
|
|
1775
|
+
const target = e.target as HTMLElement;
|
|
1776
|
+
const related = e.relatedTarget as Node | null;
|
|
1777
|
+
|
|
1778
|
+
// Cell mouseleave
|
|
1779
|
+
const td = getClosestTd(target);
|
|
1780
|
+
if (td && (!related || !td.contains(related))) {
|
|
1781
|
+
const { row, col } = getCellEventData(e);
|
|
1782
|
+
emits('cell-mouseleave', e, row, col);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// Tr mouseleave
|
|
1786
|
+
const tr = getClosestTr(target);
|
|
1787
|
+
if (tr && (!related || !tr.contains(related))) {
|
|
1788
|
+
currentHoverRow = null;
|
|
1789
|
+
if (props.showTrHoverClass) {
|
|
1790
|
+
currentHoverRowKey.value = null;
|
|
1791
|
+
}
|
|
1792
|
+
if (props.rowHover) {
|
|
1793
|
+
updateHoverMergedCells(void 0);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
/** Delegated drop on tbody: extracts rowIndex from the closest tr and calls onTrDrop. */
|
|
1799
|
+
function onBodyDrop(e: DragEvent) {
|
|
1800
|
+
const trIndex = getClosestTrIndex(e.target as HTMLElement);
|
|
1801
|
+
if (trIndex < 0) return;
|
|
1802
|
+
onTrDrop(e, getAbsoluteRowIndex(trIndex));
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
function onCellMouseDown(e: MouseEvent) {
|
|
1806
|
+
const { row, col, rowIndex } = getCellEventData(e);
|
|
1807
|
+
emits('cell-mousedown', e, row, col, { rowIndex });
|
|
1808
|
+
|
|
1809
|
+
if (areaSelectionConfig.value.enabled) {
|
|
1810
|
+
onSelectionMouseDown(e);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
// isWheeling: true when wheel event is triggered, auto reset to false after delay
|
|
1815
|
+
const [isWheeling, setIsWheeling] = useWheeling();
|
|
1816
|
+
|
|
1817
|
+
/**
|
|
1818
|
+
* proxy scroll, prevent white screen
|
|
1819
|
+
* @param e
|
|
1820
|
+
*/
|
|
1821
|
+
function onTableWheel(e: WheelEvent) {
|
|
1822
|
+
if (props.smoothScroll) return;
|
|
1823
|
+
// if is resizing, not allow scroll
|
|
1824
|
+
if (isColResizing.value) {
|
|
1825
|
+
e.stopPropagation();
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
const dom = tableContainerRef.value as HTMLElement;
|
|
1829
|
+
|
|
1830
|
+
const { deltaY, deltaX, shiftKey } = e;
|
|
1831
|
+
|
|
1832
|
+
if (virtual_on.value && deltaY && !shiftKey) {
|
|
1833
|
+
const { containerHeight, scrollTop, scrollHeight } = virtualScroll.value;
|
|
1834
|
+
// overflow: hidden mode: manually control scroll, preventDefault to block parent scroll
|
|
1835
|
+
const canScrollDown = scrollTop < scrollHeight - containerHeight - 1;
|
|
1836
|
+
const canScrollUp = scrollTop > 1;
|
|
1837
|
+
|
|
1838
|
+
if ((deltaY > 0 && canScrollDown) || (deltaY < 0 && canScrollUp)) {
|
|
1839
|
+
// Table can still scroll → mark wheeling & prevent parent
|
|
1840
|
+
setIsWheeling(true);
|
|
1841
|
+
e.preventDefault();
|
|
1842
|
+
} else if (isWheeling()) {
|
|
1843
|
+
// At boundary but user is still actively wheeling → briefly prevent parent for smooth transition
|
|
1844
|
+
e.preventDefault();
|
|
1845
|
+
}
|
|
1846
|
+
// At boundary + isWheeling expired → no preventDefault → browser scrolls parent naturally
|
|
1847
|
+
|
|
1848
|
+
if (isExperimentalScrollY.value) {
|
|
1849
|
+
rafUpdateVirtualScrollYForWheel(scrollTop + deltaY);
|
|
1850
|
+
updateCustomScrollbar();
|
|
1851
|
+
} else {
|
|
1852
|
+
dom.scrollTop += deltaY;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
if (virtualX_on.value) {
|
|
1856
|
+
const { containerWidth, scrollLeft, scrollWidth } = virtualScrollX.value;
|
|
1857
|
+
let distance = deltaX;
|
|
1858
|
+
if (shiftKey && deltaY) {
|
|
1859
|
+
distance = deltaY;
|
|
1860
|
+
}
|
|
1861
|
+
const canScrollRight = scrollLeft < scrollWidth - containerWidth - 1;
|
|
1862
|
+
const canScrollLeft = scrollLeft > 1;
|
|
1863
|
+
|
|
1864
|
+
if ((distance > 0 && canScrollRight) || (distance < 0 && canScrollLeft)) {
|
|
1865
|
+
setIsWheeling(true);
|
|
1866
|
+
e.preventDefault();
|
|
1867
|
+
} else if (isWheeling()) {
|
|
1868
|
+
e.preventDefault();
|
|
1869
|
+
}
|
|
1870
|
+
dom.scrollLeft += distance;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
/** Prevent re-entrant requestAnimationFrame in onTableScroll */
|
|
1875
|
+
let scrollRAFScheduled = false;
|
|
1876
|
+
|
|
1877
|
+
/**
|
|
1878
|
+
* @param e scrollEvent
|
|
1879
|
+
*/
|
|
1880
|
+
function onTableScroll(e: Event) {
|
|
1881
|
+
if (!e?.target || scrollRAFScheduled) return;
|
|
1882
|
+
scrollRAFScheduled = true;
|
|
1883
|
+
requestAnimationFrame(() => {
|
|
1884
|
+
scrollRAFScheduled = false;
|
|
1885
|
+
const { scrollTop, scrollLeft } = e.target as HTMLElement;
|
|
1886
|
+
const { scrollTop: vScrollTop } = virtualScroll.value;
|
|
1887
|
+
const { scrollLeft: vScrollLeft } = virtualScrollX.value;
|
|
1888
|
+
// 在 experimental.scrollY 模式下,纵向滚动通过 transform 模拟,DOM scrollTop 始终为 0,
|
|
1889
|
+
// 不能用来判断纵向滚动,否则横向滚动时会误触发纵向滚动重置
|
|
1890
|
+
const isYScroll = isExperimentalScrollY.value ? false : scrollTop !== vScrollTop;
|
|
1891
|
+
const isXScroll = scrollLeft !== vScrollLeft;
|
|
1892
|
+
|
|
1893
|
+
if (isYScroll) {
|
|
1894
|
+
updateVirtualScrollY(scrollTop);
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
if (isXScroll) {
|
|
1898
|
+
if (virtualX_on.value) {
|
|
1899
|
+
updateVirtualScrollX(scrollLeft);
|
|
1900
|
+
} else {
|
|
1901
|
+
// en: Record the scroll position. Used to determine isXScroll
|
|
1902
|
+
virtualScrollX.value.scrollLeft = scrollLeft;
|
|
1903
|
+
}
|
|
1904
|
+
updateFixedShadow(virtualScrollX);
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
if (isYScroll) {
|
|
1908
|
+
const { startIndex, endIndex } = virtualScroll.value;
|
|
1909
|
+
emits('scroll', e, { startIndex, endIndex });
|
|
1910
|
+
}
|
|
1911
|
+
if (isXScroll) {
|
|
1912
|
+
emits('scroll-x', e);
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
updateCustomScrollbar();
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
/** tr hover */
|
|
1920
|
+
function onTrMouseOver(e: MouseEvent) {
|
|
1921
|
+
const tr = getClosestTr(e.target as HTMLElement);
|
|
1922
|
+
if (!tr) return;
|
|
1923
|
+
const rowIndex = Number(tr.dataset.rowI);
|
|
1924
|
+
const row = dataSourceCopy.value[rowIndex];
|
|
1925
|
+
if (currentHoverRow === row) return;
|
|
1926
|
+
currentHoverRow = row;
|
|
1927
|
+
const rowKey = tr.dataset.rowKey;
|
|
1928
|
+
if (props.showTrHoverClass) {
|
|
1929
|
+
currentHoverRowKey.value = rowKey || null;
|
|
1930
|
+
}
|
|
1931
|
+
if (props.rowHover) {
|
|
1932
|
+
updateHoverMergedCells(rowKey);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
/**
|
|
1937
|
+
* 选中一行
|
|
1938
|
+
*
|
|
1939
|
+
* en: Select a row
|
|
1940
|
+
* @param {string} rowKeyOrRow selected rowKey, undefined to unselect
|
|
1941
|
+
* @param {boolean} option.silent if set true not emit `current-change`. default:false
|
|
1942
|
+
* @param {boolean} option.deep if set true, deep search in children. default:false
|
|
1943
|
+
*/
|
|
1944
|
+
function setCurrentRow(rowKeyOrRow: string | undefined | DT, option: { silent?: boolean; deep?: boolean } = { silent: false, deep: false }) {
|
|
1945
|
+
const select = rowKeyOrRow !== void 0;
|
|
1946
|
+
const currentRowTemp = currentRow.value;
|
|
1947
|
+
if (!select) {
|
|
1948
|
+
currentRow.value = void 0;
|
|
1949
|
+
currentRowKey.value = void 0;
|
|
1950
|
+
updateActiveMergedCells(true);
|
|
1951
|
+
} else if (typeof rowKeyOrRow === 'string') {
|
|
1952
|
+
const findRowByKey = (data: DT[], key: string): DT | null => {
|
|
1953
|
+
for (let i = 0; i < data.length; i++) {
|
|
1954
|
+
const item = data[i];
|
|
1955
|
+
if (rowKeyGen(item) === key) {
|
|
1956
|
+
return item;
|
|
1957
|
+
}
|
|
1958
|
+
if (option.deep && item.children?.length) {
|
|
1959
|
+
const found = findRowByKey(item.children, key);
|
|
1960
|
+
if (found) {
|
|
1961
|
+
return found;
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
return null;
|
|
1966
|
+
};
|
|
1967
|
+
|
|
1968
|
+
currentRowKey.value = rowKeyOrRow;
|
|
1969
|
+
updateActiveMergedCells(false, currentRowKey.value);
|
|
1970
|
+
const row = findRowByKey(dataSourceCopy.value || [], rowKeyOrRow);
|
|
1971
|
+
if (!row) {
|
|
1972
|
+
console.warn('setCurrentRow failed.rowKey:', rowKeyOrRow);
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
currentRow.value = row;
|
|
1976
|
+
} else {
|
|
1977
|
+
currentRow.value = rowKeyOrRow;
|
|
1978
|
+
currentRowKey.value = rowKeyGen(rowKeyOrRow);
|
|
1979
|
+
updateActiveMergedCells(false, currentRowKey.value);
|
|
1980
|
+
}
|
|
1981
|
+
if (!option.silent) {
|
|
1982
|
+
emits('current-change', /** no Event */ null, select ? currentRow.value : currentRowTemp, { select });
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
/**
|
|
1987
|
+
* set highlight active cell (props.cellActive=true)
|
|
1988
|
+
* @param row row if undefined, clear highlight
|
|
1989
|
+
* @param col column
|
|
1990
|
+
* @param option.silent if emit current-change. default:false(not emit `current-change`)
|
|
1991
|
+
*/
|
|
1992
|
+
function setSelectedCell(row?: DT, col?: StkTableColumn<DT>, option = { silent: false }) {
|
|
1993
|
+
if (!dataSourceCopy.value.length) return;
|
|
1994
|
+
const select = row !== void 0 && col !== void 0;
|
|
1995
|
+
currentSelectedCellKey.value = select ? cellKeyGen(row, col) : void 0;
|
|
1996
|
+
if (!option.silent) {
|
|
1997
|
+
emits('cell-selected', /** no Event */ null, { row, col, select });
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
/** smooth 滚动动画时长(ms) */
|
|
2002
|
+
const SMOOTH_SCROLL_DURATION = 300;
|
|
2003
|
+
let smoothScrollRafId = 0;
|
|
2004
|
+
/** 动画代际序号,取消后旧动画的 rAF 回调不再生效 */
|
|
2005
|
+
let smoothScrollSeq = 0;
|
|
2006
|
+
/** 当前动画的手势监听清理函数(监听直接挂 cancelSmoothScroll,无额外包装) */
|
|
2007
|
+
let removeGestureListeners: (() => void) | null = null;
|
|
2008
|
+
|
|
2009
|
+
/** 取消进行中的平滑滚动动画 */
|
|
2010
|
+
function cancelSmoothScroll() {
|
|
2011
|
+
smoothScrollSeq++;
|
|
2012
|
+
if (smoothScrollRafId) {
|
|
2013
|
+
cancelAnimationFrame(smoothScrollRafId);
|
|
2014
|
+
smoothScrollRafId = 0;
|
|
2015
|
+
}
|
|
2016
|
+
if (removeGestureListeners) {
|
|
2017
|
+
removeGestureListeners();
|
|
2018
|
+
removeGestureListeners = null;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
/**
|
|
2023
|
+
* 解析滚动轴目标为绝对像素坐标
|
|
2024
|
+
* @param target 数字坐标或 { index, key, px } 目标,undefined 表示不改变该轴
|
|
2025
|
+
* @param resolveByIndex 按 index 解析基准偏移,失败返回 null
|
|
2026
|
+
* @param resolveByKey 按 key 解析基准偏移,失败返回 null
|
|
2027
|
+
* @returns null 表示该轴不滚动
|
|
2028
|
+
*/
|
|
2029
|
+
function resolveScrollAxis(
|
|
2030
|
+
target: number | ScrollAxisTarget | undefined,
|
|
2031
|
+
resolveByIndex: (index: number) => number | null,
|
|
2032
|
+
resolveByKey: (key: string | number) => number | null,
|
|
2033
|
+
): number | null {
|
|
2034
|
+
if (target === undefined) return null;
|
|
2035
|
+
if (typeof target === 'number') return target;
|
|
2036
|
+
let base: number | null;
|
|
2037
|
+
if (target.index !== undefined) {
|
|
2038
|
+
// index 优先于 key
|
|
2039
|
+
base = resolveByIndex(target.index);
|
|
2040
|
+
} else if (target.key !== undefined) {
|
|
2041
|
+
base = resolveByKey(target.key);
|
|
2042
|
+
} else {
|
|
2043
|
+
// 仅传 px:基准为 0
|
|
2044
|
+
base = 0;
|
|
2045
|
+
}
|
|
2046
|
+
if (base === null) return null;
|
|
2047
|
+
return base + (target.px ?? 0);
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
/** top 轴基准偏移:目标行之前所有行的累计高度 */
|
|
2051
|
+
function resolveTopByIndex(index: number): number | null {
|
|
2052
|
+
if (index < 0 || index >= dataSourceCopy.value.length) return null;
|
|
2053
|
+
return getRowsHeight(index);
|
|
2054
|
+
}
|
|
2055
|
+
function resolveTopByKey(key: string | number): number | null {
|
|
2056
|
+
const index = dataSourceCopy.value.findIndex(row => rowKeyGen(row) === key);
|
|
2057
|
+
return index === -1 ? null : getRowsHeight(index);
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
/** left 轴基准偏移:目标列之前所有列的宽度之和(与区域选择内部 getColPosition 同算法) */
|
|
2061
|
+
function getColLeft(colIndex: number): number {
|
|
2062
|
+
const cols = tableHeaderLast.value;
|
|
2063
|
+
let left = 0;
|
|
2064
|
+
for (let i = 0; i < colIndex; i++) {
|
|
2065
|
+
left += getCalculatedColWidth(cols[i]);
|
|
2066
|
+
}
|
|
2067
|
+
return left;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
/** top 轴最大可滚动距离:基于理论内容高度(所有行高之和),不依赖 DOM 测量 */
|
|
2071
|
+
function getMaxScrollTop(): number {
|
|
2072
|
+
return Math.max(0, getRowsHeight(dataSourceCopy.value.length) - virtualScroll.value.containerHeight);
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
function resolveLeftByIndex(index: number): number | null {
|
|
2076
|
+
if (index < 0 || index >= tableHeaderLast.value.length) return null;
|
|
2077
|
+
return getColLeft(index);
|
|
2078
|
+
}
|
|
2079
|
+
function resolveLeftByKey(key: string | number): number | null {
|
|
2080
|
+
const index = tableHeaderLast.value.findIndex(col => col.dataIndex === key);
|
|
2081
|
+
return index === -1 ? null : getColLeft(index);
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
/** 执行滚动(数字重载与 options 重载共用) */
|
|
2085
|
+
function applyScrollPosition(top: number | null, left: number | null) {
|
|
2086
|
+
if (top !== null) {
|
|
2087
|
+
if (isExperimentalScrollY.value) {
|
|
2088
|
+
updateVirtualScrollY(top);
|
|
2089
|
+
updateCustomScrollbar();
|
|
2090
|
+
} else {
|
|
2091
|
+
tableContainerRef.value!.scrollTop = top;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
if (left !== null) tableContainerRef.value!.scrollLeft = left;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
/** 平滑滚动到目标位置(rAF 动画,ease-out);每帧走与用户滚动相同的路径 */
|
|
2098
|
+
function smoothScrollTo(top: number | null, left: number | null) {
|
|
2099
|
+
const container = tableContainerRef.value;
|
|
2100
|
+
if (!container || (top === null && left === null)) return;
|
|
2101
|
+
const startTop = isExperimentalScrollY.value ? virtualScroll.value.scrollTop : container.scrollTop;
|
|
2102
|
+
const startLeft = container.scrollLeft;
|
|
2103
|
+
const deltaTop = top === null ? 0 : top - startTop;
|
|
2104
|
+
const deltaLeft = left === null ? 0 : left - startLeft;
|
|
2105
|
+
if (!deltaTop && !deltaLeft) return;
|
|
2106
|
+
|
|
2107
|
+
const startTime = Date.now();
|
|
2108
|
+
const ease = (t: number) => 1 - Math.pow(1 - t, 3);
|
|
2109
|
+
// 用户手势介入时取消动画
|
|
2110
|
+
container.addEventListener('wheel', cancelSmoothScroll, { passive: true });
|
|
2111
|
+
container.addEventListener('touchstart', cancelSmoothScroll, { passive: true });
|
|
2112
|
+
removeGestureListeners = () => {
|
|
2113
|
+
container.removeEventListener('wheel', cancelSmoothScroll);
|
|
2114
|
+
container.removeEventListener('touchstart', cancelSmoothScroll);
|
|
2115
|
+
};
|
|
2116
|
+
|
|
2117
|
+
const seq = smoothScrollSeq;
|
|
2118
|
+
const step = () => {
|
|
2119
|
+
// 动画已被取消(新调用或用户手势),旧回调直接退出
|
|
2120
|
+
if (seq !== smoothScrollSeq) return;
|
|
2121
|
+
const t = Math.min(1, (Date.now() - startTime) / SMOOTH_SCROLL_DURATION);
|
|
2122
|
+
const eased = ease(t);
|
|
2123
|
+
applyScrollPosition(top === null ? null : startTop + deltaTop * eased, left === null ? null : startLeft + deltaLeft * eased);
|
|
2124
|
+
if (t < 1) {
|
|
2125
|
+
smoothScrollRafId = requestAnimationFrame(step);
|
|
2126
|
+
} else {
|
|
2127
|
+
smoothScrollRafId = 0;
|
|
2128
|
+
removeGestureListeners!();
|
|
2129
|
+
removeGestureListeners = null;
|
|
2130
|
+
}
|
|
2131
|
+
};
|
|
2132
|
+
smoothScrollRafId = requestAnimationFrame(step);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
/**
|
|
2136
|
+
* set scroll bar position
|
|
2137
|
+
* - 数字重载(向后兼容):`scrollTo(top, left)`,null 表示不改变该轴,省略默认 0
|
|
2138
|
+
* - options 重载:`scrollTo({ top, left, behavior })`,每轴可传像素数字或 `{ index, key, px }` 目标,
|
|
2139
|
+
* 省略的轴不改变位置;目标无法解析(index 越界 / key 不存在)时该轴静默跳过
|
|
2140
|
+
* @param options null to not change
|
|
2141
|
+
* @param leftArg null to not change
|
|
2142
|
+
*/
|
|
2143
|
+
function scrollTo(top?: number | null, left?: number | null): void;
|
|
2144
|
+
function scrollTo(options: ScrollToOptions): void;
|
|
2145
|
+
function scrollTo(options?: ScrollToOptions | number | null, leftArg?: number | null): void {
|
|
2146
|
+
if (!tableContainerRef.value) return;
|
|
2147
|
+
cancelSmoothScroll();
|
|
2148
|
+
if (options === undefined || options === null || typeof options === 'number') {
|
|
2149
|
+
// 数字重载:保持既有行为
|
|
2150
|
+
applyScrollPosition(options === undefined ? 0 : options, leftArg === undefined ? 0 : leftArg);
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
let top: number | null = resolveScrollAxis(options.top, resolveTopByIndex, resolveTopByKey);
|
|
2154
|
+
let left: number | null = resolveScrollAxis(options.left, resolveLeftByIndex, resolveLeftByKey);
|
|
2155
|
+
if (top === null && left === null) return;
|
|
2156
|
+
// 钳制到合法滚动范围(基于理论内容尺寸,与滚动条语义一致)
|
|
2157
|
+
if (top !== null) top = Math.min(Math.max(top, 0), getMaxScrollTop());
|
|
2158
|
+
if (left !== null)
|
|
2159
|
+
left = Math.min(Math.max(left, 0), Math.max(0, getColLeft(tableHeaderLast.value.length) - virtualScrollX.value.containerWidth));
|
|
2160
|
+
if (options.behavior === 'smooth') {
|
|
2161
|
+
smoothScrollTo(top, left);
|
|
2162
|
+
} else {
|
|
2163
|
+
applyScrollPosition(top, left);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
// 编译期断言:实现签名与公共类型 ScrollToFn 保持一致(type-tests 依赖此类型)
|
|
2167
|
+
void (scrollTo satisfies ScrollToFn);
|
|
2168
|
+
|
|
2169
|
+
/** get current table data */
|
|
2170
|
+
function getTableData() {
|
|
2171
|
+
return toRaw(dataSourceCopy.value);
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
/**
|
|
2175
|
+
* 清空 mergeCells 结果缓存并强制重算合并结果。
|
|
2176
|
+
*
|
|
2177
|
+
* 缓存以「绝对行索引 + 叶子列索引」为键、行引用同一性校验命中,
|
|
2178
|
+
* 仅在 dataSource/列配置变化时自动清空。业务代码原地修改行字段
|
|
2179
|
+
* (行对象引用不变,常见于响应式行对象)且 mergeCells 结果依赖这些字段时,
|
|
2180
|
+
* 缓存会命中旧结果,需调用本方法显式失效。
|
|
2181
|
+
*/
|
|
2182
|
+
function clearMergeCellsCache() {
|
|
2183
|
+
mergeCellsCache.clear();
|
|
2184
|
+
// 重建 dataSourceCopy(新数组引用):触发缓存清空 watch 与渲染更新,
|
|
2185
|
+
// 使 hiddenCellMap/hoverRowMap 及渲染出的 rowspan/colspan 全部重算
|
|
2186
|
+
initDataSource();
|
|
2187
|
+
updateMaxRowSpan();
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
defineExpose({
|
|
2191
|
+
/**
|
|
2192
|
+
* 重新计算虚拟列表宽高
|
|
2193
|
+
*
|
|
2194
|
+
* en: calc virtual scroll x & y info
|
|
2195
|
+
* @see {@link initVirtualScroll}
|
|
2196
|
+
*/
|
|
2197
|
+
initVirtualScroll,
|
|
2198
|
+
/**
|
|
2199
|
+
* 重新计算虚拟列表宽度
|
|
2200
|
+
*
|
|
2201
|
+
* en: calc virtual scroll x
|
|
2202
|
+
* @see {@link initVirtualScrollX}
|
|
2203
|
+
*/
|
|
2204
|
+
initVirtualScrollX,
|
|
2205
|
+
/**
|
|
2206
|
+
* 重新计算虚拟列表高度
|
|
2207
|
+
*
|
|
2208
|
+
* en: calc virtual scroll y
|
|
2209
|
+
* @see {@link initVirtualScrollY}
|
|
2210
|
+
*/
|
|
2211
|
+
initVirtualScrollY,
|
|
2212
|
+
/**
|
|
2213
|
+
* 清空 mergeCells 结果缓存并强制重算合并结果
|
|
2214
|
+
*
|
|
2215
|
+
* en: Clear mergeCells result cache and force recomputation
|
|
2216
|
+
* @see {@link clearMergeCellsCache}
|
|
2217
|
+
*/
|
|
2218
|
+
clearMergeCellsCache,
|
|
2219
|
+
/**
|
|
2220
|
+
* 选中一行
|
|
2221
|
+
*
|
|
2222
|
+
* en:select a row
|
|
2223
|
+
* @see {@link setCurrentRow}
|
|
2224
|
+
*/
|
|
2225
|
+
setCurrentRow,
|
|
2226
|
+
/**
|
|
2227
|
+
* 取消选中单元格
|
|
2228
|
+
*
|
|
2229
|
+
* en: set highlight active cell (props.cellActive=true)
|
|
2230
|
+
* @see {@link setSelectedCell}
|
|
2231
|
+
*/
|
|
2232
|
+
setSelectedCell,
|
|
2233
|
+
/**
|
|
2234
|
+
* 设置高亮单元格
|
|
2235
|
+
*
|
|
2236
|
+
* en: Set highlight cell
|
|
2237
|
+
* @see {@link setHighlightDimCell}
|
|
2238
|
+
*/
|
|
2239
|
+
setHighlightDimCell,
|
|
2240
|
+
/**
|
|
2241
|
+
* 设置高亮行
|
|
2242
|
+
*
|
|
2243
|
+
* en: Set highlight row
|
|
2244
|
+
* @see {@link setHighlightDimRow}
|
|
2245
|
+
*/
|
|
2246
|
+
setHighlightDimRow,
|
|
2247
|
+
/**
|
|
2248
|
+
* 表格排序列colKey
|
|
2249
|
+
*
|
|
2250
|
+
* en: Table sort column colKey
|
|
2251
|
+
*/
|
|
2252
|
+
sortCol,
|
|
2253
|
+
/**
|
|
2254
|
+
* 排序状态数组
|
|
2255
|
+
*
|
|
2256
|
+
* en: Multi-column sort states array
|
|
2257
|
+
*/
|
|
2258
|
+
sortStates,
|
|
2259
|
+
/**
|
|
2260
|
+
* 表格排序列顺序
|
|
2261
|
+
*
|
|
2262
|
+
* en: get current sort info
|
|
2263
|
+
* @see {@link getSortColumns}
|
|
2264
|
+
*/
|
|
2265
|
+
getSortColumns,
|
|
2266
|
+
/**
|
|
2267
|
+
* 设置表头排序状态
|
|
2268
|
+
*
|
|
2269
|
+
* en: Set the sort status of the table header
|
|
2270
|
+
* @see {@link setSorter}
|
|
2271
|
+
*/
|
|
2272
|
+
setSorter,
|
|
2273
|
+
/**
|
|
2274
|
+
* 重置sorter状态
|
|
2275
|
+
*
|
|
2276
|
+
* en: Reset the sorter status
|
|
2277
|
+
* @see {@link resetSorter}
|
|
2278
|
+
*/
|
|
2279
|
+
resetSorter,
|
|
2280
|
+
/**
|
|
2281
|
+
* 滚动至
|
|
2282
|
+
* - 数字参数:scrollTo(top, left),null 表示不改变该轴
|
|
2283
|
+
* - 对象参数:scrollTo({ top, left, behavior }),top/left 可传像素数字或 { index, key, px } 目标
|
|
2284
|
+
*
|
|
2285
|
+
* en: Scroll to position or target row/column
|
|
2286
|
+
* @see {@link scrollTo}
|
|
2287
|
+
*/
|
|
2288
|
+
scrollTo,
|
|
2289
|
+
/**
|
|
2290
|
+
* 获取表格数据
|
|
2291
|
+
*
|
|
2292
|
+
* en: Get table data
|
|
2293
|
+
* @see {@link getTableData}
|
|
2294
|
+
*/
|
|
2295
|
+
getTableData,
|
|
2296
|
+
getRowIndex,
|
|
2297
|
+
getColumnIndex,
|
|
2298
|
+
/**
|
|
2299
|
+
* 设置展开的行
|
|
2300
|
+
*
|
|
2301
|
+
* en: Set expanded rows
|
|
2302
|
+
* @see {@link setRowExpand}
|
|
2303
|
+
*/
|
|
2304
|
+
setRowExpand,
|
|
2305
|
+
/**
|
|
2306
|
+
* 不定行高时,如果行高有变化,则调用此方法更新行高。
|
|
2307
|
+
*
|
|
2308
|
+
* en: When the row height is not fixed, call this method to update the row height if the row height changes.
|
|
2309
|
+
* @see {@link setAutoHeight}
|
|
2310
|
+
*/
|
|
2311
|
+
setAutoHeight,
|
|
2312
|
+
/**
|
|
2313
|
+
* 清除所有行高
|
|
2314
|
+
*
|
|
2315
|
+
* en: Clear all row heights
|
|
2316
|
+
* @see {@link clearAllAutoHeight}
|
|
2317
|
+
*/
|
|
2318
|
+
clearAllAutoHeight,
|
|
2319
|
+
/**
|
|
2320
|
+
* 设置树节点展开状态
|
|
2321
|
+
*
|
|
2322
|
+
* en: Set tree node expand state
|
|
2323
|
+
* @see {@link setTreeExpand}
|
|
2324
|
+
*/
|
|
2325
|
+
setTreeExpand,
|
|
2326
|
+
/**
|
|
2327
|
+
* 获取拖选选中的单元格信息
|
|
2328
|
+
*
|
|
2329
|
+
* en: Get selected cells info (areaSelection=true)
|
|
2330
|
+
* @see {@link getSelectedArea}
|
|
2331
|
+
*/
|
|
2332
|
+
getSelectedArea,
|
|
2333
|
+
/**
|
|
2334
|
+
* 设置拖选选区
|
|
2335
|
+
*
|
|
2336
|
+
* en: Set cell selection range (areaSelection=true)
|
|
2337
|
+
* @see {@link setAreaSelection}
|
|
2338
|
+
*/
|
|
2339
|
+
setAreaSelection,
|
|
2340
|
+
/**
|
|
2341
|
+
* 清空拖选选区
|
|
2342
|
+
*
|
|
2343
|
+
* en: Clear cell selection range (areaSelection=true)
|
|
2344
|
+
* @see {@link clearSelectedArea}
|
|
2345
|
+
*/
|
|
2346
|
+
clearSelectedArea,
|
|
2347
|
+
/**
|
|
2348
|
+
* 复制选区内容到剪贴板
|
|
2349
|
+
*
|
|
2350
|
+
* en: Copy selected area to clipboard (areaSelection=true)
|
|
2351
|
+
* @see {@link copySelectedArea}
|
|
2352
|
+
*/
|
|
2353
|
+
copySelectedArea,
|
|
2354
|
+
/**
|
|
2355
|
+
* 设置筛选状态(Beta)
|
|
2356
|
+
*
|
|
2357
|
+
* en: Set filter status(Beta)
|
|
2358
|
+
* @see {@link setFilter}
|
|
2359
|
+
*/
|
|
2360
|
+
setFilter,
|
|
2361
|
+
});
|
|
2362
|
+
</script>
|