@gulibs/react-vtable 0.0.18 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,13 +2,46 @@
2
2
 
3
3
  ![NPM version](https://img.shields.io/npm/v/@gulibs/react-vtable.svg?style=flat-square)
4
4
 
5
+ A powerful and flexible React table component library built with shadcn/ui and TanStack Table.
6
+
5
7
  [English](#english) | [中文](#chinese)
6
8
 
7
9
  ---
8
10
 
9
11
  ## English
10
12
 
11
- A powerful React table component library built with shadcn/ui components and TanStack Table.
13
+ ### Table of Contents
14
+
15
+ - [Features](#features)
16
+ - [Prerequisites](#prerequisites)
17
+ - [Installation](#installation)
18
+ - [Quick Start](#quick-start)
19
+ - [Core Concepts](#core-concepts)
20
+ - [Complete API Reference](#complete-api-reference)
21
+ - [Advanced Examples](#advanced-examples)
22
+ - [Best Practices](#best-practices)
23
+ - [Development](#development)
24
+ - [License](#license)
25
+
26
+ ### Features
27
+
28
+ - ✨ **Powerful Table**: Built on TanStack Table v8 for maximum flexibility and performance
29
+ - 🎨 **shadcn/ui Components**: Beautiful, accessible, and customizable UI components
30
+ - 🔍 **Search & Filter**: Advanced search with auto-filtering for local/remote data
31
+ - 📄 **Pagination**: Client-side and server-side pagination support
32
+ - ✅ **Row Selection**: Single/multiple selection with cross-page selection support
33
+ - 🎯 **Batch Actions**: Flexible batch operations with smart button grouping
34
+ - ✏️ **Inline Editing**: Edit rows inline with validation support
35
+ - 📌 **Fixed Columns**: Pin columns to left/right with automatic shadow effects
36
+ - 🌳 **Tree Table**: Hierarchical data with expand/collapse support
37
+ - 🔄 **Drag & Drop**: Row and column reordering with @dnd-kit
38
+ - 📱 **Responsive**: Mobile-friendly design
39
+ - 🌐 **i18n**: Built-in English and Chinese locales
40
+ - 🎭 **Rich Value Types**: 20+ built-in value types (date, money, status, etc.)
41
+ - 📋 **Copy to Clipboard**: One-click copy for cells
42
+ - 💬 **Tooltip**: Customizable tooltips for cells and headers
43
+ - 📏 **Text Ellipsis**: Single-line and multi-line text truncation
44
+ - 🔧 **TypeScript**: Full TypeScript support with comprehensive types
12
45
 
13
46
  ### Prerequisites
14
47
 
@@ -17,16 +50,24 @@ This library requires **Tailwind CSS** to be installed and configured in your pr
17
50
  ### Installation
18
51
 
19
52
  ```bash
20
- # Install the library
53
+ # Using pnpm (recommended)
21
54
  pnpm add @gulibs/react-vtable
22
55
 
23
- # Install peer dependencies
24
- pnpm add react react-dom tailwindcss
56
+ # Using npm
57
+ npm install @gulibs/react-vtable
58
+
59
+ # Using yarn
60
+ yarn add @gulibs/react-vtable
61
+
62
+ # Peer dependencies (if not already installed)
63
+ pnpm add react react-dom @tanstack/react-table tailwindcss
25
64
  ```
26
65
 
27
66
  ### Configuration
28
67
 
29
- #### Configure Tailwind CSS
68
+ #### Tailwind CSS Setup
69
+
70
+ Add the library path to your `tailwind.config.js`:
30
71
 
31
72
  ```javascript
32
73
  // tailwind.config.js
@@ -39,294 +80,646 @@ export default {
39
80
  }
40
81
  ```
41
82
 
42
- > 💡 **Note**: The library automatically injects its CSS styles, so no manual CSS imports are required.
83
+ > 💡 **Note**: The library automatically injects its CSS styles. No manual CSS imports are required.
43
84
 
44
- ### Usage
85
+ ### Quick Start
45
86
 
46
87
  ```tsx
47
88
  import { ProTable } from '@gulibs/react-vtable'
48
- // No need to manually import CSS, styles are automatically inlined
49
89
 
50
90
  function App() {
51
91
  const columns = [
52
- {
53
- title: 'Name',
54
- dataIndex: 'name',
55
- key: 'name',
56
- },
57
- {
58
- title: 'Age',
59
- dataIndex: 'age',
60
- key: 'age',
61
- },
92
+ { title: 'Name', dataIndex: 'name', key: 'name' },
93
+ { title: 'Age', dataIndex: 'age', key: 'age' },
94
+ { title: 'Email', dataIndex: 'email', key: 'email' },
62
95
  ]
63
96
 
64
97
  const dataSource = [
65
- { name: 'John', age: 30 },
66
- { name: 'Jane', age: 25 },
98
+ { id: 1, name: 'John Doe', age: 30, email: 'john@example.com' },
99
+ { id: 2, name: 'Jane Smith', age: 25, email: 'jane@example.com' },
67
100
  ]
68
101
 
69
102
  return (
70
103
  <ProTable
71
104
  columns={columns}
72
105
  dataSource={dataSource}
73
- rowKey="name"
106
+ rowKey="id"
74
107
  />
75
108
  )
76
109
  }
77
110
  ```
78
111
 
79
- ### Features
112
+ ### Core Concepts
80
113
 
81
- - **Powerful Table**: Built on TanStack Table for maximum flexibility
82
- - **shadcn/ui Components**: Beautiful, accessible UI components
83
- - **Drag & Drop**: Row and column reordering with @dnd-kit
84
- - **Search & Filter**: Advanced search and filtering capabilities with default behavior for local and remote data modes
85
- - **Pagination**: Built-in pagination with customizable options
86
- - **Column Settings**: Show/hide columns, drag to reorder
87
- - **Row Selection**: Single and multiple row selection with batch actions, **cross-page selection support** (selection state persists across pages when using `request` or `pagination.onChange`)
88
- - **Editable Rows**: Inline editing capabilities with various value types
89
- - **Fixed Columns**: Left and right fixed columns with automatic position calculation and subtle shadow effects (powered by TanStack Table column pinning)
90
- - **Tree Table**: Hierarchical data display with expandable/collapsible rows, custom expand icons, and accordion mode
91
- - **Internationalization**: Built-in i18n support with English and Chinese locales
92
- - **Horizontal Scrolling**: Configurable table width with scroll support
93
- - **Copy to Clipboard**: One-click copy functionality for cells
94
- - **Text Ellipsis**: Single-line and multi-line text truncation with ellipsis
95
- - **Tooltip**: Hover tooltips for cells and headers with customizable content
96
- - **Responsive**: Mobile-friendly design
97
- - **TypeScript**: Full TypeScript support
98
-
99
- ### Examples
100
-
101
- #### Basic Table
114
+ #### 1. Data Modes
115
+
116
+ **Local Data Mode** (using `dataSource`):
117
+ ```tsx
118
+ <ProTable
119
+ dataSource={localData}
120
+ columns={columns}
121
+ rowKey="id"
122
+ />
123
+ ```
102
124
 
125
+ **Remote Data Mode** (using `request`):
103
126
  ```tsx
104
127
  <ProTable
128
+ request={async (params) => {
129
+ // params includes: current, pageSize, filters, sorter
130
+ const res = await fetchData(params)
131
+ return {
132
+ data: res.items,
133
+ total: res.total,
134
+ success: true
135
+ }
136
+ }}
105
137
  columns={columns}
106
- dataSource={dataSource}
107
138
  rowKey="id"
108
139
  />
109
140
  ```
110
141
 
111
- #### With Search
142
+ #### 2. Search Behavior
112
143
 
113
- The table provides **default search behavior** that works automatically:
144
+ The table provides **automatic search behavior**:
114
145
 
115
- - **Local Data Mode** (no `request` prop): Search values are automatically converted to TanStack Table's `columnFilters`, and the table filters data in real-time using `getFilteredRowModel()`.
116
- - **Remote Data Mode** (with `request` prop): Search triggers a new data fetch with search parameters passed to the backend.
146
+ - **Local Mode**: Converts search values to TanStack Table's `columnFilters` and filters in real-time
147
+ - **Remote Mode**: Triggers data fetch with search parameters sent to backend
117
148
 
118
- The default filter function supports:
119
- - Case-insensitive string matching (contains) for **text-like** fields
120
- - **Exact match** for **enum/filter** fields (e.g. `valueEnum`, `filters`, `valueType: 'select' | 'status'`)
121
- - Array-based multi-select filtering (exact match for enum/filter fields, contains match for text fields)
122
- - Automatic handling of empty/null values
149
+ Default filter supports:
150
+ - Case-insensitive substring matching for text fields
151
+ - Exact match for enum/select fields (`valueEnum`, `filters`, `valueType: 'select'`)
152
+ - Array-based multi-select filtering
153
+ - Automatic empty/null value handling
123
154
 
155
+ #### 3. Pagination Modes
156
+
157
+ **Client-side Pagination** (default with `dataSource`):
124
158
  ```tsx
125
159
  <ProTable
126
- columns={columns}
127
- dataSource={dataSource}
128
- rowKey="id"
129
- search={{
130
- filterType: 'query',
131
- searchText: 'Search',
132
- resetText: 'Reset'
160
+ dataSource={data}
161
+ pagination={{ pageSize: 10 }}
162
+ />
163
+ ```
164
+
165
+ **Server-side Pagination** (with `request`):
166
+ ```tsx
167
+ <ProTable
168
+ request={fetchData}
169
+ pagination={{ pageSize: 10 }}
170
+ />
171
+ ```
172
+
173
+ **Manual Server Pagination** (without `request`):
174
+ ```tsx
175
+ <ProTable
176
+ dataSource={currentPageData}
177
+ pagination={{
178
+ mode: 'server', // Important!
179
+ current: page,
180
+ pageSize: pageSize,
181
+ total: total,
182
+ onChange: (page, pageSize) => fetchPage(page, pageSize)
133
183
  }}
134
184
  />
135
185
  ```
136
186
 
137
- **Custom Search Behavior:**
187
+ ---
188
+
189
+ ## Complete API Reference
190
+
191
+ ### ProTable Props
192
+
193
+ #### Core Props
194
+
195
+ | Property | Type | Default | Description |
196
+ |----------|------|---------|-------------|
197
+ | `columns` | `ProColumn<T>[]` | `[]` | Column definitions |
198
+ | `dataSource` | `T[]` | `[]` | Local data array |
199
+ | `request` | `(params) => Promise<Response>` | - | Remote data fetcher |
200
+ | `rowKey` | `string \| (record) => string` | `'id'` | Unique row identifier |
201
+ | `loading` | `boolean` | `false` | Loading state |
202
+ | `defaultData` | `T[]` | `[]` | Default data for initial render |
203
+ | `postData` | `(data: T[]) => T[]` | - | Transform data after fetch |
204
+ | `params` | `Record<string, any>` | - | Extra params for `request` |
205
+
206
+ #### Layout Props
207
+
208
+ | Property | Type | Default | Description |
209
+ |----------|------|---------|-------------|
210
+ | `size` | `'small' \| 'middle' \| 'large'` | `'middle'` | Table size |
211
+ | `bordered` | `boolean` | `false` | Show table borders |
212
+ | `tableLayout` | `'auto' \| 'fixed'` | `'auto'` | Table layout algorithm |
213
+ | `scroll` | `{ x?: number \| string \| true; y?: number \| string }` | - | Scrollable area config |
214
+ | `sticky` | `boolean \| { offsetHeader?: number }` | `false` | Sticky header config |
215
+ | `showHeader` | `boolean` | `true` | Show table header |
216
+ | `className` | `string` | - | Custom class name |
217
+ | `style` | `CSSProperties` | - | Custom styles |
218
+ | `tableStyle` | `CSSProperties` | - | Table element styles |
219
+
220
+ #### Feature Props
221
+
222
+ | Property | Type | Default | Description |
223
+ |----------|------|---------|-------------|
224
+ | `search` | `ProTableSearch \| false` | `false` | Search form config |
225
+ | `toolbar` | `ProTableToolBar \| false` | `false` | Toolbar config |
226
+ | `pagination` | `ProTablePagination \| false` | `false` | Pagination config |
227
+ | `rowSelection` | `ProTableRowSelection<T>` | - | Row selection config |
228
+ | `batchActions` | `ProTableBatchActions<T>` | - | Batch actions config |
229
+ | `editable` | `ProTableEditable<T>` | - | Inline editing config |
230
+ | `expandable` | `ProTableExpandable<T> \| false` | - | Tree table config |
231
+ | `draggable` | `ProTableDraggable \| false` | - | Drag & drop config |
232
+ | `columnsState` | `ProColumnState` | - | Column state management |
233
+
234
+ #### Callback Props
235
+
236
+ | Property | Type | Description |
237
+ |----------|------|-------------|
238
+ | `onChange` | `(pagination, filters, sorter, extra) => void` | Table change callback |
239
+ | `onLoad` | `(dataSource: T[]) => void` | Called after data loaded |
240
+ | `onLoadingChange` | `(loading: boolean) => void` | Loading state change |
241
+ | `onRequestError` | `(error: Error) => void` | Request error callback |
242
+ | `onSubmit` | `(params: any) => void` | Search submit callback |
243
+ | `onReset` | `() => void` | Search reset callback |
244
+ | `onRow` | `(record, index) => HTMLAttributes` | Row props callback |
245
+
246
+ #### Advanced Props
247
+
248
+ | Property | Type | Description |
249
+ |----------|------|-------------|
250
+ | `headerTitle` | `ReactNode` | Table header title |
251
+ | `headerSubTitle` | `ReactNode` | Table header subtitle |
252
+ | `footer` | `(data) => ReactNode` | Table footer render |
253
+ | `emptyRender` | `ReactNode` | Empty state render |
254
+ | `tableRender` | `(props, dom, domList) => ReactNode` | Custom table render |
255
+ | `tableExtraRender` | `(props, data) => ReactNode` | Extra content after table |
256
+ | `paginationRender` | `(opts) => ReactNode` | Custom pagination render |
257
+ | `searchFormRender` | `(props, dom) => ReactNode` | Custom search form render |
258
+ | `actionRef` | `Ref<ProTableAction<T>>` | Table action reference |
259
+ | `formRef` | `Ref<FormInstance>` | Search form reference |
260
+ | `locale` | `ProTableLocale` | i18n locale config |
261
+
262
+ ---
263
+
264
+ ### ProColumn Configuration
265
+
266
+ #### Basic Props
267
+
268
+ | Property | Type | Description |
269
+ |----------|------|-------------|
270
+ | `title` | `string` | Column header text |
271
+ | `dataIndex` | `keyof T \| string \| string[]` | Data field path (supports nested: `'user.name'` or `['user', 'name']`) |
272
+ | `dataPath` | `string` | Alternative to dataIndex (string path only: `'user.name'`) |
273
+ | `key` | `string` | Unique column key |
274
+ | `width` | `number \| string` | Column width (required for fixed columns) |
275
+ | `align` | `'left' \| 'center' \| 'right'` | Text alignment |
276
+ | `fixed` | `'left' \| 'right'` | Pin column to left/right |
277
+
278
+ #### Display Props
279
+
280
+ | Property | Type | Description |
281
+ |----------|------|-------------|
282
+ | `valueType` | `ProFieldValueType` | Value display type (20+ types) |
283
+ | `valueEnum` | `Record<string, { text: string; status?: string; color?: string }>` | Enum value mapping |
284
+ | `render` | `(value, record, index) => ReactNode` | Custom cell render (takes priority over `valueType`) |
285
+ | `ellipsis` | `boolean \| ProColumnEllipsis` | Text ellipsis config |
286
+ | `tooltip` | `boolean \| ProColumnTooltip` | Tooltip config |
287
+ | `headerEllipsis` | `boolean \| ProColumnEllipsis` | Header ellipsis config |
288
+ | `headerTooltip` | `boolean \| ProColumnTooltip` | Header tooltip config |
289
+ | `copyable` | `boolean \| ProColumnCopyable` | Enable copy to clipboard |
290
+
291
+ #### Search Props
292
+
293
+ | Property | Type | Description |
294
+ |----------|------|-------------|
295
+ | `search` | `boolean \| ProColumnSearch` | Include in search form |
296
+ | `hideInSearch` | `boolean` | Hide in search form |
297
+ | `searchFormItemProps` | `FormItemProps` | Search form item props |
298
+ | `renderFormItem` | `(value, onChange, field, column) => ReactNode` | Custom search input render |
299
+
300
+ #### Table Display Props
301
+
302
+ | Property | Type | Description |
303
+ |----------|------|-------------|
304
+ | `hideInTable` | `boolean` | Hide in table |
305
+ | `sorter` | `boolean \| ((a, b) => number)` | Enable sorting |
306
+ | `filters` | `Array<{ text: string; value: any }>` | Filter options |
307
+ | `onFilter` | `(value, record) => boolean` | Filter function |
308
+
309
+ #### Edit Props
310
+
311
+ | Property | Type | Description |
312
+ |----------|------|-------------|
313
+ | `editable` | `boolean \| ((record) => boolean)` | Enable inline editing |
314
+ | `hideInForm` | `boolean` | Hide in edit form |
315
+ | `formItemProps` | `FormItemProps` | Form item props |
316
+ | `fieldProps` | `Record<string, any>` | Field component props |
317
+
318
+ ---
319
+
320
+ ### Value Types Reference
321
+
322
+ Supported `valueType` values:
323
+
324
+ | Type | Description | Example |
325
+ |------|-------------|---------|
326
+ | `text` | Plain text | "Hello World" |
327
+ | `textarea` | Multi-line text | Long content |
328
+ | `number` | Number | 123 |
329
+ | `money` | Currency | $1,234.56 |
330
+ | `percent` | Percentage | 75% |
331
+ | `date` | Date | 2024-01-01 |
332
+ | `dateTime` | Date with time | 2024-01-01 10:30:00 |
333
+ | `dateRange` | Date range | 2024-01-01 ~ 2024-01-31 |
334
+ | `select` | Select dropdown | Uses `valueEnum` |
335
+ | `status` | Status badge | Uses `valueEnum` |
336
+ | `tags` | Multiple tags | Array display |
337
+ | `switch` | Boolean switch | true/false |
338
+ | `avatar` | Avatar image | URL display |
339
+ | `image` | Image | URL display |
340
+ | `progress` | Progress bar | 0-100 |
341
+ | `code` | Code block | Monospace text |
342
+ | `fromNow` | Relative time | "2 hours ago" |
343
+ | `email` | Email address | With mailto link |
344
+ | `phone` | Phone number | Formatted |
345
+ | `url` | URL link | Clickable link |
346
+ | `color` | Color picker | Color swatch |
347
+ | `rate` | Rating stars | 1-5 stars |
348
+ | `custom` | Custom render | Use `render` function |
349
+
350
+ ---
351
+
352
+ ### Search Configuration
353
+
354
+ ```typescript
355
+ interface ProTableSearch {
356
+ filterType?: 'query' | 'light' // Query: form above table, Light: inline filters
357
+ searchText?: string // Search button text
358
+ resetText?: string // Reset button text
359
+ submitText?: string // Submit button text
360
+ labelWidth?: number | 'auto' // Label width
361
+ span?: number // Grid column span
362
+ defaultCollapsed?: boolean // Initially collapsed
363
+ collapsed?: boolean // Controlled collapse state
364
+ onCollapse?: (collapsed: boolean) => void
365
+ optionRender?: (config, props, dom) => ReactNode[] // Custom action buttons
366
+ }
367
+ ```
138
368
 
139
- You can provide custom `onSearch` and `onReset` callbacks to override the default behavior:
369
+ **Example:**
140
370
 
141
371
  ```tsx
142
372
  <ProTable
143
- columns={columns}
144
- dataSource={dataSource}
145
- rowKey="id"
146
373
  search={{
147
374
  filterType: 'query',
148
- onSearch: (values) => {
149
- // Custom search logic
150
- console.log('Search values:', values)
151
- },
152
- onReset: () => {
153
- // Custom reset logic
154
- console.log('Reset search')
155
- }
375
+ searchText: 'Search',
376
+ resetText: 'Reset',
377
+ labelWidth: 'auto',
378
+ defaultCollapsed: false,
156
379
  }}
157
380
  />
158
381
  ```
159
382
 
160
- #### With Pagination
383
+ ---
384
+
385
+ ### Pagination Configuration
386
+
387
+ ```typescript
388
+ interface ProTablePagination {
389
+ mode?: 'client' | 'server' // Pagination mode
390
+ pageSize?: number // Items per page
391
+ current?: number // Current page (controlled)
392
+ total?: number // Total items
393
+ showSizeChanger?: boolean // Show page size selector
394
+ showQuickJumper?: boolean // Show quick jump input
395
+ showTotal?: (total, range) => ReactNode
396
+ pageSizeOptions?: string[] // Page size options
397
+ size?: 'default' | 'small'
398
+ simple?: boolean // Simple pagination
399
+ disabled?: boolean
400
+ onChange?: (page, pageSize) => void
401
+ onShowSizeChange?: (current, size) => void
402
+ }
403
+ ```
404
+
405
+ ---
406
+
407
+ ### Row Selection Configuration
408
+
409
+ ```typescript
410
+ interface ProTableRowSelection<T> {
411
+ type?: 'checkbox' | 'radio' // Selection type
412
+ selectedRowKeys?: React.Key[] // Controlled selection
413
+ onChange?: (keys, rows) => void
414
+ preserveSelectedRowKeys?: boolean // Keep selection across pages (default: true)
415
+ getCheckboxProps?: (record) => any
416
+ selections?: boolean | any[]
417
+ hideSelectAll?: boolean
418
+ fixed?: boolean // Fix selection column
419
+ columnWidth?: number | string
420
+ renderCell?: (checked, record, index, originNode) => ReactNode
421
+ alwaysShowAlert?: boolean // Always show batch actions bar
422
+ batchActions?: ProTableBatchActions<T> // Batch operations config
423
+ }
424
+ ```
425
+
426
+ #### Cross-Page Selection
427
+
428
+ When using `request` or `pagination.onChange`, selection automatically persists across pages:
161
429
 
162
430
  ```tsx
163
431
  <ProTable
164
- columns={columns}
165
- dataSource={dataSource}
166
- rowKey="id"
167
- pagination={{
168
- pageSize: 10,
169
- showSizeChanger: true
432
+ request={fetchData}
433
+ rowSelection={{
434
+ type: 'checkbox',
435
+ preserveSelectedRowKeys: true, // Default: true
436
+ onChange: (selectedRowKeys, selectedRows) => {
437
+ // selectedRowKeys: all selected keys across all pages
438
+ // selectedRows: rows from current page only
439
+ console.log('Total selected:', selectedRowKeys.length)
440
+ }
170
441
  }}
171
442
  />
172
443
  ```
173
444
 
174
- #### Remote Pagination (with `request`)
445
+ ---
446
+
447
+ ### Batch Actions Configuration (Updated API)
175
448
 
176
- In **Remote Data Mode** (when you pass `request`), pagination works like this:
449
+ ```typescript
450
+ interface ProTableBatchActions<T> {
451
+ actions?: Array<ProTableBatchAction<T>> // Batch action list
452
+ maxVisibleActions?: number // Max buttons to show directly (default: 3)
453
+ hideClearButton?: boolean // Hide clear selection button
454
+ }
177
455
 
178
- - **`total` controls the page count**: return `total` from `request` (recommended) and/or pass `pagination.total`.
179
- - **Do not pass `pagination.current/pageSize` as constants** (e.g. `current: 1`), unless you intend to lock the table to that page. If you want controlled pagination, store them in state and update in `pagination.onChange`.
456
+ interface ProTableBatchAction<T> {
457
+ key: string // Unique action key
458
+ label: string // Button text
459
+ icon?: ReactNode // Button icon
460
+ onClick: (rows: T[]) => void // Click handler
461
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
462
+ clearSelection?: boolean // Clear selection after action (default: false)
463
+ showInMore?: boolean // Show in "More" menu (default: false)
464
+ isExportFormat?: boolean // Mark as export format (auto-groups multiple export formats)
465
+ }
466
+ ```
180
467
 
181
- **Uncontrolled (recommended if you don't need to control page state):**
468
+ **Example:**
182
469
 
183
470
  ```tsx
471
+ import { Trash2, Download, FileText, Mail } from 'lucide-react'
472
+
184
473
  <ProTable
185
- columns={columns}
186
- rowKey="id"
187
- request={async (params) => {
188
- // params includes: current, pageSize, ...filters, ...sorter
189
- const res = await fetchUsers(params)
190
- return { data: res.items, total: res.total, success: true }
191
- }}
192
- pagination={{
193
- pageSize: 10,
194
- showSizeChanger: true,
195
- showQuickJumper: true,
474
+ rowSelection={{
475
+ type: 'checkbox',
476
+ batchActions: {
477
+ maxVisibleActions: 3, // Show max 3 buttons directly
478
+ actions: [
479
+ {
480
+ key: 'delete',
481
+ label: 'Delete',
482
+ icon: <Trash2 className="h-3 w-3 mr-1" />,
483
+ onClick: (rows) => {
484
+ console.log('Delete:', rows)
485
+ },
486
+ variant: 'destructive',
487
+ clearSelection: true, // Clear after delete
488
+ },
489
+ {
490
+ key: 'export',
491
+ label: 'Export',
492
+ icon: <Download className="h-3 w-3 mr-1" />,
493
+ onClick: (rows) => {
494
+ console.log('Export:', rows)
495
+ },
496
+ variant: 'outline',
497
+ },
498
+ {
499
+ key: 'exportExcel',
500
+ label: 'Export Excel',
501
+ icon: <FileText className="h-3 w-3 mr-1" />,
502
+ onClick: (rows) => {
503
+ console.log('Export Excel:', rows)
504
+ },
505
+ variant: 'outline',
506
+ isExportFormat: true, // Groups with other export formats
507
+ showInMore: true,
508
+ },
509
+ {
510
+ key: 'exportCSV',
511
+ label: 'Export CSV',
512
+ icon: <FileText className="h-3 w-3 mr-1" />,
513
+ onClick: (rows) => {
514
+ console.log('Export CSV:', rows)
515
+ },
516
+ variant: 'outline',
517
+ isExportFormat: true,
518
+ showInMore: true,
519
+ },
520
+ {
521
+ key: 'email',
522
+ label: 'Send Email',
523
+ icon: <Mail className="h-3 w-3 mr-1" />,
524
+ onClick: (rows) => {
525
+ console.log('Email:', rows)
526
+ },
527
+ variant: 'outline',
528
+ showInMore: true, // Move to "More" menu
529
+ },
530
+ ],
531
+ },
196
532
  }}
197
533
  />
198
534
  ```
199
535
 
200
- **Controlled (when you need to sync pagination with external state / URL):**
536
+ **Features:**
537
+ - Smart button grouping: buttons exceeding `maxVisibleActions` auto-move to "More" (⋯) menu
538
+ - Export format grouping: multiple `isExportFormat: true` actions auto-merge into dropdown
539
+ - Flexible action control: use `clearSelection` to auto-clear selection after certain actions
201
540
 
202
- ```tsx
203
- function Page() {
204
- const [pagination, setPagination] = useState({
205
- current: 1,
206
- pageSize: 10,
207
- total: 0,
208
- })
541
+ ---
209
542
 
210
- return (
211
- <ProTable
212
- columns={columns}
213
- rowKey="id"
214
- request={async (params) => {
215
- const res = await fetchUsers(params)
216
- setPagination((prev) => ({ ...prev, total: res.total }))
217
- return { data: res.items, total: res.total, success: true }
218
- }}
219
- pagination={{
220
- current: pagination.current,
221
- pageSize: pagination.pageSize,
222
- total: pagination.total,
223
- showSizeChanger: true,
224
- showQuickJumper: true,
225
- onChange: (current, pageSize) => {
226
- setPagination((prev) => ({ ...prev, current, pageSize }))
227
- },
228
- }}
229
- />
230
- )
543
+ ### Editable Configuration
544
+
545
+ ```typescript
546
+ interface ProTableEditable<T> {
547
+ type?: 'single' | 'multiple' // Edit mode
548
+ form?: any // React Hook Form instance
549
+ formProps?: Record<string, any>
550
+ onSave?: (key, record, originRow) => Promise<void>
551
+ onDelete?: (key, record) => Promise<void>
552
+ onCancel?: (key, record, originRow) => void
553
+ actionRender?: (record, config) => ReactNode[] // Custom action buttons
554
+ deletePopconfirmMessage?: ReactNode
555
+ onlyOneLineEditorAlertMessage?: ReactNode
556
+ onlyAddOneLineAlertMessage?: ReactNode
231
557
  }
232
558
  ```
233
559
 
234
- #### Server Pagination (NO `request`, you fetch data yourself)
560
+ ---
235
561
 
236
- If you **do not** use `request`, but you still want **server-side pagination** (i.e. `dataSource` only contains **current page items**),
237
- set `pagination.mode: 'server'` to prevent TanStack Table from slicing again.
562
+ ### Expandable Configuration (Tree Table)
238
563
 
239
- ```tsx
240
- function Page() {
241
- const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 })
242
- const [data, setData] = useState<any[]>([])
564
+ ```typescript
565
+ interface ProTableExpandable<T> {
566
+ childrenColumnName?: string // Children field name (default: 'children')
567
+ defaultExpandedRowKeys?: React.Key[] // Default expanded rows
568
+ expandedRowKeys?: React.Key[] // Controlled expanded rows
569
+ onExpand?: (expanded, record) => void
570
+ onExpandedRowsChange?: (keys) => void
571
+ expandIcon?: (props: {
572
+ expanded: boolean
573
+ onExpand: () => void
574
+ record: T
575
+ }) => ReactNode
576
+ showExpandColumn?: boolean // Show expand column (default: true)
577
+ expandColumnWidth?: number // Expand column width (default: 50)
578
+ defaultExpandAllRows?: boolean // Expand all by default
579
+ accordion?: boolean // Accordion mode (only one expanded)
580
+ }
581
+ ```
243
582
 
244
- const fetchPage = async (current: number, pageSize: number) => {
245
- const res = await fetchUsers({ current, pageSize })
246
- setData(res.items)
247
- setPagination((prev) => ({ ...prev, current, pageSize, total: res.total }))
248
- }
583
+ ---
249
584
 
250
- useEffect(() => {
251
- void fetchPage(pagination.current, pagination.pageSize)
252
- }, [])
585
+ ### Ellipsis Configuration
253
586
 
254
- return (
255
- <ProTable
256
- columns={columns}
257
- rowKey="id"
258
- dataSource={data}
259
- pagination={{
260
- mode: "server",
261
- current: pagination.current,
262
- pageSize: pagination.pageSize,
263
- total: pagination.total,
264
- onChange: (current, pageSize) => void fetchPage(current, pageSize),
265
- }}
266
- />
267
- )
587
+ ```typescript
588
+ interface ProColumnEllipsis {
589
+ multiline?: boolean // Multi-line ellipsis
590
+ rows?: number // Number of rows (1-5, default: 2)
591
+ }
592
+ ```
593
+
594
+ **Examples:**
595
+
596
+ ```tsx
597
+ // Single-line ellipsis
598
+ ellipsis: true
599
+
600
+ // Multi-line ellipsis (2 lines)
601
+ ellipsis: {
602
+ multiline: true,
603
+ rows: 2
268
604
  }
269
605
  ```
270
606
 
271
- #### Custom Pagination Rendering (`paginationRender`)
607
+ ---
608
+
609
+ ### Tooltip Configuration
272
610
 
273
- Use `paginationRender` when you want to **render your own pagination UI** (in any layout), while still reusing ProTable’s built-in
274
- pagination state and default Pagination component.
611
+ ```typescript
612
+ interface ProColumnTooltip<T> {
613
+ content?: ReactNode | ((value, record, index) => ReactNode)
614
+ side?: 'top' | 'right' | 'bottom' | 'left'
615
+ align?: 'start' | 'center' | 'end'
616
+ delayDuration?: number
617
+ disabled?: boolean
618
+ }
619
+ ```
275
620
 
276
- Notes:
277
- - `paginationRender` receives `pagination`, `onChange`, and `defaultDom`.
278
- - If you are in **server pagination mode** (`pagination.mode: 'server'`) you must also provide `pagination.onChange` to fetch data.
621
+ **Examples:**
279
622
 
280
623
  ```tsx
281
- <ProTable
282
- columns={columns}
283
- rowKey="id"
284
- dataSource={data}
285
- pagination={{
286
- mode: "server",
287
- current,
288
- pageSize,
289
- total,
290
- onChange: (page, pageSize) => void fetchPage(page, pageSize),
291
- }}
292
- paginationRender={({ pagination, onChange, defaultDom }) => (
293
- <div className="space-y-2">
294
- <div className="flex items-center justify-between rounded border p-2">
295
- <div className="text-sm text-muted-foreground">
296
- Page {pagination.current} / Size {pagination.pageSize} / Total {pagination.total}
297
- </div>
298
- <div className="flex gap-2">
299
- <button onClick={() => onChange(Math.max(1, pagination.current - 1), pagination.pageSize)}>
300
- Prev
301
- </button>
302
- <button onClick={() => onChange(pagination.current + 1, pagination.pageSize)}>
303
- Next
304
- </button>
305
- </div>
306
- </div>
307
- {/* reuse built-in Pagination UI */}
308
- {defaultDom}
309
- </div>
310
- )}
311
- />
624
+ // Simple tooltip (shows cell value)
625
+ tooltip: true
626
+
627
+ // Custom tooltip
628
+ tooltip: {
629
+ content: 'Custom tooltip text',
630
+ side: 'top',
631
+ delayDuration: 300
632
+ }
633
+
634
+ // Function-based tooltip
635
+ tooltip: {
636
+ content: (value, record) => `Full: ${value} (ID: ${record.id})`,
637
+ side: 'top'
638
+ }
639
+ ```
640
+
641
+ ---
642
+
643
+ ### Action Reference
644
+
645
+ Access table methods via `actionRef`:
646
+
647
+ ```typescript
648
+ interface ProTableAction<T> {
649
+ reload: (resetPageIndex?: boolean) => Promise<void> // Reload data
650
+ reloadAndRest: () => Promise<void> // Reload and reset to page 1
651
+ reset: () => void // Reset filters and search
652
+ clearSelected: () => void // Clear row selection
653
+ startEditable: (rowKey) => boolean // Start editing row
654
+ cancelEditable: (rowKey) => boolean // Cancel editing row
655
+ saveEditable: (rowKey, record) => boolean // Save edited row
656
+ getRowData: (rowKey) => T | undefined // Get row data
657
+ getTableData: () => T[] // Get all table data
658
+ setTableData: (data: T[]) => void // Set table data
659
+ }
312
660
  ```
313
661
 
314
- #### With Internationalization
662
+ **Example:**
315
663
 
316
664
  ```tsx
317
- import { ProTable, zh_CN } from '@gulibs/react-vtable'
665
+ const actionRef = useRef<ProTableAction<DataType>>(null)
666
+
667
+ // Reload data
668
+ actionRef.current?.reload()
669
+
670
+ // Reset filters
671
+ actionRef.current?.reset()
672
+
673
+ // Clear selection
674
+ actionRef.current?.clearSelected()
675
+ ```
676
+
677
+ ---
678
+
679
+ ## Advanced Examples
318
680
 
681
+ ### 1. Remote Data with Search and Pagination
682
+
683
+ ```tsx
319
684
  <ProTable
320
- columns={columns}
321
- dataSource={dataSource}
685
+ request={async (params) => {
686
+ // params includes: current, pageSize, keyword, ...filters, ...sorter
687
+ const response = await fetch('/api/users', {
688
+ method: 'POST',
689
+ body: JSON.stringify(params)
690
+ })
691
+ const data = await response.json()
692
+ return {
693
+ data: data.items,
694
+ total: data.total,
695
+ success: true
696
+ }
697
+ }}
698
+ columns={[
699
+ { title: 'Name', dataIndex: 'name', search: true },
700
+ { title: 'Email', dataIndex: 'email', search: true },
701
+ {
702
+ title: 'Status',
703
+ dataIndex: 'status',
704
+ valueType: 'select',
705
+ search: true,
706
+ valueEnum: {
707
+ active: { text: 'Active', status: 'success' },
708
+ inactive: { text: 'Inactive', status: 'default' },
709
+ },
710
+ },
711
+ ]}
712
+ search={{ filterType: 'query' }}
713
+ pagination={{
714
+ pageSize: 10,
715
+ showSizeChanger: true,
716
+ showQuickJumper: true,
717
+ }}
322
718
  rowKey="id"
323
- locale={zh_CN}
324
719
  />
325
720
  ```
326
721
 
327
- #### With Fixed Columns
328
-
329
- Fixed columns are implemented using TanStack Table's column pinning feature. The library automatically calculates positions and applies subtle shadow effects to indicate fixed column boundaries.
722
+ ### 2. Fixed Columns with Horizontal Scroll
330
723
 
331
724
  ```tsx
332
725
  <ProTable
@@ -334,1268 +727,574 @@ Fixed columns are implemented using TanStack Table's column pinning feature. The
334
727
  {
335
728
  title: 'ID',
336
729
  dataIndex: 'id',
337
- key: 'id',
338
730
  width: 80,
339
- fixed: 'left' // Fixed to the left
731
+ fixed: 'left', // Pin to left
340
732
  },
341
733
  {
342
734
  title: 'Name',
343
735
  dataIndex: 'name',
344
- key: 'name',
345
- width: 120
736
+ width: 150,
737
+ fixed: 'left',
346
738
  },
739
+ { title: 'Email', dataIndex: 'email', width: 200 },
740
+ { title: 'Phone', dataIndex: 'phone', width: 150 },
741
+ { title: 'Address', dataIndex: 'address', width: 300 },
347
742
  {
348
743
  title: 'Actions',
349
- dataIndex: 'actions',
350
744
  key: 'actions',
351
745
  width: 100,
352
- fixed: 'right' // Fixed to the right
353
- }
746
+ fixed: 'right', // Pin to right
747
+ render: (_, record) => (
748
+ <Button size="sm">Edit</Button>
749
+ ),
750
+ },
354
751
  ]}
355
- dataSource={dataSource}
752
+ dataSource={data}
753
+ scroll={{ x: 1200 }} // Enable horizontal scroll
754
+ tableLayout="fixed" // Required for fixed columns
356
755
  rowKey="id"
357
- scroll={{ x: 1000 }} // Enable horizontal scrolling
358
- tableLayout="fixed" // Recommended for fixed columns
359
756
  />
360
757
  ```
361
758
 
362
- **Important Notes:**
363
- - Fixed columns require a `width` property to calculate positions correctly
364
- - The library automatically handles position calculations using TanStack Table's `getStart()` and `getAfter()` methods
365
- - Subtle shadow effects are automatically applied to the last left-pinned column and first right-pinned column
366
- - No additional CSS is required - all styles are applied inline
367
-
368
- #### With Tree Table (Expandable Rows)
369
-
370
- Tree tables support hierarchical data structures with expandable/collapsible rows. The data structure should include a `children` property (or custom field name) containing nested rows.
759
+ ### 3. Tree Table with Expandable Rows
371
760
 
372
761
  ```tsx
373
762
  <ProTable
374
- columns={columns}
763
+ columns={[
764
+ { title: 'Name', dataIndex: 'name', width: 200 },
765
+ { title: 'Size', dataIndex: 'size' },
766
+ { title: 'Type', dataIndex: 'type' },
767
+ ]}
375
768
  dataSource={[
376
769
  {
377
770
  id: 1,
378
- name: 'Parent Node',
771
+ name: 'Folder 1',
772
+ type: 'folder',
379
773
  children: [
380
- { id: 11, name: 'Child Node 1' },
381
- { id: 12, name: 'Child Node 2' }
382
- ]
383
- }
384
- ]}
385
- rowKey="id"
386
- expandable={{
387
- childrenColumnName: 'children', // Default: 'children'
388
- defaultExpandAllRows: false, // Expand all rows by default
389
- defaultExpandedRowKeys: [1], // Initially expanded rows
390
- accordion: false, // Accordion mode (only one row expanded at a time)
391
- showExpandColumn: true, // Show expand icon column
392
- expandColumnWidth: 50, // Width of expand column
393
- onExpand: (expanded, record) => {
394
- console.log('Expand:', expanded, record)
774
+ { id: 11, name: 'File 1.1', type: 'file', size: '1.2 MB' },
775
+ { id: 12, name: 'File 1.2', type: 'file', size: '2.5 MB' },
776
+ ],
395
777
  },
396
- onExpandedRowsChange: (expandedRowKeys) => {
397
- console.log('Expanded keys:', expandedRowKeys)
778
+ {
779
+ id: 2,
780
+ name: 'Folder 2',
781
+ type: 'folder',
782
+ children: [
783
+ { id: 21, name: 'File 2.1', type: 'file', size: '3.1 MB' },
784
+ ],
398
785
  },
399
- expandIcon: ({ expanded, onExpand, record }) => (
400
- <Button onClick={onExpand}>
401
- {expanded ? '▼' : '▶'}
402
- </Button>
403
- )
786
+ ]}
787
+ expandable={{
788
+ defaultExpandAllRows: false,
789
+ accordion: false, // Allow multiple rows expanded
404
790
  }}
791
+ rowKey="id"
405
792
  />
406
793
  ```
407
794
 
408
- **Tree Table Features:**
409
- - **Controlled/Uncontrolled Mode**: Use `expandedRowKeys` for controlled mode, or `defaultExpandedRowKeys` for uncontrolled
410
- - **Default Expand All**: Set `defaultExpandAllRows: true` to expand all rows by default
411
- - **Accordion Mode**: Set `accordion: true` to allow only one row to be expanded at a time
412
- - **Custom Children Field**: Use `childrenColumnName` to specify a custom field name for children (default: `'children'`)
413
- - **Custom Expand Icon**: Provide a custom `expandIcon` function to customize the expand/collapse icon
414
- - **Expand Callbacks**: Use `onExpand` and `onExpandedRowsChange` to handle expand state changes
415
-
416
- #### With Editable Rows
795
+ ### 4. Inline Editing
417
796
 
418
797
  ```tsx
798
+ const [editableKeys, setEditableKeys] = useState<React.Key[]>([])
799
+
419
800
  <ProTable
420
- columns={columns}
421
- dataSource={dataSource}
422
- rowKey="id"
801
+ columns={[
802
+ {
803
+ title: 'Name',
804
+ dataIndex: 'name',
805
+ editable: true,
806
+ },
807
+ {
808
+ title: 'Age',
809
+ dataIndex: 'age',
810
+ valueType: 'number',
811
+ editable: true,
812
+ },
813
+ {
814
+ title: 'Email',
815
+ dataIndex: 'email',
816
+ editable: true,
817
+ },
818
+ ]}
819
+ dataSource={data}
423
820
  editable={{
424
821
  type: 'multiple',
822
+ editableKeys,
823
+ onChange: setEditableKeys,
425
824
  onSave: async (key, record, originRow) => {
426
- console.log('Save:', { key, record, originRow })
427
- // Handle save logic
428
- }
825
+ // Save to backend
826
+ await updateUser(key, record)
827
+ message.success('Saved successfully')
828
+ },
429
829
  }}
830
+ rowKey="id"
430
831
  />
431
832
  ```
432
833
 
433
- #### With Row Selection and Batch Actions
834
+ ### 5. Drag & Drop Reordering
434
835
 
435
836
  ```tsx
436
837
  <ProTable
437
838
  columns={columns}
438
- dataSource={dataSource}
439
- rowKey="id"
440
- rowSelection={{
441
- type: 'checkbox',
442
- onChange: (selectedRowKeys, selectedRows) => {
443
- console.log('Selected:', selectedRowKeys, selectedRows)
839
+ dataSource={data}
840
+ draggable={{
841
+ enabled: true,
842
+ handle: true, // Show drag handle
843
+ onDragEnd: (result) => {
844
+ const { items, oldIndex, newIndex } = result
845
+ console.log('New order:', items)
846
+ // Update backend order
847
+ updateOrder(items.map(item => item.id))
444
848
  },
445
- batchActions: {
446
- onDelete: (rows) => {
447
- console.log('Delete:', rows)
448
- },
449
- onExport: (rows) => {
450
- console.log('Export:', rows)
451
- }
452
- }
453
849
  }}
850
+ rowKey="id"
454
851
  />
455
852
  ```
456
853
 
457
- **Cross-Page Selection Support:**
458
-
459
- When using `request` or `pagination.onChange` for remote data fetching, the table automatically maintains selection state across pages. This means:
460
-
461
- - Selections made on one page are preserved when navigating to other pages
462
- - The batch actions bar shows the **total count** of selected rows across all pages
463
- - When you return to a previously visited page, your selections are automatically restored
464
- - The `onChange` callback receives all selected row keys across all pages
854
+ ### 6. Custom Cell Rendering
465
855
 
466
856
  ```tsx
467
857
  <ProTable
468
- columns={columns}
469
- rowKey="id"
470
- request={async (params) => {
471
- const res = await fetchUsers(params)
472
- return { data: res.items, total: res.total, success: true }
473
- }}
474
- rowSelection={{
475
- type: 'checkbox',
476
- onChange: (selectedRowKeys, selectedRows) => {
477
- // selectedRowKeys contains ALL selected keys across all pages
478
- // selectedRows contains rows from the current page only
479
- console.log('Total selected across all pages:', selectedRowKeys.length)
858
+ columns={[
859
+ {
860
+ title: 'Avatar',
861
+ dataIndex: 'avatar',
862
+ valueType: 'avatar',
863
+ render: (_, record) => (
864
+ <Avatar>
865
+ <AvatarImage src={record.avatar} />
866
+ <AvatarFallback>{record.name[0]}</AvatarFallback>
867
+ </Avatar>
868
+ ),
480
869
  },
481
- batchActions: {
482
- onDelete: (rows) => {
483
- // This will receive rows from current page only
484
- // Use selectedRowKeys from onChange for full selection
485
- console.log('Delete current page rows:', rows)
486
- }
487
- }
488
- }}
489
- pagination={{
490
- pageSize: 10,
491
- showSizeChanger: true,
492
- onChange: (current, pageSize) => {
493
- // Selection state is automatically preserved when page changes
494
- }
495
- }}
870
+ {
871
+ title: 'Status',
872
+ dataIndex: 'status',
873
+ valueType: 'status',
874
+ valueEnum: {
875
+ active: { text: 'Active', status: 'success' },
876
+ inactive: { text: 'Inactive', status: 'default' },
877
+ pending: { text: 'Pending', status: 'warning' },
878
+ },
879
+ render: (_, record) => (
880
+ <Badge variant={record.status === 'active' ? 'success' : 'secondary'}>
881
+ {record.status}
882
+ </Badge>
883
+ ),
884
+ },
885
+ {
886
+ title: 'Progress',
887
+ dataIndex: 'progress',
888
+ valueType: 'progress',
889
+ render: (_, record) => (
890
+ <div className="flex items-center gap-2">
891
+ <Progress value={record.progress} className="w-20" />
892
+ <span className="text-xs">{record.progress}%</span>
893
+ </div>
894
+ ),
895
+ },
896
+ ]}
897
+ dataSource={data}
898
+ rowKey="id"
496
899
  />
497
900
  ```
498
901
 
499
- **Important Notes:**
500
- - Cross-page selection works automatically when using `request` or `pagination.onChange`
501
- - With `dataSource` (local data), selection works normally but doesn't need cross-page support
502
- - The batch actions bar always shows the total count of selected rows across all pages
503
- - Use `rowSelection.onChange` to get all selected keys if you need to perform operations on all selected rows
504
-
505
- **Automatic Selection Clearing:**
506
-
507
- After certain batch operations, the selection state is automatically cleared:
508
-
509
- - **Delete operation**: Automatically clears selection after deletion (since the data is removed)
510
- - **Archive operation**: Automatically clears selection after archiving
511
- - **Custom actions**: You can control whether to clear selection using the `clearSelection` option
902
+ ### 7. Multi-line Ellipsis with Tooltip
512
903
 
513
904
  ```tsx
514
905
  <ProTable
515
- rowSelection={{
516
- type: 'checkbox',
517
- batchActions: {
518
- onDelete: (rows) => {
519
- // Delete operation - selection will be automatically cleared
520
- console.log('Delete:', rows)
906
+ columns={[
907
+ {
908
+ title: 'Description',
909
+ dataIndex: 'description',
910
+ width: 300,
911
+ ellipsis: {
912
+ multiline: true,
913
+ rows: 3, // Show 3 lines max
521
914
  },
522
- onArchive: (rows) => {
523
- // Archive operation - selection will be automatically cleared
524
- console.log('Archive:', rows)
915
+ tooltip: {
916
+ content: (value) => value,
917
+ side: 'top',
525
918
  },
526
- // Hide the clear button if needed
527
- hideClearButton: false,
528
- customActions: [
529
- {
530
- key: 'approve',
531
- label: 'Approve',
532
- onClick: (rows) => {
533
- console.log('Approve:', rows)
534
- },
535
- // Control whether to clear selection after this action
536
- clearSelection: true, // Set to true to clear selection after this action
537
- },
538
- {
539
- key: 'export',
540
- label: 'Export',
541
- onClick: (rows) => {
542
- console.log('Export:', rows)
543
- },
544
- clearSelection: false, // Keep selection after export (default)
545
- }
546
- ]
547
- }
548
- }}
919
+ },
920
+ {
921
+ title: 'Long Title Column with Header Tooltip',
922
+ dataIndex: 'data',
923
+ width: 150,
924
+ headerEllipsis: true, // Header ellipsis
925
+ headerTooltip: {
926
+ content: 'This is a very long header title that needs tooltip',
927
+ side: 'bottom',
928
+ },
929
+ ellipsis: true, // Cell ellipsis
930
+ tooltip: true, // Cell tooltip
931
+ },
932
+ ]}
933
+ dataSource={data}
934
+ rowKey="id"
549
935
  />
550
936
  ```
551
937
 
552
- #### With Text Ellipsis and Tooltip
938
+ ### 8. Custom Search Form
553
939
 
554
940
  ```tsx
555
941
  <ProTable
556
942
  columns={[
557
943
  {
558
- title: 'Description',
559
- dataIndex: 'description',
560
- key: 'description',
561
- width: 200,
562
- // Single-line ellipsis with tooltip
563
- ellipsis: true,
564
- tooltip: true
565
- },
566
- {
567
- title: 'Content',
568
- dataIndex: 'content',
569
- key: 'content',
570
- width: 250,
571
- // Multi-line ellipsis (2 rows) with tooltip
572
- ellipsis: {
573
- multiline: true,
574
- rows: 2
575
- },
576
- tooltip: true
944
+ title: 'Name',
945
+ dataIndex: 'name',
946
+ search: true,
577
947
  },
578
948
  {
579
- title: 'Custom Tooltip',
580
- dataIndex: 'name',
581
- key: 'name',
582
- width: 150,
583
- ellipsis: true,
584
- tooltip: {
585
- content: (value, record) => `Full: ${record.fullName || value}`,
586
- side: 'top',
587
- delayDuration: 300
588
- }
949
+ title: 'Date Range',
950
+ dataIndex: 'dateRange',
951
+ valueType: 'dateRange',
952
+ search: true,
953
+ renderFormItem: (value, onChange) => (
954
+ <DateRangePicker
955
+ value={value}
956
+ onChange={onChange}
957
+ />
958
+ ),
589
959
  },
590
960
  {
591
- title: 'Header with Ellipsis',
592
- dataIndex: 'email',
593
- key: 'email',
594
- width: 180,
595
- // Header-specific ellipsis and tooltip
596
- headerEllipsis: true,
597
- headerTooltip: {
598
- content: 'This is the full header title',
599
- side: 'bottom'
961
+ title: 'Custom Filter',
962
+ dataIndex: 'customField',
963
+ search: {
964
+ transform: (value) => {
965
+ // Transform before sending to backend
966
+ return { customFieldQuery: value.toUpperCase() }
967
+ },
600
968
  },
601
- // Cell ellipsis and tooltip
602
- ellipsis: true,
603
- tooltip: true
604
- }
969
+ renderFormItem: (value, onChange) => (
970
+ <CustomFilterComponent
971
+ value={value}
972
+ onChange={onChange}
973
+ />
974
+ ),
975
+ },
605
976
  ]}
606
- dataSource={dataSource}
977
+ request={fetchData}
978
+ search={{ filterType: 'query' }}
607
979
  rowKey="id"
608
980
  />
609
981
  ```
610
982
 
611
- ### API Reference
983
+ ---
612
984
 
613
- #### ProTable Props
985
+ ## Best Practices
614
986
 
615
- | Prop | Type | Default | Description |
616
- |------|------|---------|-------------|
617
- | `columns` | `ProColumn[]` | `[]` | Table column definitions |
618
- | `dataSource` | `T[]` | `[]` | Table data source |
619
- | `rowKey` | `string \| (record: T) => string` | `'id'` | Unique key for each row |
620
- | `loading` | `boolean` | `false` | Loading state |
621
- | `pagination` | `ProTablePagination \| false` | `false` | Pagination configuration |
622
- | `search` | `ProTableSearch \| false` | `false` | Search form configuration |
623
- | `rowSelection` | `ProTableRowSelection` | `undefined` | Row selection configuration |
624
- | `editable` | `ProTableEditable` | `undefined` | Editable rows configuration |
625
- | `draggable` | `ProTableDraggable` | `undefined` | Drag and drop configuration |
626
- | `expandable` | `ProTableExpandable \| false` | `undefined` | Tree table expandable configuration |
627
- | `scroll` | `{ x?: number \| true; y?: number \| string }` | `undefined` | Scroll configuration |
628
- | `tableLayout` | `'auto' \| 'fixed'` | `'auto'` | Table layout mode |
629
- | `locale` | `ProTableLocale` | `en_US` | Internationalization locale |
630
- | `size` | `'small' \| 'middle' \| 'large'` | `'middle'` | Table size |
631
- | `bordered` | `boolean` | `false` | Show table borders |
987
+ ### 1. Performance Optimization
632
988
 
633
- #### Column Configuration
989
+ #### Use `rowKey` Correctly
990
+ Always provide a stable, unique `rowKey`:
634
991
 
635
- | Prop | Type | Description |
636
- |------|------|-------------|
637
- | `title` | `string` | Column header text |
638
- | `dataIndex` | `string` | Data field name |
639
- | `key` | `string` | Unique column key |
640
- | `width` | `number` | Column width in pixels |
641
- | `fixed` | `'left' \| 'right'` | Fixed column position |
642
- | `sorter` | `boolean` | Enable sorting |
643
- | `editable` | `boolean` | Enable inline editing |
644
- | `copyable` | `boolean` | Enable copy to clipboard |
645
- | `ellipsis` | `boolean \| ProColumnEllipsis` | Enable text ellipsis (single-line or multi-line) |
646
- | `tooltip` | `boolean \| ProColumnTooltip` | Enable tooltip on hover |
647
- | `headerEllipsis` | `boolean \| ProColumnEllipsis` | Enable ellipsis for header (defaults to `ellipsis`) |
648
- | `headerTooltip` | `boolean \| ProColumnTooltip` | Enable tooltip for header (defaults to `tooltip`) |
649
- | `search` | `boolean` | Include in search form |
650
- | `valueType` | `ProFieldValueType` | Value type for rendering |
651
- | `valueEnum` | `Record<string, { text: string; status?: string }>` | Enum values for select |
652
- | `filters` | `Array<{ text: string; value: any }>` | Filter options |
653
- | `render` | `(value: any, record: T, index: number) => ReactNode` | Custom render function |
992
+ ```tsx
993
+ // Good: Use unique ID
994
+ <ProTable rowKey="id" />
654
995
 
655
- #### Render Function and valueType Priority
996
+ // Good: Use function for complex keys
997
+ <ProTable rowKey={(record) => `${record.type}-${record.id}`} />
656
998
 
657
- When both `render` and `valueType` are configured, the `render` function takes **priority** over `valueType` for table cell rendering. This allows you to:
999
+ // Bad: Use index (causes re-render issues)
1000
+ <ProTable rowKey={(record, index) => index} />
1001
+ ```
658
1002
 
659
- - Use `valueType` for **search form** rendering (e.g., `valueType: "select"` generates a select dropdown in the search form)
660
- - Use custom `render` function for **table cell** rendering (e.g., custom badges, icons, or complex layouts)
1003
+ #### Memoize Large Data
1004
+ For large datasets, memoize your data:
661
1005
 
662
- **Rendering Priority:**
663
- 1. **`render` function** (if provided) - Used for table cell rendering
664
- 2. **`valueType`** (if provided) - Used for table cell rendering when `render` is not provided
665
- 3. **Default rendering** - Raw value display
1006
+ ```tsx
1007
+ const data = useMemo(() => generateLargeData(), [])
666
1008
 
667
- **Example:**
1009
+ <ProTable dataSource={data} />
1010
+ ```
1011
+
1012
+ #### Optimize Column Rendering
1013
+ Use `useMemo` for column definitions with complex render functions:
668
1014
 
669
1015
  ```tsx
670
- const columns = [
1016
+ const columns = useMemo(() => [
671
1017
  {
672
- title: "Level",
673
- dataIndex: "level",
674
- key: "level",
675
- valueType: "select", // Used for search form
676
- search: true,
677
- valueEnum: {
678
- "1": { text: "Level 1", status: "default" },
679
- "2": { text: "Level 2", status: "default" },
680
- "3": { text: "Level 3", status: "default" },
681
- },
682
- // Custom render function takes priority for table cell rendering
683
- render: (_value, record) => {
684
- const levelNum = parseInt(record.level);
685
- const colors = [
686
- "bg-blue-100 text-blue-800",
687
- "bg-green-100 text-green-800",
688
- "bg-yellow-100 text-yellow-800",
689
- ];
690
- return (
691
- <Badge className={colors[levelNum - 1]}>
692
- L{record.level}
693
- </Badge>
694
- );
695
- },
1018
+ title: 'Name',
1019
+ render: (_, record) => <ComplexComponent record={record} />,
696
1020
  },
697
- ];
1021
+ ], [dependencies])
698
1022
  ```
699
1023
 
700
- In this example:
701
- - **Search form**: Shows a select dropdown (because of `valueType: "select"`)
702
- - **Table cell**: Shows a custom Badge component (because of `render` function)
703
-
704
- **Note**: The `select` valueType also supports `valueEnum` for rendering when no `render` function is provided.
1024
+ ### 2. Request Best Practices
705
1025
 
706
- #### Ellipsis Configuration
1026
+ #### Handle Errors Gracefully
1027
+ ```tsx
1028
+ <ProTable
1029
+ request={async (params) => {
1030
+ try {
1031
+ const res = await fetchData(params)
1032
+ return { data: res.items, total: res.total, success: true }
1033
+ } catch (error) {
1034
+ message.error('Failed to load data')
1035
+ return { data: [], total: 0, success: false }
1036
+ }
1037
+ }}
1038
+ onRequestError={(error) => {
1039
+ console.error('Request error:', error)
1040
+ // Report to error tracking service
1041
+ }}
1042
+ />
1043
+ ```
707
1044
 
708
- The `ellipsis` and `headerEllipsis` properties support the following configurations:
1045
+ #### Debounce Search Requests
1046
+ Use `params` prop to trigger re-fetch:
709
1047
 
710
1048
  ```tsx
711
- // Single-line ellipsis
712
- ellipsis: true
1049
+ const [searchParams, setSearchParams] = useState({})
1050
+ const debouncedSearch = useMemo(
1051
+ () => debounce((value) => setSearchParams({ keyword: value }), 500),
1052
+ []
1053
+ )
713
1054
 
714
- // Multi-line ellipsis
715
- ellipsis: {
716
- multiline: true,
717
- rows: 2 // Number of lines (1-5)
718
- }
1055
+ <ProTable
1056
+ request={fetchData}
1057
+ params={searchParams}
1058
+ toolbar={{
1059
+ search: {
1060
+ onSearch: debouncedSearch,
1061
+ },
1062
+ }}
1063
+ />
719
1064
  ```
720
1065
 
721
- #### Tooltip Configuration
1066
+ ### 3. State Management
722
1067
 
723
- The `tooltip` and `headerTooltip` properties support the following configurations:
1068
+ #### Controlled Components
1069
+ For complex state management, use controlled mode:
724
1070
 
725
1071
  ```tsx
726
- // Simple tooltip (shows cell value)
727
- tooltip: true
1072
+ const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([])
1073
+ const [pagination, setPagination] = useState({ current: 1, pageSize: 10 })
728
1074
 
729
- // Custom tooltip
730
- tooltip: {
731
- content: 'Custom tooltip text',
732
- side: 'top' | 'right' | 'bottom' | 'left',
733
- align: 'start' | 'center' | 'end',
734
- delayDuration: 300,
735
- disabled: false
1075
+ <ProTable
1076
+ rowSelection={{
1077
+ selectedRowKeys,
1078
+ onChange: setSelectedRowKeys,
1079
+ }}
1080
+ pagination={{
1081
+ ...pagination,
1082
+ onChange: (current, pageSize) => {
1083
+ setPagination({ current, pageSize })
1084
+ },
1085
+ }}
1086
+ />
1087
+ ```
1088
+
1089
+ #### Use Action Ref
1090
+ Access table methods via `actionRef`:
1091
+
1092
+ ```tsx
1093
+ const actionRef = useRef<ProTableAction<DataType>>(null)
1094
+
1095
+ const handleRefresh = () => {
1096
+ actionRef.current?.reload()
736
1097
  }
737
1098
 
738
- // Function-based tooltip
739
- tooltip: {
740
- content: (value, record, index) => `Full: ${value}\nID: ${record.id}`,
741
- side: 'top'
1099
+ const handleReset = () => {
1100
+ actionRef.current?.reset()
1101
+ actionRef.current?.clearSelected()
742
1102
  }
1103
+
1104
+ <ProTable actionRef={actionRef} />
743
1105
  ```
744
1106
 
745
- **Note**: When `ellipsis` is enabled and `tooltip` is `true`, the tooltip will automatically show the full original value (not the rendered value) when text is truncated.
1107
+ ### 4. Accessibility
746
1108
 
747
- #### Expandable (Tree Table) Configuration
1109
+ - Always provide meaningful `title` for columns
1110
+ - Use `aria-label` for action buttons
1111
+ - Ensure color contrast for status badges
1112
+ - Test with keyboard navigation
748
1113
 
749
- The `expandable` property supports the following configurations:
1114
+ ### 5. Internationalization
750
1115
 
751
1116
  ```tsx
752
- expandable: {
753
- // Custom children field name (default: 'children')
754
- childrenColumnName?: string
755
-
756
- // Default expanded row keys (uncontrolled mode)
757
- defaultExpandedRowKeys?: React.Key[]
1117
+ import { ProTable, zh_CN, en_US } from '@gulibs/react-vtable'
1118
+ import { useState } from 'react'
758
1119
 
759
- // Currently expanded row keys (controlled mode)
760
- expandedRowKeys?: React.Key[]
761
-
762
- // Expand/collapse callback
763
- onExpand?: (expanded: boolean, record: T) => void
1120
+ function App() {
1121
+ const [locale, setLocale] = useState(en_US)
764
1122
 
765
- // Expanded rows change callback
766
- onExpandedRowsChange?: (expandedRowKeys: React.Key[]) => void
1123
+ return (
1124
+ <>
1125
+ <Button onClick={() => setLocale(zh_CN)}>中文</Button>
1126
+ <Button onClick={() => setLocale(en_US)}>English</Button>
1127
+
1128
+ <ProTable
1129
+ locale={locale}
1130
+ columns={columns}
1131
+ dataSource={data}
1132
+ />
1133
+ </>
1134
+ )
1135
+ }
1136
+ ```
767
1137
 
768
- // Custom expand icon
769
- expandIcon?: (props: {
770
- expanded: boolean
771
- onExpand: () => void
772
- record: T
773
- }) => ReactNode
1138
+ ### 6. Type Safety
774
1139
 
775
- // Show expand column (default: true)
776
- showExpandColumn?: boolean
1140
+ Always provide TypeScript types for your data:
777
1141
 
778
- // Expand column width (default: 50)
779
- expandColumnWidth?: number
1142
+ ```tsx
1143
+ interface User {
1144
+ id: string
1145
+ name: string
1146
+ email: string
1147
+ age: number
1148
+ status: 'active' | 'inactive'
1149
+ }
780
1150
 
781
- // Expand all rows by default (default: false)
782
- defaultExpandAllRows?: boolean
1151
+ const columns: ProColumn<User>[] = [
1152
+ {
1153
+ title: 'Name',
1154
+ dataIndex: 'name',
1155
+ // TypeScript will validate dataIndex, valueType, render function params, etc.
1156
+ },
1157
+ ]
783
1158
 
784
- // Accordion mode - only one row expanded at a time (default: false)
785
- accordion?: boolean
786
- }
1159
+ <ProTable<User>
1160
+ columns={columns}
1161
+ dataSource={users}
1162
+ rowKey="id"
1163
+ />
787
1164
  ```
788
1165
 
789
- ### Development
1166
+ ---
1167
+
1168
+ ## Development
790
1169
 
791
1170
  ```bash
792
1171
  # Install dependencies
793
1172
  pnpm install
794
1173
 
795
- # Build the library
796
- pnpm run build
797
-
798
1174
  # Start development server
799
1175
  pnpm run dev
800
- ```
801
1176
 
802
- ### License
1177
+ # Build library
1178
+ pnpm run build
803
1179
 
804
- MIT
1180
+ # Lint code
1181
+ pnpm run lint
1182
+ ```
805
1183
 
806
1184
  ---
807
1185
 
808
- ## Chinese
809
-
810
- 一个基于 shadcn/ui 组件和 TanStack Table 构建的强大 React 表格组件库。
811
-
812
- ### 前置要求
813
-
814
- 此库需要您的项目中安装并配置 **Tailwind CSS**。
815
-
816
- ### 安装
817
-
818
- ```bash
819
- # 安装库
820
- pnpm add @gulibs/react-vtable
1186
+ ## License
821
1187
 
822
- # 安装对等依赖
823
- pnpm add react react-dom tailwindcss
824
- ```
1188
+ MIT © [@gulibs](https://github.com/gulibs)
825
1189
 
826
- ### 配置
1190
+ ---
827
1191
 
828
- #### 配置 Tailwind CSS
1192
+ ## Chinese
829
1193
 
830
- ```javascript
831
- // tailwind.config.js
832
- export default {
833
- content: [
834
- './src/**/*.{js,ts,jsx,tsx}',
835
- './node_modules/@gulibs/react-vtable/dist/**/*.js'
836
- ],
837
- // 无需额外配置 - 样式会自动注入
838
- }
839
- ```
1194
+ ## 中文文档
840
1195
 
841
- > 💡 **注意**: 库会自动注入其 CSS 样式,因此无需手动导入 CSS。
1196
+ [查看完整中文文档 ↗](./README.zh-CN.md)
842
1197
 
843
- ### 使用方法
1198
+ ### 快速开始
844
1199
 
845
1200
  ```tsx
846
- import { ProTable } from '@gulibs/react-vtable'
847
- // 无需手动导入 CSS,样式已自动内联
1201
+ import { ProTable, zh_CN } from '@gulibs/react-vtable'
848
1202
 
849
1203
  function App() {
850
1204
  const columns = [
851
- {
852
- title: '姓名',
853
- dataIndex: 'name',
854
- key: 'name',
855
- },
856
- {
857
- title: '年龄',
858
- dataIndex: 'age',
859
- key: 'age',
860
- },
1205
+ { title: '姓名', dataIndex: 'name', key: 'name' },
1206
+ { title: '年龄', dataIndex: 'age', key: 'age' },
861
1207
  ]
862
1208
 
863
1209
  const dataSource = [
864
- { name: '张三', age: 30 },
865
- { name: '李四', age: 25 },
1210
+ { id: 1, name: '张三', age: 30 },
1211
+ { id: 2, name: '李四', age: 25 },
866
1212
  ]
867
1213
 
868
1214
  return (
869
1215
  <ProTable
870
1216
  columns={columns}
871
1217
  dataSource={dataSource}
872
- rowKey="name"
1218
+ rowKey="id"
1219
+ locale={zh_CN}
873
1220
  />
874
1221
  )
875
1222
  }
876
1223
  ```
877
1224
 
878
- ### 功能特性
879
-
880
- - **强大表格**: 基于 TanStack Table 构建,提供最大灵活性
881
- - **shadcn/ui 组件**: 美观、可访问的 UI 组件
882
- - **拖拽排序**: 使用 @dnd-kit 实现行和列重排序
883
- - **搜索筛选**: 高级搜索和筛选功能,支持本地和远程数据模式的默认行为
884
- - **分页**: 内置分页,支持自定义选项
885
- - **列设置**: 显示 / 隐藏列,拖拽重排序
886
- - **行选择**: 单选和多选行,支持批量操作,**跨页选中支持**(使用 `request` 或 `pagination.onChange` 时,选中状态会在切换页面时保留)
887
- - **可编辑行**: 内联编辑功能,支持多种值类型
888
- - **固定列**: 左右固定列,自动计算位置并应用柔和阴影效果(基于 TanStack Table column pinning)
889
- - **树形表格**: 层次数据展示,支持展开 / 收起行、自定义展开图标和手风琴模式
890
- - **国际化**: 内置 i18n 支持,提供中英文语言包
891
- - **横向滚动**: 可配置表格宽度,支持滚动
892
- - **复制功能**: 一键复制单元格内容
893
- - **文本省略**: 单行和多行文本截断,支持省略号
894
- - **提示框**: 单元格和表头悬停提示,支持自定义内容
895
- - **响应式**: 移动端友好设计
896
- - **TypeScript**: 完整的 TypeScript 支持
897
-
898
- ### 示例
899
-
900
- #### 基础表格
1225
+ ### 主要特性
1226
+
1227
+ - 强大的表格:基于 TanStack Table v8
1228
+ - 🎨 精美组件:shadcn/ui 设计
1229
+ - 🔍 搜索筛选:自动处理本地/远程数据
1230
+ - 📄 分页支持:客户端和服务端分页
1231
+ - 行选择:支持跨页选中
1232
+ - 🎯 批量操作:灵活的批量操作配置
1233
+ - ✏️ 内联编辑:支持验证
1234
+ - 📌 固定列:左右固定,自动阴影
1235
+ - 🌳 树形表格:层次数据展示
1236
+ - 🔄 拖拽排序:行列拖拽重排
1237
+ - 📱 响应式设计:移动端友好
1238
+ - 🌐 国际化:内置中英文
1239
+ - 🎭 丰富类型:20+ 值类型
1240
+ - 💬 提示框:单元格和表头提示
1241
+ - 📏 文本省略:单行和多行截断
1242
+ - 🔧 TypeScript:完整类型支持
1243
+
1244
+ ### 批量操作示例
901
1245
 
902
1246
  ```tsx
1247
+ import { Trash2, Download, FileText } from 'lucide-react'
1248
+
903
1249
  <ProTable
904
- columns={columns}
905
- dataSource={dataSource}
1250
+ rowSelection={{
1251
+ type: 'checkbox',
1252
+ batchActions: {
1253
+ maxVisibleActions: 3,
1254
+ actions: [
1255
+ {
1256
+ key: 'delete',
1257
+ label: '删除',
1258
+ icon: <Trash2 className="h-3 w-3 mr-1" />,
1259
+ onClick: (rows) => console.log('删除:', rows),
1260
+ variant: 'destructive',
1261
+ clearSelection: true,
1262
+ },
1263
+ {
1264
+ key: 'export',
1265
+ label: '导出',
1266
+ icon: <Download className="h-3 w-3 mr-1" />,
1267
+ onClick: (rows) => console.log('导出:', rows),
1268
+ variant: 'outline',
1269
+ },
1270
+ {
1271
+ key: 'exportExcel',
1272
+ label: '导出 Excel',
1273
+ icon: <FileText className="h-3 w-3 mr-1" />,
1274
+ onClick: (rows) => console.log('导出 Excel:', rows),
1275
+ variant: 'outline',
1276
+ isExportFormat: true,
1277
+ showInMore: true,
1278
+ },
1279
+ ],
1280
+ },
1281
+ }}
1282
+ dataSource={data}
906
1283
  rowKey="id"
1284
+ locale={zh_CN}
907
1285
  />
908
1286
  ```
909
1287
 
910
- #### 带搜索
911
-
912
- 表格提供了**默认搜索行为**,会自动工作:
913
-
914
- - **本地数据模式**(无 `request` 属性):搜索值会自动转换为 TanStack Table 的 `columnFilters`,表格使用 `getFilteredRowModel()` 实时过滤数据。
915
- - **远程数据模式**(有 `request` 属性):搜索会触发新的数据获取,搜索参数会传递给后端。
1288
+ ### 更多文档
916
1289
 
917
- 默认过滤函数支持:
918
- - **文本类字段**:不区分大小写的字符串匹配(包含)
919
- - **枚举 / 筛选类字段**:精确匹配(例如 `valueEnum`、`filters`、`valueType: 'select' | 'status'`)
920
- - 基于数组的多选过滤(枚举 / 筛选类字段为精确匹配;文本类字段为包含匹配)
921
- - 自动处理空值 /null 值
1290
+ 完整的中文文档和 API 参考,请查看英文部分。所有功能和 API 在中英文版本中都是一致的。
922
1291
 
923
- ```tsx
924
- <ProTable
925
- columns={columns}
926
- dataSource={dataSource}
927
- rowKey="id"
928
- search={{
929
- filterType: 'query',
930
- searchText: '搜索',
931
- resetText: '重置'
932
- }}
933
- />
934
- ```
935
-
936
- **自定义搜索行为:**
1292
+ ---
937
1293
 
938
- 您可以提供自定义的 `onSearch` 和 `onReset` 回调来覆盖默认行为:
1294
+ ## Contributing
939
1295
 
940
- ```tsx
941
- <ProTable
942
- columns={columns}
943
- dataSource={dataSource}
944
- rowKey="id"
945
- search={{
946
- filterType: 'query',
947
- onSearch: (values) => {
948
- // 自定义搜索逻辑
949
- console.log('搜索值:', values)
950
- },
951
- onReset: () => {
952
- // 自定义重置逻辑
953
- console.log('重置搜索')
954
- }
955
- }}
956
- />
957
- ```
958
-
959
- #### 带分页
960
-
961
- ```tsx
962
- <ProTable
963
- columns={columns}
964
- dataSource={dataSource}
965
- rowKey="id"
966
- pagination={{
967
- pageSize: 10,
968
- showSizeChanger: true
969
- }}
970
- />
971
- ```
972
-
973
- #### 远程分页(配合 `request`)
974
-
975
- 在**远程数据模式**(传入 `request`)下,分页规则如下:
976
-
977
- - **`total` 决定页码数量**:推荐让 `request` 返回 `{ total }`,也可以 / 同时传入 `pagination.total`。
978
- - **不要把 `pagination.current/pageSize` 写死**(例如 `current: 1`),除非你就是想把表格锁死在那一页;如果需要受控分页,请把它们放到 state 里,并在 `pagination.onChange` 里更新。
979
-
980
- **非受控(推荐:不需要外部同步分页状态时)**:
981
-
982
- ```tsx
983
- <ProTable
984
- columns={columns}
985
- rowKey="id"
986
- request={async (params) => {
987
- // params 包含:current, pageSize, ...filters, ...sorter
988
- const res = await fetchUsers(params)
989
- return { data: res.items, total: res.total, success: true }
990
- }}
991
- pagination={{
992
- pageSize: 10,
993
- showSizeChanger: true,
994
- showQuickJumper: true,
995
- }}
996
- />
997
- ```
998
-
999
- **受控(需要把分页状态同步到外部 state / URL 时)**:
1000
-
1001
- ```tsx
1002
- function Page() {
1003
- const [pagination, setPagination] = useState({
1004
- current: 1,
1005
- pageSize: 10,
1006
- total: 0,
1007
- })
1008
-
1009
- return (
1010
- <ProTable
1011
- columns={columns}
1012
- rowKey="id"
1013
- request={async (params) => {
1014
- const res = await fetchUsers(params)
1015
- setPagination((prev) => ({ ...prev, total: res.total }))
1016
- return { data: res.items, total: res.total, success: true }
1017
- }}
1018
- pagination={{
1019
- current: pagination.current,
1020
- pageSize: pagination.pageSize,
1021
- total: pagination.total,
1022
- showSizeChanger: true,
1023
- showQuickJumper: true,
1024
- onChange: (current, pageSize) => {
1025
- setPagination((prev) => ({ ...prev, current, pageSize }))
1026
- },
1027
- }}
1028
- />
1029
- )
1030
- }
1031
- ```
1032
-
1033
- #### 服务端分页(不传 `request`,外部自行请求)
1034
-
1035
- 如果你**不使用** `request`,但希望实现**服务端分页**(也就是 `dataSource` 只提供“当前页 items”),请设置
1036
- `pagination.mode: 'server'`,避免 TanStack Table 再次 slice 导致第 2 页为空 / 数据错乱。
1037
-
1038
- ```tsx
1039
- function Page() {
1040
- const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 })
1041
- const [data, setData] = useState<any[]>([])
1042
-
1043
- const fetchPage = async (current: number, pageSize: number) => {
1044
- const res = await fetchUsers({ current, pageSize })
1045
- setData(res.items)
1046
- setPagination((prev) => ({ ...prev, current, pageSize, total: res.total }))
1047
- }
1048
-
1049
- useEffect(() => {
1050
- void fetchPage(pagination.current, pagination.pageSize)
1051
- }, [])
1052
-
1053
- return (
1054
- <ProTable
1055
- columns={columns}
1056
- rowKey="id"
1057
- dataSource={data}
1058
- pagination={{
1059
- mode: "server",
1060
- current: pagination.current,
1061
- pageSize: pagination.pageSize,
1062
- total: pagination.total,
1063
- onChange: (current, pageSize) => void fetchPage(current, pageSize),
1064
- }}
1065
- />
1066
- )
1067
- }
1068
- ```
1069
-
1070
- #### 自定义分页渲染(`paginationRender`)
1071
-
1072
- 当你想把分页 UI 放到任意布局 / 卡片里,并且仍复用 ProTable 内置分页状态 / 逻辑时,使用 `paginationRender`:
1073
-
1074
- - `paginationRender` 会拿到 `pagination`、`onChange`、`defaultDom`
1075
- - 如果你是 **服务端分页模式**(`pagination.mode: 'server'`),务必同时提供 `pagination.onChange` 去请求数据
1076
-
1077
- ```tsx
1078
- <ProTable
1079
- columns={columns}
1080
- rowKey="id"
1081
- dataSource={data}
1082
- pagination={{
1083
- mode: "server",
1084
- current,
1085
- pageSize,
1086
- total,
1087
- onChange: (page, pageSize) => void fetchPage(page, pageSize),
1088
- }}
1089
- paginationRender={({ pagination, onChange, defaultDom }) => (
1090
- <div className="space-y-2">
1091
- <div className="flex items-center justify-between rounded border p-2">
1092
- <div className="text-sm text-muted-foreground">
1093
- 自定义分页:第 {pagination.current} 页 / 每页 {pagination.pageSize} 条 / 共 {pagination.total} 条
1094
- </div>
1095
- <div className="flex gap-2">
1096
- <button onClick={() => onChange(Math.max(1, pagination.current - 1), pagination.pageSize)}>
1097
- 上一页
1098
- </button>
1099
- <button onClick={() => onChange(pagination.current + 1, pagination.pageSize)}>
1100
- 下一页
1101
- </button>
1102
- </div>
1103
- </div>
1104
- {defaultDom}
1105
- </div>
1106
- )}
1107
- />
1108
- ```
1109
-
1110
- #### 带国际化
1111
-
1112
- ```tsx
1113
- import { ProTable, zh_CN } from '@gulibs/react-vtable'
1114
-
1115
- <ProTable
1116
- columns={columns}
1117
- dataSource={dataSource}
1118
- rowKey="id"
1119
- locale={zh_CN}
1120
- />
1121
- ```
1122
-
1123
- #### 带固定列
1124
-
1125
- 固定列功能基于 TanStack Table 的 column pinning 实现。库会自动计算位置并应用柔和的阴影效果来标识固定列边界。
1126
-
1127
- ```tsx
1128
- <ProTable
1129
- columns={[
1130
- {
1131
- title: 'ID',
1132
- dataIndex: 'id',
1133
- key: 'id',
1134
- width: 80,
1135
- fixed: 'left' // 固定在左侧
1136
- },
1137
- {
1138
- title: '姓名',
1139
- dataIndex: 'name',
1140
- key: 'name',
1141
- width: 120
1142
- },
1143
- {
1144
- title: '操作',
1145
- dataIndex: 'actions',
1146
- key: 'actions',
1147
- width: 100,
1148
- fixed: 'right' // 固定在右侧
1149
- }
1150
- ]}
1151
- dataSource={dataSource}
1152
- rowKey="id"
1153
- scroll={{ x: 1000 }} // 启用横向滚动
1154
- tableLayout="fixed" // 固定列推荐使用 fixed 布局
1155
- />
1156
- ```
1157
-
1158
- **重要提示:**
1159
- - 固定列需要设置 `width` 属性才能正确计算位置
1160
- - 库会自动使用 TanStack Table 的 `getStart()` 和 `getAfter()` 方法计算位置
1161
- - 最后一个左侧固定列和第一个右侧固定列会自动应用柔和的阴影效果
1162
- - 无需额外 CSS - 所有样式都通过内联样式应用
1163
-
1164
- #### 带树形表格(可展开行)
1165
-
1166
- 树形表格支持层次数据结构,可展开 / 收起行。数据结构应包含 `children` 属性(或自定义字段名)来存储嵌套行。
1167
-
1168
- ```tsx
1169
- <ProTable
1170
- columns={columns}
1171
- dataSource={[
1172
- {
1173
- id: 1,
1174
- name: '父节点',
1175
- children: [
1176
- { id: 11, name: '子节点 1' },
1177
- { id: 12, name: '子节点 2' }
1178
- ]
1179
- }
1180
- ]}
1181
- rowKey="id"
1182
- expandable={{
1183
- childrenColumnName: 'children', // 默认: 'children'
1184
- defaultExpandAllRows: false, // 默认展开所有行
1185
- defaultExpandedRowKeys: [1], // 初始展开的行
1186
- accordion: false, // 手风琴模式(同时只能展开一行)
1187
- showExpandColumn: true, // 显示展开图标列
1188
- expandColumnWidth: 50, // 展开列宽度
1189
- onExpand: (expanded, record) => {
1190
- console.log('展开:', expanded, record)
1191
- },
1192
- onExpandedRowsChange: (expandedRowKeys) => {
1193
- console.log('展开的行:', expandedRowKeys)
1194
- },
1195
- expandIcon: ({ expanded, onExpand, record }) => (
1196
- <Button onClick={onExpand}>
1197
- {expanded ? '▼' : '▶'}
1198
- </Button>
1199
- )
1200
- }}
1201
- />
1202
- ```
1203
-
1204
- **树形表格特性:**
1205
- - **受控 / 非受控模式**: 使用 `expandedRowKeys` 实现受控模式,或使用 `defaultExpandedRowKeys` 实现非受控模式
1206
- - **默认全部展开**: 设置 `defaultExpandAllRows: true` 来默认展开所有行
1207
- - **手风琴模式**: 设置 `accordion: true` 来同时只允许展开一行
1208
- - **自定义子字段**: 使用 `childrenColumnName` 指定自定义的子字段名(默认:`'children'`)
1209
- - **自定义展开图标**: 提供自定义 `expandIcon` 函数来定制展开 / 收起图标
1210
- - **展开回调**: 使用 `onExpand` 和 `onExpandedRowsChange` 来处理展开状态变化
1211
-
1212
- #### 带可编辑行
1213
-
1214
- ```tsx
1215
- <ProTable
1216
- columns={columns}
1217
- dataSource={dataSource}
1218
- rowKey="id"
1219
- editable={{
1220
- type: 'multiple',
1221
- onSave: async (key, record, originRow) => {
1222
- console.log('保存:', { key, record, originRow })
1223
- // 处理保存逻辑
1224
- }
1225
- }}
1226
- />
1227
- ```
1228
-
1229
- #### 带行选择和批量操作
1230
-
1231
- ```tsx
1232
- <ProTable
1233
- columns={columns}
1234
- dataSource={dataSource}
1235
- rowKey="id"
1236
- rowSelection={{
1237
- type: 'checkbox',
1238
- onChange: (selectedRowKeys, selectedRows) => {
1239
- console.log('已选择:', selectedRowKeys, selectedRows)
1240
- },
1241
- batchActions: {
1242
- onDelete: (rows) => {
1243
- console.log('删除:', rows)
1244
- },
1245
- onExport: (rows) => {
1246
- console.log('导出:', rows)
1247
- }
1248
- }
1249
- }}
1250
- />
1251
- ```
1252
-
1253
- **跨页选中支持:**
1254
-
1255
- 当使用 `request` 或 `pagination.onChange` 进行远程数据获取时,表格会自动维护跨页选中状态。这意味着:
1256
-
1257
- - 在某一页选择的行,在切换到其他页面时会保留
1258
- - 批量操作栏会显示**所有页面**的选中总数
1259
- - 当返回到之前访问过的页面时,之前的选中状态会自动恢复
1260
- - `onChange` 回调会接收到所有页面的选中行 keys
1261
-
1262
- ```tsx
1263
- <ProTable
1264
- columns={columns}
1265
- rowKey="id"
1266
- request={async (params) => {
1267
- const res = await fetchUsers(params)
1268
- return { data: res.items, total: res.total, success: true }
1269
- }}
1270
- rowSelection={{
1271
- type: 'checkbox',
1272
- onChange: (selectedRowKeys, selectedRows) => {
1273
- // selectedRowKeys 包含所有页面的选中 keys
1274
- // selectedRows 仅包含当前页的行数据
1275
- console.log('所有页面选中的总数:', selectedRowKeys.length)
1276
- },
1277
- batchActions: {
1278
- onDelete: (rows) => {
1279
- // 这里只会收到当前页的行数据
1280
- // 如需操作所有选中的行,请使用 onChange 中的 selectedRowKeys
1281
- console.log('删除当前页行:', rows)
1282
- }
1283
- }
1284
- }}
1285
- pagination={{
1286
- pageSize: 10,
1287
- showSizeChanger: true,
1288
- onChange: (current, pageSize) => {
1289
- // 切换页面时,选中状态会自动保留
1290
- }
1291
- }}
1292
- />
1293
- ```
1294
-
1295
- **重要提示:**
1296
- - 跨页选中在使用 `request` 或 `pagination.onChange` 时会自动生效
1297
- - 使用 `dataSource`(本地数据)时,选中功能正常工作,但不需要跨页支持
1298
- - 批量操作栏始终显示所有页面的选中总数
1299
- - 如需对所有选中的行进行操作,请使用 `rowSelection.onChange` 获取所有选中的 keys
1300
-
1301
- **自动清空选中状态:**
1302
-
1303
- 某些批量操作后,选中状态会自动清空:
1304
-
1305
- - **删除操作**:删除后自动清空选中状态(因为数据已被删除)
1306
- - **归档操作**:归档后自动清空选中状态
1307
- - **自定义操作**:可以通过 `clearSelection` 选项控制是否清空选中状态
1308
- - **隐藏清空按钮**:可以通过 `hideClearButton` 选项隐藏清空按钮
1309
-
1310
- ```tsx
1311
- <ProTable
1312
- rowSelection={{
1313
- type: 'checkbox',
1314
- batchActions: {
1315
- onDelete: (rows) => {
1316
- // 删除操作 - 选中状态会自动清空
1317
- console.log('删除:', rows)
1318
- },
1319
- onArchive: (rows) => {
1320
- // 归档操作 - 选中状态会自动清空
1321
- console.log('归档:', rows)
1322
- },
1323
- // 如需隐藏清空按钮
1324
- hideClearButton: false,
1325
- customActions: [
1326
- {
1327
- key: 'approve',
1328
- label: '批量审批',
1329
- onClick: (rows) => {
1330
- console.log('审批:', rows)
1331
- },
1332
- // 控制操作后是否清空选中状态
1333
- clearSelection: true, // 设置为 true 表示操作后清空选中状态
1334
- },
1335
- {
1336
- key: 'export',
1337
- label: '导出',
1338
- onClick: (rows) => {
1339
- console.log('导出:', rows)
1340
- },
1341
- clearSelection: false, // 导出后保留选中状态(默认值)
1342
- }
1343
- ]
1344
- }
1345
- }}
1346
- />
1347
- ```
1348
-
1349
- #### 带文本省略和提示框
1350
-
1351
- ```tsx
1352
- <ProTable
1353
- columns={[
1354
- {
1355
- title: '描述',
1356
- dataIndex: 'description',
1357
- key: 'description',
1358
- width: 200,
1359
- // 单行省略,带提示框
1360
- ellipsis: true,
1361
- tooltip: true
1362
- },
1363
- {
1364
- title: '内容',
1365
- dataIndex: 'content',
1366
- key: 'content',
1367
- width: 250,
1368
- // 多行省略(2行),带提示框
1369
- ellipsis: {
1370
- multiline: true,
1371
- rows: 2
1372
- },
1373
- tooltip: true
1374
- },
1375
- {
1376
- title: '自定义提示',
1377
- dataIndex: 'name',
1378
- key: 'name',
1379
- width: 150,
1380
- ellipsis: true,
1381
- tooltip: {
1382
- content: (value, record) => `完整内容: ${record.fullName || value}`,
1383
- side: 'top',
1384
- delayDuration: 300
1385
- }
1386
- },
1387
- {
1388
- title: '表头省略',
1389
- dataIndex: 'email',
1390
- key: 'email',
1391
- width: 180,
1392
- // 表头独立的省略和提示配置
1393
- headerEllipsis: true,
1394
- headerTooltip: {
1395
- content: '这是完整的表头标题',
1396
- side: 'bottom'
1397
- },
1398
- // 单元格省略和提示
1399
- ellipsis: true,
1400
- tooltip: true
1401
- }
1402
- ]}
1403
- dataSource={dataSource}
1404
- rowKey="id"
1405
- />
1406
- ```
1407
-
1408
- ### API 参考
1409
-
1410
- #### ProTable 属性
1411
-
1412
- | 属性 | 类型 | 默认值 | 描述 |
1413
- |------|------|--------|------|
1414
- | `columns` | `ProColumn[]` | `[]` | 表格列定义 |
1415
- | `dataSource` | `T[]` | `[]` | 表格数据源 |
1416
- | `rowKey` | `string \| (record: T) => string` | `'id'` | 每行的唯一键 |
1417
- | `loading` | `boolean` | `false` | 加载状态 |
1418
- | `pagination` | `ProTablePagination \| false` | `false` | 分页配置 |
1419
- | `search` | `ProTableSearch \| false` | `false` | 搜索表单配置 |
1420
- | `rowSelection` | `ProTableRowSelection` | `undefined` | 行选择配置 |
1421
- | `editable` | `ProTableEditable` | `undefined` | 可编辑行配置 |
1422
- | `draggable` | `ProTableDraggable` | `undefined` | 拖拽配置 |
1423
- | `expandable` | `ProTableExpandable \| false` | `undefined` | 树形表格展开配置 |
1424
- | `scroll` | `{ x?: number \| true; y?: number \| string }` | `undefined` | 滚动配置 |
1425
- | `tableLayout` | `'auto' \| 'fixed'` | `'auto'` | 表格布局模式 |
1426
- | `locale` | `ProTableLocale` | `en_US` | 国际化语言包 |
1427
- | `size` | `'small' \| 'middle' \| 'large'` | `'middle'` | 表格尺寸 |
1428
- | `bordered` | `boolean` | `false` | 显示表格边框 |
1429
-
1430
- #### 列配置
1431
-
1432
- | 属性 | 类型 | 描述 |
1433
- |------|------|------|
1434
- | `title` | `string` | 列头文本 |
1435
- | `dataIndex` | `string` | 数据字段名 |
1436
- | `key` | `string` | 唯一列键 |
1437
- | `width` | `number` | 列宽度(像素) |
1438
- | `fixed` | `'left' \| 'right'` | 固定列位置 |
1439
- | `sorter` | `boolean` | 启用排序 |
1440
- | `editable` | `boolean` | 启用内联编辑 |
1441
- | `copyable` | `boolean` | 启用复制到剪贴板 |
1442
- | `ellipsis` | `boolean \| ProColumnEllipsis` | 启用文本省略(单行或多行) |
1443
- | `tooltip` | `boolean \| ProColumnTooltip` | 启用悬停提示框 |
1444
- | `headerEllipsis` | `boolean \| ProColumnEllipsis` | 启用表头省略(默认使用 `ellipsis`) |
1445
- | `headerTooltip` | `boolean \| ProColumnTooltip` | 启用表头提示框(默认使用 `tooltip`) |
1446
- | `search` | `boolean` | 包含在搜索表单中 |
1447
- | `valueType` | `ProFieldValueType` | 值类型渲染 |
1448
- | `valueEnum` | `Record<string, { text: string; status?: string }>` | 枚举值选择 |
1449
- | `filters` | `Array<{ text: string; value: any }>` | 筛选选项 |
1450
- | `render` | `(value: any, record: T, index: number) => ReactNode` | 自定义渲染函数 |
1451
-
1452
- #### render 函数与 valueType 优先级
1453
-
1454
- 当同时配置 `render` 和 `valueType` 时,`render` 函数在表格单元格渲染中具有**优先权**。这允许您:
1455
-
1456
- - 使用 `valueType` 进行**搜索表单**渲染(例如,`valueType: "select"` 会在搜索表单中生成下拉选择框)
1457
- - 使用自定义 `render` 函数进行**表格单元格**渲染(例如,自定义徽章、图标或复杂布局)
1458
-
1459
- **渲染优先级:**
1460
- 1. **`render` 函数**(如果提供)- 用于表格单元格渲染
1461
- 2. **`valueType`**(如果提供)- 当没有 `render` 时用于表格单元格渲染
1462
- 3. **默认渲染** - 原始值显示
1463
-
1464
- **示例:**
1465
-
1466
- ```tsx
1467
- const columns = [
1468
- {
1469
- title: "等级",
1470
- dataIndex: "level",
1471
- key: "level",
1472
- valueType: "select", // 用于搜索表单
1473
- search: true,
1474
- valueEnum: {
1475
- "1": { text: "一级", status: "default" },
1476
- "2": { text: "二级", status: "default" },
1477
- "3": { text: "三级", status: "default" },
1478
- },
1479
- // 自定义渲染函数在表格单元格渲染中具有优先权
1480
- render: (_value, record) => {
1481
- const levelNum = parseInt(record.level);
1482
- const colors = [
1483
- "bg-blue-100 text-blue-800",
1484
- "bg-green-100 text-green-800",
1485
- "bg-yellow-100 text-yellow-800",
1486
- ];
1487
- return (
1488
- <Badge className={colors[levelNum - 1]}>
1489
- L{record.level}
1490
- </Badge>
1491
- );
1492
- },
1493
- },
1494
- ];
1495
- ```
1496
-
1497
- 在这个示例中:
1498
- - **搜索表单**:显示下拉选择框(因为 `valueType: "select"`)
1499
- - **表格单元格**:显示自定义 Badge 组件(因为 `render` 函数)
1500
-
1501
- **注意**:`select` 类型的 `valueType` 在没有 `render` 函数时也支持使用 `valueEnum` 进行渲染。
1502
-
1503
- #### 省略号配置
1504
-
1505
- `ellipsis` 和 `headerEllipsis` 属性支持以下配置:
1506
-
1507
- ```tsx
1508
- // 单行省略
1509
- ellipsis: true
1510
-
1511
- // 多行省略
1512
- ellipsis: {
1513
- multiline: true,
1514
- rows: 2 // 行数(1-5)
1515
- }
1516
- ```
1517
-
1518
- #### 提示框配置
1519
-
1520
- `tooltip` 和 `headerTooltip` 属性支持以下配置:
1521
-
1522
- ```tsx
1523
- // 简单提示框(显示单元格值)
1524
- tooltip: true
1525
-
1526
- // 自定义提示框
1527
- tooltip: {
1528
- content: '自定义提示文本',
1529
- side: 'top' | 'right' | 'bottom' | 'left',
1530
- align: 'start' | 'center' | 'end',
1531
- delayDuration: 300,
1532
- disabled: false
1533
- }
1534
-
1535
- // 函数式提示框
1536
- tooltip: {
1537
- content: (value, record, index) => `完整内容: ${value}\nID: ${record.id}`,
1538
- side: 'top'
1539
- }
1540
- ```
1541
-
1542
- **注意**: 当 `ellipsis` 启用且 `tooltip` 为 `true` 时,如果文本被截断,提示框会自动显示完整的原始值(而不是渲染后的值)。
1543
-
1544
- #### 展开配置(树形表格)
1545
-
1546
- `expandable` 属性支持以下配置:
1547
-
1548
- ```tsx
1549
- expandable: {
1550
- // 自定义子字段名(默认: 'children')
1551
- childrenColumnName?: string
1552
-
1553
- // 默认展开的行 key 数组(非受控模式)
1554
- defaultExpandedRowKeys?: React.Key[]
1555
-
1556
- // 当前展开的行 key 数组(受控模式)
1557
- expandedRowKeys?: React.Key[]
1558
-
1559
- // 展开/收起回调
1560
- onExpand?: (expanded: boolean, record: T) => void
1561
-
1562
- // 展开的行 key 变化回调
1563
- onExpandedRowsChange?: (expandedRowKeys: React.Key[]) => void
1564
-
1565
- // 自定义展开图标
1566
- expandIcon?: (props: {
1567
- expanded: boolean
1568
- onExpand: () => void
1569
- record: T
1570
- }) => ReactNode
1571
-
1572
- // 显示展开列(默认: true)
1573
- showExpandColumn?: boolean
1574
-
1575
- // 展开列宽度(默认: 50)
1576
- expandColumnWidth?: number
1577
-
1578
- // 默认展开所有行(默认: false)
1579
- defaultExpandAllRows?: boolean
1580
-
1581
- // 手风琴模式 - 同时只能展开一行(默认: false)
1582
- accordion?: boolean
1583
- }
1584
- ```
1585
-
1586
- ### 开发
1587
-
1588
- ```bash
1589
- # 安装依赖
1590
- pnpm install
1591
-
1592
- # 构建库
1593
- pnpm run build
1594
-
1595
- # 启动开发服务器
1596
- pnpm run dev
1597
- ```
1296
+ Contributions are welcome! Please feel free to submit a Pull Request.
1598
1297
 
1599
- ### 许可证
1298
+ ## Support
1600
1299
 
1601
- MIT
1300
+ If you have any questions or need help, please open an issue on [GitHub](https://github.com/gulibs/react-vtable).