vue3-smart-table 1.0.2 → 1.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.
- package/README.md +1209 -169
- package/dist/index.css +1 -0
- package/dist/vue3-smart-table.cjs.js +2 -21
- package/dist/vue3-smart-table.cjs.js.map +1 -0
- package/dist/vue3-smart-table.es.js +560 -397
- package/dist/vue3-smart-table.es.js.map +1 -0
- package/dist/vue3-smart-table.umd.js +3 -0
- package/dist/vue3-smart-table.umd.js.map +1 -0
- package/package.json +19 -6
- package/src/assets/vue.svg +1 -0
- package/src/components/SmartTable/column/index.vue +170 -0
- package/src/components/SmartTable/config.ts +124 -0
- package/src/components/SmartTable/hooks/useOperationColumn.ts +136 -0
- package/src/components/SmartTable/hooks/useTableColumns.ts +143 -0
- package/src/components/SmartTable/index.vue +99 -0
- package/src/components/SmartTable/renderer.ts +173 -0
- package/src/components/SmartTable/renderers/index.ts +307 -0
- package/src/components/SmartTable/renderers/input.vue +31 -0
- package/src/components/SmartTable/renderers/inputNumber.vue +33 -0
- package/src/components/SmartTable/renderers/select.vue +41 -0
- package/src/components/SmartTable/types.ts +229 -0
- package/src/components/SmartTable/utils/path.ts +29 -0
- package/src/index.ts +38 -0
- package/src/types/enhanced.ts +51 -0
- package/dist/vue3-smart-table.css +0 -1
package/README.md
CHANGED
|
@@ -1,45 +1,292 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Vue3 Smart Table
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> 基于 Vue 3 + Element Plus 的高可复用表格组件库 - 插件化架构,支持自定义渲染器
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 特性
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
7
|
+
- **插件化架构** - 支持自定义渲染器,灵活扩展
|
|
8
|
+
- **开箱即用** - 内置 12+ 种常用渲染器
|
|
9
|
+
- **类型安全** - 完整的 TypeScript 类型支持
|
|
10
|
+
- **主题定制** - 使用 CSS 变量,轻松定制主题
|
|
11
|
+
- **按需引入** - Tree-shaking 友好,减小打包体积
|
|
12
|
+
- **性能优化** - 列配置缓存、虚拟滚动支持
|
|
13
|
+
- **简单易用** - 专注于表格功能,不侵入数据管理
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install vue3-smart-table
|
|
19
|
+
# or
|
|
20
|
+
yarn add vue3-smart-table
|
|
21
|
+
# or
|
|
22
|
+
pnpm add vue3-smart-table
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 快速开始
|
|
26
|
+
|
|
27
|
+
### 1️⃣ 安装依赖
|
|
28
|
+
|
|
29
|
+
SmartTable 依赖 Element Plus,需要先安装:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# 安装 SmartTable
|
|
33
|
+
npm install vue3-smart-table
|
|
34
|
+
|
|
35
|
+
# 安装 Element Plus(如果还没安装)
|
|
36
|
+
npm install element-plus
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2️⃣ 完整引入
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// main.ts
|
|
43
|
+
import { createApp } from 'vue'
|
|
44
|
+
import App from './App.vue'
|
|
45
|
+
|
|
46
|
+
// 引入 Element Plus
|
|
47
|
+
import ElementPlus from 'element-plus'
|
|
48
|
+
import 'element-plus/dist/index.css'
|
|
49
|
+
|
|
50
|
+
// 引入 SmartTable
|
|
51
|
+
import SmartTable from 'vue3-smart-table'
|
|
52
|
+
import 'vue3-smart-table/dist/style.css'
|
|
53
|
+
|
|
54
|
+
const app = createApp(App)
|
|
55
|
+
app.use(ElementPlus)
|
|
56
|
+
app.use(SmartTable)
|
|
57
|
+
app.mount('#app')
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3️⃣ 基础使用
|
|
61
|
+
|
|
62
|
+
```vue
|
|
63
|
+
<template>
|
|
64
|
+
<SmartTable :columns="columns" :data="tableData" />
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script setup lang="ts">
|
|
68
|
+
import { ref } from 'vue'
|
|
69
|
+
|
|
70
|
+
// 表格列配置
|
|
71
|
+
const columns = [
|
|
72
|
+
{ key: 'name', label: '姓名', width: 120 },
|
|
73
|
+
{ key: 'age', label: '年龄', width: 80 },
|
|
74
|
+
{ key: 'email', label: '邮箱' }
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
// 表格数据
|
|
78
|
+
const tableData = ref([
|
|
79
|
+
{ id: 1, name: '张三', age: 25, email: 'zhangsan@example.com' },
|
|
80
|
+
{ id: 2, name: '李四', age: 30, email: 'lisi@example.com' },
|
|
81
|
+
{ id: 3, name: '王五', age: 28, email: 'wangwu@example.com' }
|
|
82
|
+
])
|
|
83
|
+
</script>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 4️⃣ 按需引入(推荐)
|
|
87
|
+
|
|
88
|
+
如果你只需要在特定页面使用:
|
|
89
|
+
|
|
90
|
+
```vue
|
|
91
|
+
<template>
|
|
92
|
+
<SmartTable :columns="columns" :data="tableData" />
|
|
93
|
+
</template>
|
|
94
|
+
|
|
95
|
+
<script setup lang="ts">
|
|
96
|
+
import { ref } from 'vue'
|
|
97
|
+
import { SmartTable } from 'vue3-smart-table'
|
|
98
|
+
// 确保在 main.ts 中已经全局引入了样式
|
|
99
|
+
</script>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 5️⃣ 完整示例(带加载状态和分页)
|
|
103
|
+
|
|
104
|
+
```vue
|
|
105
|
+
<template>
|
|
106
|
+
<div>
|
|
107
|
+
<SmartTable
|
|
108
|
+
:columns="columns"
|
|
109
|
+
:data="tableData"
|
|
110
|
+
:loading="loading"
|
|
111
|
+
:pagination="{ page: 1, size: 10, total: total }"
|
|
112
|
+
/>
|
|
113
|
+
|
|
114
|
+
<el-pagination
|
|
115
|
+
v-model:current-page="currentPage"
|
|
116
|
+
v-model:page-size="pageSize"
|
|
117
|
+
:total="total"
|
|
118
|
+
@current-change="handlePageChange"
|
|
119
|
+
@size-change="handleSizeChange"
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
</template>
|
|
123
|
+
|
|
124
|
+
<script setup lang="ts">
|
|
125
|
+
import { ref, onMounted } from 'vue'
|
|
126
|
+
import { SmartTable } from 'vue3-smart-table'
|
|
127
|
+
|
|
128
|
+
const columns = [
|
|
129
|
+
{ key: 'name', label: '姓名' },
|
|
130
|
+
{ key: 'age', label: '年龄' },
|
|
131
|
+
{ key: 'email', label: '邮箱' }
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
const tableData = ref([])
|
|
135
|
+
const loading = ref(false)
|
|
136
|
+
const currentPage = ref(1)
|
|
137
|
+
const pageSize = ref(10)
|
|
138
|
+
const total = ref(0)
|
|
139
|
+
|
|
140
|
+
// 模拟数据获取
|
|
141
|
+
const fetchData = async () => {
|
|
142
|
+
loading.value = true
|
|
143
|
+
try {
|
|
144
|
+
// 实际项目中替换为你的 API 调用
|
|
145
|
+
const response = await fetch(`/api/users?page=${currentPage.value}&size=${pageSize.value}`)
|
|
146
|
+
const result = await response.json()
|
|
147
|
+
tableData.value = result.data
|
|
148
|
+
total.value = result.total
|
|
149
|
+
} finally {
|
|
150
|
+
loading.value = false
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const handlePageChange = (page: number) => {
|
|
155
|
+
currentPage.value = page
|
|
156
|
+
fetchData()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const handleSizeChange = (size: number) => {
|
|
160
|
+
pageSize.value = size
|
|
161
|
+
currentPage.value = 1
|
|
162
|
+
fetchData()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
onMounted(() => {
|
|
166
|
+
fetchData()
|
|
167
|
+
})
|
|
168
|
+
</script>
|
|
169
|
+
```
|
|
12
170
|
|
|
13
171
|
---
|
|
14
172
|
|
|
15
173
|
## 目录结构
|
|
16
174
|
|
|
17
175
|
```txt
|
|
18
|
-
|
|
19
|
-
├─
|
|
20
|
-
│ ├─
|
|
21
|
-
│
|
|
22
|
-
├─
|
|
23
|
-
│ ├─
|
|
24
|
-
│
|
|
25
|
-
├─
|
|
26
|
-
├─ types.ts
|
|
27
|
-
└─
|
|
176
|
+
src/
|
|
177
|
+
├─ components/SmartTable/ # 主组件(完全自包含)
|
|
178
|
+
│ ├─ column/ # TableColumn 子组件
|
|
179
|
+
│ ├─ hooks/ # 组件内部 Hooks(不对外暴露)
|
|
180
|
+
│ ├─ renderers/ # 内置渲染器
|
|
181
|
+
│ ├─ renderer.ts # 渲染器管理器
|
|
182
|
+
│ ├─ config.ts # 全局配置管理
|
|
183
|
+
│ ├─ utils/ # 内部工具函数
|
|
184
|
+
│ ├─ types.ts # 类型定义
|
|
185
|
+
│ └─ index.vue # SmartTable 主组件
|
|
186
|
+
├─ types/ # 类型工具(对外提供)
|
|
187
|
+
└─ index.ts # 入口文件
|
|
28
188
|
```
|
|
29
189
|
|
|
190
|
+
**架构优势**:
|
|
191
|
+
- ✅ SmartTable 组件完全自包含
|
|
192
|
+
- ✅ utils 和 styles 在组件内部
|
|
193
|
+
- ✅ 无外部依赖,易于移植和维护
|
|
194
|
+
- ✅ 清晰的内部和外部边界
|
|
195
|
+
|
|
30
196
|
---
|
|
31
197
|
|
|
32
|
-
##
|
|
198
|
+
## 核心概念
|
|
199
|
+
|
|
200
|
+
### 📦 SmartTable 是什么?
|
|
201
|
+
|
|
202
|
+
SmartTable 是一个**完全自包含的表格组件**,专注于表格渲染和交互功能。它:
|
|
203
|
+
|
|
204
|
+
- ✅ **不侵入你的数据管理** - 你可以用任何方式管理数据(ref、computed、VueUse 等)
|
|
205
|
+
- ✅ **专注于表格功能** - 渲染、编辑、排序、筛选等表格特有的功能
|
|
206
|
+
- ✅ **高度可定制** - 通过渲染器系统扩展任何自定义单元格
|
|
207
|
+
|
|
208
|
+
### 🎯 设计理念
|
|
209
|
+
|
|
210
|
+
与传统表格库不同,SmartTable 遵循以下理念:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// ❌ 传统方式:强制使用组件的数据管理
|
|
214
|
+
import { useTable } from 'some-table-lib' // 被迫使用特定的数据管理方案
|
|
215
|
+
const { data, loading, refresh } = useTable(...)
|
|
216
|
+
|
|
217
|
+
// ✅ SmartTable:你自己管理数据
|
|
218
|
+
import { ref } from 'vue' // 使用你熟悉的方式
|
|
219
|
+
const data = ref([])
|
|
220
|
+
const loading = ref(false)
|
|
221
|
+
// 完全的控制权!
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**为什么这样做?**
|
|
225
|
+
|
|
226
|
+
1. **灵活性** - 不同项目有不同的数据管理需求
|
|
227
|
+
2. **简单性** - 不需要学习特定的数据管理 API
|
|
228
|
+
3. **兼容性** - 可以与任何状态管理方案配合使用
|
|
229
|
+
4. **标准性** - 符合 Vue 生态的最佳实践
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
### 1. 插件化架构
|
|
236
|
+
|
|
237
|
+
支持动态注册自定义渲染器:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { getRendererManager, createFunctionalRenderer } from 'vue3-smart-table'
|
|
241
|
+
import { h } from 'vue'
|
|
242
|
+
|
|
243
|
+
// 创建自定义渲染器
|
|
244
|
+
const myRenderer = createFunctionalRenderer((props) => {
|
|
245
|
+
const val = props.row[props.col.key]
|
|
246
|
+
return h('span', { class: 'custom-renderer' }, `前缀: ${val}`)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
// 注册渲染器
|
|
250
|
+
getRendererManager().register('my-renderer', myRenderer)
|
|
251
|
+
|
|
252
|
+
// 使用
|
|
253
|
+
const columns = [
|
|
254
|
+
{ key: 'name', render: 'my-renderer' }
|
|
255
|
+
]
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 2. 全局配置
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { setSmartTableConfig } from 'vue3-smart-table'
|
|
262
|
+
|
|
263
|
+
setSmartTableConfig({
|
|
264
|
+
defaultPagination: {
|
|
265
|
+
page: 1,
|
|
266
|
+
size: 20
|
|
267
|
+
},
|
|
268
|
+
renderers: {
|
|
269
|
+
// 全局注册自定义渲染器
|
|
270
|
+
'custom-renderer': MyRendererComponent
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## API 文档
|
|
278
|
+
|
|
279
|
+
### SmartTable Props
|
|
33
280
|
|
|
34
281
|
| 属性 | 类型 | 默认值 | 说明 |
|
|
35
282
|
| --- | --- | --- | --- |
|
|
36
|
-
| data | `any[]` | `[]` | 表格数据 |
|
|
37
|
-
| columns | `ColumnConfig[]` | `[]`
|
|
38
|
-
| rowKey | `string` | `'id'` |
|
|
39
|
-
| loading | `boolean` | `false` |
|
|
40
|
-
| permissions | `string[]` | `[]` |
|
|
41
|
-
|
|
|
42
|
-
|
|
|
283
|
+
| data | `any[]` | `[]` | 必需 - 表格数据 |
|
|
284
|
+
| columns | `ColumnConfig[]` | `[]` |必需 - 列配置数组,支持 v-model:columns 双向绑定|
|
|
285
|
+
| rowKey | `string` | `'id'` | 行数据的唯一标识字段 |
|
|
286
|
+
| loading | `boolean` | `false` | 加载状态,显示加载动画 |
|
|
287
|
+
| permissions | `string[]` | `[]` | 当前用户权限列表,用于操作列权限控制 |
|
|
288
|
+
| cacheKey | `string` | - | **列缓存键(推荐)**,如果提供则直接使用,格式:`table_columns_{userId}_{pageKey}` 或自定义 |
|
|
289
|
+
| pagination | `{page: number, size: number}` | - | 序号列计算序号(可选),page:当前页,size:当前页显示条数,不填则默认序号 |
|
|
43
290
|
|
|
44
291
|
> 其余属性将 **透传给 el-table**。
|
|
45
292
|
|
|
@@ -56,6 +303,7 @@ export interface ColumnConfig<R = any> {
|
|
|
56
303
|
visible?: boolean
|
|
57
304
|
inControl?: boolean
|
|
58
305
|
render?: string
|
|
306
|
+
slot?: string // render为slot时可自定slot否则使用key
|
|
59
307
|
|
|
60
308
|
renderProps?: Record<string, any>
|
|
61
309
|
columnProps?: Record<string, any>
|
|
@@ -75,6 +323,7 @@ export interface ColumnConfig<R = any> {
|
|
|
75
323
|
- `selection / index / operation` 为 **核心列**
|
|
76
324
|
- 核心列必须:`inControl = false`
|
|
77
325
|
- 普通列通过 `visible` 控制显示 / 隐藏
|
|
326
|
+
- 可通过 render 使用内置 renderer 或自定义插槽
|
|
78
327
|
|
|
79
328
|
---
|
|
80
329
|
|
|
@@ -106,207 +355,949 @@ export interface ButtonConfig<R = any> {
|
|
|
106
355
|
|
|
107
356
|
---
|
|
108
357
|
|
|
109
|
-
## 4.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
|
121
|
-
|
|
122
|
-
| `
|
|
123
|
-
| `
|
|
124
|
-
| `
|
|
125
|
-
|
|
126
|
-
|
|
358
|
+
## 4. 内置渲染器完整指南
|
|
359
|
+
|
|
360
|
+
SmartTable 提供 13 种内置渲染器,按功能分为 4 类:
|
|
361
|
+
|
|
362
|
+
- **📝 展示型** (7种):html、copy、img、dict、map、formatter、icon
|
|
363
|
+
- **✏️ 编辑型** (3种):input、input-number、select
|
|
364
|
+
- **🔘 操作型** (2种):button、link
|
|
365
|
+
- **🔧 扩展型** (1种):slot
|
|
366
|
+
|
|
367
|
+
### 快速查找表
|
|
368
|
+
|
|
369
|
+
| 渲染器 | 类型 | 配置复杂度 | 支持事件 |
|
|
370
|
+
|--------|------|-----------|---------|
|
|
371
|
+
| `html` | 展示 | ⭐ | ❌ |
|
|
372
|
+
| `copy` | 展示 | ⭐⭐ | ❌ |
|
|
373
|
+
| `img` | 展示 | ⭐⭐ | ❌ |
|
|
374
|
+
| `dict` | 展示 | ⭐⭐⭐ | ❌ |
|
|
375
|
+
| `map` | 展示 | ⭐⭐ | ❌ |
|
|
376
|
+
| `formatter` | 展示 | ⭐⭐ | ❌ |
|
|
377
|
+
| `icon` | 展示 | ⭐⭐ | ❌ |
|
|
378
|
+
| `input` | 编辑 | ⭐⭐ | ✅ |
|
|
379
|
+
| `input-number` | 编辑 | ⭐⭐ | ✅ |
|
|
380
|
+
| `select` | 编辑 | ⭐⭐ | ✅ |
|
|
381
|
+
| `button` | 操作 | ⭐ | ✅ |
|
|
382
|
+
| `link` | 操作 | ⭐ | ✅ |
|
|
383
|
+
| `slot` | 扩展 | ⭐⭐⭐⭐ | ❌ |
|
|
127
384
|
|
|
128
|
-
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
### 📝 展示型渲染器
|
|
388
|
+
|
|
389
|
+
用于数据展示,不涉及用户交互。
|
|
390
|
+
|
|
391
|
+
#### 1. `html` - HTML 内容渲染
|
|
392
|
+
|
|
393
|
+
**功能**:渲染 HTML 内容,支持多行文本截断(最多 2 行)。
|
|
394
|
+
|
|
395
|
+
**配置**:
|
|
396
|
+
```typescript
|
|
129
397
|
{
|
|
130
|
-
key: '
|
|
131
|
-
label: '
|
|
132
|
-
render: '
|
|
398
|
+
key: 'description',
|
|
399
|
+
label: '描述',
|
|
400
|
+
render: 'html',
|
|
401
|
+
renderProps: {
|
|
402
|
+
style?: string // 自定义样式
|
|
403
|
+
class?: string // 自定义类名
|
|
404
|
+
}
|
|
133
405
|
}
|
|
134
406
|
```
|
|
135
407
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
408
|
+
**示例**:
|
|
409
|
+
```typescript
|
|
410
|
+
const columns = [
|
|
411
|
+
{
|
|
412
|
+
key: 'content',
|
|
413
|
+
label: '商品描述',
|
|
414
|
+
render: 'html'
|
|
415
|
+
}
|
|
416
|
+
]
|
|
417
|
+
|
|
418
|
+
const tableData = [
|
|
419
|
+
{
|
|
420
|
+
content: '<p>这是一段<strong>加粗</strong>的文本</p>'
|
|
421
|
+
}
|
|
422
|
+
]
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**效果**:
|
|
426
|
+
- 自动截断为 2 行
|
|
427
|
+
- 支持富文本 HTML
|
|
428
|
+
- 超出部分显示省略号
|
|
139
429
|
|
|
140
430
|
---
|
|
141
|
-
|
|
142
|
-
|
|
431
|
+
|
|
432
|
+
#### 2. `copy` - 可复制文本
|
|
433
|
+
|
|
434
|
+
**功能**:hover 显示复制按钮,点击复制文本到剪贴板。
|
|
435
|
+
|
|
436
|
+
**配置**:
|
|
437
|
+
```typescript
|
|
143
438
|
{
|
|
144
|
-
key: '
|
|
145
|
-
label: '
|
|
146
|
-
render: '
|
|
147
|
-
columnProps: { minWidth: 150},
|
|
439
|
+
key: 'code',
|
|
440
|
+
label: '编号',
|
|
441
|
+
render: 'copy',
|
|
148
442
|
renderProps: {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
443
|
+
iconColor?: string // 图标颜色,默认 '#409EFF'
|
|
444
|
+
copyTitle?: string // 复制提示文本,默认 '复制'
|
|
445
|
+
successText?: string // 成功提示,默认 '复制成功'
|
|
446
|
+
errorText?: string // 失败提示,默认 '复制失败'
|
|
153
447
|
}
|
|
154
|
-
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**示例**:
|
|
452
|
+
```typescript
|
|
453
|
+
const columns = [
|
|
454
|
+
{
|
|
455
|
+
key: 'orderNo',
|
|
456
|
+
label: '订单号',
|
|
457
|
+
render: 'copy',
|
|
458
|
+
renderProps: {
|
|
459
|
+
iconColor: '#67C23A',
|
|
460
|
+
successText: '订单号已复制',
|
|
461
|
+
errorText: '复制失败,请重试'
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
]
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**效果**:
|
|
468
|
+
- hover 显示复制按钮图标
|
|
469
|
+
- 点击自动复制到剪贴板
|
|
470
|
+
- 使用 ElMessage 提示结果
|
|
471
|
+
- 支持单行文本截断
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
#### 3. `img` - 图片预览
|
|
476
|
+
|
|
477
|
+
**功能**:图片展示,支持单图/多图预览。
|
|
478
|
+
|
|
479
|
+
**配置**:
|
|
480
|
+
```typescript
|
|
155
481
|
{
|
|
156
|
-
key: '
|
|
157
|
-
label: '
|
|
482
|
+
key: 'avatar',
|
|
483
|
+
label: '头像',
|
|
158
484
|
render: 'img',
|
|
159
|
-
columnProps: { minWidth: 150},
|
|
160
485
|
renderProps: {
|
|
161
|
-
width
|
|
162
|
-
height
|
|
486
|
+
width?: string | number // 图片宽度,默认 '80px'
|
|
487
|
+
height?: string | number // 图片高度,默认 '80px'
|
|
488
|
+
fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down' // 适应方式
|
|
489
|
+
previewSrcList?: string[] // 预览图片列表(可选)
|
|
490
|
+
placeholder?: string // 无图片时的占位文本
|
|
491
|
+
style?: string // 自定义样式
|
|
163
492
|
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
**示例 - 单图**:
|
|
497
|
+
```typescript
|
|
498
|
+
const columns = [
|
|
499
|
+
{
|
|
500
|
+
key: 'avatar',
|
|
501
|
+
label: '头像',
|
|
502
|
+
render: 'img',
|
|
503
|
+
renderProps: {
|
|
504
|
+
width: '60px',
|
|
505
|
+
height: '60px',
|
|
506
|
+
fit: 'cover'
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
]
|
|
510
|
+
|
|
511
|
+
const tableData = [
|
|
512
|
+
{ avatar: 'https://example.com/avatar.jpg' }
|
|
513
|
+
]
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**示例 - 多图**:
|
|
517
|
+
```typescript
|
|
518
|
+
const columns = [
|
|
519
|
+
{
|
|
520
|
+
key: 'gallery',
|
|
521
|
+
label: '相册',
|
|
522
|
+
render: 'img',
|
|
523
|
+
renderProps: {
|
|
524
|
+
width: '80px',
|
|
525
|
+
height: '80px'
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
]
|
|
529
|
+
|
|
530
|
+
const tableData = [
|
|
531
|
+
{
|
|
168
532
|
gallery: [
|
|
169
|
-
'https://
|
|
170
|
-
'https://
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
533
|
+
'https://example.com/img1.jpg',
|
|
534
|
+
'https://example.com/img2.jpg',
|
|
535
|
+
'https://example.com/img3.jpg'
|
|
536
|
+
]
|
|
537
|
+
}
|
|
538
|
+
]
|
|
174
539
|
```
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
-
|
|
178
|
-
-
|
|
540
|
+
|
|
541
|
+
**效果**:
|
|
542
|
+
- **单图**:直接显示,点击预览
|
|
543
|
+
- **多图**:显示第一张 + 数量标记(如:+2),点击预览全部
|
|
544
|
+
- 支持 Element Plus Image 的所有预览功能
|
|
179
545
|
- 无图片时显示占位符或空内容
|
|
180
546
|
|
|
181
|
-
|
|
547
|
+
---
|
|
182
548
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
{
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
render: "map",
|
|
549
|
+
#### 4. `dict` - 字典标签映射
|
|
550
|
+
|
|
551
|
+
**功能**:将值映射为标签显示(使用 ElTag)。
|
|
552
|
+
|
|
553
|
+
**配置**:
|
|
554
|
+
```typescript
|
|
555
|
+
{
|
|
556
|
+
key: 'status',
|
|
557
|
+
label: '状态',
|
|
558
|
+
render: 'dict',
|
|
194
559
|
renderProps: {
|
|
195
|
-
options:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
560
|
+
options: Array<{ // 字典配置(必需)
|
|
561
|
+
label: string // 显示文本
|
|
562
|
+
value: any // 值
|
|
563
|
+
listClass?: string // ElTag 类型:'primary' | 'success' | 'warning' | 'danger' | 'info'
|
|
564
|
+
cssClass?: string // 自定义类名
|
|
565
|
+
}>
|
|
566
|
+
showValue?: boolean // 是否显示未匹配的值,默认 false
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
```
|
|
199
570
|
|
|
571
|
+
**示例 - 单值**:
|
|
572
|
+
```typescript
|
|
573
|
+
const columns = [
|
|
574
|
+
{
|
|
575
|
+
key: 'status',
|
|
576
|
+
label: '状态',
|
|
577
|
+
render: 'dict',
|
|
578
|
+
renderProps: {
|
|
579
|
+
options: [
|
|
580
|
+
{ label: '启用', value: 1, listClass: 'success' },
|
|
581
|
+
{ label: '禁用', value: 0, listClass: 'danger' },
|
|
582
|
+
{ label: '审核中', value: 2, listClass: 'warning' }
|
|
583
|
+
]
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
]
|
|
587
|
+
|
|
588
|
+
const tableData = [
|
|
589
|
+
{ status: 1 } // 显示:[启用]
|
|
590
|
+
]
|
|
200
591
|
```
|
|
201
|
-
- 根据值映射显示文本
|
|
202
|
-
- 不匹配则显示空字符串
|
|
203
592
|
|
|
204
|
-
|
|
593
|
+
**示例 - 多值**:
|
|
594
|
+
```typescript
|
|
595
|
+
const columns = [
|
|
596
|
+
{
|
|
597
|
+
key: 'tags',
|
|
598
|
+
label: '标签',
|
|
599
|
+
render: 'dict',
|
|
600
|
+
renderProps: {
|
|
601
|
+
options: [
|
|
602
|
+
{ label: '重要', value: 'important', listClass: 'danger' },
|
|
603
|
+
{ label: '紧急', value: 'urgent', listClass: 'warning' }
|
|
604
|
+
],
|
|
605
|
+
showValue: true // 显示未匹配的值
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
]
|
|
205
609
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
{ label: '启用', value: 1, listClass: 'primary' },
|
|
209
|
-
{ label: '禁用', value: 0, listClass: 'warning' }
|
|
610
|
+
const tableData = [
|
|
611
|
+
{ tags: ['important', 'urgent'] } // 显示:[重要] [紧急]
|
|
210
612
|
]
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
**效果**:
|
|
616
|
+
- 单值映射为单个标签
|
|
617
|
+
- 多值映射为多个标签
|
|
618
|
+
- 未匹配的值根据 `showValue` 决定是否显示
|
|
619
|
+
- 支持自定义标签颜色和样式
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
#### 5. `map` - 键值映射
|
|
624
|
+
|
|
625
|
+
**功能**:简单的 key-value 映射。
|
|
211
626
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
627
|
+
**配置**:
|
|
628
|
+
```typescript
|
|
629
|
+
{
|
|
630
|
+
key: 'gender',
|
|
631
|
+
label: '性别',
|
|
632
|
+
render: 'map',
|
|
217
633
|
renderProps: {
|
|
218
|
-
options:
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
},
|
|
634
|
+
options: Record<string | number, any> // 映射配置(必需)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
222
637
|
```
|
|
638
|
+
|
|
639
|
+
**示例**:
|
|
640
|
+
```typescript
|
|
641
|
+
const columns = [
|
|
642
|
+
{
|
|
643
|
+
key: 'gender',
|
|
644
|
+
label: '性别',
|
|
645
|
+
render: 'map',
|
|
646
|
+
renderProps: {
|
|
647
|
+
options: {
|
|
648
|
+
1: '男',
|
|
649
|
+
2: '女',
|
|
650
|
+
0: '未知'
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
]
|
|
655
|
+
|
|
656
|
+
const tableData = [
|
|
657
|
+
{ gender: 1 }, // 显示:男
|
|
658
|
+
{ gender: 2 }, // 显示:女
|
|
659
|
+
{ gender: 99 } // 显示:(空)
|
|
660
|
+
]
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
**效果**:
|
|
664
|
+
- 根据值映射显示对应文本
|
|
665
|
+
- 未匹配的值显示空字符串
|
|
666
|
+
- 比 `dict` 更简单,适合不需要标签样式的场景
|
|
667
|
+
|
|
223
668
|
---
|
|
224
|
-
- 支持多选值
|
|
225
|
-
- 可通过 showValue 显示未匹配的值
|
|
226
|
-
- 可自定义 tag 类型(listClass)
|
|
227
669
|
|
|
228
|
-
|
|
229
|
-
|
|
670
|
+
#### 6. `formatter` - 自定义格式化
|
|
671
|
+
|
|
672
|
+
**功能**:使用自定义函数格式化显示。
|
|
673
|
+
|
|
674
|
+
**配置**:
|
|
675
|
+
```typescript
|
|
230
676
|
{
|
|
231
677
|
key: 'price',
|
|
232
678
|
label: '价格',
|
|
233
679
|
render: 'formatter',
|
|
234
|
-
formatter: (
|
|
680
|
+
formatter: (value: any, row: any) => string // 格式化函数
|
|
235
681
|
}
|
|
236
682
|
```
|
|
237
|
-
- 使用自定义函数格式化显示内容
|
|
238
683
|
|
|
239
|
-
|
|
684
|
+
**示例**:
|
|
685
|
+
```typescript
|
|
686
|
+
const columns = [
|
|
687
|
+
{
|
|
688
|
+
key: 'price',
|
|
689
|
+
label: '价格',
|
|
690
|
+
render: 'formatter',
|
|
691
|
+
formatter: (value, row) => {
|
|
692
|
+
return `¥${Number(value).toFixed(2)}`
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
key: 'date',
|
|
697
|
+
label: '日期',
|
|
698
|
+
render: 'formatter',
|
|
699
|
+
formatter: (value) => {
|
|
700
|
+
return new Date(value).toLocaleDateString('zh-CN')
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
]
|
|
240
704
|
|
|
241
|
-
|
|
705
|
+
const tableData = [
|
|
706
|
+
{ price: 99.9, date: '2024-01-01' }
|
|
707
|
+
]
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
**效果**:
|
|
711
|
+
- 完全自定义格式化逻辑
|
|
712
|
+
- 可以访问当前行数据
|
|
713
|
+
- 适合复杂的格式化需求
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
717
|
+
#### 7. `icon` - 图标渲染
|
|
718
|
+
|
|
719
|
+
**功能**:支持多种图标格式。
|
|
720
|
+
|
|
721
|
+
**配置**:
|
|
722
|
+
```typescript
|
|
723
|
+
{
|
|
724
|
+
key: 'icon',
|
|
725
|
+
label: '图标',
|
|
726
|
+
render: 'icon',
|
|
727
|
+
renderProps: {
|
|
728
|
+
style?: string // 自定义样式
|
|
729
|
+
size?: number // 图标大小(像素)
|
|
730
|
+
class?: string // 自定义类名
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
**示例**:
|
|
736
|
+
```typescript
|
|
737
|
+
const columns = [
|
|
738
|
+
{
|
|
739
|
+
key: 'avatarIcon',
|
|
740
|
+
label: '头像',
|
|
741
|
+
render: 'icon',
|
|
742
|
+
renderProps: {
|
|
743
|
+
style: 'font-size: 32px; color: #409EFF'
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
]
|
|
747
|
+
|
|
748
|
+
const tableData = [
|
|
749
|
+
// 1. 网络图片
|
|
750
|
+
{ icon: 'https://example.com/icon.png' },
|
|
751
|
+
|
|
752
|
+
// 2. SVG 源码
|
|
753
|
+
{ icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">...</svg>' },
|
|
754
|
+
|
|
755
|
+
// 3. iconfont 类名
|
|
756
|
+
{ icon: 'iconfont icon-user' }
|
|
757
|
+
]
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
**效果**:
|
|
761
|
+
- 自动识别图标类型
|
|
762
|
+
- 网络图片:显示为 ElImage
|
|
763
|
+
- SVG:直接渲染
|
|
764
|
+
- iconfont:应用类名样式
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
768
|
+
### ✏️ 编辑型渲染器
|
|
769
|
+
|
|
770
|
+
支持单元格编辑,触发 `cellChange`、`cellBlur`、`cellEnter` 事件。
|
|
771
|
+
|
|
772
|
+
#### 8. `input` - 可编辑输入框
|
|
773
|
+
|
|
774
|
+
**配置**:
|
|
775
|
+
```typescript
|
|
776
|
+
{
|
|
777
|
+
key: 'username',
|
|
778
|
+
label: '用户名',
|
|
779
|
+
render: 'input',
|
|
780
|
+
renderProps: {
|
|
781
|
+
placeholder?: string // 占位文本,默认 ''
|
|
782
|
+
size?: 'large' | 'default' | 'small' // 尺寸,默认 'small'
|
|
783
|
+
clearable?: boolean // 是否可清空,默认 true
|
|
784
|
+
// ... 其他 ElInput 属性
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
**示例**:
|
|
790
|
+
```typescript
|
|
791
|
+
const columns = [
|
|
792
|
+
{
|
|
793
|
+
key: 'username',
|
|
794
|
+
label: '用户名',
|
|
795
|
+
render: 'input',
|
|
796
|
+
renderProps: {
|
|
797
|
+
placeholder: '请输入用户名',
|
|
798
|
+
clearable: true
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
]
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
**事件处理**:
|
|
805
|
+
```vue
|
|
806
|
+
<script setup>
|
|
807
|
+
const handleCellChange = (row, col) => {
|
|
808
|
+
console.log('值已修改:', row[col.key])
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const handleCellBlur = (row, col) => {
|
|
812
|
+
console.log('失去焦点')
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const handleCellEnter = (row, col) => {
|
|
816
|
+
console.log('回车确认')
|
|
817
|
+
}
|
|
818
|
+
</script>
|
|
819
|
+
|
|
820
|
+
<template>
|
|
821
|
+
<SmartTable
|
|
822
|
+
:columns="columns"
|
|
823
|
+
:data="tableData"
|
|
824
|
+
@cellChange="handleCellChange"
|
|
825
|
+
@cellBlur="handleCellBlur"
|
|
826
|
+
@cellEnter="handleCellEnter"
|
|
827
|
+
/>
|
|
828
|
+
</template>
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
#### 9. `input-number` - 可编辑数字输入框
|
|
834
|
+
|
|
835
|
+
**配置**:
|
|
836
|
+
```typescript
|
|
242
837
|
{
|
|
243
838
|
key: 'age',
|
|
244
839
|
label: '年龄',
|
|
245
840
|
render: 'input-number',
|
|
246
|
-
renderProps: {
|
|
841
|
+
renderProps: {
|
|
842
|
+
min?: number // 最小值
|
|
843
|
+
max?: number // 最大值
|
|
844
|
+
step?: number // 步长
|
|
845
|
+
precision?: number // 精度
|
|
846
|
+
size?: 'large' | 'default' | 'small'
|
|
847
|
+
controls?: boolean // 是否显示增减按钮
|
|
848
|
+
// ... 其他 ElInputNumber 属性
|
|
849
|
+
}
|
|
247
850
|
}
|
|
248
851
|
```
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
852
|
+
|
|
853
|
+
**示例**:
|
|
854
|
+
```typescript
|
|
855
|
+
const columns = [
|
|
856
|
+
{
|
|
857
|
+
key: 'orderNum',
|
|
858
|
+
label: '序号',
|
|
859
|
+
render: 'input-number',
|
|
860
|
+
renderProps: {
|
|
861
|
+
min: 0,
|
|
862
|
+
max: 100,
|
|
863
|
+
step: 1,
|
|
864
|
+
controls: false // 隐藏增减按钮
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
]
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
---
|
|
871
|
+
|
|
872
|
+
#### 10. `select` - 可编辑下拉选择
|
|
873
|
+
|
|
874
|
+
**配置**:
|
|
875
|
+
```typescript
|
|
256
876
|
{
|
|
257
|
-
key: '
|
|
258
|
-
label: '
|
|
259
|
-
render: '
|
|
260
|
-
renderProps: {
|
|
877
|
+
key: 'status',
|
|
878
|
+
label: '状态',
|
|
879
|
+
render: 'select',
|
|
880
|
+
renderProps: {
|
|
881
|
+
options: Array<{ // 选项配置(必需)
|
|
882
|
+
label: string
|
|
883
|
+
value: any
|
|
884
|
+
}>
|
|
885
|
+
placeholder?: string // 占位文本,默认 '请选择'
|
|
886
|
+
size?: 'large' | 'default' | 'small'
|
|
887
|
+
clearable?: boolean // 是否可清空
|
|
888
|
+
// ... 其他 ElSelect 属性
|
|
889
|
+
}
|
|
261
890
|
}
|
|
262
891
|
```
|
|
263
|
-
- 支持网络图片 URL
|
|
264
|
-
- 支持 svg 字符串
|
|
265
|
-
- 支持 iconfont class
|
|
266
892
|
|
|
267
|
-
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
{
|
|
893
|
+
**示例**:
|
|
894
|
+
```typescript
|
|
895
|
+
const columns = [
|
|
896
|
+
{
|
|
897
|
+
key: 'role',
|
|
898
|
+
label: '角色',
|
|
899
|
+
render: 'select',
|
|
900
|
+
renderProps: {
|
|
901
|
+
placeholder: '请选择角色',
|
|
902
|
+
clearable: true,
|
|
903
|
+
options: [
|
|
904
|
+
{ label: '管理员', value: 'admin' },
|
|
905
|
+
{ label: '普通用户', value: 'user' },
|
|
906
|
+
{ label: '访客', value: 'guest' }
|
|
907
|
+
]
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
]
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
---
|
|
914
|
+
|
|
915
|
+
### 🔘 操作型渲染器
|
|
916
|
+
|
|
917
|
+
用于触发操作或跳转。
|
|
918
|
+
|
|
919
|
+
#### 11. `button` - 操作按钮
|
|
920
|
+
|
|
921
|
+
**配置**:
|
|
922
|
+
```typescript
|
|
923
|
+
{
|
|
924
|
+
key: 'action',
|
|
925
|
+
label: '操作',
|
|
926
|
+
render: 'button',
|
|
927
|
+
renderProps: {
|
|
928
|
+
label?: string // 按钮文本
|
|
929
|
+
type?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'text'
|
|
930
|
+
size?: 'large' | 'default' | 'small'
|
|
931
|
+
disabled?: boolean
|
|
932
|
+
// ... 其他 ElButton 属性
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
```
|
|
271
936
|
|
|
937
|
+
**示例**:
|
|
938
|
+
```typescript
|
|
939
|
+
const columns = [
|
|
940
|
+
{
|
|
941
|
+
key: 'edit',
|
|
942
|
+
label: '编辑',
|
|
943
|
+
render: 'button',
|
|
944
|
+
renderProps: {
|
|
945
|
+
label: '编辑',
|
|
946
|
+
type: 'primary',
|
|
947
|
+
size: 'small'
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
]
|
|
272
951
|
```
|
|
273
|
-
- 支持事件:
|
|
274
|
-
- cellClick(row, col) 点击事件
|
|
275
952
|
|
|
953
|
+
**事件处理**:
|
|
954
|
+
```vue
|
|
955
|
+
<script setup>
|
|
956
|
+
const handleCellClick = (row, col) => {
|
|
957
|
+
if (col.key === 'edit') {
|
|
958
|
+
console.log('编辑行:', row)
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
</script>
|
|
276
962
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
963
|
+
<template>
|
|
964
|
+
<SmartTable
|
|
965
|
+
:columns="columns"
|
|
966
|
+
:data="tableData"
|
|
967
|
+
@cellClick="handleCellClick"
|
|
968
|
+
/>
|
|
969
|
+
</template>
|
|
280
970
|
```
|
|
281
971
|
|
|
282
|
-
|
|
972
|
+
---
|
|
283
973
|
|
|
284
|
-
|
|
285
|
-
- ✅ **只缓存 visible**
|
|
286
|
-
- ❌ 不缓存 render / action / 函数
|
|
287
|
-
- ❌ 不侵入 store / 登录体系
|
|
974
|
+
#### 12. `link` - 链接
|
|
288
975
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
976
|
+
**配置**:
|
|
977
|
+
```typescript
|
|
978
|
+
{
|
|
979
|
+
key: 'detail',
|
|
980
|
+
label: '详情',
|
|
981
|
+
render: 'link',
|
|
982
|
+
renderProps: {
|
|
983
|
+
label?: string // 链接文本
|
|
984
|
+
href: string // 链接地址(必需)
|
|
985
|
+
blank?: boolean // 是否新窗口打开,默认 false
|
|
986
|
+
style?: string // 自定义样式
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
**示例**:
|
|
992
|
+
```typescript
|
|
993
|
+
const columns = [
|
|
994
|
+
{
|
|
995
|
+
key: 'url',
|
|
996
|
+
label: '查看',
|
|
997
|
+
render: 'link',
|
|
998
|
+
renderProps: {
|
|
999
|
+
label: '查看详情',
|
|
1000
|
+
href: 'https://example.com',
|
|
1001
|
+
blank: true,
|
|
1002
|
+
style: 'color: #409EFF'
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
]
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
---
|
|
1009
|
+
|
|
1010
|
+
### 🔧 扩展型渲染器
|
|
1011
|
+
|
|
1012
|
+
用于自定义复杂场景。
|
|
1013
|
+
|
|
1014
|
+
#### 13. `slot` - 自定义插槽
|
|
1015
|
+
|
|
1016
|
+
**功能**:使用 Vue 插槽完全自定义列内容。
|
|
1017
|
+
|
|
1018
|
+
**配置**:
|
|
1019
|
+
```typescript
|
|
1020
|
+
{
|
|
1021
|
+
key: 'attachments',
|
|
1022
|
+
label: '附件',
|
|
1023
|
+
render: 'slot',
|
|
1024
|
+
slot?: string // 插槽名称,默认使用 key
|
|
1025
|
+
}
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
**示例**:
|
|
1029
|
+
```vue
|
|
1030
|
+
<script setup>
|
|
1031
|
+
const columns = [
|
|
1032
|
+
{
|
|
1033
|
+
key: 'attachments',
|
|
1034
|
+
label: '附件列表',
|
|
1035
|
+
render: 'slot',
|
|
1036
|
+
slot: 'attachments'
|
|
1037
|
+
}
|
|
1038
|
+
]
|
|
1039
|
+
|
|
1040
|
+
const tableData = ref([
|
|
1041
|
+
{
|
|
1042
|
+
id: 1,
|
|
1043
|
+
attachments: [
|
|
1044
|
+
{ name: 'file1.pdf', url: '/files/file1.pdf' },
|
|
1045
|
+
{ name: 'file2.jpg', url: '/files/file2.jpg' }
|
|
1046
|
+
]
|
|
1047
|
+
}
|
|
1048
|
+
])
|
|
1049
|
+
|
|
1050
|
+
const download = (url) => {
|
|
1051
|
+
console.log('下载:', url)
|
|
1052
|
+
}
|
|
1053
|
+
</script>
|
|
1054
|
+
|
|
1055
|
+
<template>
|
|
1056
|
+
<SmartTable :columns="columns" :data="tableData">
|
|
1057
|
+
<template #attachments="{ row }">
|
|
1058
|
+
<div v-for="(file, index) in row.attachments" :key="index">
|
|
1059
|
+
<el-button type="text" @click="download(file.url)">
|
|
1060
|
+
{{ file.name }}
|
|
1061
|
+
</el-button>
|
|
1062
|
+
</div>
|
|
1063
|
+
</template>
|
|
1064
|
+
</SmartTable>
|
|
1065
|
+
</template>
|
|
294
1066
|
```
|
|
295
1067
|
|
|
296
|
-
|
|
297
|
-
-
|
|
1068
|
+
**效果**:
|
|
1069
|
+
- 完全自定义列内容
|
|
1070
|
+
- 访问完整的行数据
|
|
1071
|
+
- 可以包含复杂的交互逻辑
|
|
1072
|
+
|
|
1073
|
+
---
|
|
1074
|
+
|
|
1075
|
+
### TypeScript 类型支持
|
|
1076
|
+
|
|
1077
|
+
所有渲染器的 `renderProps` 都有完整的 TypeScript 类型定义:
|
|
1078
|
+
|
|
1079
|
+
```typescript
|
|
1080
|
+
import type { ColumnConfig, RendererPropsMap } from 'vue3-smart-table'
|
|
1081
|
+
|
|
1082
|
+
// 类型安全
|
|
1083
|
+
const column: ColumnConfig = {
|
|
1084
|
+
key: 'status',
|
|
1085
|
+
label: '状态',
|
|
1086
|
+
render: 'dict',
|
|
1087
|
+
renderProps: {
|
|
1088
|
+
options: [ // ✅ 类型提示和检查
|
|
1089
|
+
{ label: '启用', value: 1 }
|
|
1090
|
+
]
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// 提取特定渲染器的 props 类型
|
|
1095
|
+
type DictProps = RendererPropsMap['dict']
|
|
1096
|
+
const dictConfig: DictProps = {
|
|
1097
|
+
options: [],
|
|
1098
|
+
showValue: true
|
|
1099
|
+
}
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
---
|
|
1103
|
+
|
|
1104
|
+
### 最佳实践
|
|
1105
|
+
|
|
1106
|
+
1. **选择合适的渲染器**
|
|
1107
|
+
- 简单映射 → `map`
|
|
1108
|
+
- 需要标签样式 → `dict`
|
|
1109
|
+
- 复杂逻辑 → `formatter`
|
|
1110
|
+
- 自定义内容 → `slot`
|
|
1111
|
+
|
|
1112
|
+
2. **性能优化**
|
|
1113
|
+
- 大量数据时避免 `formatter`,使用 `map` 或 `dict`
|
|
1114
|
+
- 复杂自定义内容优先使用 `slot`
|
|
1115
|
+
|
|
1116
|
+
3. **用户体验**
|
|
1117
|
+
- 图片显示添加 `placeholder`
|
|
1118
|
+
- 复制功能添加友好的提示文本
|
|
1119
|
+
- 编辑单元格添加合适的 `placeholder`
|
|
298
1120
|
|
|
299
1121
|
---
|
|
300
|
-
## 6. 事件
|
|
301
|
-
- 支持类型:input / number / select
|
|
302
|
-
- 支持事件:
|
|
303
|
-
- cellChange(row, col) 值变化
|
|
304
|
-
- cellBlur(row, col) 失去焦点
|
|
305
|
-
- cellEnter(row, col) 回车事件(input)
|
|
306
|
-
- cellClick(row, col) 点击事件
|
|
307
1122
|
|
|
1123
|
+
## 5. 事件
|
|
308
1124
|
|
|
309
|
-
|
|
1125
|
+
### 单元格编辑事件
|
|
1126
|
+
支持类型:input / number / select
|
|
1127
|
+
|
|
1128
|
+
- `cellChange(row, col)` - 值变化
|
|
1129
|
+
- `cellBlur(row, col)` - 失去焦点
|
|
1130
|
+
- `cellEnter(row, col)` - 回车事件(input)
|
|
1131
|
+
- `cellClick(row, col)` - 点击事件(button/link)
|
|
1132
|
+
|
|
1133
|
+
### 完整事件列表
|
|
1134
|
+
|
|
1135
|
+
```typescript
|
|
1136
|
+
interface SmartTableEmits {
|
|
1137
|
+
(e: 'cellChange', row: any, col: any): void
|
|
1138
|
+
(e: 'cellBlur', row: any, col: any): void
|
|
1139
|
+
(e: 'cellEnter', row: any, col: any): void
|
|
1140
|
+
(e: 'cellClick', row: any, col: any): void
|
|
1141
|
+
(e: 'selectionChange', selection: any[]): void
|
|
1142
|
+
(e: 'sortChange', sort: any): void
|
|
1143
|
+
// ... Element Plus Table 事件透传
|
|
1144
|
+
}
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
---
|
|
1148
|
+
|
|
1149
|
+
## 6. 按需引入
|
|
1150
|
+
|
|
1151
|
+
### 只引入需要的部分
|
|
1152
|
+
|
|
1153
|
+
```typescript
|
|
1154
|
+
// 只引入类型
|
|
1155
|
+
import type { ColumnConfig, ButtonConfig } from 'vue3-smart-table'
|
|
1156
|
+
|
|
1157
|
+
// 只引入渲染器工具
|
|
1158
|
+
import {
|
|
1159
|
+
getRendererManager,
|
|
1160
|
+
createFunctionalRenderer,
|
|
1161
|
+
wrapSFCComponent
|
|
1162
|
+
} from 'vue3-smart-table'
|
|
1163
|
+
|
|
1164
|
+
// 只引入类型工具
|
|
1165
|
+
import { defineColumn } from 'vue3-smart-table'
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
### Tree-shaking 支持
|
|
1169
|
+
|
|
1170
|
+
库已优化为支持 Tree-shaking,只会打包你实际使用的代码:
|
|
1171
|
+
|
|
1172
|
+
```typescript
|
|
1173
|
+
// ✅ 只会打包 SmartTable 组件
|
|
1174
|
+
import { SmartTable } from 'vue3-smart-table'
|
|
1175
|
+
|
|
1176
|
+
// ✅ 只会打包渲染器管理器
|
|
1177
|
+
import { getRendererManager, createFunctionalRenderer } from 'vue3-smart-table'
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
---
|
|
1181
|
+
|
|
1182
|
+
## 7. 高级用法
|
|
1183
|
+
|
|
1184
|
+
### 自定义渲染器(3种方式)
|
|
1185
|
+
|
|
1186
|
+
#### 方式一:函数式渲染器
|
|
1187
|
+
|
|
1188
|
+
```typescript
|
|
1189
|
+
import { createFunctionalRenderer } from 'vue3-smart-table'
|
|
1190
|
+
import { h } from 'vue'
|
|
1191
|
+
|
|
1192
|
+
const statusRenderer = createFunctionalRenderer((props) => {
|
|
1193
|
+
const val = props.row[props.col.key]
|
|
1194
|
+
const color = val === 1 ? 'green' : 'red'
|
|
1195
|
+
return h('span', { style: { color } }, val === 1 ? '启用' : '禁用')
|
|
1196
|
+
})
|
|
1197
|
+
|
|
1198
|
+
getRendererManager().register('status', statusRenderer)
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
#### 方式二:SFC 组件
|
|
1202
|
+
|
|
1203
|
+
```vue
|
|
1204
|
+
<!-- StatusRenderer.vue -->
|
|
1205
|
+
<template>
|
|
1206
|
+
<el-tag :type="statusType">{{ statusText }}</el-tag>
|
|
1207
|
+
</template>
|
|
1208
|
+
|
|
1209
|
+
<script setup lang="ts">
|
|
1210
|
+
import { computed } from 'vue'
|
|
1211
|
+
|
|
1212
|
+
const props = defineProps<{
|
|
1213
|
+
row: any
|
|
1214
|
+
col: any
|
|
1215
|
+
}>()
|
|
1216
|
+
|
|
1217
|
+
const statusType = computed(() =>
|
|
1218
|
+
props.row[props.col.key] === 1 ? 'success' : 'danger'
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
const statusText = computed(() =>
|
|
1222
|
+
props.row[props.col.key] === 1 ? '启用' : '禁用'
|
|
1223
|
+
)
|
|
1224
|
+
</script>
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
```typescript
|
|
1228
|
+
import { wrapSFCComponent } from 'vue3-smart-table'
|
|
1229
|
+
import StatusRenderer from './StatusRenderer.vue'
|
|
1230
|
+
|
|
1231
|
+
getRendererManager().register('status', wrapSFCComponent(StatusRenderer))
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
#### 方式三:全局配置
|
|
1235
|
+
|
|
1236
|
+
```typescript
|
|
1237
|
+
import { setSmartTableConfig } from 'vue3-smart-table'
|
|
1238
|
+
import StatusRenderer from './StatusRenderer.vue'
|
|
1239
|
+
|
|
1240
|
+
setSmartTableConfig({
|
|
1241
|
+
renderers: {
|
|
1242
|
+
'status': StatusRenderer
|
|
1243
|
+
}
|
|
1244
|
+
})
|
|
1245
|
+
```
|
|
1246
|
+
|
|
1247
|
+
### 类型安全的列配置
|
|
1248
|
+
|
|
1249
|
+
```typescript
|
|
1250
|
+
import { defineColumn } from 'vue3-smart-table'
|
|
1251
|
+
|
|
1252
|
+
interface User {
|
|
1253
|
+
id: number
|
|
1254
|
+
name: string
|
|
1255
|
+
email: string
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
const columns = [
|
|
1259
|
+
defineColumn<User>('id', { label: 'ID' }),
|
|
1260
|
+
defineColumn<User>('name', { label: '姓名' }),
|
|
1261
|
+
defineColumn<User>('email', {
|
|
1262
|
+
label: '邮箱',
|
|
1263
|
+
render: 'copy'
|
|
1264
|
+
})
|
|
1265
|
+
]
|
|
1266
|
+
```
|
|
1267
|
+
|
|
1268
|
+
### 操作列权限控制
|
|
1269
|
+
|
|
1270
|
+
```typescript
|
|
1271
|
+
const columns = [
|
|
1272
|
+
{
|
|
1273
|
+
type: 'operation',
|
|
1274
|
+
key: 'operation',
|
|
1275
|
+
label: '操作',
|
|
1276
|
+
buttons: [
|
|
1277
|
+
{
|
|
1278
|
+
label: '编辑',
|
|
1279
|
+
type: 'primary',
|
|
1280
|
+
permission: 'user:edit', // 需要权限
|
|
1281
|
+
action: (row) => handleEdit(row)
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
label: '删除',
|
|
1285
|
+
type: 'danger',
|
|
1286
|
+
permission: ['user:delete', 'admin'], // 多个权限之一
|
|
1287
|
+
action: (row) => handleDelete(row),
|
|
1288
|
+
visible: (row) => row.status === 1 // 行级可见性
|
|
1289
|
+
}
|
|
1290
|
+
]
|
|
1291
|
+
}
|
|
1292
|
+
]
|
|
1293
|
+
|
|
1294
|
+
// 传入用户权限
|
|
1295
|
+
const permissions = ['user:edit', 'user:view']
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
---
|
|
1299
|
+
|
|
1300
|
+
## 8. 完整示例
|
|
310
1301
|
|
|
311
1302
|
```vue
|
|
312
1303
|
<!-- 全局注册 -->
|
|
@@ -327,15 +1318,24 @@ import { SmartTable } from 'vue3-smart-table'
|
|
|
327
1318
|
v-model:columns="columns"
|
|
328
1319
|
:border="true"
|
|
329
1320
|
:loading="loading"
|
|
330
|
-
:pageKey="route.name"
|
|
331
1321
|
:rowKey="'appId'"
|
|
332
1322
|
:data="tabList"
|
|
333
|
-
:userId="userInfo?.userId"
|
|
334
1323
|
:permissions="userStore.permissions"
|
|
1324
|
+
:cacheKey="`table_columns_${userInfo?.userId}_APPFeedback`"
|
|
335
1325
|
@cellChange="onCellChange"
|
|
336
1326
|
@cellBlur="onCellBlur"
|
|
337
1327
|
@cellEnter="onCellEnter"
|
|
338
1328
|
@cellClick="onCellClick" >
|
|
1329
|
+
<!-- 自定义复杂列 -->
|
|
1330
|
+
<template #attachments="{ row }">
|
|
1331
|
+
<div v-for="(item, index) in row.attachments" :key="index">
|
|
1332
|
+
<el-image v-if="item.fileType === 1" :src="item.thumbnailUrl" :preview-src-list="row.imgPaths"/>
|
|
1333
|
+
<el-button v-if="item.fileType === 0" type="text" @click="download(item.fileUrl)">下载日志</el-button>
|
|
1334
|
+
<div v-if="item.fileType === 2" @click="handleVideo(item.fileUrl)">
|
|
1335
|
+
<img :src="item.thumbnailUrl" alt="video"/>
|
|
1336
|
+
</div>
|
|
1337
|
+
</div>
|
|
1338
|
+
</template>
|
|
339
1339
|
</SmartTable>
|
|
340
1340
|
```
|
|
341
1341
|
## 完整示例代码
|
|
@@ -349,12 +1349,11 @@ import { SmartTable } from 'vue3-smart-table'
|
|
|
349
1349
|
class-name="table-flex"
|
|
350
1350
|
:border="true"
|
|
351
1351
|
:loading="loading"
|
|
352
|
-
:pageKey="'route.name'"
|
|
353
1352
|
:rowKey="'id'"
|
|
354
1353
|
:data="tableData"
|
|
355
1354
|
v-model:columns="columns"
|
|
356
|
-
:userId="'userId'"
|
|
357
1355
|
:permissions="permissions"
|
|
1356
|
+
:cacheKey="`table_columns_${userInfo?.userId}_APPFeedback`"
|
|
358
1357
|
@cell-blur="onCellBlur"
|
|
359
1358
|
@cell-enter="onCellEnter"
|
|
360
1359
|
@cell-change="onCellChange"
|
|
@@ -500,6 +1499,18 @@ import { SmartTable } from 'vue3-smart-table'
|
|
|
500
1499
|
columnProps: { minWidth: 100, sortable: true, align: 'left'},
|
|
501
1500
|
formatter: (val: string) => `${val}-123`,
|
|
502
1501
|
},
|
|
1502
|
+
{
|
|
1503
|
+
key: "regionCode",
|
|
1504
|
+
label: "自定义复杂列",
|
|
1505
|
+
visible: true,
|
|
1506
|
+
columnProps: { minWidth: 100, align: 'right'},
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
key: "handling.feedbackId",
|
|
1510
|
+
label: "key.key取值",
|
|
1511
|
+
visible: true,
|
|
1512
|
+
columnProps: { minWidth: 100, align: 'right'},
|
|
1513
|
+
},
|
|
503
1514
|
])
|
|
504
1515
|
|
|
505
1516
|
const tableData = reactive([
|
|
@@ -512,6 +1523,35 @@ import { SmartTable } from 'vue3-smart-table'
|
|
|
512
1523
|
'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png',
|
|
513
1524
|
'https://iconfont.alicdn.com/p/illus_3d/file/UMAqlm6KX5gw/8e357f00-9a4e-44c4-b0c5-bbed255cff24.png',
|
|
514
1525
|
],
|
|
1526
|
+
attachments: [
|
|
1527
|
+
{
|
|
1528
|
+
"id": 1337611,
|
|
1529
|
+
"feedbackId": 1334127,
|
|
1530
|
+
"fileType": 1,
|
|
1531
|
+
"fileUrl": "http://xxxxxxxxxxxxxxxx/attachment/cn.com.blackview.dashcam/2025/12/17/193000-1334127-1.jpg",
|
|
1532
|
+
"fileSize": 298696,
|
|
1533
|
+
"thumbnailUrl": "http://xxxxxxxxxxxxxxxxxx/attachment/cn.com.blackview.dashcam/2025/12/17/193000-1334127-1-thumbnail.jpg"
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
"id": 1337612,
|
|
1537
|
+
"feedbackId": 1334127,
|
|
1538
|
+
"fileType": 0,
|
|
1539
|
+
"fileUrl": "http://xxxxxxxxxxxxxxxxx/attachment/cn.com.blackview.dashcam/2025/12/17/193000-1334127-2.txt",
|
|
1540
|
+
"fileSize": 1619,
|
|
1541
|
+
"thumbnailUrl": null
|
|
1542
|
+
}
|
|
1543
|
+
],
|
|
1544
|
+
handling: {
|
|
1545
|
+
"id": 1334076,
|
|
1546
|
+
"feedbackId": 1334160,
|
|
1547
|
+
"problemCategory": null,
|
|
1548
|
+
"handlePerson": null,
|
|
1549
|
+
"handleTime": "2025-12-19 09:51:05",
|
|
1550
|
+
"handleRemark": null,
|
|
1551
|
+
"handleStatus": 1,
|
|
1552
|
+
"callbackStatus": 1,
|
|
1553
|
+
"solveStatus": 1
|
|
1554
|
+
}
|
|
515
1555
|
},
|
|
516
1556
|
])
|
|
517
1557
|
|