bw-pro-table 0.0.1 → 0.0.3

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.
Files changed (49) hide show
  1. package/README.md +833 -151
  2. package/dist/bw-pro-table.css +1 -1
  3. package/dist/bw-pro-table.js +2733 -2205
  4. package/dist/bw-pro-table.umd.cjs +9 -9
  5. package/dist/types/BwProTable.vue.d.ts +37 -12
  6. package/dist/types/BwProTableBatchEditModal.vue.d.ts +4 -4
  7. package/dist/types/BwProTableColumnSearch.vue.d.ts +3 -3
  8. package/dist/types/BwProTableColumnSetting.vue.d.ts +4 -4
  9. package/dist/types/BwProTableCore.vue.d.ts +8 -5
  10. package/dist/types/BwProTableImportModal.vue.d.ts +40 -0
  11. package/dist/types/BwProTableSearch.vue.d.ts +18 -8
  12. package/dist/types/BwProTableToolbar.vue.d.ts +21 -7
  13. package/dist/types/composables/useBwKeyboard.d.ts +1 -1
  14. package/dist/types/composables/useBwPermission.d.ts +2 -2
  15. package/dist/types/composables/useBwRowEdit.d.ts +10 -8
  16. package/dist/types/composables/useBwRowSelection.d.ts +2 -2
  17. package/dist/types/composables/useColumnHeaderSearch.d.ts +2 -2
  18. package/dist/types/composables/useContextMenu.d.ts +4 -2
  19. package/dist/types/composables/useDragSort.d.ts +2 -2
  20. package/dist/types/composables/useExportExcel.d.ts +14 -7
  21. package/dist/types/composables/useImportExcel.d.ts +14 -8
  22. package/dist/types/composables/useLocale.d.ts +8 -4
  23. package/dist/types/composables/usePersistence.d.ts +4 -2
  24. package/dist/types/composables/useQueryParams.d.ts +2 -2
  25. package/dist/types/composables/useTableView.d.ts +4 -2
  26. package/dist/types/constants.d.ts +8 -4
  27. package/dist/types/drag-icon.vue.d.ts +3 -3
  28. package/dist/types/index.d.ts +1 -1
  29. package/dist/types/injection-keys.d.ts +7 -10
  30. package/dist/types/locales/zh-CN.d.ts +1 -4
  31. package/dist/types/renderers/action-renderer.d.ts +2 -2
  32. package/dist/types/renderers/avatar-renderer.d.ts +1 -1
  33. package/dist/types/renderers/boolean-renderer.d.ts +1 -1
  34. package/dist/types/renderers/currency-renderer.d.ts +1 -1
  35. package/dist/types/renderers/date-renderer.d.ts +1 -1
  36. package/dist/types/renderers/image-renderer.d.ts +1 -1
  37. package/dist/types/renderers/index-renderer.d.ts +1 -1
  38. package/dist/types/renderers/index.d.ts +1 -9
  39. package/dist/types/renderers/link-renderer.d.ts +1 -1
  40. package/dist/types/renderers/number-renderer.d.ts +1 -1
  41. package/dist/types/renderers/progress-renderer.d.ts +1 -1
  42. package/dist/types/renderers/registry.d.ts +6 -2
  43. package/dist/types/renderers/tag-enum-renderer.d.ts +1 -1
  44. package/dist/types/renderers/text-renderer.d.ts +1 -1
  45. package/dist/types/types.d.ts +79 -10
  46. package/locales/zh-CN.ts +1 -0
  47. package/package.json +12 -11
  48. package/dist/types/BwProTableAdvancedFilter.vue.d.ts +0 -17
  49. package/dist/types/BwProTableContextMenu.vue.d.ts +0 -20
package/README.md CHANGED
@@ -1,20 +1,34 @@
1
1
  # BwProTable
2
2
 
3
- 基于 **ant-design-vue 3/4** 的高级业务表格组件,提供 ProTable 级别的开箱即用体验。
3
+ 基于 **ant-design-vue 4** 的高级业务表格组件,提供 ProTable 级别的开箱即用体验。采用 Vue 3 Composition API + TypeScript 编写,提供完整的类型推导。
4
4
 
5
5
  ## 特性
6
6
 
7
- - 🚀 **双模式数据加载**:支持 `request` 异步请求模式 和 `dataSource` 静态数据模式,无缝切换
8
- - ✏️ **行内编辑**:单元格级/行级编辑、撤销/重做、字段校验、批量编辑
9
- - 🔍 **多管道筛选**:搜索栏 + 表头筛选 + 列头搜索,三套独立管道协同工作
10
- - 🎨 **12 种内置渲染器**:textnumberdatetagimagelinkprogressavataractionindexcurrencyboolean,支持 `defineColumnType` 自定义扩展
11
- - 📊 **列管理**:拖拽排序列、列显隐/列宽调节,支持持久化到 localStorage
12
- - 📎 **Excel 导入导出**:支持 xlsx 导出/导入预览/模板下载(xlsx 按需动态加载)
13
- - **跨页多选**:跨页保持选中状态,支持全选所有页
14
- - 🔄 **工具栏**:刷新、密度切换、全屏、列设置、边框/斑马纹切换
15
- - 💾 **状态持久化**:列配置、分页、密度、筛选、排序自动持久化,可选 sessionStorage 或自定义 StorageAdapter
16
- - ⌨️ **键盘导航**:方向键行导航,支持 Enter/Space/Delete 等快捷键
17
- - 🎯 **TypeScript 全类型支持**:完整的 Props/Events/Slots/Methods 类型推导
7
+ ### 已集成功能
8
+
9
+ - 🚀 **双模式数据加载**:`request` 异步请求模式 `dataSource` 静态数据模式互斥,支持自动竞态保护与页码越界校验
10
+ - 🎨 **12 种内置渲染器**:`text`、`number`、`date`、`tag`、`image`、`link`、`progress`、`avatar`、`action`、`index`、`currency`、`boolean`(含 `enum` 别名),支持 `defineColumnType` 注册自定义类型
11
+ - 🔍 **三管道筛选系统**:搜索栏 + 表头筛选(ant-design-vue 原生)+ 列头搜索(Popover),独立运行并合并为 `mergedSearchParams`
12
+ - 📊 **客户端筛选与排序**:DataSource 模式下自动应用 `applyClientFilters`(三管道合并)与 `applyClientSort`(支持自定义 sorter 函数)
13
+ - ✏️ **行内编辑**:单元格级 / 行级双模式编辑,草稿数据管理,字段校验(required/type/min/max/pattern/validator),撤销/重做(Command Pattern),批量编辑
14
+ - 📋 **列管理**:HTML5 拖拽排序列、列宽调节(rAF 批处理节流)、列显隐设置面板,特殊列自动排除
15
+ - 💾 **状态持久化**:列配置、分页、密度、筛选、排序持久化到 `localStorage` / `sessionStorage` / 自定义 `StorageAdapter`,支持版本管理与数据校验
16
+ - **跨页多选**:跨页保持选中状态,支持全选所有页(DataSource 内存 / Request 回调),最大选择数限制
17
+ - 📎 **Excel 导入导出**:xlsx 按需懒加载,导出 Excel/CSV/选中行,导入预览/自动列映射/校验/模板下载
18
+ - 🔄 **工具栏**:刷新、密度切换、全屏、列设置、边框/斑马纹切换、清除筛选、选中计数显示
19
+ - 🌐 **国际化**:vue-i18n 集成,`locale` prop 部分覆写,优先级:`props.locale` > `vue-i18n` > `zh-CN` 默认
20
+ - 🎯 **TypeScript 全类型支持**:完整的 Props / Events / Slots / Methods / Composables 类型导出
21
+
22
+ ### 已编码但未集成的功能
23
+
24
+ 以下功能已在 `composables/` 中实现对应逻辑,类型定义已就绪,但**尚未在主组件 `BwProTable.vue` 中集成**,运行时不生效。详见 [未集成功能说明](#未集成功能说明) 章节。
25
+
26
+ - 右键菜单(`BwProTableContextMenu.vue`)
27
+ - 高级筛选面板(`BwProTableAdvancedFilter.vue`)
28
+ - 视图管理(`useTableView`)
29
+ - 键盘导航(`useBwKeyboard`)
30
+ - 权限过滤(`useBwPermission`)
31
+ - 行拖拽排序(`useDragSort`)
18
32
 
19
33
  ## 安装
20
34
 
@@ -24,19 +38,22 @@ npm install bw-pro-table
24
38
 
25
39
  ### 依赖要求
26
40
 
41
+ **必选 peerDependencies**:
42
+
27
43
  | 依赖 | 版本 | 说明 |
28
44
  |------|------|------|
29
45
  | `vue` | `^3.3.0` | 核心框架 |
30
- | `ant-design-vue` | `^3.2.0 \|\| ^4.0.0` | UI 组件库 |
31
- | `@ant-design/icons-vue` | `^6.0.0 \|\| ^7.0.0` | 图标库 |
32
- | `dayjs` | `^1.11.0` | 日期处理 |
46
+ | `ant-design-vue` | `^4.0.0` | UI 组件库 |
47
+ | `@ant-design/icons-vue` | `^7.0.0` | 图标库 |
48
+ | `dayjs` | `^1.11.0` | 日期处理(渲染器与编辑态使用) |
49
+ | `vue-i18n` | `^9.0.0 \|\| ^10.0.0 \|\| ^11.0.0` | 国际化 |
33
50
 
34
- **可选依赖**(按需安装):
51
+ **可选 peerDependencies**(按需安装):
35
52
 
36
- | 依赖 | 说明 |
37
- |------|------|
38
- | `xlsx` | Excel 导入导出功能需要 |
39
- | `screenfull` | 全屏切换功能需要 |
53
+ | 依赖 | 说明 | 何时需要 |
54
+ |------|------|----------|
55
+ | `xlsx` | `^0.18.0` | Excel 导入导出功能(按需动态 `import()` 懒加载) |
56
+ | `screenfull` | `^6.0.0` | 工具栏全屏切换功能 |
40
57
 
41
58
  ## 快速开始
42
59
 
@@ -91,8 +108,8 @@ const fetchUsers = async (params: {
91
108
  };
92
109
 
93
110
  const rowSelection = {
94
- type: 'checkbox',
95
- onChange: (keys: any[], rows: any[]) => {
111
+ type: 'checkbox' as const,
112
+ onChange: (keys: (string | number)[], rows: User[]) => {
96
113
  console.log('选中:', keys, rows);
97
114
  },
98
115
  };
@@ -103,161 +120,445 @@ const rowSelection = {
103
120
 
104
121
  ### Props
105
122
 
123
+ #### 核心数据
124
+
125
+ | 参数 | 类型 | 默认值 | 说明 |
126
+ |------|------|--------|------|
127
+ | `columns` | `ProColumnsType<RecordType>` | **必填** | 列定义数组,详见 [ProColumnType](#procolumntype-列定义) |
128
+ | `request` | `(params) => Promise<{data, total, success}>` | - | 异步数据请求函数,与 `dataSource` 互斥 |
129
+ | `dataSource` | `RecordType[]` | - | 静态数据源,与 `request` 互斥 |
130
+ | `rowKey` | `string \| GetRowKey` | `'id'` | 行唯一标识字段名或函数(函数式 rowKey 时编辑/选择等功能可能受限,建议用字符串) |
131
+ | `tableId` | `string` | 随机生成 | 表格唯一标识,持久化命名空间必需 |
132
+ | `cacheKey` | `string` | - | 持久化缓存键(与 `tableId` 二选一,优先使用 `cacheKey`) |
133
+ | `title` | `string` | - | 表格标题(显示在工具栏) |
134
+
135
+ #### 搜索
136
+
137
+ | 参数 | 类型 | 默认值 | 说明 |
138
+ |------|------|--------|------|
139
+ | `search` | `boolean \| SearchConfig` | - | 搜索栏配置,`false` 隐藏搜索栏,`true` 全部使用默认值 |
140
+ | `columnSearch` | `false \| HeaderSearchConfig` | - | 列头搜索全局配置,`false` 禁用所有列的 `headerSearch` |
141
+
142
+ > **SearchConfig**:`{ labelWidth?: number; defaultCollapsed?: boolean; resetText?: string; searchText?: string; collapsedCount?: number; colon?: boolean }`
143
+
144
+ #### 工具栏
145
+
146
+ | 参数 | 类型 | 默认值 | 说明 |
147
+ |------|------|--------|------|
148
+ | `toolbar` | `false \| ToolbarConfig` | - | 工具栏配置,`false` 隐藏整个工具栏 |
149
+
150
+ > **ToolbarConfig**:`{ refresh?: boolean; density?: boolean; fullscreen?: boolean; columnSetting?: boolean; export?: boolean \| ExportConfig; clearFilter?: boolean; import?: boolean \| ImportConfig; viewManager?: boolean; bordered?: boolean; striped?: boolean }`
151
+
152
+ #### 功能配置
153
+
154
+ | 参数 | 类型 | 默认值 | 说明 |
155
+ |------|------|--------|------|
156
+ | `editConfig` | `EditConfig` | - | 行内编辑配置,详见 [行内编辑](#行内编辑) |
157
+ | `dragConfig` | `DragConfig` | - | 拖拽配置(当前仅 `columnDraggable` 生效) |
158
+ | `rowSelection` | `TableRowSelection` | - | 行选择配置(透传 ant-design-vue) |
159
+ | `crossPageSelection` | `false \| CrossPageSelectionConfig` | - | 跨页多选配置,`false` 禁用 |
160
+ | `exportConfig` | `ExportConfig` | - | 导出配置(也可通过 `toolbar.export` 传入) |
161
+ | `importConfig` | `ImportConfig` | - | 导入配置(也可通过 `toolbar.import` 传入) |
162
+ | `contextMenu` | `ContextMenuConfig` | - | 右键菜单配置(**类型已定义,运行时未集成**) |
163
+ | `persistence` | `false \| PersistenceConfig` | - | 持久化配置,`false` 禁用 |
164
+
165
+ #### 样式与透传
166
+
106
167
  | 参数 | 类型 | 默认值 | 说明 |
107
168
  |------|------|--------|------|
108
- | `columns` | `ProColumnsType` | **必填** | 列定义数组 |
109
- | `request` | `(params) => Promise<{data, total, success}>` | - | 异步数据请求函数,与 dataSource 互斥 |
110
- | `dataSource` | `any[]` | - | 静态数据源,与 request 互斥 |
111
- | `rowKey` | `string` | `'id'` | 行数据唯一标识字段名 |
112
- | `title` | `string` | - | 表格标题 |
113
169
  | `size` | `'small' \| 'middle' \| 'default'` | `'default'` | 表格尺寸 |
114
- | `pagination` | `false \| TablePaginationConfig` | `{}` | 分页配置,设为 false 禁用 |
115
170
  | `bordered` | `boolean` | `false` | 是否显示边框 |
116
171
  | `striped` | `boolean` | `false` | 是否显示斑马纹 |
172
+ | `defaultEmptyText` | `string` | `'-'` | 空单元格占位符(`null`/`undefined`/`''` 时显示) |
173
+ | `loading` | `boolean` | - | 外部加载态(与内部状态机合并) |
174
+ | `pagination` | `false \| TablePaginationConfig` | `{}` | 分页配置,`false` 禁用分页 |
117
175
  | `scroll` | `{ x?, y? }` | - | 表格滚动配置 |
118
176
  | `sticky` | `boolean \| { offsetHeader? }` | - | 表头固定 |
119
- | `search` | `false \| SearchConfig` | - | 搜索栏配置,false 隐藏 |
120
- | `toolbar` | `false \| ToolbarConfig` | - | 工具栏配置,false 隐藏 |
121
- | `tableId` | `string` | 随机生成 | 表格唯一标识,持久化必需 |
122
- | `cacheKey` | `string` | - | 持久化缓存键(与 tableId 二选一) |
123
- | `persistence` | `false \| PersistenceConfig` | - | 持久化配置 |
124
- | `editConfig` | `EditConfig` | - | 行内编辑配置 |
125
- | `dragConfig` | `DragConfig` | - | 列拖拽配置 |
126
- | `rowSelection` | `TableRowSelection` | - | 行选择配置 |
127
- | `crossPageSelection` | `false \| CrossPageSelectionConfig` | - | 跨页多选配置 |
128
- | `exportConfig` | `ExportConfig` | - | 导出配置 |
129
- | `importConfig` | `ImportConfig` | - | 导入配置 |
130
- | `viewConfig` | `ViewConfig` | - | 视图管理配置 |
131
177
  | `expandable` | `ExpandableConfig` | - | 行展开配置 |
178
+ | `rowClassName` | `(record, index) => string` | - | 行 className 函数(与内置斑马纹/选中样式合并) |
179
+ | `class` | `string` | - | 表格根元素 CSS 类名 |
180
+ | `style` | `CSSProperties` | - | 表格根元素样式 |
181
+ | `locale` | `Partial<BwProTableLocale>` | - | 国际化语言包,传入部分字段即可覆写 |
132
182
 
133
183
  ### Events
134
184
 
135
185
  | 事件 | 参数 | 说明 |
136
186
  |------|------|------|
137
- | `load` | `{ dataSource, total }` | 数据加载完成 |
187
+ | `load` | `{ dataSource: any[], total: number }` | 数据加载成功完成 |
138
188
  | `error` | `Error` | 数据加载失败 |
139
- | `change` | `(pagination, filters, sorter, extra)` | 表格变化(排序/筛选) |
140
- | `resizeColumn` | `(width, column)` | 列宽调整 |
189
+ | `change` | `(pagination, filters, sorter, extra)` | 表格变化(分页/排序/筛选),`extra` 含 `action: 'paginate' \| 'sort' \| 'filter'` 和 `currentDataSource: any[]` |
190
+ | `resizeColumn` | `(width: number, column: ProColumnType)` | 列宽调整 |
141
191
 
142
192
  ### Methods(通过 ref 调用)
143
193
 
144
- | 方法 | 说明 |
145
- |------|------|
146
- | `fetchData()` | 手动刷新数据 |
147
- | `getSelectedRowKeys()` | 获取当前页选中行 keys |
148
- | `getSelectedRows()` | 获取当前页选中行数据 |
149
- | `getDataSource()` | 获取当前数据源 |
150
- | `clearSelection()` | 清空跨页选择 |
151
- | `getCrossPageSelectedKeys()` | 获取跨页选中的 keys |
152
- | `getCrossPageSelectedRows()` | 获取跨页选中的行数据 |
194
+ #### 数据与选择
195
+
196
+ | 方法 | 签名 | 说明 |
197
+ |------|------|------|
198
+ | `fetchData` | `() => Promise<void>` | 手动刷新数据 |
199
+ | `getDataSource` | `() => any[]` | 获取当前数据源 |
200
+ | `getSelectedRowKeys` | `() => (string \| number)[]` | 获取当前页选中行 keys |
201
+ | `getSelectedRows` | `() => any[]` | 获取当前页选中行数据 |
202
+ | `clearSelection` | `() => void` | 清空所有选择(含跨页) |
203
+ | `getCrossPageSelectedKeys` | `() => (string \| number)[]` | 获取跨页选中的所有 keys |
204
+ | `getCrossPageSelectedRows` | `() => (any \| null)[]` | 获取跨页选中的行数据(Request 模式下部分可能为 `null`) |
205
+
206
+ #### 行内编辑
207
+
208
+ | 方法 | 签名 | 说明 |
209
+ |------|------|------|
210
+ | `startRowEdit` | `(rowKey) => Promise<void>` | 进入行编辑模式 |
211
+ | `startCellEdit` | `(rowKey, field) => Promise<void>` | 进入单元格编辑模式 |
212
+ | `saveEdit` | `(rowKey) => Promise<boolean>` | 保存编辑(返回是否校验通过) |
213
+ | `cancelEdit` | `(rowKey, field?) => void` | 取消编辑(传入 field 时仅取消该单元格) |
214
+ | `addRow` | `(position?: 'top' \| 'bottom') => void` | 新增空行(默认追加到底部) |
215
+ | `cloneRow` | `(rowKey) => void` | 克隆行(插入到顶部) |
216
+ | `deleteRow` | `(rowKey) => Promise<void>` | 删除单行 |
217
+ | `deleteRows` | `(keys) => Promise<void>` | 批量删除行 |
218
+ | `isCellEditing` | `(rowKey, field) => boolean` | 判断单元格是否编辑中 |
219
+ | `getEditingKeys` | `() => Set<string \| number>` | 获取当前编辑中的行 keys |
220
+ | `canUndo` | `() => boolean` | 是否可撤销 |
221
+ | `canRedo` | `() => boolean` | 是否可重做 |
222
+ | `undo` | `() => void` | 撤销 |
223
+ | `redo` | `() => void` | 重做 |
224
+ | `openBatchEdit` | `() => void` | 打开批量编辑弹窗 |
225
+
226
+ #### 导入导出
227
+
228
+ | 方法 | 签名 | 说明 |
229
+ |------|------|------|
230
+ | `exportExcel` | `() => Promise<void>` | 导出 Excel |
231
+ | `exportCsv` | `() => Promise<void>` | 导出 CSV |
232
+ | `exportSelected` | `(rows, keys?) => Promise<void>` | 导出选中行 |
233
+ | `openFileDialog` | `() => void` | 打开导入文件对话框 |
234
+ | `downloadTemplate` | `(columns: { title: string; dataIndex: string }[]) => Promise<void>` | 下载导入模板 |
153
235
 
154
236
  ### Slots
155
237
 
156
- | 插槽名 | 说明 |
157
- |--------|------|
158
- | `search` | 自定义搜索栏 |
159
- | `toolbar` | 自定义工具栏 |
160
- | `toolbar-extra` | 工具栏左侧额外内容 |
161
- | 动态列插槽 | `bodyCell`、`headerCell` ant-design-vue 原生插槽均支持 |
238
+ | 插槽名 | 作用域参数 | 说明 |
239
+ |--------|-----------|------|
240
+ | `search` | - | 自定义整个搜索栏(替换默认组件) |
241
+ | `toolbar` | - | 自定义整个工具栏(替换默认组件) |
242
+ | `toolbar-extra` | - | 工具栏左侧额外内容(追加到默认工具栏) |
243
+ | `toolbar-right` | - | 工具栏右侧额外内容(追加到内置按钮组左侧) |
244
+ | `bodyCell` | `{ column, record, index, text, value }` | 自定义单元格渲染(覆盖 valueType 渲染器) |
245
+ | `headerCell` | `{ column }` | 自定义表头单元格(列头搜索 Popover 仍会追加在右侧) |
246
+
247
+ > **插槽透传机制**:除上述具名插槽外,所有 ant-design-vue Table 原生插槽均通过 `v-for="$slots"` 透传支持。
162
248
 
163
249
  ## ProColumnType 列定义
164
250
 
251
+ `ProColumnType` 在 ant-design-vue `TableColumnType` 基础上扩展以下字段:
252
+
165
253
  ```ts
166
- interface ProColumnType<RecordType = any> extends ColumnType<RecordType> {
254
+ interface ProColumnType<RecordType = any> extends Omit<TableColumnType<RecordType>, 'children'> {
167
255
  // === 值渲染 ===
168
256
  valueType?: ValueType; // 内置渲染器类型
169
- valueEnum?: ValueEnumMap; // 枚举值映射(tag 渲染)
170
- customRender?: RenderFn; // 自定义渲染函数
257
+ valueEnum?: ValueEnumMap; // 枚举值映射(tag/enum 渲染 + 搜索栏 select)
258
+ dateFormat?: string; // 日期格式(valueType='date',默认 'YYYY-MM-DD')
259
+ precision?: number; // 小数精度(valueType='number'/'currency')
260
+ currencyPrefix?: string; // 货币前缀(默认 '¥')
261
+ currencySuffix?: string; // 货币后缀(默认 '')
262
+ customRender?: RenderFn; // 自定义渲染函数(无 valueType 匹配时回退)
263
+
264
+ // === 空值与溢出 ===
265
+ emptyText?: string; // 空单元格占位符(覆盖全局 defaultEmptyText)
266
+ overflow?: 'ellipsis' | 'wrap' | 'clip'; // 溢出策略
171
267
 
172
268
  // === 搜索 ===
173
- search?: boolean | SearchFieldConfig; // 搜索栏字段配置
174
- headerSearch?: boolean; // 列头搜索(仅 text 类型)
269
+ search?: boolean | SearchFieldConfig; // 搜索栏字段配置(true 启用自动匹配控件)
270
+ headerSearch?: boolean | HeaderSearchConfig; // 列头搜索(仅 valueType='text' 生效)
271
+ renderFormItem?: (context: { model, search, resetField }) => VNode; // 自定义搜索表单控件
272
+ hideInSearch?: boolean; // 是否在搜索栏中隐藏该列(默认 false)
175
273
 
176
274
  // === 编辑 ===
177
- editable?: boolean; // 是否可编辑
275
+ editable?: boolean | ((record) => boolean); // 是否可编辑
178
276
  formRules?: FormRule[]; // 编辑校验规则
179
- editComponent?: string | Component; // 自定义编辑组件
180
- editProps?: Record<string, any>; // 编辑组件属性
277
+ editComponent?: string; // 编辑态组件类型(当前按 valueType 自动匹配)
278
+ editComponentProps?: Record<string, any>; // 编辑态组件额外属性
279
+
280
+ // === 渲染器专属 ===
281
+ actions?: ActionItem[]; // 操作列按钮(valueType='action')
282
+ imageWidth?: number; // 图片宽度(默认 32)
283
+ imageHeight?: number; // 图片高度(默认 32)
284
+ linkProps?: { href?, target?, onClick? }; // 链接配置
285
+ progressProps?: { strokeColor? }; // 进度条配置
286
+ avatarProps?: { srcField?, nameField?, showName?, size?, shape? }; // 头像配置
287
+ booleanSwitch?: boolean; // 布尔列使用 Switch 开关(默认图标)
181
288
 
182
289
  // === 其他 ===
183
290
  hidden?: boolean; // 是否隐藏列
184
- copyable?: boolean; // 是否可复制
185
- ellipsis?: boolean; // 是否省略
186
- tooltip?: string | Function; // 提示
291
+ description?: string; // 表头说明文字(Tooltip 显示)
292
+ columnGroup?: string; // 列所属分组
293
+ authority?: string | string[]; // 列级权限标识(类型已定义,运行时未集成)
294
+ children?: ProColumnsType; // 子列(分组表头)
295
+ }
296
+ ```
297
+
298
+ ### ActionItem 操作列按钮
299
+
300
+ ```ts
301
+ interface ActionItem<RecordType = any> {
302
+ label: string; // 按钮文字
303
+ type?: 'primary' | 'dashed' | 'link' | 'text' | 'default'; // 按钮类型(默认 'link')
304
+ danger?: boolean; // 危险操作样式
305
+ icon?: string; // @ant-design/icons-vue 图标名
306
+ confirm?: string; // 气泡确认文案(设置后点击先弹 Popconfirm)
307
+ authority?: string | string[]; // 权限标识(未集成)
308
+ disabled?: boolean | ((record) => boolean); // 是否禁用
309
+ hidden?: boolean | ((record) => boolean); // 是否隐藏
310
+ onClick?: (record, index) => void | Promise<void>; // 点击回调
311
+ }
312
+ ```
313
+
314
+ ### FormRule 校验规则
315
+
316
+ ```ts
317
+ interface FormRule {
318
+ required?: boolean; // 是否必填
319
+ message?: string; // 校验失败消息
320
+ type?: 'string' | 'number' | 'boolean' | 'email' | 'url'; // 值类型
321
+ min?: number; // 最小值(number 类型)或最小长度(string 类型)
322
+ max?: number; // 最大值(number 类型)或最大长度(string 类型)
323
+ pattern?: RegExp; // 正则校验
324
+ validator?: (value, record) => Promise<void> | void; // 自定义校验(throw 或 reject 表示失败)
187
325
  }
188
326
  ```
189
327
 
190
- ## ValueType 内置渲染器
328
+ ## ValueType 渲染器
329
+
330
+ ### 内置渲染器
331
+
332
+ | valueType | 渲染输出 | 支持的额外列属性 |
333
+ |-----------|----------|------------------|
334
+ | `text` | 文本 + Tooltip(`ellipsis !== false` 时) | `emptyText`、`ellipsis` |
335
+ | `number` | 千分位格式化数字 | `precision`、`emptyText` |
336
+ | `date` | dayjs 格式化日期 | `dateFormat`(默认 `'YYYY-MM-DD'`)、`emptyText` |
337
+ | `currency` | 货币符号 + 千分位 | `precision`、`currencyPrefix`(默认 `'¥'`)、`currencySuffix`、`emptyText` |
338
+ | `tag` / `enum` | `a-tag` + 颜色 | `valueEnum`、`emptyText` |
339
+ | `image` | `a-image` 缩略图(含预览) | `imageWidth`(默认 32)、`imageHeight`(默认 32)、`emptyText` |
340
+ | `link` | `<a>` 链接 + `LinkOutlined` 图标 | `linkProps: { href?(无时默认 `'#'` 并阻止跳转), target?(默认 `'_blank'`, 自动设 `rel='noopener noreferrer'`), onClick?: (record, event) => void }`、`emptyText` |
341
+ | `progress` | `a-progress` 进度条 | `progressProps: { strokeColor? }`、`emptyText` |
342
+ | `avatar` | `a-avatar` + 名称 | `avatarProps: { srcField?(默认 `'avatar'`), nameField?(默认 `'name'`), showName?(默认 `true`), size?(默认 32), shape?(默认 `'circle'`) }`、`emptyText` |
343
+ | `boolean` | ✓/✗ 图标(默认)或 `a-switch`(`booleanSwitch: true`) | `booleanSwitch`、`emptyText` |
344
+ | `action` | 按钮组 + Popconfirm | `actions: ActionItem[]` |
345
+ | `index` | 行号(`index + 1`) | - |
191
346
 
192
- | valueType | 说明 | 支持的额外属性 |
193
- |-----------|------|----------------|
194
- | `text` | 文本 | `copyable` |
195
- | `number` | 数字 | `precision` |
196
- | `date` | 日期 | `dateFormat` |
197
- | `currency` | 货币 | `precision`, `currencySymbol` |
198
- | `tag` | 标签 | `valueEnum` |
199
- | `image` | 图片 | `imageWidth`, `imageHeight` |
200
- | `link` | 链接 | `linkText` |
201
- | `progress` | 进度条 | - |
202
- | `avatar` | 头像 | `avatarSize` |
203
- | `boolean` | 布尔值 | `trueText`, `falseText` |
204
- | `action` | 操作列 | `actions` |
205
- | `index` | 序号列 | `indexOffset` |
347
+ > **空值约定**:所有渲染器对 `null`/`undefined`/`''` 显示 `emptyText`(列级)或 `defaultEmptyText`(全局,默认 `'-'`)。`boolean` 类型中 `false` 是合法值(显示红叉),不视为空值。
206
348
 
207
349
  ### 自定义 valueType
208
350
 
351
+ 通过 `defineColumnType` 注册自定义渲染器:
352
+
209
353
  ```ts
210
354
  import { defineColumnType } from 'bw-pro-table';
355
+ import { h } from 'vue';
211
356
 
212
- defineColumnType('customStatus', ({ text, record, column }) => {
213
- return h('span', { style: { color: text === 'ok' ? 'green' : 'red' } }, text);
357
+ defineColumnType('customStatus', ({ value, record, index, column }) => {
358
+ return h('span', { style: { color: value === 'ok' ? 'green' : 'red' } }, value);
214
359
  });
360
+
361
+ // 在列定义中使用
362
+ const columns = [
363
+ { title: '状态', dataIndex: 'status', valueType: 'customStatus' as any },
364
+ ];
215
365
  ```
216
366
 
217
- ## 搜索配置
367
+ **渲染器函数签名**:
218
368
 
219
- ```vue
220
- <BwProTable
221
- :columns="columns"
222
- :search="{
223
- labelWidth: 100,
224
- collapsedRows: 3,
225
- submitText: '查询',
226
- resetText: '重置',
227
- }"
228
- />
369
+ ```ts
370
+ type RendererFn = (ctx: {
371
+ value: any; // 单元格值
372
+ record: any; // 行数据
373
+ index: number; // 行索引
374
+ column: ProColumnType; // 列配置
375
+ }) => VNode | string | number;
229
376
  ```
230
377
 
231
- 列上标注可搜索字段:
378
+ ### 渲染器 API
379
+
380
+ | API | 说明 |
381
+ |-----|------|
382
+ | `defineColumnType(type, renderer)` | 注册自定义渲染器(已存在会警告并覆盖) |
383
+ | `registerRenderer(type, renderer)` | 注册渲染器(底层 API) |
384
+ | `getRenderer(type)` | 获取渲染器 |
385
+ | `hasRenderer(type)` | 判断渲染器是否存在 |
386
+ | `getRendererSafe(type)` | 获取渲染器(自动初始化内置渲染器) |
387
+ | `ensureRenderers()` | 惰性初始化所有内置渲染器(幂等) |
388
+ | `resetRenderers()` | 重置注册表(仅用于测试隔离) |
389
+
390
+ ## 数据加载模式
391
+
392
+ ### Request 模式(服务端)
393
+
394
+ 传入 `request` 异步函数,组件管理分页、筛选、排序:
395
+
396
+ ```ts
397
+ const request = async (params: {
398
+ current: number; // 当前页码
399
+ pageSize: number; // 每页条数
400
+ [key: string]: any; // 合并的搜索/筛选/排序参数
401
+ }) => {
402
+ const res = await api.fetch(params);
403
+ return {
404
+ data: res.list, // 当前页数据数组
405
+ total: res.total, // 数据总数
406
+ success: true, // 是否成功
407
+ };
408
+ };
409
+ ```
410
+
411
+ **参数合并**:`mergedSearchParams` 将以下参数合并后传给 `request`:
412
+ - `searchFormParams`(搜索栏表单值)
413
+ - `headerSearchParams`(列头搜索文本)
414
+ - `tableFilterParams`(表头筛选值)
415
+ - `sortState`(排序状态,展开为 `sortField` + `sortOrder`)
416
+
417
+ **竞态保护**:`fetchData` 使用版本号(`fetchVersion`)防止过期响应覆盖最新数据。
418
+
419
+ **页码越界校验**:数据返回后若当前页超过最大页数,自动回退到最后一页并重试一次(防止后端 total 不稳定导致无限递归)。
420
+
421
+ ### DataSource 模式(客户端)
422
+
423
+ 传入 `dataSource` 静态数组,组件自动应用客户端筛选与排序:
232
424
 
233
425
  ```ts
234
- { title: '姓名', dataIndex: 'name', search: true }
235
- { title: '状态', dataIndex: 'status', search: { label: '状态', widget: 'select', options: [...] } }
426
+ const dataSource = ref<User[]>([...]);
427
+
428
+ // 模板
429
+ <BwProTable :columns="columns" :data-source="dataSource" />
236
430
  ```
237
431
 
432
+ **客户端筛选 `applyClientFilters`**:
433
+
434
+ 按以下顺序对数据应用三管道筛选:
435
+
436
+ 1. **searchFormParams**(搜索栏):
437
+ - `tag`/`boolean`/`number`/`currency`/`date` 类型 → 精确匹配(`===`)
438
+ - `text`/其他类型 → 子串匹配(大小写不敏感)
439
+ 2. **headerSearchParams**(列头搜索):子串匹配(大小写不敏感)
440
+ 3. **tableFilterParams**(表头筛选):数组 `includes` 或精确匹配
441
+
442
+ **客户端排序 `applyClientSort`**:
443
+
444
+ - 若列定义了 `sorter` 函数(`CompareFn` 或 `{ compare }`)→ 使用自定义比较
445
+ - 否则 → 通用比较(数字用 `-`,字符串用 `localeCompare`,`null` 值排末尾)
446
+ - 支持多列排序(取第一个排序项作为主排序)
447
+
448
+ ### 模式切换
449
+
450
+ 监听 `request` prop 变化自动切换模式:
451
+ - 切换到 Request 模式 → 重置页码到 1 并请求数据
452
+ - 切换到 DataSource 模式 → 校验页码边界并刷新数据
453
+
454
+ ## 搜索系统
455
+
456
+ ### 三管道架构
457
+
458
+ | 管道 | 来源 | 触发方式 | 适用模式 |
459
+ |------|------|----------|----------|
460
+ | 搜索栏 | `BwProTableSearch.vue` 表单 | 点击"查询"按钮 | 两种模式 |
461
+ | 表头筛选 | ant-design-vue 原生 `column.filters` | 列头筛选下拉 | 两种模式(Request 传后端,DataSource 客户端) |
462
+ | 列头搜索 | `BwProTableColumnSearch.vue` Popover | 点击搜索图标 | 两种模式 |
463
+
464
+ 三个管道独立维护状态,最终合并到 `mergedSearchParams` 传给 `request` 或在客户端筛选中应用。
465
+
466
+ ### 搜索栏控件自动匹配
467
+
468
+ 搜索栏根据列的 `valueType` 自动渲染对应控件:
469
+
470
+ | valueType | 搜索控件 |
471
+ |-----------|----------|
472
+ | `text` / `link` / 无 valueType | `a-input`(文本输入) |
473
+ | `tag` | `a-select`(选项来自 `valueEnum`) |
474
+ | `boolean` | `a-select`(是/否选项) |
475
+ | `number` / `currency` | `a-input-number` |
476
+ | `date` | `a-date-picker` |
477
+ | `image` / `avatar` / `progress` / `action` / `index` | 不可搜索(自动排除) |
478
+ | 其他 | `a-input`(兜底) |
479
+
480
+ > **`hideInSearch`**:列上设置 `hideInSearch: true` 可跳过搜索栏控件生成,适用于 ID、操作列等无需搜索的字段。
481
+
482
+ **自定义搜索控件**:通过 `renderFormItem` 完全自定义(优先级高于 valueType 自动匹配):
483
+
484
+ ```ts
485
+ {
486
+ title: '状态',
487
+ dataIndex: 'status',
488
+ renderFormItem: ({ model, search, resetField }) => {
489
+ return h(ASelect, {
490
+ value: model.status,
491
+ 'onUpdate:value': (v) => { model.status = v; search(); },
492
+ options: [...],
493
+ });
494
+ },
495
+ }
496
+ ```
497
+
498
+ > `renderFormItem` 接收 `{ model, search, resetField }` 上下文:`model` 可直接读写表单值,`search()` 触发查询,`resetField(dataIndex)` 清除指定字段。
499
+
500
+ ### 列头搜索(headerSearch)
501
+
502
+ 仅 `valueType='text'` 的列支持列头搜索(`HEADER_SEARCHABLE_TYPES` 白名单)。在列上配置:
503
+
504
+ ```ts
505
+ { title: '姓名', dataIndex: 'name', valueType: 'text', headerSearch: true }
506
+ // 或带配置
507
+ { title: '姓名', dataIndex: 'name', valueType: 'text', headerSearch: { placeholder: '搜索姓名' } }
508
+ ```
509
+
510
+ **HeaderSearchConfig**:`{ enabled?, placeholder?, searchText?, resetText? }`
511
+
512
+ 全局配置通过 `columnSearch` prop:
513
+ - `columnSearch: false` → 禁用所有列的列头搜索
514
+ - `columnSearch: { placeholder: '...' }` → 覆写默认配置
515
+
516
+ ### 清除筛选
517
+
518
+ 调用 `clearFilter` 或工具栏"清除筛选"按钮会清空所有三个管道的筛选状态及排序,并重置到第一页。
519
+
238
520
  ## 行内编辑
239
521
 
240
- ```vue
241
- <BwProTable
242
- :columns="columns"
243
- :editConfig="{
244
- mode: 'cell', // 'cell' | 'row'
245
- saveText: '保存',
246
- cancelText: '取消',
247
- deleteText: '删除',
248
- addText: '新增行',
249
- maxHistory: 20,
250
- onSave: async (record) => {
251
- await api.save(record);
252
- },
253
- onDelete: async (record) => {
254
- await api.delete(record.id);
255
- },
256
- }"
257
- />
522
+ ### EditConfig 配置
523
+
524
+ ```ts
525
+ interface EditConfig<RecordType = any> {
526
+ mode?: 'cell' | 'row'; // 编辑模式(默认 'cell'
527
+ newRecord?: Partial<RecordType>; // 新增行默认值
528
+ onEditStart?: (record, field?) => Promise<boolean> | boolean; // 编辑前回调(返回 false 阻止)
529
+ onSave?: (record, isNew) => Promise<void> | void; // 保存回调
530
+ onDelete?: (keys) => Promise<void> | void; // 删除回调
531
+ addable?: boolean; // 是否允许新增(默认 true)
532
+ deletable?: boolean; // 是否允许删除(默认 true)
533
+ }
258
534
  ```
259
535
 
260
- 列上标注可编辑字段:
536
+ ### 编辑模式
537
+
538
+ | 模式 | 触发 | 行为 |
539
+ |------|------|------|
540
+ | `cell` | `startCellEdit(rowKey, field)` | 仅单个单元格进入编辑态,其他单元格正常显示 |
541
+ | `row` | `startRowEdit(rowKey)` | 整行进入编辑态 |
542
+
543
+ **草稿数据管理**:编辑期间的修改存储在 `draftData: Map<rowKey, Record<field, value>>`,保存时合并到数据源,取消时回滚到 `originalData` 备份。
544
+
545
+ ### 编辑组件自动匹配
546
+
547
+ 编辑态根据列的 `valueType` 自动渲染编辑组件:
548
+
549
+ | valueType | 编辑组件 |
550
+ |-----------|----------|
551
+ | `number` / `currency` | `a-input-number` |
552
+ | `date` | `a-date-picker`(格式使用 `dateFormat` 或默认 `'YYYY-MM-DD'`) |
553
+ | `tag` / `enum` | `a-select`(选项来自 `valueEnum`) |
554
+ | `boolean` | `a-switch` |
555
+ | 其他(`text` 等) | `a-input` |
556
+
557
+ 校验失败时编辑组件边框变红,下方显示错误消息。
558
+
559
+ ### 字段校验
560
+
561
+ 在列上配置 `formRules`,保存时自动校验:
261
562
 
262
563
  ```ts
263
564
  {
@@ -271,49 +572,194 @@ defineColumnType('customStatus', ({ text, record, column }) => {
271
572
  }
272
573
  ```
273
574
 
274
- ## 持久化配置
575
+ 校验规则执行顺序:`required` → `type` → `min` → `max` → `pattern` → `validator`(每个字段仅报第一个错误)。
275
576
 
276
- ```vue
277
- <BwProTable
278
- tableId="user-table"
279
- :persistence="{
280
- enabled: true,
281
- storage: 'localStorage', // 'localStorage' | 'sessionStorage' | StorageAdapter
282
- keyPrefix: 'my-app',
283
- debug: false,
284
- onError: (err, action) => console.warn('持久化错误:', err),
285
- }"
286
- />
577
+ `editable` 支持函数形式动态判断可编辑性:
578
+
579
+ ```ts
580
+ { dataIndex: 'status', editable: (record) => record.role === 'admin' }
287
581
  ```
288
582
 
289
- 支持的持久化范围:列显隐/顺序/宽度、分页大小、当前页码、密度、筛选、排序。
583
+ ### 撤销/重做(Command Pattern)
290
584
 
291
- ## 导出/导入
585
+ 编辑操作以命令(`EditCommand`)形式压入撤销栈:
292
586
 
293
- 需要安装 `xlsx` 依赖:
587
+ ```ts
588
+ interface EditCommand {
589
+ type: 'update' | 'add' | 'delete';
590
+ rowKey: string | number;
591
+ field?: string; // update 命令的字段名
592
+ oldValue?: any; // update 的旧值
593
+ newValue?: any; // update 的新值
594
+ record?: any; // add/delete 的完整记录
595
+ index?: number; // delete 的原始索引(用于撤销时恢复位置)
596
+ }
597
+ ```
294
598
 
295
- ```bash
296
- npm install xlsx
599
+ - **撤销栈上限**:`2 * DEFAULT_PAGE_SIZE - 1`(默认 19 条),超出时移除最旧命令
600
+ - **新操作入栈时清空重做栈**
601
+ - `undo()`/`redo()` 通过 `ref` 方法或键盘快捷键调用
602
+
603
+ ### 批量编辑
604
+
605
+ 通过 `openBatchEdit()` 打开批量编辑弹窗,选择字段和值后应用到所有选中行:
606
+
607
+ ```ts
608
+ const tableRef = ref();
609
+ // 选中多行后
610
+ tableRef.value.openBatchEdit();
611
+ // 弹窗确认后自动调用 onSave 回调逐行保存
297
612
  ```
298
613
 
614
+ 跨页多选模式下,批量编辑应用于所有跨页选中的行。
615
+
616
+ ### 新增与克隆行
617
+
618
+ ```ts
619
+ tableRef.value.addRow('top'); // 在顶部新增空行
620
+ tableRef.value.addRow('bottom'); // 在底部新增空行(默认)
621
+ tableRef.value.cloneRow(rowKey); // 克隆行(插入到顶部)
622
+ ```
623
+
624
+ 新增行使用 `editConfig.newRecord` 作为默认值,自动进入编辑态。新增行的 key 格式为 `new_{timestamp}_{random}`。
625
+
626
+ ## 列管理
627
+
628
+ ### 列拖拽排序
629
+
630
+ 启用列拖拽后,表头显示拖拽图标,可拖动调整列顺序:
631
+
299
632
  ```vue
300
633
  <BwProTable
301
- :toolbar="{ export: true, import: true }"
302
- :exportConfig="{
303
- filename: '用户列表',
304
- chunkSize: 5000,
305
- columnMapping: { name: '姓名', age: '年龄' },
306
- }"
307
- :importConfig="{
308
- validateRules: [{ required: true, message: '不能有空值' }],
309
- onImport: async (data) => { await api.batchCreate(data); },
310
- beforeImport: (data) => data.map(item => ({ ...item, source: 'import' })),
311
- }"
634
+ :columns="columns"
635
+ :drag-config="{ columnDraggable: true }"
312
636
  />
313
637
  ```
314
638
 
639
+ **实现细节**:
640
+ - 使用 HTML5 Drag and Drop API
641
+ - 拖拽时自定义幽灵图(蓝色背景白色文字的列标题)
642
+ - `dragVersion` 计数器变化触发表头重渲染(更新拖拽视觉反馈)
643
+ - `reorderColumns(fromDataIndex, toDataIndex)` 分离可见/隐藏列各自保持相对顺序
644
+ - **特殊列排除**:`valueType='index'`、`valueType='action'`、`dataIndex='action'` 的列不参与拖拽(`isSpecialColumn`)
645
+
646
+ ### 列宽调节
647
+
648
+ 列宽调节通过 rAF(`requestAnimationFrame`)批处理节流,避免每像素触发响应式更新:
649
+
650
+ - 宽度变更先收集到 `pendingWidths` 对象
651
+ - 每帧通过 `flushColumnWidths()` 统一写入 `shallowRef<Record<string, number>>`
652
+ - 持久化写入也合并到同一 rAF 周期
653
+ - 组件卸载时确保刷新残留的待持久化数据
654
+
655
+ ### 列设置面板
656
+
657
+ 工具栏的列设置按钮打开面板,支持:
658
+ - 列显隐切换(`checked`)
659
+ - 列顺序调整(`order`)
660
+ - 列宽显示
661
+ - 重置到初始状态(`onColumnReset`)
662
+
663
+ 列配置变化通过 `onColumnChange` 自动持久化。
664
+
665
+ ## 持久化系统
666
+
667
+ ### PersistenceConfig 配置
668
+
669
+ ```ts
670
+ interface PersistenceConfig {
671
+ enabled?: boolean; // 是否启用(默认 true)
672
+ storage?: 'localStorage' | 'sessionStorage' | StorageAdapter; // 存储方式(默认 'localStorage')
673
+ keyPrefix?: string; // 键名前缀(默认 'bw-pro-table')
674
+ onError?: (error: Error, operation: 'get' | 'set' | 'remove') => void; // 错误回调
675
+ debug?: boolean; // 调试模式(输出 console.warn 日志,默认 false)
676
+ }
677
+ ```
678
+
679
+ ### 持久化范围
680
+
681
+ | 状态 | 说明 |
682
+ |------|------|
683
+ | 列配置 | 显隐(`checked`)、顺序(`order`)、宽度(`width`) |
684
+ | 分页大小 | `pageSize` |
685
+ | 当前页码 | `current` |
686
+ | 密度 | `density`(`'small' \| 'middle' \| 'default'`) |
687
+ | 筛选状态 | `filters`(合并 `tableFilterParams` + `headerSearchParams`) |
688
+ | 排序状态 | `sorter`(`{ field, order }`) |
689
+
690
+ ### 存储键格式
691
+
692
+ ```
693
+ {keyPrefix}:{tableId 或 cacheKey}:cache
694
+ ```
695
+
696
+ 默认示例:`bw-pro-table:user-table:cache`
697
+
698
+ > **注意**:启用持久化时建议设置固定的 `tableId` 或 `cacheKey`,否则使用随机 `tableId` 会导致刷新后配置无法恢复(调试模式下会输出警告)。
699
+
700
+ ### 版本管理
701
+
702
+ - 当前版本:`V1.0.0`(`PERSISTED_DATA_VERSION`)
703
+ - 加载时校验 `version` 字段,版本不匹配自动清除缓存
704
+ - `validatePersistedState()` 校验数据结构完整性(`columns` 数组每项需含 `key`/`checked`/`order`)
705
+
706
+ ### 自定义 StorageAdapter
707
+
708
+ ```ts
709
+ interface StorageAdapter {
710
+ get: (key: string) => PersistedTableState | null;
711
+ set: (key: string, value: PersistedTableState) => void;
712
+ remove: (key: string) => void;
713
+ }
714
+
715
+ // 示例:使用 IndexedDB
716
+ const customAdapter: StorageAdapter = {
717
+ get: (key) => { /* 从 IndexedDB 读取 */ },
718
+ set: (key, value) => { /* 写入 IndexedDB */ },
719
+ remove: (key) => { /* 删除 */ },
720
+ };
721
+
722
+ <BwProTable :persistence="{ storage: customAdapter }" />
723
+ ```
724
+
315
725
  ## 跨页多选
316
726
 
727
+ ### CrossPageSelectionConfig 配置
728
+
729
+ ```ts
730
+ interface CrossPageSelectionConfig {
731
+ enabled?: boolean; // 是否启用(默认 false)
732
+ onSelectAllPages?: (searchParams) => Promise<(string | number)[]> | (string | number)[]; // Request 模式下全选回调
733
+ maxSelectCount?: number; // 最大选择数(0 表示不限制)
734
+ }
735
+ ```
736
+
737
+ ### 工作机制
738
+
739
+ - **crossPageKeys**:`Set<string | number>` 存储所有跨页选中的 key
740
+ - **selectedRowsMap**:`Map<key, record>` 存储对应的行数据
741
+ - **syncSelectedRowKeys()**:每次数据更新后,用 `crossPageKeys ∩ dataSource` 计算当前页应显示的选中状态
742
+ - **diffing 逻辑**:翻页选择时,通过对比前后 `selectedRowKeys` 差集更新 `crossPageKeys`
743
+
744
+ ### 全选所有页
745
+
746
+ | 模式 | 行为 |
747
+ |------|------|
748
+ | DataSource | 直接从内存 `dataSource` 提取所有 key |
749
+ | Request | 调用 `onSelectAllPages(mergedSearchParams)` 回调获取全量 key |
750
+
751
+ > Request 模式下"选择所有页"按钮仅当提供 `onSelectAllPages` 回调时才显示(该操作需后端返回全量 key,是重操作)。DataSource 模式下始终显示。
752
+
753
+ ### 跨页选择菜单
754
+
755
+ 跨页模式下自动注入以下选择菜单项到 ant-design-vue `selections`:
756
+ - 选择当前页(`SELECTION_ALL`)
757
+ - 反选当前页(`SELECTION_INVERT`)
758
+ - 选择所有页(条件显示)
759
+ - 清空所有(`clear-all`)
760
+
761
+ ### 示例
762
+
317
763
  ```vue
318
764
  <BwProTable
319
765
  :row-selection="{ type: 'checkbox' }"
@@ -321,7 +767,6 @@ npm install xlsx
321
767
  enabled: true,
322
768
  maxSelectCount: 100,
323
769
  onSelectAllPages: async (params) => {
324
- // Request 模式下返回所有匹配行的 key
325
770
  const res = await api.getAllKeys(params);
326
771
  return res.keys;
327
772
  },
@@ -329,13 +774,250 @@ npm install xlsx
329
774
  />
330
775
  ```
331
776
 
332
- ## 从源码使用(开发)
777
+ ## Excel 导入导出
778
+
779
+ 需要安装 `xlsx` 依赖(按需动态 `import()` 懒加载,不影响首屏体积):
780
+
781
+ ```bash
782
+ npm install xlsx
783
+ ```
784
+
785
+ ### ExportConfig 导出配置
333
786
 
334
787
  ```ts
335
- import { BwProTable } from 'bw-pro-table';
336
- import 'bw-pro-table/dist/style.css';
788
+ interface ExportConfig {
789
+ filename?: string; // 导出文件名(默认 'export'
790
+ chunkSize?: number; // 分片大小(默认 10000,超出时分片构建+进度条)
791
+ maxRows?: number; // 最大导出行数警告阈值(默认 50000,不阻止仅警告)
792
+ type?: 'xlsx' | 'csv'; // 导出类型
793
+ columnMapping?: Record<string, string>; // 自定义列映射(dataIndex → 表头文字)
794
+ beforeExport?: () => Promise<boolean> | boolean; // 导出前拦截(返回 false 阻止)
795
+ onExportAll?: (params) => Promise<any[]> | any[]; // Request 模式获取全量数据
796
+ onExportSelected?: (keys) => Promise<any[]> | any[]; // 获取选中行完整数据
797
+ }
798
+ ```
799
+
800
+ ### 导出方式
801
+
802
+ | 方法 | 说明 | 数据来源 |
803
+ |------|------|----------|
804
+ | `exportExcel()` | 导出 `.xlsx` | `onExportAll` 回调或当前 `dataSource` |
805
+ | `exportCsv()` | 导出 `.csv`(UTF-8 BOM) | `onExportAll` 回调或当前 `dataSource` |
806
+ | `exportSelected(rows, keys?)` | 导出选中行为 `.xlsx` | 传入的 `rows` 或 `onExportSelected(keys)` 回调 |
807
+
808
+ **导出列处理**:自动排除 `hidden: true` 的列和特殊列(`index`/`action`),表头使用 `columnMapping[dataIndex]` 或 `column.title`。
809
+
810
+ **分片导出**:数据量超过 `chunkSize` 时分片构建 sheet,每 5 片 yield 到主线程避免阻塞,进度通过 `exportProgress` ref 暴露。
811
+
812
+ ### ImportConfig 导入配置
813
+
814
+ ```ts
815
+ interface ImportConfig {
816
+ columnMapping?: Record<string, string>; // 预设列映射(col_X → dataIndex)
817
+ validateRules?: ImportValidateRule[]; // 校验规则(含必填 field 字段)
818
+ beforeImport?: (data) => Promise<any[]> | any[]; // 导入前数据转换
819
+ onImport?: (data) => Promise<void> | void; // 导入提交回调
820
+ }
821
+
822
+ interface ImportValidateRule extends FormRule {
823
+ field: string; // 校验目标字段名(必填)
824
+ }
825
+ ```
826
+
827
+ ### 导入流程
828
+
829
+ 1. **文件选择**:`openFileDialog()` 打开文件选择器(接受 `.xlsx/.xls/.csv`)
830
+ 2. **解析预览**:使用 FileReader + xlsx 解析,第一行为表头,预览最多 10 行(`IMPORT_PREVIEW_MAX_ROWS`)
831
+ 3. **自动列映射**:`autoMapColumns` 匹配 Excel 表头与 `column.title`/`dataIndex`(忽略大小写),用户预设的 `columnMapping` 优先级最高
832
+ 4. **手动映射**:预览弹窗中可手动调整列映射
833
+ 5. **校验**:`runValidation` 基于 `validateRules` + 列级 `formRules` 逐行逐字段校验(含 `validator` 自定义函数)
834
+ 6. **转换**:`beforeImport` 回调转换数据
835
+ 7. **提交**:`onImport` 回调提交数据,成功后 Request 模式自动刷新数据
836
+
837
+ ### 模板下载
838
+
839
+ ```ts
840
+ tableRef.value.downloadTemplate(
841
+ columns
842
+ .filter(c => c.dataIndex && !isSpecialColumn(c))
843
+ .map(c => ({ title: c.title, dataIndex: c.dataIndex }))
844
+ );
845
+ ```
846
+
847
+ 生成仅含表头行的空 `.xlsx` 模板文件。
848
+
849
+ ## 工具栏与全屏
850
+
851
+ ### 工具栏配置
852
+
853
+ ```vue
854
+ <BwProTable
855
+ :toolbar="{
856
+ refresh: true, // 刷新按钮
857
+ density: true, // 密度切换(small/middle/default)
858
+ fullscreen: true, // 全屏切换
859
+ columnSetting: true, // 列设置面板
860
+ export: true, // 导出按钮
861
+ import: true, // 导入按钮
862
+ clearFilter: true, // 清除筛选按钮
863
+ bordered: true, // 显示边框切换 Switch
864
+ striped: true, // 显示斑马纹切换 Switch
865
+ }"
866
+ />
337
867
  ```
338
868
 
869
+ ### 全屏
870
+
871
+ 使用 `screenfull` 库实现全屏切换。全屏时动态切换 `getPopupContainer`,确保 Popover/Dropdown 挂载到全屏容器内(通过 `a-config-provider` 实现)。
872
+
873
+ ### 密度切换
874
+
875
+ 切换表格 `size`(`'small' | 'middle' | 'default'`),变更后自动持久化。
876
+
877
+ ### 边框与斑马纹
878
+
879
+ 工具栏提供边框(`bordered`)和斑马纹(`striped`)Switch 切换按钮,分别通过 `toolbar.bordered`、`toolbar.striped` 控制显隐(默认 `true`,传 `false` 隐藏对应开关)。斑马纹通过 `getRowClassName` 注入 `bw-pro-table-row-striped` 类(偶数行)。
880
+
881
+ ## 国际化
882
+
883
+ ### 优先级链
884
+
885
+ ```
886
+ props.locale > vue-i18n 全局消息 > zh-CN 默认值
887
+ ```
888
+
889
+ ### 使用方式
890
+
891
+ **方式一:`locale` prop 部分覆写**
892
+
893
+ ```vue
894
+ <BwProTable
895
+ :locale="{ toolbar: { refresh: 'Reload', export: 'Export Excel' } }"
896
+ />
897
+ ```
898
+
899
+ **方式二:vue-i18n 集成**
900
+
901
+ 在 vue-i18n 配置中添加 `bwProTable` 命名空间的消息,组件会自动读取当前 locale 对应的消息。
902
+
903
+ **方式三:导入默认中文包**
904
+
905
+ ```ts
906
+ import { zhCN } from 'bw-pro-table';
907
+ ```
908
+
909
+ ### BwProTableLocale 接口
910
+
911
+ 语言包按功能分类:`search`、`toolbar`、`table`、`columnSetting`、`edit`、`export`、`import`、`contextMenu`、`view`、`error`、`selection`、`filter`、`boolean`。完整结构见 `types.ts` 中的 `BwProTableLocale` 接口。
912
+
913
+ ## 独立 Composables
914
+
915
+ 以下 composables 可独立于 `BwProTable` 组件使用(均已从 `index.ts` 导出):
916
+
917
+ | Composable | 说明 |
918
+ |------------|------|
919
+ | `useBwRowEdit` | 行内编辑(草稿/校验/撤销重做/批量编辑) |
920
+ | `useExportExcel` | Excel 导出(xlsx 懒加载/分片/进度) |
921
+ | `useImportExcel` | Excel 导入(解析/映射/校验/预览) |
922
+ | `usePersistence` | 状态持久化(localStorage/sessionStorage/自定义) |
923
+ | `useBwRowSelection` | 行选择管理 |
924
+ | `useQueryParams` | 查询参数管理 |
925
+ | `useColumnHeaderSearch` | 列头搜索状态管理 |
926
+ | `useContextMenu` | 右键菜单状态管理(未集成) |
927
+ | `useDragSort` | 拖拽排序(未集成) |
928
+ | `useBwKeyboard` | 键盘导航(未集成) |
929
+ | `useTableView` | 视图管理(未集成) |
930
+ | `useBwPermission` | 权限过滤(未集成) |
931
+ | `useLocale` / `mergeLocale` | 国际化工具 |
932
+
933
+ 示例:
934
+
935
+ ```ts
936
+ import { useBwRowEdit, useExportExcel } from 'bw-pro-table';
937
+
938
+ const { editingKeys, startRowEdit, saveEdit, undo, redo } = useBwRowEdit({
939
+ dataSource,
940
+ columns,
941
+ rowKey: 'id',
942
+ editConfig,
943
+ onSave: async (record, isNew) => { /* ... */ },
944
+ });
945
+ ```
946
+
947
+ ## 未集成功能说明
948
+
949
+ 以下功能已在 `composables/` 或独立组件中实现,但**尚未在主组件 `BwProTable.vue` 中集成**,运行时不生效。相关类型定义已就绪,可在 types.ts 中查阅。
950
+
951
+ | 功能 | 位置 | 类型定义 | 状态 |
952
+ |------|------|----------|------|
953
+ | 右键菜单 | `BwProTableContextMenu.vue` + `useContextMenu` | `ContextMenuConfig`、`ContextMenuItem` | 组件已实现,主组件未绑定 `contextmenu` 事件 |
954
+ | 高级筛选 | `BwProTableAdvancedFilter.vue` | `AdvancedFilterCondition`、`AdvancedFilterGroup`、`FilterOperator` | 组件已实现,工具栏未添加入口 |
955
+ | 视图管理 | `useTableView` | `ViewConfig` | composable 已实现,工具栏 `viewManager` 按钮未渲染 |
956
+ | 键盘导航 | `useBwKeyboard` | `KEYBOARD_NAV_KEYS` 常量 | composable 已实现,核心组件未挂载 |
957
+ | 权限过滤 | `useBwPermission` | `authority` 字段(`ProColumnType`) | composable 已实现,`displayColumns` 未集成过滤 |
958
+ | 行拖拽 | `useDragSort` | `DragConfig.rowDraggable` | composable 已实现,仅实现了列拖拽 |
959
+
960
+ > 这些功能的类型已导出,配置 prop 可传入但不会产生运行时效果。如需使用,可独立引入对应 composable 自行集成。
961
+
962
+ ## 高级用法
963
+
964
+ ### 自定义搜索控件
965
+
966
+ ```ts
967
+ import { h } from 'vue';
968
+ import { Select as ASelect } from 'ant-design-vue';
969
+
970
+ const columns = [
971
+ {
972
+ title: '状态',
973
+ dataIndex: 'status',
974
+ renderFormItem: ({ model, search }) => {
975
+ return h(ASelect, {
976
+ value: model.status,
977
+ 'onUpdate:value': (v) => { model.status = v; search(); },
978
+ options: [
979
+ { label: '启用', value: 'active' },
980
+ { label: '禁用', value: 'inactive' },
981
+ ],
982
+ });
983
+ },
984
+ },
985
+ ];
986
+ ```
987
+
988
+ ### 自定义渲染器
989
+
990
+ ```ts
991
+ import { defineColumnType } from 'bw-pro-table';
992
+ import { h } from 'vue';
993
+ import { Tag } from 'ant-design-vue';
994
+
995
+ defineColumnType('priority', ({ value }) => {
996
+ const colorMap: Record<string, string> = { high: 'red', medium: 'orange', low: 'green' };
997
+ return h(Tag, { color: colorMap[value] }, value);
998
+ });
999
+
1000
+ // 使用
1001
+ const columns = [{ title: '优先级', dataIndex: 'priority', valueType: 'priority' as any }];
1002
+ ```
1003
+
1004
+ ### 自定义 bodyCell 插槽
1005
+
1006
+ ```vue
1007
+ <BwProTable :columns="columns" :request="request">
1008
+ <template #bodyCell="{ column, record, index, text }">
1009
+ <template v-if="column.dataIndex === 'name'">
1010
+ <a-tag color="blue">{{ text }}</a-tag>
1011
+ </template>
1012
+ <template v-else>
1013
+ {{ text }}
1014
+ </template>
1015
+ </template>
1016
+ </BwProTable>
1017
+ ```
1018
+
1019
+ > 使用 `bodyCell` 插槽会覆盖 valueType 渲染器的自动匹配。
1020
+
339
1021
  ## License
340
1022
 
341
1023
  MIT