es-plus-ui 1.0.0 → 1.0.2
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 +1058 -942
- package/dist/es-plus.js +938 -922
- package/dist/es-plus.js.map +1 -1
- package/dist/es-plus.umd.cjs +1 -1
- package/dist/es-plus.umd.cjs.map +1 -1
- package/dist/src/composables/use-form-request.d.ts +1 -1
- package/dist/style.css +1 -1
- package/package.json +70 -70
package/README.md
CHANGED
|
@@ -1,942 +1,1058 @@
|
|
|
1
|
-
# es-plus
|
|
2
|
-
|
|
3
|
-
基于 Vue 3 + Element Plus
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/es-plus)
|
|
6
|
-
[](https://www.npmjs.com/package/es-plus-ui)
|
|
6
|
+
[](https://www.npmjs.com/package/es-plus-ui)
|
|
7
|
+
[](https://www.npmjs.com/package/es-plus-ui)
|
|
8
|
+
[](https://github.com/liujiaao/es-plus)
|
|
9
|
+
|
|
10
|
+
**[在线文档](https://liujiaao.github.io/es-plus/)** · **[Playground](https://liujiaao.github.io/es-plus/#/playground)** · **[GitHub](https://github.com/liujiaao/es-plus)** · **[更新日志](https://github.com/liujiaao/es-plus/releases)**
|
|
11
|
+
|
|
12
|
+
## 核心特性
|
|
13
|
+
|
|
14
|
+
- **配置化开发** — JSON 配置生成复杂表单与表格,替代大量模板代码
|
|
15
|
+
- **表单↔表格↔弹窗联动** — 查询/重置/分页全自动,零事件代码
|
|
16
|
+
- **编程式弹窗** — `useDialog` Hook 命令式调用,支持 JSX 渲染、嵌套弹窗
|
|
17
|
+
- **自适应高度** — `ResizeObserver` 自动重算表格高度,表单展开/收起自动响应
|
|
18
|
+
- **跨页选择** — `rowkey` + `cachePageSelection` 解决分页选择丢失痛点
|
|
19
|
+
- **任意后端适配** — `configTableOut` + `qrcb` 配置化适配不同后端响应格式
|
|
20
|
+
- **TypeScript** — 完整类型定义
|
|
21
|
+
- **13 种表单类型** — Input、Select、datePicker、timePicker、Slider、ColorPicker、Transfer、Cascader、Radio、Checkbox、Switch、Rate、Upload
|
|
22
|
+
|
|
23
|
+
## 为什么选择 es-plus-ui?
|
|
24
|
+
|
|
25
|
+
> 同样的 CRUD 页面,传统写法 ~200 行,es-plus-ui ~20 行
|
|
26
|
+
|
|
27
|
+
| 痛点 | 传统 Element Plus | es-plus-ui |
|
|
28
|
+
|------|-------------------|------------|
|
|
29
|
+
| 表单字段 | 每个字段 5-8 行 `el-form-item` + `v-model` | 一行 `{ prop, label, formtype }` |
|
|
30
|
+
| 查询/重置 | 手动 `@click` + `resetFields()` | `triggerEvent: true` 自动处理 |
|
|
31
|
+
| 分页请求 | 手动绑定 `current-page` + `size-change` 事件 | 分页切换自动请求 |
|
|
32
|
+
| 表单↔表格 | 手动传参、手动刷新 | 自动发现、自动合并参数 |
|
|
33
|
+
| 弹窗管理 | 模板声明 + `visible` 状态 | `useDialog()` 编程式调用 |
|
|
34
|
+
| 选择丢失 | 分页后选中清空 | 跨页选择缓存 + 去重 |
|
|
35
|
+
| 后端适配 | 每个接口手动映射字段 | `configTableOut` 统一映射 |
|
|
36
|
+
|
|
37
|
+
## 安装
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install es-plus-ui element-plus @element-plus/icons-vue
|
|
41
|
+
# 或
|
|
42
|
+
yarn add es-plus-ui element-plus @element-plus/icons-vue
|
|
43
|
+
# 或
|
|
44
|
+
pnpm add es-plus-ui element-plus @element-plus/icons-vue
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
前置依赖:`vue ^3.2.0`、`element-plus ^2.2.0`、`@element-plus/icons-vue ^2.1.0`
|
|
48
|
+
|
|
49
|
+
## 快速上手
|
|
50
|
+
|
|
51
|
+
### 全局引入
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { createApp } from 'vue'
|
|
55
|
+
import ElementPlus from 'element-plus'
|
|
56
|
+
import 'element-plus/dist/index.css'
|
|
57
|
+
import EsPlus from 'es-plus-ui'
|
|
58
|
+
import 'es-plus-ui/dist/style.css'
|
|
59
|
+
import App from './App.vue'
|
|
60
|
+
|
|
61
|
+
const app = createApp(App)
|
|
62
|
+
app.use(ElementPlus)
|
|
63
|
+
app.use(EsPlus)
|
|
64
|
+
app.mount('#app')
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 全局配置
|
|
68
|
+
|
|
69
|
+
通过 `app.use(EsPlus, options)` 第二个参数配置全局默认值,避免每个组件重复传入相同的请求方法、字段映射、分页布局等配置:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import axios from 'axios'
|
|
73
|
+
|
|
74
|
+
const app = createApp(App)
|
|
75
|
+
|
|
76
|
+
app.use(EsPlus, {
|
|
77
|
+
EsTable: {
|
|
78
|
+
methods: {
|
|
79
|
+
// 全局 HTTP 请求方法,所有 EsTable 共用
|
|
80
|
+
$httpRequest: async ({ url, formParams, headers, ...rest }) => {
|
|
81
|
+
const res = await axios({ url, method: rest.method || 'GET', headers, params: formParams, ...rest })
|
|
82
|
+
return res.data
|
|
83
|
+
},
|
|
84
|
+
// 分页布局配置
|
|
85
|
+
paginationLayout: () => ({
|
|
86
|
+
layout: 'total, sizes, prev, pager, next, jumper',
|
|
87
|
+
pageSizes: [10, 20, 50, 100],
|
|
88
|
+
isSmall: true,
|
|
89
|
+
background: true
|
|
90
|
+
}),
|
|
91
|
+
// API 响应字段映射(后端返回字段 → 组件内部字段)
|
|
92
|
+
configQueryFieldOutput: () => ({
|
|
93
|
+
total: 'total', // 后端总数字段名
|
|
94
|
+
pageSize: 'pageSize', // 后端每页条数字段名
|
|
95
|
+
current: 'pageIndex', // 后端当前页码字段名
|
|
96
|
+
tableData: 'data' // 后端数据列表字段名
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
EsForm: {
|
|
101
|
+
methods: {
|
|
102
|
+
// 全局 HTTP 请求方法,所有 EsForm 共用
|
|
103
|
+
$httpRequest: async ({ url, formParams, headers, ...rest }) => {
|
|
104
|
+
const res = await axios({ url, method: rest.method || 'GET', headers, params: formParams, ...rest })
|
|
105
|
+
return res.data
|
|
106
|
+
},
|
|
107
|
+
// API 响应字段映射(后端返回字段 → 组件内部字段)
|
|
108
|
+
fieldFieldOutput: () => ({
|
|
109
|
+
total: 'total', // 后端总数字段名
|
|
110
|
+
pageSize: 'pageSize', // 后端每页条数字段名
|
|
111
|
+
current: 'pageIndex', // 后端当前页码字段名
|
|
112
|
+
listData: 'data' // 后端选项列表字段名
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### 配置项说明
|
|
120
|
+
|
|
121
|
+
| 组件 | 配置键 | 类型 | 说明 |
|
|
122
|
+
|------|--------|------|------|
|
|
123
|
+
| EsTable | `$httpRequest` | `(params) => Promise` | 全局请求方法,未传 `options.httpRequest` 时使用 |
|
|
124
|
+
| EsTable | `paginationLayout` | `() => PaginationLayoutConfig` | 分页布局配置(layout/pageSizes/isSmall/background) |
|
|
125
|
+
| EsTable | `configQueryFieldOutput` | `() => FieldMap` | API 响应字段映射,未传 `options.configTableOut` 时使用 |
|
|
126
|
+
| EsForm | `$httpRequest` | `(params) => Promise` | 全局请求方法,未传 `formItem.httpRequest` 时使用 |
|
|
127
|
+
| EsForm | `fieldFieldOutput` | `(defaults) => FieldMap` | API 响应字段映射,未传 `formItem.configFormOut` 时使用 |
|
|
128
|
+
|
|
129
|
+
> **优先级**:组件 props / 选项 > 全局配置 > 组件默认值。例如 `options.configTableOut` 优先于 `configQueryFieldOutput`。
|
|
130
|
+
|
|
131
|
+
#### paginationLayout 配置
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
paginationLayout: () => ({
|
|
135
|
+
layout: 'total, sizes, prev, pager, next, jumper', // Element Plus 分页布局字符串
|
|
136
|
+
pageSizes: [10, 20, 50, 100], // 每页条数选项
|
|
137
|
+
isSmall: true, // 是否使用小型分页器
|
|
138
|
+
background: true // 是否显示背景色
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### fieldFieldOutput / configQueryFieldOutput 配置
|
|
143
|
+
|
|
144
|
+
函数接收默认字段映射作为参数,返回自定义映射:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// 默认映射(不配置时的值)
|
|
148
|
+
{
|
|
149
|
+
total: 'records',
|
|
150
|
+
pageSize: 'pageSize',
|
|
151
|
+
current: 'pageNo',
|
|
152
|
+
listData: 'rows' // EsForm 用 listData
|
|
153
|
+
// tableData: 'rows' // EsTable 用 tableData
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 自定义示例:后端返回 { data: { list: [...], pagination: { total: 100 } } }
|
|
157
|
+
fieldFieldOutput: (defaults) => ({
|
|
158
|
+
total: 'total',
|
|
159
|
+
pageSize: 'pageSize',
|
|
160
|
+
current: 'pageNo',
|
|
161
|
+
listData: 'list'
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 按需引入
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { EsForm, EsTable, useDialog } from 'es-plus-ui'
|
|
169
|
+
import 'es-plus-ui/dist/style.css'
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 最小示例
|
|
173
|
+
|
|
174
|
+
**EsForm**:
|
|
175
|
+
|
|
176
|
+
```vue
|
|
177
|
+
<template>
|
|
178
|
+
<es-form :model="form" :form-item-list="items" :config-btn="btns" />
|
|
179
|
+
</template>
|
|
180
|
+
|
|
181
|
+
<script setup>
|
|
182
|
+
import { reactive } from 'vue'
|
|
183
|
+
const form = reactive({ keyword: '', status: '' })
|
|
184
|
+
const items = [
|
|
185
|
+
{ prop: 'keyword', label: '关键词', formtype: 'Input', span: 6, attrs: { clearable: true } },
|
|
186
|
+
{ prop: 'status', label: '状态', formtype: 'Select', span: 6, dataOptions: [{ label: '启用', value: 1 }, { label: '禁用', value: 0 }] }
|
|
187
|
+
]
|
|
188
|
+
const btns = [
|
|
189
|
+
{ name: '查询', type: 'primary', key: 'query', triggerEvent: true },
|
|
190
|
+
{ name: '重置', key: 'reset', triggerEvent: true }
|
|
191
|
+
]
|
|
192
|
+
</script>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**EsTable**:
|
|
196
|
+
|
|
197
|
+
```vue
|
|
198
|
+
<template>
|
|
199
|
+
<es-table
|
|
200
|
+
:columns="columns"
|
|
201
|
+
:options="options"
|
|
202
|
+
v-model:data-source="tableData"
|
|
203
|
+
v-model:pagination="pagination"
|
|
204
|
+
/>
|
|
205
|
+
</template>
|
|
206
|
+
|
|
207
|
+
<script setup>
|
|
208
|
+
import { ref } from 'vue'
|
|
209
|
+
const tableData = ref([])
|
|
210
|
+
const pagination = ref({ pageSize: 10, current: 1, total: 0 })
|
|
211
|
+
const columns = [
|
|
212
|
+
{ prop: 'id', label: 'ID', width: 80 },
|
|
213
|
+
{ prop: 'name', label: '姓名' },
|
|
214
|
+
{ prop: 'status', label: '状态' }
|
|
215
|
+
]
|
|
216
|
+
const mockRequest = async (params) => {
|
|
217
|
+
const { formParams, ...rest } = params || {}
|
|
218
|
+
const { pageIndex = 1, pageSize = 10 } = { ...formParams, ...rest }
|
|
219
|
+
// 实际项目中替换为真实 API 调用
|
|
220
|
+
return { data: [], total: 0, pageSize, pageIndex }
|
|
221
|
+
}
|
|
222
|
+
const options = {
|
|
223
|
+
border: true,
|
|
224
|
+
httpRequest: mockRequest,
|
|
225
|
+
apiParams: { url: '/api/list', method: 'GET' },
|
|
226
|
+
configTableOut: { total: 'total', tableData: 'data', pageSize: 'pageSize', current: 'pageIndex' },
|
|
227
|
+
rowkey: 'id',
|
|
228
|
+
heightType: 'height',
|
|
229
|
+
tabHeight: 400
|
|
230
|
+
}
|
|
231
|
+
</script>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**useDialog**:
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
import { useDialog } from 'es-plus-ui'
|
|
238
|
+
import EsForm from 'es-plus-ui/components/es-form'
|
|
239
|
+
|
|
240
|
+
const dialog = useDialog()
|
|
241
|
+
function openAddDialog() {
|
|
242
|
+
dialog({
|
|
243
|
+
title: '新增',
|
|
244
|
+
width: '600px',
|
|
245
|
+
render: (h, { registerRef }) => (
|
|
246
|
+
<EsForm
|
|
247
|
+
ref={(el) => { if (el) registerRef('formRef', el) }}
|
|
248
|
+
model={formData}
|
|
249
|
+
formItemList={[{ prop: 'name', label: '名称', formtype: 'Input', span: 24 }]}
|
|
250
|
+
/>
|
|
251
|
+
),
|
|
252
|
+
configBtn: [
|
|
253
|
+
{ name: '取消', click: (_, { close }) => close() },
|
|
254
|
+
{ name: '提交', type: 'primary', click: (_, { close, getRefs }) => {
|
|
255
|
+
getRefs('formRef')?.validate().then(() => { close() })
|
|
256
|
+
}}
|
|
257
|
+
]
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## EsForm 表单组件
|
|
265
|
+
|
|
266
|
+
配置化驱动的表单组件,支持 13 种输入类型、动态显隐、异步数据加载、折叠展开等功能。
|
|
267
|
+
|
|
268
|
+
### Props
|
|
269
|
+
|
|
270
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
271
|
+
|------|------|--------|------|
|
|
272
|
+
| model | `Record<string, unknown>` | `{}` | 表单数据对象(必填) |
|
|
273
|
+
| formItemList | `FormItemOption[]` | `[]` | 表单项配置数组(必填) |
|
|
274
|
+
| layoutFormProps | `LayoutFormProps` | `{}` | 布局配置 |
|
|
275
|
+
| configBtn | `BtnConfig[]` | `[]` | 按钮配置 |
|
|
276
|
+
| renderBtn | `Function \| boolean` | `false` | 自定义按钮渲染函数 |
|
|
277
|
+
| btnColSpanRow | `boolean` | `true` | 按钮是否独占一行 |
|
|
278
|
+
| rules | `Record<string, unknown>` | `{}` | 验证规则(Element Plus 格式) |
|
|
279
|
+
| fieldFieldOutput | `(defaults) => Record<string, string>` | — | API 响应字段映射 |
|
|
280
|
+
|
|
281
|
+
### FormItemOption 配置
|
|
282
|
+
|
|
283
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
284
|
+
|------|------|--------|------|
|
|
285
|
+
| prop | `string` | — | 字段名(必填) |
|
|
286
|
+
| label | `string` | — | 标签文本(必填) |
|
|
287
|
+
| formtype | `string` | — | 输入组件类型(见下表) |
|
|
288
|
+
| span | `number` | `6` | 栅格占列数(24 列布局) |
|
|
289
|
+
| attrs | `Record<string, unknown>` | — | 透传给 Element Plus 组件的属性(placeholder、clearable 等) |
|
|
290
|
+
| on | `Record<string, unknown>` | — | 事件监听(change、input 等) |
|
|
291
|
+
| dataOptions | `Array<{ label, value }>` | — | 选项数据(Select/Radio/Checkbox/Cascader) |
|
|
292
|
+
| isHidden | `(model, item, formProps) => boolean` | — | 条件隐藏函数,返回 `true` 时隐藏 |
|
|
293
|
+
| render | `(h, model, ctx) => VNode \| string` | — | 自定义渲染函数 |
|
|
294
|
+
| apiParams | `ApiParams` | — | 从接口加载选项数据 |
|
|
295
|
+
| isInitRun | `boolean` | `true` | 是否在初始化时自动加载接口数据 |
|
|
296
|
+
| callOptionListFormat | `(data) => unknown[]` | — | 将 API 响应转换为 dataOptions 格式 |
|
|
297
|
+
| httpRequest | `(params) => Promise` | — | 自定义请求方法(覆盖全局配置) |
|
|
298
|
+
| listenToCallBack | `Record<string, Function>` | — | 回调映射,`crtn` 用于选项格式转换 |
|
|
299
|
+
| width | `number \| string` | — | 字段宽度 |
|
|
300
|
+
| formItemOptions | `Record<string, unknown>` | — | el-form-item 附加属性(rules、labelWidth 等) |
|
|
301
|
+
| components | `Record<string, unknown>` | — | 自定义组件映射 |
|
|
302
|
+
|
|
303
|
+
### formtype 类型
|
|
304
|
+
|
|
305
|
+
| 类型 | 对应组件 | 常用配置 |
|
|
306
|
+
|------|----------|----------|
|
|
307
|
+
| `Input` | ElInput | `attrs: { placeholder, clearable, type: 'textarea' }` |
|
|
308
|
+
| `Select` | ElSelect | `dataOptions: [{ label, value }]`, `attrs: { clearable, multiple }` |
|
|
309
|
+
| `datePicker` | ElDatePicker | `attrs: { type: 'daterange/datetimerange', valueFormat }` |
|
|
310
|
+
| `timePicker` | ElTimePicker | `attrs: { isRange }` |
|
|
311
|
+
| `Slider` | ElSlider | `attrs: { min, max, step }` |
|
|
312
|
+
| `ColorPicker` | ElColorPicker | `attrs: { showAlpha }` |
|
|
313
|
+
| `Transfer` | ElTransfer | `dataOptions` |
|
|
314
|
+
| `Cascader` | ElCascader | `dataOptions`(树形结构), `attrs: { props: { checkStrictly: true } }` |
|
|
315
|
+
| `Radio` | ElRadioGroup | `dataOptions: [{ label, value }]` |
|
|
316
|
+
| `Checkbox` | ElCheckboxGroup | `dataOptions: [{ label, value }]` |
|
|
317
|
+
| `Switch` | ElSwitch | `attrs: { activeText, inactiveText }` |
|
|
318
|
+
| `Rate` | ElRate | `attrs: { max, allowHalf }` |
|
|
319
|
+
| `Upload` | ElUpload | `attrs: { action, listType, limit }` |
|
|
320
|
+
|
|
321
|
+
### LayoutFormProps 配置
|
|
322
|
+
|
|
323
|
+
| 字段 | 类型 | 说明 |
|
|
324
|
+
|------|------|------|
|
|
325
|
+
| fromLayProps | `Object` | 表单级别属性 |
|
|
326
|
+
| fromLayProps.labelWidth | `string` | 标签宽度,如 `'100px'` |
|
|
327
|
+
| fromLayProps.minFoldRows | `number` | 折叠时显示的行数,`0` 不折叠 |
|
|
328
|
+
| fromLayProps.isBtnHidden | `boolean` | 是否隐藏按钮区域 |
|
|
329
|
+
| fromLayProps.btnColSpan | `number` | 按钮列占位宽度 |
|
|
330
|
+
| fromLayProps.labelBtnWidth | `string \| number` | 按钮标签宽度 |
|
|
331
|
+
| rowLayProps | `Object` | 行级别属性 |
|
|
332
|
+
| rowLayProps.gutter | `number` | 栅格间距 |
|
|
333
|
+
| setOptions | `boolean` | 是否启用设置下拉 |
|
|
334
|
+
|
|
335
|
+
### BtnConfig 配置
|
|
336
|
+
|
|
337
|
+
| 字段 | 类型 | 说明 |
|
|
338
|
+
|------|------|------|
|
|
339
|
+
| name | `string` | 按钮文本 |
|
|
340
|
+
| key | `string` | 按钮唯一标识 |
|
|
341
|
+
| type | `string` | 按钮类型(primary/success/warning/danger/info) |
|
|
342
|
+
| icon | `string` | 图标名称 |
|
|
343
|
+
| size | `string` | 按钮尺寸 |
|
|
344
|
+
| direction | `'left' \| 'right'` | 按钮位置 |
|
|
345
|
+
| loading | `boolean` | 加载状态 |
|
|
346
|
+
| disabled | `boolean \| () => boolean` | 禁用状态,支持函数形式 |
|
|
347
|
+
| click | `(model, formRef, httpRequestInstance?) => void` | 点击回调 |
|
|
348
|
+
| triggerEvent | `boolean` | `true` 时自动触发表格查询/表单重置 |
|
|
349
|
+
|
|
350
|
+
> `triggerEvent: true` + `key: 'query'` → 自动调用父级 EsTable 的 `httpRequestInstance`;`triggerEvent: true` + `key: 'reset'` → 自动重置表单。
|
|
351
|
+
|
|
352
|
+
### Events
|
|
353
|
+
|
|
354
|
+
| 事件 | 参数 | 说明 |
|
|
355
|
+
|------|------|------|
|
|
356
|
+
| confirm | `(formRef, model)` | 点击确认按钮时触发 |
|
|
357
|
+
| reset | `(formRef, model)` | 点击重置按钮时触发 |
|
|
358
|
+
|
|
359
|
+
### Methods(通过 ref 调用)
|
|
360
|
+
|
|
361
|
+
| 方法 | 说明 |
|
|
362
|
+
|------|------|
|
|
363
|
+
| `validate()` | 校验整个表单,返回 `Promise<boolean>` |
|
|
364
|
+
| `resetFields()` | 重置所有字段 |
|
|
365
|
+
| `clearValidate(props?)` | 清除校验状态 |
|
|
366
|
+
| `validateField(props)` | 校验指定字段 |
|
|
367
|
+
| `scrollToField(prop)` | 滚动到指定字段 |
|
|
368
|
+
| `formItmeRequestInstance(propsList)` | 手动触发指定字段的 API 数据加载 |
|
|
369
|
+
| `getFormRef()` | 获取底层 ElForm 实例 |
|
|
370
|
+
|
|
371
|
+
### 高级用法
|
|
372
|
+
|
|
373
|
+
#### 条件隐藏
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
const items = [
|
|
377
|
+
{ prop: 'type', label: '类型', formtype: 'Select', span: 6,
|
|
378
|
+
dataOptions: [{ label: '个人', value: 'personal' }, { label: '企业', value: 'company' }]
|
|
379
|
+
},
|
|
380
|
+
{ prop: 'companyName', label: '企业名称', formtype: 'Input', span: 6,
|
|
381
|
+
isHidden: (model) => model.type !== 'company' // 类型不是企业时隐藏
|
|
382
|
+
}
|
|
383
|
+
]
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
#### 异步数据加载
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
const items = [
|
|
390
|
+
{ prop: 'category', label: '分类', formtype: 'Select', span: 6,
|
|
391
|
+
apiParams: { url: '/api/categories', method: 'GET' },
|
|
392
|
+
callOptionListFormat: (data) => data.map(item => ({ label: item.name, value: item.id }))
|
|
393
|
+
}
|
|
394
|
+
]
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### 自定义渲染
|
|
398
|
+
|
|
399
|
+
```tsx
|
|
400
|
+
const items = [
|
|
401
|
+
{ prop: 'amount', label: '金额', span: 6,
|
|
402
|
+
render: (h, model) => <span style="color: red">¥{model.amount?.toLocaleString()}</span>
|
|
403
|
+
}
|
|
404
|
+
]
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## EsTable 表格组件
|
|
410
|
+
|
|
411
|
+
配置化驱动的数据表格,内置分页、远程数据、跨页选择、自适应高度等功能。
|
|
412
|
+
|
|
413
|
+
### Props
|
|
414
|
+
|
|
415
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
416
|
+
|------|------|--------|------|
|
|
417
|
+
| dataSource | `Record<string, unknown>[]` | `[]` | 表格数据(必填,支持 v-model) |
|
|
418
|
+
| columns | `TableColumn[]` | `[]` | 列配置数组(必填) |
|
|
419
|
+
| options | `TableOptions` | `{}` | 表格选项配置 |
|
|
420
|
+
| pagination | `PaginationConfig` | — | 分页配置(支持 v-model) |
|
|
421
|
+
| initTabHeight | `number` | `400` | 初始表格高度 |
|
|
422
|
+
| showHeaderBar | `boolean` | `true` | 是否显示头部栏区域 |
|
|
423
|
+
| headBarClass | `string \| Object` | — | 头部栏样式类 |
|
|
424
|
+
|
|
425
|
+
### TableColumn 配置
|
|
426
|
+
|
|
427
|
+
| 字段 | 类型 | 说明 |
|
|
428
|
+
|------|------|------|
|
|
429
|
+
| prop | `string` | 数据字段名 |
|
|
430
|
+
| key | `string` | 列唯一标识 |
|
|
431
|
+
| label | `string` | 列标题 |
|
|
432
|
+
| width | `number \| string` | 列宽度 |
|
|
433
|
+
| minWidth | `number \| string` | 最小列宽度 |
|
|
434
|
+
| align | `string` | 对齐方式 |
|
|
435
|
+
| fixed | `boolean \| string` | 固定列(`'left'` / `'right'`) |
|
|
436
|
+
| formatter | `(row) => string` | 格式化函数 |
|
|
437
|
+
| render | `(h, { row, value, index }) => VNode` | 自定义渲染函数 |
|
|
438
|
+
| scopedSlots | `{ customRender: string }` | 插槽配置 |
|
|
439
|
+
| groups | `TableColumn[]` | 多级表头子列 |
|
|
440
|
+
| ellipsis | `boolean` | 文本溢出省略 |
|
|
441
|
+
| hidCol | `boolean` | 是否隐藏该列 |
|
|
442
|
+
| btns | `Array<{ name, type?, clickEvent? }>` | 行操作按钮 |
|
|
443
|
+
| type | `string` | 特殊列类型(`'selection'`、`'expand'`、`'index'`) |
|
|
444
|
+
|
|
445
|
+
#### render 函数
|
|
446
|
+
|
|
447
|
+
```tsx
|
|
448
|
+
{
|
|
449
|
+
prop: 'status', label: '状态', width: 100,
|
|
450
|
+
render: (_, { row }) => {
|
|
451
|
+
const map = { active: 'success', leave: 'warning', resigned: 'danger' }
|
|
452
|
+
return <ElTag type={map[row.status]} size="small">{row.status}</ElTag>
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
#### btns 行操作按钮
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
{
|
|
461
|
+
prop: 'operate', label: '操作', width: 160,
|
|
462
|
+
btns: [
|
|
463
|
+
{ name: '编辑', type: 'primary', clickEvent: (row) => openEditDialog(row) },
|
|
464
|
+
{ name: '删除', type: 'danger', clickEvent: (row) => handleDelete(row) }
|
|
465
|
+
]
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
#### 多级表头
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
{
|
|
473
|
+
label: '基本信息',
|
|
474
|
+
groups: [
|
|
475
|
+
{ prop: 'name', label: '姓名' },
|
|
476
|
+
{ prop: 'age', label: '年龄' },
|
|
477
|
+
{ label: '地址', groups: [
|
|
478
|
+
{ prop: 'province', label: '省份' },
|
|
479
|
+
{ prop: 'city', label: '城市' }
|
|
480
|
+
]}
|
|
481
|
+
]
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### TableOptions 配置
|
|
486
|
+
|
|
487
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
488
|
+
|------|------|--------|------|
|
|
489
|
+
| border | `boolean` | `false` | 是否显示边框 |
|
|
490
|
+
| stripe | `boolean` | `false` | 是否显示斑马纹 |
|
|
491
|
+
| size | `'large' \| 'default' \| 'small'` | `'small'` | 表格尺寸 |
|
|
492
|
+
| headerCellStyle | `Record<string, unknown>` | `{ background: '#f5f7fa' }` | 表头样式 |
|
|
493
|
+
| highlightCurrentRow | `boolean` | `true` | 高亮当前行 |
|
|
494
|
+
| multiSelect | `boolean` | `false` | 启用多选列 |
|
|
495
|
+
| expand | `boolean` | `false` | 启用展开行 |
|
|
496
|
+
| snIndex | `boolean` | `false` | 显示序号列 |
|
|
497
|
+
| loading | `boolean` | `false` | 加载状态 |
|
|
498
|
+
| heightType | `'height' \| 'auto'` | — | 高度类型(`'height'` 推荐,`'auto'` 为 maxHeight) |
|
|
499
|
+
| tabHeight | `number \| string` | — | 表格容器高度(配合 heightType 使用) |
|
|
500
|
+
| cachePageSelection | `boolean` | `true` | 启用跨页选择缓存 |
|
|
501
|
+
| rowkey | `string` | — | 行唯一标识字段名(跨页选择必填) |
|
|
502
|
+
| isInitRun | `boolean` | — | 初始化时是否自动请求数据 |
|
|
503
|
+
| httpRequest | `(params) => Promise` | — | 自定义请求方法 |
|
|
504
|
+
| apiParams | `ApiParams` | — | API 请求配置 |
|
|
505
|
+
| configTableOut | `Record<string, string>` | — | 响应字段映射 |
|
|
506
|
+
| listenToCallBack | `Record<string, Function>` | — | 请求/响应回调管线 |
|
|
507
|
+
| entryQuery | `Record<string, unknown>` | — | 默认查询参数 |
|
|
508
|
+
| actionUrl | `string` | — | 请求地址(简写) |
|
|
509
|
+
|
|
510
|
+
### configTableOut 字段映射
|
|
511
|
+
|
|
512
|
+
映射后端响应字段到表格内部使用的字段:
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
configTableOut: {
|
|
516
|
+
total: 'total', // 总数对应的字段名
|
|
517
|
+
tableData: 'data', // 数据列表对应的字段名
|
|
518
|
+
pageSize: 'pageSize', // 每页条数对应的字段名
|
|
519
|
+
current: 'pageIndex' // 当前页码对应的字段名
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
> **注意**:`configTableOut` 的值应使用简单 key 名(如 `'total'`、`'list'`),不支持点号路径(如 `'result.pagination.total'`)。内部使用 `findValueByKey` 递归查找嵌套对象中的 key,无需写完整路径。
|
|
524
|
+
|
|
525
|
+
### listenToCallBack 回调管线
|
|
526
|
+
|
|
527
|
+
```typescript
|
|
528
|
+
listenToCallBack: {
|
|
529
|
+
// 请求前回调(Before Request CallBack)— 转换请求参数
|
|
530
|
+
brcb: (params) => {
|
|
531
|
+
return { ...params, timestamp: Date.now() }
|
|
532
|
+
},
|
|
533
|
+
// 请求后回调(Query Result CallBack)— 转换响应数据
|
|
534
|
+
qrcb: (res) => {
|
|
535
|
+
if (!res?.data) return res
|
|
536
|
+
return {
|
|
537
|
+
...res,
|
|
538
|
+
data: res.data.map(item => ({
|
|
539
|
+
id: item.emp_id,
|
|
540
|
+
name: item.emp_name // 后端蛇形字段转前端驼峰
|
|
541
|
+
}))
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### PaginationConfig 配置
|
|
548
|
+
|
|
549
|
+
| 字段 | 类型 | 说明 |
|
|
550
|
+
|------|------|------|
|
|
551
|
+
| pageSize | `number` | 每页条数 |
|
|
552
|
+
| current | `number` | 当前页码 |
|
|
553
|
+
| total | `number` | 总条数 |
|
|
554
|
+
| pageSizes | `number[]` | 每页条数选项 |
|
|
555
|
+
| size | `'large' \| 'default' \| 'small'` | 分页器尺寸 |
|
|
556
|
+
| isSmall | `boolean` | 使用小型分页器 |
|
|
557
|
+
|
|
558
|
+
### Events
|
|
559
|
+
|
|
560
|
+
| 事件 | 参数 | 说明 |
|
|
561
|
+
|------|------|------|
|
|
562
|
+
| update:dataSource | `(data)` | 数据更新 |
|
|
563
|
+
| update:pagination | `(pagination)` | 分页更新 |
|
|
564
|
+
| selection-change | `(rows)` | 选择变化(通过 fallthrough attrs 传递) |
|
|
565
|
+
| pagination-current-change | `(current)` | 页码变化 |
|
|
566
|
+
| size-change | `(pageSize)` | 每页条数变化 |
|
|
567
|
+
| change-table-sort | `(sort)` | 排序变化 |
|
|
568
|
+
|
|
569
|
+
### Methods(通过 ref 调用)
|
|
570
|
+
|
|
571
|
+
| 方法 | 说明 |
|
|
572
|
+
|------|------|
|
|
573
|
+
| `httpRequestInstance(model?)` | 手动触发表格数据请求,可传入额外查询参数 |
|
|
574
|
+
| `getSelectionRows()` | 获取当前选中行(含跨页缓存) |
|
|
575
|
+
| `clearSelection()` | 清除当前页选择 |
|
|
576
|
+
| `clearAllSelection()` | 清除所有页面选择(含跨页缓存) |
|
|
577
|
+
| `refresh()` | 强制重新计算表格布局(`doLayout`) |
|
|
578
|
+
|
|
579
|
+
### 高级用法
|
|
580
|
+
|
|
581
|
+
#### 远程数据请求
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
const mockRequest = async (params) => {
|
|
585
|
+
const { formParams, ...rest } = params || {}
|
|
586
|
+
const { pageIndex = 1, pageSize = 10, ...filters } = { ...formParams, ...rest }
|
|
587
|
+
const res = await fetch('/api/list')
|
|
588
|
+
const data = await res.json()
|
|
589
|
+
const total = data.length
|
|
590
|
+
const start = (pageIndex - 1) * pageSize
|
|
591
|
+
return { data: data.slice(start, start + pageSize), total, pageSize, pageIndex }
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const options = {
|
|
595
|
+
border: true,
|
|
596
|
+
httpRequest: mockRequest,
|
|
597
|
+
apiParams: { url: '/api/list', method: 'GET', model: queryModel },
|
|
598
|
+
configTableOut: { total: 'total', tableData: 'data', pageSize: 'pageSize', current: 'pageIndex' },
|
|
599
|
+
rowkey: 'id'
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
#### 跨页选择持久化
|
|
604
|
+
|
|
605
|
+
```vue
|
|
606
|
+
<es-table
|
|
607
|
+
ref="tableRef"
|
|
608
|
+
:columns="columns"
|
|
609
|
+
:options="{ rowkey: 'id', cachePageSelection: true, multiSelect: true }"
|
|
610
|
+
@selection-change="onSelectionChange"
|
|
611
|
+
/>
|
|
612
|
+
|
|
613
|
+
<script setup>
|
|
614
|
+
const tableRef = ref(null)
|
|
615
|
+
const selectedCount = ref(0)
|
|
616
|
+
|
|
617
|
+
const onSelectionChange = (rows) => {
|
|
618
|
+
selectedCount.value = rows?.length || 0
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function getSelected() {
|
|
622
|
+
return tableRef.value?.getSelectionRows() || []
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function clearSelected() {
|
|
626
|
+
tableRef.value?.clearAllSelection()
|
|
627
|
+
}
|
|
628
|
+
</script>
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
#### 自适应高度
|
|
632
|
+
|
|
633
|
+
```typescript
|
|
634
|
+
const options = {
|
|
635
|
+
heightType: 'height', // 必须 'height',非 'auto'
|
|
636
|
+
tabHeight: 400 // 容器高度,表格自动 = 容器 - 表单 - 分页
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
> 表单展开/收起时,`ResizeObserver` 自动触发高度重算,无需手动监听。
|
|
641
|
+
|
|
642
|
+
#### 动态 options 需使用 `:key`
|
|
643
|
+
|
|
644
|
+
> es-table 的 `httpRequest`、`configTableOut`、`listenToCallBack` 等选项在挂载后不可动态响应。如需切换,请使用 `:key` 强制重建:
|
|
645
|
+
|
|
646
|
+
```vue
|
|
647
|
+
<es-table :key="activeFormat" :options="currentOptions" ... />
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## EsDialog 弹窗组件
|
|
653
|
+
|
|
654
|
+
模板式增强弹窗,支持拖拽、全屏切换、自定义头尾等功能。
|
|
655
|
+
|
|
656
|
+
### Props
|
|
657
|
+
|
|
658
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
659
|
+
|------|------|--------|------|
|
|
660
|
+
| title | `string` | — | 弹窗标题 |
|
|
661
|
+
| visible | `boolean` | `false` | 显示状态(支持 v-model) |
|
|
662
|
+
| width | `string \| number` | `'50%'` | 弹窗宽度 |
|
|
663
|
+
| isDraggable | `boolean` | `false` | 是否可拖拽 |
|
|
664
|
+
| fullscreen | `boolean` | `false` | 是否全屏 |
|
|
665
|
+
| hiddenFullBtn | `boolean` | `false` | 隐藏全屏切换按钮 |
|
|
666
|
+
| isHiddenFooter | `boolean` | `false` | 隐藏底部按钮区 |
|
|
667
|
+
| maxHeight | `string \| number` | — | 内容区最大高度 |
|
|
668
|
+
| appendTo | `string \| HTMLElement` | — | 挂载目标 |
|
|
669
|
+
| confirmText | `string` | — | 确认按钮文本 |
|
|
670
|
+
| cancelText | `string` | — | 取消按钮文本 |
|
|
671
|
+
| configBtn | `BtnConfig[]` | `[]` | 底部按钮配置 |
|
|
672
|
+
| render | `Function` | — | 自定义内容渲染函数 |
|
|
673
|
+
| renderHeader | `Function` | — | 自定义头部渲染函数 |
|
|
674
|
+
| renderFooter | `Function` | — | 自定义底部渲染函数 |
|
|
675
|
+
|
|
676
|
+
### Events
|
|
677
|
+
|
|
678
|
+
| 事件 | 说明 |
|
|
679
|
+
|------|------|
|
|
680
|
+
| update:visible | 显示状态变化 |
|
|
681
|
+
| closed | 弹窗关闭后触发 |
|
|
682
|
+
| submit | 点击确认时触发 |
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
## useDialog 编程式弹窗 Hook
|
|
687
|
+
|
|
688
|
+
命令式调用弹窗,支持 JSX 渲染、表单集成、嵌套弹窗等高级功能。
|
|
689
|
+
|
|
690
|
+
### 基本用法
|
|
691
|
+
|
|
692
|
+
```typescript
|
|
693
|
+
import { useDialog } from 'es-plus-ui'
|
|
694
|
+
|
|
695
|
+
const dialog = useDialog()
|
|
696
|
+
|
|
697
|
+
// 打开弹窗
|
|
698
|
+
dialog({
|
|
699
|
+
title: '提示',
|
|
700
|
+
width: '500px',
|
|
701
|
+
render: (h) => <div>弹窗内容</div>,
|
|
702
|
+
configBtn: [
|
|
703
|
+
{ name: '确定', type: 'primary', click: (_, { close }) => close() }
|
|
704
|
+
]
|
|
705
|
+
})
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### DialogOptions 配置
|
|
709
|
+
|
|
710
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
711
|
+
|------|------|--------|------|
|
|
712
|
+
| title | `string` | — | 弹窗标题 |
|
|
713
|
+
| width | `string \| number` | `'50%'` | 弹窗宽度 |
|
|
714
|
+
| key | `string` | — | 唯一标识(相同 key 复用实例) |
|
|
715
|
+
| height | `string \| number` | — | 弹窗高度 |
|
|
716
|
+
| maxHeight | `string \| number` | — | 内容区最大高度 |
|
|
717
|
+
| render | `(h, instance, components) => VNode` | — | 内容渲染函数 |
|
|
718
|
+
| renderHeader | `(h, instance) => VNode` | — | 头部渲染函数 |
|
|
719
|
+
| renderFooter | `(h, instance) => VNode` | — | 底部渲染函数 |
|
|
720
|
+
| configBtn | `BtnConfig[]` | `[]` | 底部按钮配置 |
|
|
721
|
+
| isDraggable | `boolean` | `false` | 是否可拖拽 |
|
|
722
|
+
| fullscreen | `boolean` | `false` | 是否全屏 |
|
|
723
|
+
| hiddenFullBtn | `boolean` | `false` | 隐藏全屏按钮 |
|
|
724
|
+
| isHiddenFooter | `boolean` | `false` | 隐藏底部 |
|
|
725
|
+
| center | `boolean` | — | 垂直居中 |
|
|
726
|
+
| closeOnClickModal | `boolean` | `false` | 点击遮罩关闭 |
|
|
727
|
+
| closeOnPressEscape | `boolean` | `false` | 按 ESC 关闭 |
|
|
728
|
+
| showClose | `boolean` | `true` | 显示关闭按钮 |
|
|
729
|
+
| destroyOnClose | `boolean` | — | 关闭时销毁 |
|
|
730
|
+
| showDefaultButtons | `boolean` | — | 显示默认确定/取消按钮 |
|
|
731
|
+
| loading | `boolean` | `false` | 加载状态 |
|
|
732
|
+
| customClass | `string` | — | 自定义样式类 |
|
|
733
|
+
| appendToBody | `boolean` | — | 挂载到 body |
|
|
734
|
+
| appendTo | `string \| HTMLElement` | — | 挂载目标 |
|
|
735
|
+
| modal | `boolean` | — | 显示遮罩 |
|
|
736
|
+
| lockScroll | `boolean` | — | 锁定滚动 |
|
|
737
|
+
| onlyInstance | `boolean` | — | 单实例模式(复用同一弹窗) |
|
|
738
|
+
| onSubmit | `(close) => void` | — | 提交回调 |
|
|
739
|
+
| onClosed | `() => void` | — | 关闭回调 |
|
|
740
|
+
| onOpen | `() => void` | — | 打开回调 |
|
|
741
|
+
|
|
742
|
+
### configBtn click 回调签名
|
|
743
|
+
|
|
744
|
+
```typescript
|
|
745
|
+
{
|
|
746
|
+
name: '提交',
|
|
747
|
+
type: 'primary',
|
|
748
|
+
click: (instance, { close, getRefs, dialogVm }) => {
|
|
749
|
+
// instance: 渲染组件的内部实例
|
|
750
|
+
// close(): 关闭弹窗
|
|
751
|
+
// getRefs(name): 获取通过 registerRef 注册的引用
|
|
752
|
+
// dialogVm: 弹窗组件实例
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
### registerRef + getRefs 模式
|
|
758
|
+
|
|
759
|
+
在 render 中使用 `registerRef` 注册引用,在 configBtn 的 click 中通过 `getRefs` 获取:
|
|
760
|
+
|
|
761
|
+
```tsx
|
|
762
|
+
dialog({
|
|
763
|
+
title: '编辑',
|
|
764
|
+
render: (h, { registerRef }) => (
|
|
765
|
+
<EsForm
|
|
766
|
+
ref={(el) => { if (el) registerRef('formRef', el) }}
|
|
767
|
+
model={formData}
|
|
768
|
+
formItemList={formItems}
|
|
769
|
+
/>
|
|
770
|
+
),
|
|
771
|
+
configBtn: [
|
|
772
|
+
{ name: '取消', click: (_, { close }) => close() },
|
|
773
|
+
{ name: '提交', type: 'primary', click: (_, { close, getRefs }) => {
|
|
774
|
+
const formRef = getRefs('formRef')
|
|
775
|
+
formRef?.validate().then(() => {
|
|
776
|
+
// 提交逻辑...
|
|
777
|
+
close()
|
|
778
|
+
})
|
|
779
|
+
}}
|
|
780
|
+
]
|
|
781
|
+
})
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
### onlyInstance 模式
|
|
785
|
+
|
|
786
|
+
```typescript
|
|
787
|
+
// 默认:每次调用创建新弹窗
|
|
788
|
+
const dialog = useDialog()
|
|
789
|
+
|
|
790
|
+
// 单实例模式:复用同一弹窗,后续调用更新内容
|
|
791
|
+
const singleDialog = useDialog(null, { onlyInstance: true })
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
### 多实例独立弹窗
|
|
795
|
+
|
|
796
|
+
```typescript
|
|
797
|
+
// 创建多个独立弹窗,可同时打开
|
|
798
|
+
const dialog1 = useDialog()
|
|
799
|
+
const dialog2 = useDialog()
|
|
800
|
+
|
|
801
|
+
// 嵌套弹窗:父弹窗内打开子弹窗
|
|
802
|
+
dialog1({
|
|
803
|
+
title: '父弹窗',
|
|
804
|
+
render: (h) => (
|
|
805
|
+
<div>
|
|
806
|
+
<ElButton onClick={() => dialog2({ title: '子弹窗', render: (h) => <div>嵌套内容</div> })}>
|
|
807
|
+
打开子弹窗
|
|
808
|
+
</ElButton>
|
|
809
|
+
</div>
|
|
810
|
+
)
|
|
811
|
+
})
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
---
|
|
815
|
+
|
|
816
|
+
## SvgIcon 图标组件
|
|
817
|
+
|
|
818
|
+
支持外部 URL 图标和 SVG Symbol Sprite 的图标组件。
|
|
819
|
+
|
|
820
|
+
### Props
|
|
821
|
+
|
|
822
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
823
|
+
|------|------|--------|------|
|
|
824
|
+
| iconClass | `string` | — | 图标名称或外部 URL(必填) |
|
|
825
|
+
| className | `string` | — | 额外样式类 |
|
|
826
|
+
|
|
827
|
+
### 使用
|
|
828
|
+
|
|
829
|
+
```vue
|
|
830
|
+
<!-- SVG Sprite 图标 -->
|
|
831
|
+
<svg-icon icon-class="user" />
|
|
832
|
+
|
|
833
|
+
<!-- 外部 URL 图标(自动检测 http/https 开头) -->
|
|
834
|
+
<svg-icon icon-class="https://example.com/icon.svg" />
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
---
|
|
838
|
+
|
|
839
|
+
## TypeScript 类型
|
|
840
|
+
|
|
841
|
+
es-plus-ui 导出以下 TypeScript 接口,可直接导入使用:
|
|
842
|
+
|
|
843
|
+
```typescript
|
|
844
|
+
import type {
|
|
845
|
+
FormItemOption,
|
|
846
|
+
BtnConfig,
|
|
847
|
+
LayoutFormProps,
|
|
848
|
+
TableColumn,
|
|
849
|
+
TableOptions,
|
|
850
|
+
PaginationConfig,
|
|
851
|
+
DialogOptions,
|
|
852
|
+
ApiParams,
|
|
853
|
+
EsFormInstance,
|
|
854
|
+
EsTableInstance
|
|
855
|
+
} from 'es-plus-ui'
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
| 接口 | 说明 |
|
|
859
|
+
|------|------|
|
|
860
|
+
| `FormItemOption` | 表单项配置 |
|
|
861
|
+
| `BtnConfig` | 按钮配置 |
|
|
862
|
+
| `LayoutFormProps` | 表单布局配置 |
|
|
863
|
+
| `TableColumn` | 表格列配置 |
|
|
864
|
+
| `TableOptions` | 表格选项配置 |
|
|
865
|
+
| `PaginationConfig` | 分页配置 |
|
|
866
|
+
| `DialogOptions` | 弹窗选项配置 |
|
|
867
|
+
| `ApiParams` | API 请求配置 |
|
|
868
|
+
| `EsFormInstance` | EsForm 暴露的方法类型 |
|
|
869
|
+
| `EsTableInstance` | EsTable 暴露的方法类型 |
|
|
870
|
+
|
|
871
|
+
---
|
|
872
|
+
|
|
873
|
+
## 表单+表格+弹窗联动
|
|
874
|
+
|
|
875
|
+
es-plus-ui 的核心优势在于 EsForm、EsTable、useDialog 三者的深度联动,实现配置即开发。
|
|
876
|
+
|
|
877
|
+
### 零代码查询
|
|
878
|
+
|
|
879
|
+
将 EsForm 放入 EsTable 的 default 插槽,按钮设置 `triggerEvent: true`,即可实现零事件代码的查询/重置:
|
|
880
|
+
|
|
881
|
+
```vue
|
|
882
|
+
<es-table
|
|
883
|
+
:columns="columns"
|
|
884
|
+
:options="tableOptions"
|
|
885
|
+
v-model:data-source="tableData"
|
|
886
|
+
v-model:pagination="pagination"
|
|
887
|
+
>
|
|
888
|
+
<es-form
|
|
889
|
+
:model="queryModel"
|
|
890
|
+
:form-item-list="queryItems"
|
|
891
|
+
:config-btn="queryBtns"
|
|
892
|
+
/>
|
|
893
|
+
</es-table>
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
```typescript
|
|
897
|
+
const queryModel = reactive({ keyword: '', status: '' })
|
|
898
|
+
const queryItems = [
|
|
899
|
+
{ prop: 'keyword', label: '关键词', formtype: 'Input', span: 6 },
|
|
900
|
+
{ prop: 'status', label: '状态', formtype: 'Select', span: 6, dataOptions: [...] }
|
|
901
|
+
]
|
|
902
|
+
const queryBtns = [
|
|
903
|
+
{ name: '查询', type: 'primary', key: 'query', triggerEvent: true },
|
|
904
|
+
{ name: '重置', key: 'reset', triggerEvent: true }
|
|
905
|
+
]
|
|
906
|
+
const tableOptions = {
|
|
907
|
+
httpRequest: mockRequest,
|
|
908
|
+
apiParams: { url: '/api/list', method: 'GET', model: queryModel },
|
|
909
|
+
configTableOut: { total: 'total', tableData: 'data', pageSize: 'pageSize', current: 'pageIndex' }
|
|
910
|
+
}
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
> `triggerEvent: true` + `key: 'query'` → EsForm 自动调用父级 EsTable 的 `httpRequestInstance`;`key: 'reset'` → 自动重置表单。
|
|
914
|
+
|
|
915
|
+
### CRUD 弹窗
|
|
916
|
+
|
|
917
|
+
useDialog + JSX EsForm 实现增/编辑弹窗:
|
|
918
|
+
|
|
919
|
+
```tsx
|
|
920
|
+
const dialog = useDialog()
|
|
921
|
+
|
|
922
|
+
function openEditDialog(row) {
|
|
923
|
+
const formData = reactive({ ...row })
|
|
924
|
+
dialog({
|
|
925
|
+
title: '编辑',
|
|
926
|
+
width: '600px',
|
|
927
|
+
render: (h, { registerRef }) => (
|
|
928
|
+
<EsForm
|
|
929
|
+
ref={(el) => { if (el) registerRef('formRef', el) }}
|
|
930
|
+
model={formData}
|
|
931
|
+
formItemList={[
|
|
932
|
+
{ prop: 'name', label: '名称', formtype: 'Input', span: 24 },
|
|
933
|
+
{ prop: 'status', label: '状态', formtype: 'Select', span: 24, dataOptions: [...] }
|
|
934
|
+
]}
|
|
935
|
+
/>
|
|
936
|
+
),
|
|
937
|
+
configBtn: [
|
|
938
|
+
{ name: '取消', click: (_, { close }) => close() },
|
|
939
|
+
{ name: '保存', type: 'primary', click: (_, { close, getRefs }) => {
|
|
940
|
+
getRefs('formRef')?.validate().then(() => {
|
|
941
|
+
// 保存逻辑...
|
|
942
|
+
close()
|
|
943
|
+
tableRef.value?.httpRequestInstance() // 刷新表格
|
|
944
|
+
})
|
|
945
|
+
}}
|
|
946
|
+
]
|
|
947
|
+
})
|
|
948
|
+
}
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
### 弹窗内嵌套表格
|
|
952
|
+
|
|
953
|
+
```tsx
|
|
954
|
+
dialog({
|
|
955
|
+
title: '选择商品',
|
|
956
|
+
width: '800px',
|
|
957
|
+
render: (h, { registerRef }) => (
|
|
958
|
+
<EsTable
|
|
959
|
+
ref={(el) => { if (el) registerRef('tableRef', el) }}
|
|
960
|
+
dataSource={productList}
|
|
961
|
+
columns={productColumns}
|
|
962
|
+
options={{ border: true, multiSelect: true, rowkey: 'id' }}
|
|
963
|
+
@selection-change={(rows) => selectedRows = rows}
|
|
964
|
+
/>
|
|
965
|
+
),
|
|
966
|
+
configBtn: [
|
|
967
|
+
{ name: '取消', click: (_, { close }) => close() },
|
|
968
|
+
{ name: '确定', type: 'primary', click: (_, { close, getRefs }) => {
|
|
969
|
+
const selection = getRefs('tableRef')?.getSelectionRows() || []
|
|
970
|
+
// 处理选中数据...
|
|
971
|
+
close()
|
|
972
|
+
}}
|
|
973
|
+
]
|
|
974
|
+
})
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
---
|
|
978
|
+
|
|
979
|
+
## 常见问题
|
|
980
|
+
|
|
981
|
+
### CSS 未加载
|
|
982
|
+
|
|
983
|
+
确保引入了样式文件:`import 'es-plus-ui/dist/style.css'`
|
|
984
|
+
|
|
985
|
+
### 图标不显示
|
|
986
|
+
|
|
987
|
+
确保安装了 `@element-plus/icons-vue`:`npm install @element-plus/icons-vue`
|
|
988
|
+
|
|
989
|
+
### 表格高度不自适应
|
|
990
|
+
|
|
991
|
+
1. 设置 `heightType: 'height'`(不是 `'auto'`)
|
|
992
|
+
2. 设置 `tabHeight` 为容器高度
|
|
993
|
+
3. 确保父容器有固定高度
|
|
994
|
+
|
|
995
|
+
### configTableOut 映射不生效
|
|
996
|
+
|
|
997
|
+
使用简单 key 名(如 `'total'`、`'list'`),不要使用点号路径(如 `'result.pagination.total'`)。内部 `findValueByKey` 会递归查找嵌套对象。
|
|
998
|
+
|
|
999
|
+
### 切换 options 无效
|
|
1000
|
+
|
|
1001
|
+
es-table 的 `httpRequest`、`configTableOut`、`listenToCallBack` 等选项在挂载后不可响应。使用 `:key` 强制重建:
|
|
1002
|
+
|
|
1003
|
+
```vue
|
|
1004
|
+
<es-table :key="activeFormat" :options="currentOptions" ... />
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
### httpRequest 参数格式
|
|
1008
|
+
|
|
1009
|
+
es-table 传给 `httpRequest` 的参数格式为:
|
|
1010
|
+
|
|
1011
|
+
```typescript
|
|
1012
|
+
{
|
|
1013
|
+
url: string,
|
|
1014
|
+
method: string,
|
|
1015
|
+
headers: Record<string, string>,
|
|
1016
|
+
formParams: Record<string, unknown>, // 合并后的查询参数
|
|
1017
|
+
pageIndex: number,
|
|
1018
|
+
pageSize: number
|
|
1019
|
+
}
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
mockRequest 中应使用此模式解构:
|
|
1023
|
+
|
|
1024
|
+
```typescript
|
|
1025
|
+
const mockRequest = async (params) => {
|
|
1026
|
+
const { formParams, ...rest } = params || {}
|
|
1027
|
+
const { pageIndex = 1, pageSize = 10, ...filters } = { ...formParams, ...rest }
|
|
1028
|
+
// ...
|
|
1029
|
+
}
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
### 选择变化不触发 computed 更新
|
|
1033
|
+
|
|
1034
|
+
`getSelectionRows()` 在 computed 中不是响应式的。请使用 `@selection-change` 事件 + ref:
|
|
1035
|
+
|
|
1036
|
+
```typescript
|
|
1037
|
+
const selectedCount = ref(0)
|
|
1038
|
+
const handleSelectionChange = (rows) => {
|
|
1039
|
+
selectedCount.value = rows?.length || 0
|
|
1040
|
+
}
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
---
|
|
1044
|
+
|
|
1045
|
+
## 更新日志
|
|
1046
|
+
|
|
1047
|
+
### v1.0.0
|
|
1048
|
+
|
|
1049
|
+
- 初始发布
|
|
1050
|
+
- EsForm 配置化表单组件
|
|
1051
|
+
- EsTable 配置化表格组件
|
|
1052
|
+
- EsDialog 增强弹窗组件
|
|
1053
|
+
- useDialog 编程式弹窗 Hook
|
|
1054
|
+
- SvgIcon 图标组件
|
|
1055
|
+
|
|
1056
|
+
## License
|
|
1057
|
+
|
|
1058
|
+
MIT
|