@aimerthyr/virtual-table 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -13,3 +13,12 @@
13
13
  - 完整的 TypeScript 类型支持
14
14
  - 基于 TanStack Table 和 TanStack Virtual
15
15
  - 内置基础样式
16
+
17
+ ## [1.1.0] - 2026-03-09
18
+
19
+ ### Changed
20
+
21
+ - 支持自定义footer、加载没有更多区域、自定义展开按钮
22
+ - 支持表头分组及行列单元格合并
23
+ - 支持动态配置表格主题
24
+ - VTbale 官网发布
package/README.md CHANGED
@@ -1,14 +1,19 @@
1
1
  # @aimerthyr/virtual-table
2
2
 
3
- 高性能 Vue 3 虚拟滚动表格组件,基于 TanStack Table 和 TanStack Virtual 构建。
3
+ 基于 Vue 3TanStack Table 和 TanStack Virtual 的高性能虚拟滚动表格组件。
4
+
5
+ ## 介绍
6
+
7
+ `@aimerthyr/virtual-table` 是一个面向中大数据量场景的 Vue 3 表格组件,支持虚拟滚动、排序、筛选、分页、树形数据、展开行、行选择、固定表头、固定列、列宽调整等常用能力,并提供完整的 TypeScript 类型支持与插槽扩展能力。
4
8
 
5
9
  ## 特性
6
10
 
7
- - ✨ 虚拟滚动,支持大数据量渲染
8
- - 🎯 完整的表格功能:排序、筛选、分页、树结构、可展开、可选择、固定列、拖拽列等
9
- - 🎨 灵活的样式定制
10
- - 📦 TypeScript 支持
11
- - 🚀 高性能,基于 TanStack 生态
11
+ - 基于虚拟滚动实现大数据量高性能渲染
12
+ - 支持排序、筛选、分页、树形数据、展开行、行选择
13
+ - 支持固定表头、固定列、列宽调整
14
+ - 支持自定义单元格、自定义筛选、自定义分页、自定义主题
15
+ - 基于 Vue 3 Composition API 与 TypeScript 构建
16
+ - 底层依赖 TanStack Table 与 TanStack Virtual,扩展能力强
12
17
 
13
18
  ## 安装
14
19
 
@@ -23,36 +28,85 @@ npm install @aimerthyr/virtual-table
23
28
  yarn add @aimerthyr/virtual-table
24
29
  ```
25
30
 
26
- ### 基础用法
31
+ ## 依赖要求
32
+
33
+ - `vue >= 3.5.0`
34
+
35
+ ## 快速开始
36
+
37
+ ```ts
38
+ // main.ts 中导入
39
+ import '@aimerthyr/virtual-table/virtual-table.css'
40
+ ```
27
41
 
28
42
  ```vue
29
43
  <script setup lang="ts">
30
- import { VirtualTable } from '@aimerthyr/virtual-table'
44
+ import { ref } from 'vue'
45
+ import VTable, { type VTableColumn } from '@aimerthyr/virtual-table'
31
46
  import '@aimerthyr/virtual-table/virtual-table.css'
32
47
 
33
- const columns = [
34
- { columnKey: 'id', columnHeader: 'ID', columnWidth: 80 },
35
- { columnKey: 'name', columnHeader: '姓名', columnWidth: 150 },
36
- { columnKey: 'age', columnHeader: '年龄', columnWidth: 100 },
37
- ]
48
+ const data = ref([
49
+ { id: 1, name: '张三', age: 28, address: '北京市朝阳区' },
50
+ { id: 2, name: '李四', age: 32, address: '上海市浦东新区' },
51
+ { id: 3, name: '王五', age: 25, address: '广州市天河区' },
52
+ ])
38
53
 
39
- const data = [
40
- { id: 1, name: '张三', age: 25 },
41
- { id: 2, name: '李四', age: 30 },
42
- // ... 更多数据
54
+ const columns: VTableColumn[] = [
55
+ { columnKey: 'id', columnHeader: 'ID', columnWidth: 80 },
56
+ { columnKey: 'name', columnHeader: '姓名', columnWidth: 120 },
57
+ { columnKey: 'age', columnHeader: '年龄', columnWidth: 100, columnAlign: 'center' },
58
+ { columnKey: 'address', columnHeader: '地址' },
43
59
  ]
44
60
  </script>
45
61
 
46
62
  <template>
47
- <VirtualTable :columns="columns" :data="data" />
63
+ <!-- 建议设置 row-height,以获得更稳定的虚拟滚动体验 -->
64
+ <VTable :data="data" :columns="columns" :row-height="44" bordered />
48
65
  </template>
49
66
  ```
50
67
 
51
- ### 启用排序和筛选
68
+ ## 导入方式
69
+
70
+ ```ts
71
+ import VTable from '@aimerthyr/virtual-table'
72
+ ```
73
+
74
+ 或:
75
+
76
+ ```ts
77
+ import { VTable } from '@aimerthyr/virtual-table'
78
+ ```
79
+
80
+ 同时也支持按需导入类型:
81
+
82
+ ```ts
83
+ import type {
84
+ VTableChangeState,
85
+ VTableColumn,
86
+ VTableColumnSizingState,
87
+ VTableData,
88
+ VTableExpandedState,
89
+ VTableInstance,
90
+ VTablePaginationState,
91
+ VTableProps,
92
+ } from '@aimerthyr/virtual-table'
93
+ ```
94
+
95
+ ## 常见用法
96
+
97
+ ### 1. 启用排序和筛选
52
98
 
53
99
  ```vue
54
100
  <script setup lang="ts">
55
- const columns = [
101
+ import VTable, { type VTableColumn } from '@aimerthyr/virtual-table'
102
+ import '@aimerthyr/virtual-table/virtual-table.css'
103
+
104
+ const data = [
105
+ { id: 1, name: '张三', age: 28 },
106
+ { id: 2, name: '李四', age: 32 },
107
+ ]
108
+
109
+ const columns: VTableColumn[] = [
56
110
  {
57
111
  columnKey: 'name',
58
112
  columnHeader: '姓名',
@@ -60,46 +114,366 @@ const columns = [
60
114
  columnEnableSort: true,
61
115
  columnEnableFilter: true,
62
116
  },
63
- // ...
117
+ {
118
+ columnKey: 'age',
119
+ columnHeader: '年龄',
120
+ columnWidth: 100,
121
+ columnEnableSort: true,
122
+ },
64
123
  ]
65
124
  </script>
66
125
 
67
126
  <template>
68
- <VirtualTable :columns="columns" :data="data" />
127
+ <VTable :columns="columns" :data="data" />
69
128
  </template>
70
129
  ```
71
130
 
72
- ### 自定义单元格
131
+ ### 2. 自定义单元格
73
132
 
74
133
  ```vue
75
134
  <template>
76
- <VirtualTable :columns="columns" :data="data">
77
- <template #cell-name="{ row }">
78
- <strong>{{ row.original.name }}</strong>
135
+ <VTable :columns="columns" :data="data">
136
+ <template #bodyCell="{ columnKey, row }">
137
+ <strong v-if="columnKey === 'name'">{{ row.name }}</strong>
138
+ <template v-else>{{ row[columnKey] }}</template>
79
139
  </template>
80
- </VirtualTable>
140
+ </VTable>
141
+ </template>
142
+ ```
143
+
144
+ ### 3. 分页
145
+
146
+ ```vue
147
+ <script setup lang="ts">
148
+ import { ref } from 'vue'
149
+ import VTable, {
150
+ type VTableChangeState,
151
+ type VTableColumn,
152
+ type VTablePaginationState,
153
+ } from '@aimerthyr/virtual-table'
154
+
155
+ const loading = ref(false)
156
+ const totalCount = ref(200)
157
+
158
+ const pagination = ref<VTablePaginationState>({
159
+ pageIndex: 1,
160
+ pageSize: 10,
161
+ })
162
+
163
+ const tableData = ref<any[]>([])
164
+
165
+ const columns: VTableColumn[] = [
166
+ { columnKey: 'id', columnHeader: 'ID', columnWidth: 80 },
167
+ { columnKey: 'name', columnHeader: '姓名', columnWidth: 120 },
168
+ { columnKey: 'age', columnHeader: '年龄', columnWidth: 100 },
169
+ ]
170
+
171
+ const fetchData = async (page: number, pageSize: number) => {
172
+ loading.value = true
173
+
174
+ // 示例:实际项目中这里替换为你的接口请求
175
+ await new Promise((resolve) => setTimeout(resolve, 300))
176
+
177
+ const start = (page - 1) * pageSize
178
+ tableData.value = Array.from({ length: pageSize }, (_, i) => ({
179
+ id: start + i + 1,
180
+ name: `用户${start + i + 1}`,
181
+ age: 20 + ((start + i) % 20),
182
+ }))
183
+
184
+ loading.value = false
185
+ }
186
+
187
+ const handleTableChange = (state: VTableChangeState) => {
188
+ const { pageIndex, pageSize } = state.pagination
189
+ pagination.value = { pageIndex, pageSize }
190
+ fetchData(pageIndex, pageSize)
191
+ }
192
+
193
+ fetchData(pagination.value.pageIndex, pagination.value.pageSize)
194
+ </script>
195
+
196
+ <template>
197
+ <VTable
198
+ v-model:default-pagination="pagination"
199
+ :data="tableData"
200
+ :columns="columns"
201
+ :loading="loading"
202
+ :pagination-config="{
203
+ enabled: true,
204
+ total: totalCount,
205
+ placement: 'right',
206
+ }"
207
+ @table-change="handleTableChange"
208
+ />
81
209
  </template>
82
210
  ```
83
211
 
212
+ ### 4. 列宽调整
213
+
214
+ ```vue
215
+ <script setup lang="ts">
216
+ import { ref } from 'vue'
217
+ import VTable, { type VTableColumn, type VTableColumnSizingState } from '@aimerthyr/virtual-table'
218
+
219
+ const columnSizing = ref<VTableColumnSizingState>({})
220
+
221
+ const data = [
222
+ { id: 1, name: '张三', age: 28, email: 'zhangsan@example.com' },
223
+ { id: 2, name: '李四', age: 32, email: 'lisi@example.com' },
224
+ ]
225
+
226
+ const columns: VTableColumn[] = [
227
+ { columnKey: 'id', columnHeader: 'ID', columnWidth: 80, columnEnableResize: true },
228
+ { columnKey: 'name', columnHeader: '姓名', columnWidth: 120, columnEnableResize: true },
229
+ { columnKey: 'age', columnHeader: '年龄', columnWidth: 100, columnEnableResize: true },
230
+ { columnKey: 'email', columnHeader: '邮箱', columnEnableResize: true },
231
+ ]
232
+
233
+ const handleColumnSizingChange = (state: VTableColumnSizingState) => {
234
+ console.log('当前列宽状态:', state)
235
+ }
236
+ </script>
237
+
238
+ <template>
239
+ <VTable
240
+ v-model:default-column-sizing="columnSizing"
241
+ :data="data"
242
+ :columns="columns"
243
+ column-resize-mode="onEnd"
244
+ @column-sizing-change="handleColumnSizingChange"
245
+ />
246
+ </template>
247
+ ```
248
+
249
+ ### 5. 行选择
250
+
251
+ ```vue
252
+ <script setup lang="ts">
253
+ import VTable, { type VTableColumn } from '@aimerthyr/virtual-table'
254
+
255
+ const data = [
256
+ { id: 1, name: '张三', age: 28 },
257
+ { id: 2, name: '李四', age: 32 },
258
+ { id: 3, name: '王五', age: 25 },
259
+ ]
260
+
261
+ const columns: VTableColumn[] = [
262
+ { columnKey: 'name', columnHeader: '姓名' },
263
+ { columnKey: 'age', columnHeader: '年龄' },
264
+ ]
265
+
266
+ const handleSelectionChange = (rows: any[]) => {
267
+ console.log('选中的行:', rows)
268
+ }
269
+ </script>
270
+
271
+ <template>
272
+ <VTable
273
+ :data="data"
274
+ :columns="columns"
275
+ :row-selection-config="{
276
+ enabled: true,
277
+ onChange: handleSelectionChange,
278
+ }"
279
+ />
280
+ </template>
281
+ ```
282
+
283
+ ## Props
284
+
285
+ > 在 Vue 模板中请使用 `kebab-case`,例如 `rowHeight` 对应 `row-height`。
286
+
287
+ | 属性名 | 类型 | 默认值 | 说明 |
288
+ | ---------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------- |
289
+ | `data` | `TData[]` | `[]` | 表格数据源 |
290
+ | `columns` | `VTableColumn[]` | `[]` | 列配置 |
291
+ | `rowHeight` | `number` | `undefined` | 行高,建议设置以优化虚拟滚动表现 |
292
+ | `rowKey` | `string \| number \| ((row) => string \| number)` | `undefined` | 行唯一标识 |
293
+ | `loading` | `boolean` | `false` | 是否加载中 |
294
+ | `fixedHeader` | `boolean` | `true` | 是否固定表头 |
295
+ | `enableSortingRemoval` | `boolean` | `true` | 是否允许取消排序 |
296
+ | `bordered` | `boolean` | `false` | 是否显示边框 |
297
+ | `rowSelectionConfig` | `VTableRowSelectionConfig<TData>` | `{ enabled: false }` | 行选择配置 |
298
+ | `loadMoreConfig` | `VTableLoadMoreConfig` | `{ showNoMore: false, noMoreText: '没有更多了' }` | 滚动加载更多配置 |
299
+ | `paginationConfig` | `VTablePaginationConfig` | `{ enabled: false, placement: 'right', total: 0, mode: 'server' }` | 分页配置 |
300
+ | `treeConfig` | `VTableTreeConfig` | `{ enabled: false, childrenKey: 'children', indentSize: 16 }` | 树形结构配置 |
301
+ | `enableExpandRow` | `boolean` | `false` | 是否启用展开行 |
302
+ | `enableRowHover` | `boolean` | `true` | 是否启用行 hover 高亮 |
303
+ | `adaptiveColumnWidth` | `number` | `120` | 自适应列最小宽度 |
304
+ | `defaultExpandAllRows` | `boolean` | `false` | 是否默认展开所有行,仅初始化生效 |
305
+ | `columnResizeMode` | `'onChange' \| 'onEnd'` | `'onChange'` | 列宽调整模式 |
306
+ | `fixedFooter` | `boolean` | `false` | 是否固定表尾 |
307
+ | `themeConfig` | `VTableThemeConfig` | `{}` | 自定义主题配置 |
308
+ | `defaultCheckboxColumnWidth` | `number` | `40` | 默认 checkbox 列宽 |
309
+ | `defaultExpandColumnWidth` | `number` | `42` | 默认展开列宽 |
310
+ | `customRowAttributes` | `(row, rowIndex) => HTMLAttributes` | `() => ({})` | 自定义行属性 |
311
+ | `customHeaderCellAttributes` | `(column, colIndex) => ThHTMLAttributes` | `() => ({})` | 自定义表头单元格属性 |
312
+ | `customCellAttributes` | `(row, column, rowIndex, colIndex) => TdHTMLAttributes \| null` | `() => ({})` | 自定义表体单元格属性,返回 `colspan` 或 `rowspan` 为 `0` 时不渲染 |
313
+
314
+ ## 列配置 `VTableColumn`
315
+
316
+ | 属性名 | 类型 | 说明 |
317
+ | -------------------- | ------------------------------- | -------------------------- |
318
+ | `columnKey` | `string` | 列唯一 key,同时对应字段名 |
319
+ | `columnHeader` | `string \| VNode \| Function` | 列头内容 |
320
+ | `columnAlign` | `'left' \| 'center' \| 'right'` | 列对齐方式 |
321
+ | `columnWidth` | `number \| string` | 列宽,支持数字或百分比 |
322
+ | `columnEnableSort` | `boolean` | 是否启用排序 |
323
+ | `columnEnableFilter` | `boolean` | 是否启用筛选 |
324
+ | `columnCell` | `Function` | 单元格渲染函数 |
325
+ | `columnEnableResize` | `boolean` | 是否允许调整列宽 |
326
+ | `columnMaxWidth` | `number` | 列最大宽度 |
327
+ | `columnMinWidth` | `number` | 列最小宽度,默认 `50` |
328
+ | `columnChildren` | `VTableColumn[]` | 子列,用于表头分组 |
329
+
330
+ ## Events
331
+
332
+ > 在 Vue 模板中,事件请使用 `kebab-case` 形式监听。
333
+
334
+ | 事件名 | 回调参数 | 说明 |
335
+ | ---------------------- | ------------------------------------------ | ---------------------------------------- |
336
+ | `table-change` | `(state: VTableChangeState) => void` | 表格状态变化时触发,包括分页、排序、筛选 |
337
+ | `scroll-to-bottom` | `() => void` | 滚动到底部时触发 |
338
+ | `expanded-rows-change` | `(state: VTableExpandedState) => void` | 展开行变化时触发 |
339
+ | `column-sizing-change` | `(state: VTableColumnSizingState) => void` | 列宽调整时触发 |
340
+
341
+ ## Slots
342
+
343
+ | 插槽名 | 参数 | 说明 |
344
+ | ---------------------- | -------------------------------------------------------------- | ---------------------- |
345
+ | `customHeader` | `{ columns, table }` | 自定义整个表头 |
346
+ | `bodyCell` | `{ columnKey, column, row, rowIndex }` | 自定义单元格内容 |
347
+ | `headerCell` | `{ columnKey, column }` | 自定义表头单元格 |
348
+ | `customFilterIcon` | `{ columnKey, filtered, column }` | 自定义筛选图标 |
349
+ | `customFilterDropdown` | `{ confirm, reset, setFilterValue, column, filterModelValue }` | 自定义筛选下拉内容 |
350
+ | `expandedRowRender` | `{ row }` | 自定义展开行内容 |
351
+ | `customPopover` | `{ open, onOpenChange, trigger, content }` | 自定义 Popover |
352
+ | `customPagination` | `{ pageSize, pageIndex, total, onPageChange }` | 自定义分页器 |
353
+ | `customCheckbox` | `{ checked, disabled, indeterminate, onCheckedChange }` | 自定义复选框 |
354
+ | `customEmpty` | `-` | 自定义空状态 |
355
+ | `customLoadingIcon` | `-` | 自定义 loading 图标 |
356
+ | `customLoadNoMore` | `-` | 自定义“没有更多了”区域 |
357
+ | `customFooter` | `-` | 自定义表尾 |
358
+ | `customExpandIcon` | `{ expand, onExpandChange }` | 自定义展开图标 |
359
+
360
+ ## 实例方法
361
+
362
+ 组件暴露了以下实例能力:
363
+
364
+ | 方法名 | 类型 | 说明 |
365
+ | --------------- | -------------------------------------------------------- | ------------------- |
366
+ | `tanstackTable` | `Table<TData>` | TanStack Table 实例 |
367
+ | `scrollToIndex` | `(index: number, behavior?: 'auto' \| 'smooth') => void` | 滚动到指定行 |
368
+
369
+ 使用示例:
370
+
371
+ ```vue
372
+ <script setup lang="ts">
373
+ import { onMounted, ref } from 'vue'
374
+ import VTable, { type VTableInstance } from '@aimerthyr/virtual-table'
375
+
376
+ const tableRef = ref<VTableInstance | null>(null)
377
+
378
+ onMounted(() => {
379
+ tableRef.value?.scrollToIndex(20, 'smooth')
380
+ })
381
+ </script>
382
+
383
+ <template>
384
+ <VTable ref="tableRef" :data="data" :columns="columns" />
385
+ </template>
386
+ ```
387
+
388
+ ## 主题配置
389
+
390
+ `themeConfig` 支持覆盖默认主题配置,主要结构如下:
391
+
392
+ ```ts
393
+ type VTableThemeConfig = {
394
+ primaryColor?: string
395
+ header?: {
396
+ color?: string
397
+ backgroundColor?: string
398
+ borderRadius?: number
399
+ splitColor?: string
400
+ headerIconColor?: string
401
+ padding?: number
402
+ }
403
+ body?: {
404
+ color?: string
405
+ backgroundColor?: string
406
+ padding?: number
407
+ }
408
+ border?: {
409
+ borderStyle?: 'solid' | 'dashed'
410
+ borderColor?: string
411
+ }
412
+ rowHoverColor?: string
413
+ zIndex?: {
414
+ pinnedColumn?: number
415
+ fixedHeader?: number
416
+ fixedFooter?: number
417
+ }
418
+ }
419
+ ```
420
+
421
+ 示例:
422
+
423
+ ```vue
424
+ <template>
425
+ <VTable
426
+ :data="data"
427
+ :columns="columns"
428
+ :theme-config="{
429
+ primaryColor: '#1677ff',
430
+ header: {
431
+ backgroundColor: '#f5f7fa',
432
+ },
433
+ body: {
434
+ backgroundColor: '#ffffff',
435
+ },
436
+ rowHoverColor: '#f5f7fa',
437
+ }"
438
+ />
439
+ </template>
440
+ ```
441
+
442
+ ## 使用建议
443
+
444
+ - 大数据量场景下建议始终设置 `row-height`
445
+ - 如果需要滚动加载,建议配合 `loading` 与 `scroll-to-bottom` 一起使用,避免重复触发
446
+ - 分页模式下,如果是服务端分页,建议在 `table-change` 中统一处理分页、排序、筛选参数
447
+ - 如果有树形数据,优先配置 `tree-config`
448
+ - 如果需要自定义展开内容,使用 `enable-expand-row` 配合 `expandedRowRender`
449
+ - 如果列很多并出现横向滚动,建议配置 `adaptive-column-width`
450
+
451
+ ## 适用场景
452
+
453
+ - 后台管理系统数据表格
454
+ - 大数据量列表渲染
455
+ - 需要虚拟滚动优化的业务表格
456
+ - 需要可扩展插槽能力的 Vue 3 表格场景
457
+
84
458
  ## 开发
85
459
 
86
460
  ```bash
87
461
  # 安装依赖
88
462
  pnpm install
89
463
 
90
- # 开发模式
464
+ # 开发构建
91
465
  pnpm dev
92
466
 
93
- # 构建
467
+ # 生产构建
94
468
  pnpm build
95
469
  ```
96
470
 
97
- ## License
98
-
99
- MIT © [aimerthyr](https://github.com/aimerthyr)
100
-
101
471
  ## 相关链接
102
472
 
103
473
  - [GitHub](https://github.com/aimerthyr/virtual-table)
104
474
  - [TanStack Table](https://tanstack.com/table)
105
475
  - [TanStack Virtual](https://tanstack.com/virtual)
476
+
477
+ ## License
478
+
479
+ MIT