Form 表单
注意
表单v-model
绑定的值尽量使用 ref 定义绑定的值,以避免 reactive 带来的响应式丢失问题。 参考 reactive 的局限性
基础用法(默认通栏)
<template>
<el-card>
<PlusForm
v-model="state"
:columns="columns"
:rules="rules"
@change="handleChange"
@submit="handleSubmit"
@submit-error="handleSubmitError"
@reset="handleReset"
/>
</el-card>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
const state = ref<FieldValues>({
status: '0',
name: '',
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
endTime: [],
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const rules = {
name: [
{
required: true,
message: '请输入名称'
}
],
tag: [
{
required: true,
message: '请输入标签'
}
]
}
const columns: PlusColumn[] = [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
// v0.1.15 版本新增,支持计算属性
tooltip: computed(() => (state.value.name as string) || '提示:复制名称')
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
},
{
label: '标签',
width: 120,
prop: 'tag'
},
{
label: '执行进度',
width: 200,
prop: 'progress'
},
{
label: '评分',
width: 200,
prop: 'rate',
valueType: 'rate'
},
{
label: '是否显示',
width: 100,
prop: 'switch',
valueType: 'switch'
},
{
label: '图片',
prop: 'img',
width: 100,
valueType: 'img'
},
{
label: '时间',
prop: 'time',
valueType: 'date-picker'
},
{
label: '数量',
prop: 'number',
valueType: 'input-number',
fieldProps: { precision: 2, step: 2 }
},
{
label: '城市',
prop: 'city',
valueType: 'cascader',
options: [
{
value: '0',
label: '陕西',
children: [
{
value: '0-0',
label: '西安',
children: [
{
value: '0-0-0',
label: '新城区'
},
{
value: '0-0-1',
label: '高新区'
},
{
value: '0-0-2',
label: '灞桥区'
}
]
}
]
},
{
value: '1',
label: '山西',
children: [
{
value: '1-0',
label: '太原',
children: [
{
value: '1-0-0',
label: '小店区'
},
{
value: '1-0-1',
label: '古交市'
},
{
value: '1-0-2',
label: '万柏林区'
}
]
}
]
}
]
},
{
label: '地区',
prop: 'place',
tooltip: '请精确到门牌号',
fieldProps: {
placeholder: '请精确到门牌号'
}
},
{
label: '要求',
prop: 'demand',
valueType: 'checkbox',
options: [
{
label: '四六级',
value: '0'
},
{
label: '计算机二级证书',
value: '1'
},
{
label: '普通话证书',
value: '2'
}
]
},
{
label: '梦想',
prop: 'gift',
valueType: 'radio',
options: [
{
label: '诗',
value: '0'
},
{
label: '远方',
value: '1'
},
{
label: '美食',
value: '2'
}
]
},
{
label: '到期时间',
prop: 'endTime',
valueType: 'date-picker',
fieldProps: {
type: 'datetimerange',
startPlaceholder: '请选择开始时间',
endPlaceholder: '请选择结束时间'
}
},
{
label: '说明',
prop: 'desc',
valueType: 'textarea',
fieldProps: {
maxlength: 10,
showWordLimit: true,
autosize: { minRows: 2, maxRows: 4 }
}
}
]
const handleChange = (values: FieldValues, prop: PlusColumn) => {
console.log(values, prop, 'change')
}
const handleSubmit = (values: FieldValues) => {
console.log(values, 'Submit')
}
const handleSubmitError = (err: any) => {
console.log(err, 'err')
}
const handleReset = () => {
console.log('handleReset')
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
两列
<template>
<el-card>
<PlusForm
v-model="state"
:columns="columns"
:rules="rules"
:row-props="{ gutter: 20 }"
:col-props="{
span: 12
}"
@change="handleChange"
@submit="handleSubmit"
@submit-error="handleSubmitError"
@reset="handleReset"
/>
</el-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
const state = ref<FieldValues>({
status: '0',
name: '',
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const rules = {
name: [
{
required: true,
message: '请输入名称'
}
],
tag: [
{
required: true,
message: '请输入标签'
}
]
}
const columns: PlusColumn[] = [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
tooltip: '名称最多显示6个字符'
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
},
{
label: '标签',
width: 120,
prop: 'tag'
},
{
label: '执行进度',
width: 200,
prop: 'progress'
},
{
label: '评分',
width: 200,
prop: 'rate',
valueType: 'rate'
},
{
label: '是否显示',
width: 100,
prop: 'switch',
valueType: 'switch'
},
{
label: '图片',
prop: 'img',
width: 100,
valueType: 'img'
},
{
label: '时间',
prop: 'time',
valueType: 'date-picker'
},
{
label: '数量',
prop: 'number',
valueType: 'input-number',
fieldProps: { precision: 2, step: 2 }
},
{
label: '城市',
prop: 'city',
valueType: 'cascader',
options: [
{
value: '0',
label: '陕西',
children: [
{
value: '0-0',
label: '西安',
children: [
{
value: '0-0-0',
label: '新城区'
},
{
value: '0-0-1',
label: '高新区'
},
{
value: '0-0-2',
label: '灞桥区'
}
]
}
]
},
{
value: '1',
label: '山西',
children: [
{
value: '1-0',
label: '太原',
children: [
{
value: '1-0-0',
label: '小店区'
},
{
value: '1-0-1',
label: '古交市'
},
{
value: '1-0-2',
label: '万柏林区'
}
]
}
]
}
]
},
{
label: '地区',
prop: 'place',
tooltip: '请精确到门牌号',
fieldProps: {
placeholder: '请精确到门牌号'
}
},
{
label: '要求',
prop: 'demand',
valueType: 'checkbox',
options: [
{
label: '四六级',
value: '0'
},
{
label: '计算机二级证书',
value: '1'
},
{
label: '普通话证书',
value: '2'
}
]
},
{
label: '梦想',
prop: 'gift',
valueType: 'radio',
options: [
{
label: '诗',
value: '0'
},
{
label: '远方',
value: '1'
}
]
},
{
label: '到期时间',
prop: 'endTime',
valueType: 'date-picker',
fieldProps: {
type: 'datetimerange',
startPlaceholder: '请选择开始时间',
endPlaceholder: '请选择结束时间'
}
},
{
label: '说明',
prop: 'desc',
valueType: 'textarea',
fieldProps: {
maxlength: 10,
showWordLimit: true,
autosize: { minRows: 2, maxRows: 4 }
}
}
]
const handleChange = (values: FieldValues) => {
console.log(values, 'change')
}
const handleSubmit = (values: FieldValues) => {
console.log(values, 'Submit')
}
const handleSubmitError = (err: any) => {
console.log(err, 'err')
}
const handleReset = () => {
console.log('handleReset')
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
混合
<template>
<el-card>
<PlusForm
v-model="state"
:columns="columns"
:rules="rules"
:row-props="{ gutter: 20 }"
@change="handleChange"
@submit="handleSubmit"
@submit-error="handleSubmitError"
@reset="handleReset"
/>
</el-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
const state = ref<FieldValues>({
status: '0',
name: '',
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const rules = {
name: [
{
required: true,
message: '请输入名称'
}
],
tag: [
{
required: true,
message: '请输入标签'
}
]
}
const columns: PlusColumn[] = [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
tooltip: '名称最多显示6个字符',
colProps: {
span: 8
}
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
],
colProps: {
span: 8
}
},
{
label: '标签',
width: 120,
prop: 'tag',
colProps: {
span: 8
}
},
{
label: '执行进度',
width: 200,
prop: 'progress',
colProps: {
span: 12
}
},
{
label: '评分',
width: 200,
prop: 'rate',
valueType: 'rate',
colProps: {
span: 12
}
},
{
label: '是否显示',
width: 100,
prop: 'switch',
valueType: 'switch',
colProps: {
span: 12
}
},
{
label: '图片',
prop: 'img',
width: 100,
valueType: 'img',
colProps: {
span: 12
}
},
{
label: '时间',
prop: 'time',
valueType: 'date-picker',
colProps: {
span: 12
}
},
{
label: '数量',
prop: 'number',
valueType: 'input-number',
fieldProps: { precision: 2, step: 2 },
colProps: {
span: 12
}
},
{
label: '城市',
prop: 'city',
valueType: 'cascader',
options: [
{
value: '0',
label: '陕西',
children: [
{
value: '0-0',
label: '西安',
children: [
{
value: '0-0-0',
label: '新城区'
},
{
value: '0-0-1',
label: '高新区'
},
{
value: '0-0-2',
label: '灞桥区'
}
]
}
]
},
{
value: '1',
label: '山西',
children: [
{
value: '1-0',
label: '太原',
children: [
{
value: '1-0-0',
label: '小店区'
},
{
value: '1-0-1',
label: '古交市'
},
{
value: '1-0-2',
label: '万柏林区'
}
]
}
]
}
]
},
{
label: '地区',
prop: 'place',
tooltip: '请精确到门牌号',
fieldProps: {
placeholder: '请精确到门牌号'
}
},
{
label: '要求',
prop: 'demand',
valueType: 'checkbox',
options: [
{
label: '四六级',
value: '0'
},
{
label: '计算机二级证书',
value: '1'
},
{
label: '普通话证书',
value: '2'
}
]
},
{
label: '梦想',
prop: 'gift',
valueType: 'radio',
options: [
{
label: '诗',
value: '0'
},
{
label: '远方',
value: '1'
},
{
label: '美食',
value: '2'
}
],
colProps: {
span: 12
}
},
{
label: '到期时间',
prop: 'endTime',
valueType: 'date-picker',
fieldProps: {
type: 'datetimerange',
startPlaceholder: '请选择开始时间',
endPlaceholder: '请选择结束时间'
},
colProps: {
span: 12
}
},
{
label: '说明',
prop: 'desc',
valueType: 'textarea',
fieldProps: {
maxlength: 10,
showWordLimit: true,
autosize: { minRows: 2, maxRows: 4 }
}
}
]
const handleChange = (values: FieldValues) => {
console.log(values, 'change')
}
const handleSubmit = (values: FieldValues) => {
console.log(values, 'Submit')
}
const handleSubmitError = (err: any) => {
console.log(err, 'err')
}
const handleReset = () => {
console.log('handleReset')
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
隐藏 label
表单整体的hasLabel
设置为 false
时, 隐藏 label。配置columns中的 hasLabel
可以控制单个表单项的 label 是否显示, 优先级会更高。
<template>
<el-card>
<PlusForm v-model="state" :columns="columns" :has-label="false" />
</el-card>
<el-card style="margin-top: 20px">
<PlusForm v-model="state" :columns="columns1" />
</el-card>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
const state = ref<FieldValues>({
status: '0',
name: ''
})
const columns: PlusColumn[] = [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
tooltip: '名称最多显示6个字符',
// 优先级会更高。
hasLabel: true
},
{
label: '名称1',
width: 120,
prop: 'name1',
// 优先级会更高。
hasLabel: ref(true)
},
{
label: '名称2',
width: 120,
prop: 'name2',
// 优先级会更高。
hasLabel: computed(() => true)
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
}
]
const columns1: PlusColumn[] = [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
tooltip: '名称最多显示6个字符',
// 优先级会更高。
hasLabel: false
},
{
label: '名称1',
width: 120,
prop: 'name1',
// 优先级会更高。
hasLabel: ref(false)
},
{
label: '名称2',
width: 120,
prop: 'name2',
// 优先级会更高。
hasLabel: computed(() => false)
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
}
]
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
异步(动态)数据用法
PlusColumn 配置中的 options
支持数组,computed 推荐,函数和 Promise。 PlusColumn 配置中的 fieldProps
和formItemProps
支持对象 object,computed,函数和 Promise。
常见的使用场景是数据来自后端接口,这里调用后端接口,options 返回 OptionsRow[]数组即可。
提示
options
建议优先使用 computed
,可防止用函数在columns
配置项数据变更后函数重复执行的问题。
使用步骤为:
columns
配置项外面定义一个ref
数组
- 外部异步函数获取到值赋值到
ref
- 外部异步函数获取到值赋值到
columns
配置项里用computed
返回ref
的value
参考下面推荐写法代码示例。
<template>
<el-card>
<PlusForm
v-model="state"
:columns="columns"
:rules="rules"
@change="handleChange"
@submit="handleSubmit"
@submit-error="handleSubmitError"
@reset="handleReset"
/>
</el-card>
</template>
<script lang="ts" setup>
import type { Ref } from 'vue'
import { ref, computed, h, Fragment } from 'vue'
import { ElTag } from 'element-plus'
import type { PlusColumn, FieldValues, OptionsRow } from 'plus-pro-components'
const state = ref<FieldValues>({
status: '0',
name: '',
rate: 4,
progress: 100,
number: 100,
switch: true,
time: new Date().toString(),
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const rules = {
name: [
{
required: true,
message: '请输入名称'
}
],
tag: [
{
required: true,
message: '请输入标签'
}
]
}
// options推荐写法
// 1. 定义一个 `ref`数组
const options: Ref<OptionsRow[]> = ref([])
// 2. 异步函数获取到值赋值到 `ref`
const getOptions = async () => {
// 等待2s
await new Promise(resolve => {
setTimeout(() => {
resolve('')
}, 2000)
})
options.value = [
{ label: '未解决', value: '0', color: 'red' },
{ label: '已解决', value: '1', color: 'blue' }
]
}
getOptions()
const columns: PlusColumn[] = [
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
// options推荐写法
// 3. 用 computed 返回 ref 的 value
options: computed(() => options.value),
renderLabel: label =>
h(Fragment, null, [
h('span', label),
h(
ElTag,
{
effect: 'dark'
},
() => '推荐写法'
)
]),
formItemProps: async () => {
return { labelWidth: '120px' }
}
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: async () => {
// 等待2s
await new Promise(resolve => {
setTimeout(() => {
resolve('')
}, 2000)
})
return [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
},
formItemProps: async () => {
return { labelWidth: '100px' }
}
},
{
label: '数量',
prop: 'number',
valueType: 'input-number',
fieldProps: () => {
return { precision: 2, step: 2 }
},
formItemProps: () => {
return { labelWidth: '100px' }
}
},
{
label: '城市',
prop: 'city',
valueType: 'cascader',
options: async () => {
// 等待2s
await new Promise(resolve => {
setTimeout(() => {
resolve('')
}, 2000)
})
return [
{
value: '0',
label: '陕西',
children: [
{
value: '0-0',
label: '西安',
children: [
{
value: '0-0-0',
label: '新城区'
},
{
value: '0-0-1',
label: '高新区'
},
{
value: '0-0-2',
label: '灞桥区'
}
]
}
]
},
{
value: '1',
label: '山西',
children: [
{
value: '1-0',
label: '太原',
children: [
{
value: '1-0-0',
label: '小店区'
},
{
value: '1-0-1',
label: '古交市'
},
{
value: '1-0-2',
label: '万柏林区'
}
]
}
]
}
]
},
formItemProps: async () => {
return { labelWidth: '100px' }
}
},
{
label: '地区',
prop: 'place',
tooltip: '请精确到门牌号',
fieldProps: async () => {
// 等待2s
await new Promise(resolve => {
setTimeout(() => {
resolve('')
}, 2000)
})
return {
placeholder: '请精确到门牌号'
}
},
formItemProps: async () => {
return { labelWidth: '100px' }
}
},
{
label: '要求',
prop: 'demand',
valueType: 'checkbox',
formItemProps: () => {
return new Promise(resolve => {
resolve({ labelWidth: '100px' })
})
},
options: () => {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{
label: '四六级',
value: '0'
},
{
label: '计算机二级证书',
value: '1'
},
{
label: '普通话证书',
value: '2'
}
])
}, 2000)
})
}
},
{
label: '要求1',
prop: 'demand1',
valueType: 'checkbox',
options: computed(() =>
state.value.status === '0'
? [
{
label: '四六级',
value: '0'
},
{
label: '计算机二级证书',
value: '1'
},
{
label: '普通话证书',
value: '2'
}
]
: [
{
label: '四六级',
value: '0'
}
]
)
},
{
label: '梦想',
prop: 'gift',
valueType: 'radio',
options: new Promise(resolve => {
setTimeout(() => {
resolve([
{
label: '诗',
value: '0'
},
{
label: '远方',
value: '1'
},
{
label: '美食',
value: '2'
}
])
}, 2000)
})
},
{
label: '到期时间',
prop: 'endTime',
valueType: 'date-picker',
fieldProps: () => {
return new Promise(resolve => {
setTimeout(() => {
resolve({
type: 'datetimerange',
startPlaceholder: '请选择开始时间',
endPlaceholder: '请选择结束时间'
})
}, 2000)
})
}
},
{
label: '说明',
prop: 'desc',
valueType: 'textarea',
fieldProps: new Promise(resolve => {
setTimeout(() => {
resolve({
placeholder: '请输入详情',
maxlength: 10,
showWordLimit: true,
autosize: { minRows: 2, maxRows: 4 }
})
}, 2000)
})
}
]
const handleChange = (values: FieldValues, prop: PlusColumn) => {
console.log(values, prop, 'change')
}
const handleSubmit = (values: FieldValues) => {
console.log(values, 'Submit')
}
const handleSubmitError = (err: any) => {
console.log(err, 'err')
}
const handleReset = () => {
console.log('handleReset')
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
数据属性联动
PlusColumn的fieldProps
,formItemProps
,options
,hideInForm
等根据数据变化,动态显示设置。
如下示例,点击状态选择框,当状态为已解决
时,要求1 多选框
会变成 1 个可选, 名称 1,2 输入框
会被禁用,标签 1 输入框
会被隐藏,标签 2 输入框
会变成必填。
<template>
<el-card>
<PlusForm v-model="state" :columns="columns" />
</el-card>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
const state = ref<FieldValues>({
status: '0',
name: '',
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
endTime: [],
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const columns: PlusColumn[] = [
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
}
]
},
{
label: '要求1',
prop: 'demand1',
valueType: 'checkbox',
options: computed(() =>
state.value.status === '0'
? [
{
label: '四六级',
value: '0'
},
{
label: '计算机二级证书',
value: '1'
},
{
label: '普通话证书',
value: '2'
}
]
: [
{
label: '四六级',
value: '0'
}
]
)
},
{
label: '名称1',
width: 120,
prop: 'name1',
valueType: 'copy',
tooltip: '名称最多显示6个字符',
fieldProps: computed(() => ({ disabled: state.value.status === '1' }))
},
{
label: '标签1',
width: 120,
prop: 'tag1',
hideInForm: computed(() => state.value.status === '1')
},
{
label: '名称2',
width: 120,
prop: 'name2',
valueType: 'copy',
tooltip: '名称最多显示6个字符',
fieldProps: computed(() => ({ disabled: state.value.status === '1' }))
},
{
label: '标签2',
width: 120,
prop: 'tag2',
formItemProps: computed(() => ({ required: state.value.status === '1' }))
}
]
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
表单多级数据的双向绑定
v0.1.7columns中的 prop
支持 x.y.z
形式的 多(无限)级数据形式。
<template>
<div>
<PlusForm
v-model="state"
:columns="columns"
:rules="rules"
@change="handleChange"
@submit="handleSubmit"
/>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
import { set } from 'lodash-es'
const state = ref<FieldValues>({})
const columns: PlusColumn[] = [
{
label: '文本',
prop: 'a.b.c',
renderField: value => value + ' - 自定义'
},
{
label: 'name',
prop: 'a.b.d'
},
{
label: 'tag',
prop: 'a.b.e',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
},
{
label: 'tag1',
prop: 'a.b.f',
valueType: 'select',
fieldProps: {
multiple: true
},
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
}
]
const rules = {
'a.b.c': [
{
required: true,
message: '请输入名称'
}
],
'a.b.d': [
{
required: true,
message: '请输入标签'
}
]
}
const initValue = () => {
set(state.value, 'a.b.c', '我是一段有想法的文本')
set(state.value, 'a.b.e', '3')
set(state.value, 'a.b.f', ['0', '3'])
}
setTimeout(() => {
initValue()
}, 1000)
const handleChange = (values: FieldValues) => {
console.log(values)
}
const handleSubmit = (values: FieldValues) => {
console.log(values)
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
表单配置项 v-model 修饰符的使用
对于输入框想添加.trim
,.number
和 .lazy
这些修饰符,可以配置 columns
中 fieldProps
的modelModifiers
属性。
<template>
<el-card>
<PlusForm
v-model="state"
label-width="140px"
:columns="columns"
@change="handleChange"
@submit="handleSubmit"
@submit-error="handleSubmitError"
@reset="handleReset"
/>
</el-card>
</template>
<script lang="ts" setup>
import { ref, h } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
import { ElInput } from 'element-plus'
const state = ref<FieldValues>({
autocomplete: 'vue',
input: '100',
input1: '单行文本',
textarea: '多行文本'
})
interface RestaurantItem {
value: string
link: string
}
const restaurants = ref<RestaurantItem[]>([])
const createFilter = (queryString: string) => {
return (restaurant: RestaurantItem) => {
return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
}
}
const loadAll = () => {
return [
{ value: 'vue', link: 'https://github.com/vuejs/vue' },
{ value: 'element', link: 'https://github.com/ElemeFE/element' },
{ value: 'cooking', link: 'https://github.com/ElemeFE/cooking' },
{ value: 'mint-ui', link: 'https://github.com/ElemeFE/mint-ui' },
{ value: 'vuex', link: 'https://github.com/vuejs/vuex' },
{ value: 'vue-router', link: 'https://github.com/vuejs/vue-router' },
{ value: 'babel', link: 'https://github.com/babel/babel' }
]
}
restaurants.value = loadAll()
const columns: PlusColumn[] = [
{
label: 'autocomplete',
width: 120,
prop: 'autocomplete',
valueType: 'autocomplete',
tooltip: '自动补全输入框',
fieldProps: {
modelModifiers: { trim: true },
fetchSuggestions: (queryString: string, cb: any) => {
const results = queryString
? restaurants.value.filter(createFilter(queryString))
: restaurants.value
// call callback function to return suggestions
cb(results)
}
}
},
{
label: 'input',
width: 120,
prop: 'input',
fieldProps: {
modelModifiers: { trim: true, number: true, lazy: true }
}
},
{
label: 'input',
width: 120,
prop: 'input1',
// fieldProps 会同时作用于 renderField返回值的props,优先级低于返回值的props,建议直接写在renderField中
fieldProps: {
modelModifiers: { trim: true, number: false }
},
renderField: (value, onChange) => {
return h(ElInput, { modelValue: value as string, onChange, modelModifiers: { trim: true } })
}
},
{
label: 'textarea',
prop: 'textarea',
valueType: 'textarea',
fieldProps: {
modelModifiers: { trim: true }
}
}
]
const handleChange = (values: FieldValues, prop: PlusColumn) => {
console.log(values, prop, 'change')
}
const handleSubmit = (values: FieldValues) => {
console.log(values, 'Submit')
}
const handleSubmitError = (err: any) => {
console.log(err, 'err')
}
const handleReset = () => {
console.log('handleReset')
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
分组表单
配置 group
字段,则显示分组。配置 group
字段后,columns
则不在生效。
group
配置参考 PlusFormGroupRow
<template>
<div>
<PlusForm
v-model="state"
:group="group"
:rules="rules"
@change="handleChange"
@submit="handleSubmit"
@submit-error="handleSubmitError"
@reset="handleReset"
/>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { FieldValues, PlusColumn, PlusFormGroupRow } from 'plus-pro-components'
import { CreditCard, Calendar, Soccer } from '@element-plus/icons-vue'
const state = ref({
status: '0',
name: '',
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const rules = {
name: [
{
required: true,
message: '请输入名称'
}
],
tag: [
{
required: true,
message: '请输入标签'
}
]
}
const group: PlusFormGroupRow[] = [
{
title: '第一分组',
icon: CreditCard,
columns: [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
tooltip: '名称最多显示6个字符'
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
}
]
},
{
title: '第二分组',
icon: Calendar,
columns: [
{
label: '标签',
width: 120,
prop: 'tag'
},
{
label: '执行进度',
width: 200,
prop: 'progress'
},
{
label: '评分',
width: 200,
prop: 'rate',
valueType: 'rate'
},
{
label: '是否显示',
width: 100,
prop: 'switch',
valueType: 'switch'
}
]
},
{
title: '第三分组',
icon: Soccer,
columns: [
{
label: '时间',
prop: 'time',
valueType: 'date-picker'
},
{
label: '数量',
prop: 'number',
valueType: 'input-number',
fieldProps: { precision: 2, step: 2 }
},
{
label: '城市',
prop: 'city',
valueType: 'cascader',
options: [
{
value: '0',
label: '陕西',
children: [
{
value: '0-0',
label: '西安',
children: [
{
value: '0-0-0',
label: '新城区'
},
{
value: '0-0-1',
label: '高新区'
},
{
value: '0-0-2',
label: '灞桥区'
}
]
}
]
},
{
value: '1',
label: '山西',
children: [
{
value: '1-0',
label: '太原',
children: [
{
value: '1-0-0',
label: '小店区'
},
{
value: '1-0-1',
label: '古交市'
},
{
value: '1-0-2',
label: '万柏林区'
}
]
}
]
}
]
},
{
label: '地区',
prop: 'place',
tooltip: '请精确到门牌号',
fieldProps: {
placeholder: '请精确到门牌号'
}
},
{
label: '经度',
prop: 'lng',
tooltip: '请保留两位小数'
},
{
label: '纬度',
prop: 'lat',
tooltip: '请保留两位小数'
},
{
label: '要求',
prop: 'demand',
valueType: 'checkbox',
options: [
{
label: '四六级',
value: '0'
},
{
label: '计算机二级证书',
value: '1'
},
{
label: '普通话证书',
value: '2'
}
]
},
{
label: '梦想',
prop: 'gift',
valueType: 'radio',
options: [
{
label: '诗',
value: '0'
},
{
label: '远方',
value: '1'
},
{
label: '美食',
value: '2'
}
]
},
{
label: '到期时间',
prop: 'endTime',
valueType: 'date-picker',
fieldProps: {
type: 'datetimerange',
startPlaceholder: '请选择开始时间',
endPlaceholder: '请选择结束时间'
}
},
{
label: '奖励',
prop: 'price'
},
{
label: '提成',
prop: 'percentage'
},
{
label: '说明',
prop: 'desc',
valueType: 'textarea',
fieldProps: {
maxlength: 10,
showWordLimit: true,
autosize: { minRows: 2, maxRows: 4 }
}
}
]
}
]
const handleChange = (values: FieldValues, prop: PlusColumn) => {
console.log(values, prop, 'change')
}
const handleSubmit = (values: FieldValues) => {
console.log(values, 'Submit')
}
const handleSubmitError = (err: any) => {
console.log(err, 'err')
}
const handleReset = () => {
console.log('handleReset')
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
动态分组表单
v0.1.4配置 PlusFormGroupRow[hideInGroup
] 字段为true
,隐藏当前分组,支持计算属性。
<template>
<div>
<el-button type="primary" style="margin-bottom: 10px" @click="toggleHideInGroup">
动态隐藏分组表单
</el-button>
<PlusForm v-model="state" :group="group" />
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import type { PlusFormGroupRow } from 'plus-pro-components'
import { CreditCard, Calendar, Soccer } from '@element-plus/icons-vue'
const state = ref({
status: '0',
name: '',
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const isHideInGroup = ref(false)
const group: PlusFormGroupRow[] = [
{
title: '第一分组',
icon: CreditCard,
hideInGroup: true,
columns: [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
tooltip: '名称最多显示6个字符'
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
}
]
},
{
title: '第二分组',
icon: Calendar,
hideInGroup: computed(() => {
return isHideInGroup.value
}),
columns: [
{
label: '标签',
width: 120,
prop: 'tag'
},
{
label: '执行进度',
width: 200,
prop: 'progress'
},
{
label: '评分',
width: 200,
prop: 'rate',
valueType: 'rate'
},
{
label: '是否显示',
width: 100,
prop: 'switch',
valueType: 'switch'
}
]
},
{
title: '第三分组',
icon: Soccer,
columns: [
{
label: '时间',
prop: 'time',
valueType: 'date-picker'
},
{
label: '数量',
prop: 'number',
valueType: 'input-number',
fieldProps: { precision: 2, step: 2 }
},
{
label: '城市',
prop: 'city',
valueType: 'cascader',
options: [
{
value: '0',
label: '陕西',
children: [
{
value: '0-0',
label: '西安',
children: [
{
value: '0-0-0',
label: '新城区'
},
{
value: '0-0-1',
label: '高新区'
},
{
value: '0-0-2',
label: '灞桥区'
}
]
}
]
},
{
value: '1',
label: '山西',
children: [
{
value: '1-0',
label: '太原',
children: [
{
value: '1-0-0',
label: '小店区'
},
{
value: '1-0-1',
label: '古交市'
},
{
value: '1-0-2',
label: '万柏林区'
}
]
}
]
}
]
},
{
label: '地区',
prop: 'place',
tooltip: '请精确到门牌号',
fieldProps: {
placeholder: '请精确到门牌号'
}
},
{
label: '经度',
prop: 'lng',
tooltip: '请保留两位小数'
},
{
label: '纬度',
prop: 'lat',
tooltip: '请保留两位小数'
},
{
label: '要求',
prop: 'demand',
valueType: 'checkbox',
options: [
{
label: '四六级',
value: '0'
},
{
label: '计算机二级证书',
value: '1'
},
{
label: '普通话证书',
value: '2'
}
]
},
{
label: '梦想',
prop: 'gift',
valueType: 'radio',
options: [
{
label: '诗',
value: '0'
},
{
label: '远方',
value: '1'
},
{
label: '美食',
value: '2'
}
]
},
{
label: '到期时间',
prop: 'endTime',
valueType: 'date-picker',
fieldProps: {
type: 'datetimerange',
startPlaceholder: '请选择开始时间',
endPlaceholder: '请选择结束时间'
}
},
{
label: '奖励',
prop: 'price'
},
{
label: '提成',
prop: 'percentage'
},
{
label: '说明',
prop: 'desc',
valueType: 'textarea',
fieldProps: {
maxlength: 10,
showWordLimit: true,
autosize: { minRows: 2, maxRows: 4 }
}
}
]
}
]
const toggleHideInGroup = () => {
isHideInGroup.value = !isHideInGroup.value
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
分组表单自定义头部
添加 group-header
插槽即可实现。
<template>
<div>
<PlusForm
v-model="state"
:group="group"
:rules="rules"
@change="handleChange"
@submit="handleSubmit"
@submit-error="handleSubmitError"
@reset="handleReset"
>
<template #group-header="{ title, icon }">
<div class="custom-group-header">
<span>
{{ title }}
<el-icon> <component :is="icon" /> </el-icon>
</span>
<el-button type="primary"> 添加</el-button>
</div>
</template>
</PlusForm>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { FieldValues, PlusColumn, PlusFormGroupRow } from 'plus-pro-components'
import { CreditCard, Calendar, Soccer } from '@element-plus/icons-vue'
const state = ref({
status: '0',
name: '',
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const rules = {
name: [
{
required: true,
message: '请输入名称'
}
],
tag: [
{
required: true,
message: '请输入标签'
}
]
}
const group: PlusFormGroupRow[] = [
{
title: '第一分组',
icon: CreditCard,
columns: [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
tooltip: '名称最多显示6个字符'
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
}
]
},
{
title: '第二分组',
icon: Calendar,
columns: [
{
label: '标签',
width: 120,
prop: 'tag'
},
{
label: '执行进度',
width: 200,
prop: 'progress'
},
{
label: '评分',
width: 200,
prop: 'rate',
valueType: 'rate'
},
{
label: '是否显示',
width: 100,
prop: 'switch',
valueType: 'switch'
}
]
},
{
title: '第三分组',
icon: Soccer,
columns: [
{
label: '时间',
prop: 'time',
valueType: 'date-picker'
},
{
label: '数量',
prop: 'number',
valueType: 'input-number',
fieldProps: { precision: 2, step: 2 }
},
{
label: '城市',
prop: 'city',
valueType: 'cascader',
options: [
{
value: '0',
label: '陕西',
children: [
{
value: '0-0',
label: '西安',
children: [
{
value: '0-0-0',
label: '新城区'
},
{
value: '0-0-1',
label: '高新区'
},
{
value: '0-0-2',
label: '灞桥区'
}
]
}
]
},
{
value: '1',
label: '山西',
children: [
{
value: '1-0',
label: '太原',
children: [
{
value: '1-0-0',
label: '小店区'
},
{
value: '1-0-1',
label: '古交市'
},
{
value: '1-0-2',
label: '万柏林区'
}
]
}
]
}
]
},
{
label: '地区',
prop: 'place',
tooltip: '请精确到门牌号',
fieldProps: {
placeholder: '请精确到门牌号'
}
},
{
label: '经度',
prop: 'lng',
tooltip: '请保留两位小数'
},
{
label: '纬度',
prop: 'lat',
tooltip: '请保留两位小数'
},
{
label: '要求',
prop: 'demand',
valueType: 'checkbox',
options: [
{
label: '四六级',
value: '0'
},
{
label: '计算机二级证书',
value: '1'
},
{
label: '普通话证书',
value: '2'
}
]
},
{
label: '梦想',
prop: 'gift',
valueType: 'radio',
options: [
{
label: '诗',
value: '0'
},
{
label: '远方',
value: '1'
},
{
label: '美食',
value: '2'
}
]
},
{
label: '到期时间',
prop: 'endTime',
valueType: 'date-picker',
fieldProps: {
type: 'datetimerange',
startPlaceholder: '请选择开始时间',
endPlaceholder: '请选择结束时间'
}
},
{
label: '奖励',
prop: 'price'
},
{
label: '提成',
prop: 'percentage'
},
{
label: '说明',
prop: 'desc',
valueType: 'textarea',
fieldProps: {
maxlength: 10,
showWordLimit: true,
autosize: { minRows: 2, maxRows: 4 }
}
}
]
}
]
const handleChange = (values: FieldValues, prop: PlusColumn) => {
console.log(values, prop, 'change')
}
const handleSubmit = (values: FieldValues) => {
console.log(values, 'Submit')
}
const handleSubmitError = (err: any) => {
console.log(err, 'err')
}
const handleReset = () => {
console.log('handleReset')
}
</script>
<style lang="scss" scoped>
.custom-group-header {
display: flex;
align-items: center;
color: var(--el-color-primary);
justify-content: space-between;
.el-icon {
margin-right: 5px;
}
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
自定义表单底部按钮
组件提供 footer
插槽可以自定义表单底部,并提供默认的 提交方法、重置方法 的作用域插槽参数 {handleSubmit,handleReset}
。
<template>
<el-card>
<PlusForm v-model="state" :columns="columns" :rules="rules" @submit="mySubmit">
<template #footer="{ handleSubmit, handleReset }">
<div style="margin: 0 auto">
<el-button type="info">返回</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">通过</el-button>
<el-button v-if="hasReset" type="warning" @click="handleReset">重置</el-button>
<el-button type="danger">驳回</el-button>
</div>
</template>
</PlusForm>
</el-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
const state = ref<FieldValues>({
status: '0',
content: ''
})
const submitLoading = ref(false)
const hasReset = ref(true)
const columns: PlusColumn[] = [
{
label: '原因',
width: 120,
prop: 'content',
valueType: 'textarea'
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
}
]
}
]
const rules = {
content: [
{
required: true,
message: '请输入原因'
}
]
}
const mySubmit = () => {
console.log(state.value)
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
表单单项插槽配置
组件提供 fieldSlots
对象可以简易配置表单单项插槽, fieldSlots
的插槽配置是根据表单的 valueType 自动进行匹配的, 如:
valueType
是undefined
(默认值)时,fieldSlots
的插槽就是给 ElInput的。valueType
是autocomplete
时,fieldSlots
的插槽就是给 ElAutocomplete的。- 其他以此类推(特殊的除外)
特殊的
valueType
是checkbox
时,fieldSlots
的插槽就是给 ElCheckboxGroup 的。valueType
是radio
时,fieldSlots
的插槽就是给 ElRadioGroup 的。
要实现更强大的自定义,请使用自定义表单项。
注意
valueType 的值对应的组件本身没有插槽时,fieldSlots
不生效。
<template>
<el-card>
<PlusForm v-model="state" :columns="columns" />
</el-card>
</template>
<script lang="ts" setup>
import { ref, h } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
import { Search } from '@element-plus/icons-vue'
import { ElButton, ElIcon, ElSelect, ElOption } from 'element-plus'
interface RestaurantItem {
value: string
link: string
}
const restaurants = ref<RestaurantItem[]>([])
const querySearch = (queryString: string, cb: any) => {
const results = queryString
? restaurants.value.filter(createFilter(queryString))
: restaurants.value
// call callback function to return suggestions
cb(results)
}
const createFilter = (queryString: string) => {
return (restaurant: RestaurantItem) => {
return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
}
}
const loadAll = () => {
return [
{ value: 'vue', link: 'https://github.com/vuejs/vue' },
{ value: 'element', link: 'https://github.com/ElemeFE/element' },
{ value: 'cooking', link: 'https://github.com/ElemeFE/cooking' },
{ value: 'mint-ui', link: 'https://github.com/ElemeFE/mint-ui' },
{ value: 'vuex', link: 'https://github.com/vuejs/vuex' },
{ value: 'vue-router', link: 'https://github.com/vuejs/vue-router' },
{ value: 'babel', link: 'https://github.com/babel/babel' }
]
}
restaurants.value = loadAll()
const options = [
{
value: 'guide',
label: 'Guide',
children: [
{
value: 'disciplines',
label: 'Disciplines',
children: [
{
value: 'consistency',
label: 'Consistency'
},
{
value: 'feedback',
label: 'Feedback'
},
{
value: 'efficiency',
label: 'Efficiency'
},
{
value: 'controllability',
label: 'Controllability'
}
]
},
{
value: 'navigation',
label: 'Navigation',
children: [
{
value: 'side nav',
label: 'Side Navigation'
},
{
value: 'top nav',
label: 'Top Navigation'
}
]
}
]
},
{
value: 'component',
label: 'Component',
children: [
{
value: 'basic',
label: 'Basic',
children: [
{
value: 'layout',
label: 'Layout'
},
{
value: 'color',
label: 'Color'
},
{
value: 'typography',
label: 'Typography'
},
{
value: 'icon',
label: 'Icon'
},
{
value: 'button',
label: 'Button'
}
]
},
{
value: 'form',
label: 'Form',
children: [
{
value: 'radio',
label: 'Radio'
},
{
value: 'checkbox',
label: 'Checkbox'
},
{
value: 'input',
label: 'Input'
},
{
value: 'input-number',
label: 'InputNumber'
},
{
value: 'select',
label: 'Select'
},
{
value: 'cascader',
label: 'Cascader'
},
{
value: 'switch',
label: 'Switch'
},
{
value: 'slider',
label: 'Slider'
},
{
value: 'time-picker',
label: 'TimePicker'
},
{
value: 'date-picker',
label: 'DatePicker'
},
{
value: 'datetime-picker',
label: 'DateTimePicker'
},
{
value: 'upload',
label: 'Upload'
},
{
value: 'rate',
label: 'Rate'
},
{
value: 'form',
label: 'Form'
}
]
},
{
value: 'data',
label: 'Data',
children: [
{
value: 'table',
label: 'Table'
},
{
value: 'tag',
label: 'Tag'
},
{
value: 'progress',
label: 'Progress'
},
{
value: 'tree',
label: 'Tree'
},
{
value: 'pagination',
label: 'Pagination'
},
{
value: 'badge',
label: 'Badge'
}
]
},
{
value: 'notice',
label: 'Notice',
children: [
{
value: 'alert',
label: 'Alert'
},
{
value: 'loading',
label: 'Loading'
},
{
value: 'message',
label: 'Message'
},
{
value: 'message-box',
label: 'MessageBox'
},
{
value: 'notification',
label: 'Notification'
}
]
},
{
value: 'navigation',
label: 'Navigation',
children: [
{
value: 'menu',
label: 'Menu'
},
{
value: 'tabs',
label: 'Tabs'
},
{
value: 'breadcrumb',
label: 'Breadcrumb'
},
{
value: 'dropdown',
label: 'Dropdown'
},
{
value: 'steps',
label: 'Steps'
}
]
},
{
value: 'others',
label: 'Others',
children: [
{
value: 'dialog',
label: 'Dialog'
},
{
value: 'tooltip',
label: 'Tooltip'
},
{
value: 'popover',
label: 'Popover'
},
{
value: 'card',
label: 'Card'
},
{
value: 'carousel',
label: 'Carousel'
},
{
value: 'collapse',
label: 'Collapse'
}
]
}
]
}
]
const state = ref<FieldValues>({
it: '',
domain: '',
inputSelect: '',
cas: '',
switch: true
})
const columns: PlusColumn[] = [
{
label: '域名',
prop: 'domain',
fieldProps: { clearable: false },
fieldSlots: {
suffix: () => h(ElIcon, null, () => h(Search)),
prefix: () => '提示: ',
prepend: () => 'http://',
append: () => '.com'
}
},
{
label: '技术栈',
prop: 'it',
valueType: 'autocomplete',
fieldProps: { clearable: false, fetchSuggestions: querySearch },
fieldSlots: {
suffix: () => h(ElIcon, null, () => h(Search)),
prefix: () => 'vue 生态:',
prepend: () => '我喜欢用',
append: () =>
h(
ElButton,
{
icon: Search
},
() => '搜索'
)
}
},
{
label: 'input选择',
prop: 'inputSelect',
fieldProps: { clearable: false, placeholder: '请选择' },
fieldSlots: {
prepend: () => h(ElIcon, null, () => h(Search)),
append: () =>
h(
ElSelect,
{
style: { minWidth: '100px' }
},
() => [
h(ElOption, {
label: 'Restaurant',
value: '1'
}),
h(ElOption, {
label: 'Order',
value: '2'
}),
h(ElOption, {
label: 'Tel',
value: '3'
})
]
)
}
},
{
label: '级联',
prop: 'cas',
valueType: 'cascader',
options: options,
fieldProps: { clearable: false, placeholder: '请选择' },
fieldSlots: {
default: ({ node, data }) => {
return h(
'div',
null,
`${data.label}${!node.isLeaf ? '(' + data.children.length + ')' : ''}`
)
}
}
},
{
label: '开关',
prop: 'switch',
valueType: 'switch',
tooltip: 'element-plus@2.4.4 版本开始支持',
fieldSlots: {
// element-plus@2.4.4 版本开始支持
'active-action': () => {
return h('span', 'T')
},
// element-plus@2.4.4 版本开始支持
'inactive-action': () => {
return h('span', 'F')
}
}
}
]
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
自定义表单项 (插槽)
注意
插槽 的优先级低于 renderField,高于 valueType。
PlusForm
组件会自动根据配置项的 prop
生成对应的插槽,例如下面的配置项,则会自动生成两个名称叫做 [ plus-field-name
]和 [ plus-field-status
] 的两个插槽,插槽的生成规则就是 固定 key 值 [ plus-field- ] 然后加上 配置项的 prop
。
import { PlusColumn } from 'plus-pro-components'
const tableConfig: PlusColumn[] = [
{
label: '名称',
// 自动生成对应的插槽 'plus-field-name'
prop: 'name'
},
{
label: '状态',
// 自动生成对应的插槽 'plus-field-status'
prop: 'status'
}
]
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<PlusForm v-model="state" :rules="rules" :columns="columns" :row-props="{ gutter: 20 }">
<!--这里的plus-field-name 插槽没有生效,因为它的优先级低于renderField函数 -->
<template #plus-field-name>
<el-input v-model="state.name" placeholder="自定义输入框插槽" />
</template>
<template #plus-field-status>
<el-input v-model="state.status" placeholder="自定义输入框插槽" />
</template>
</PlusForm>
</div>
</template>
<script lang="ts" setup>
import { ref, h } from 'vue'
import type { PlusColumn } from 'plus-pro-components'
const state = ref({
status: '0',
name: '默认值',
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const rules = {
name: [
{
required: true,
message: '请输入名称'
}
],
tag: [
{
required: true,
message: '请输入标签'
}
]
}
const columns: PlusColumn[] = [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
tooltip: '名称最多显示6个字符',
renderField: value => {
return h('div', null, value as string)
}
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
}
]
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
自定义表单项 (renderField)
注意
renderField 的优先级高于插槽。
自定义表单项的核心方法是定义 renderField
方法, renderField
方法需要返回一个 VNode 或 String
提示
renderField 返回的值
的props
和配置项的fieldProps
会同时生效,renderField 返回的值
的props
优先级高于 fieldProps
.
<template>
<el-card>
<PlusForm v-model="state" :columns="columns" label-width="200px" @change="handleChange" />
</el-card>
</template>
<script lang="ts" setup>
import { ref, h, Fragment } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
/**
* @plus-pro-components/utils 是辅助plus-pro-components的工具包,如果需要使用,需要单独安装
* 参考文档 https://plus-pro-components.com/api/utils/
*/
import { fileToDataURL } from '@plus-pro-components/utils'
import { ElUpload, ElButton, ElImage, ElInput, ElTransfer } from 'element-plus'
const state = ref<FieldValues>({
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
elData: 'elData初始值',
elData1: 'elData1初始值',
elTransfer: [],
data: '初始值',
data1: '初始值1'
})
const columns: PlusColumn[] = [
{
label: '自定义el-input ',
prop: 'elData1',
fieldProps: {
// 优先级低于 renderField 的props
placeholder: '请输入'
},
renderField: (_, onChange) => {
return h(ElInput, {
// 返回VNode时,需要手动调用 renderField 的onChange 回调把值传给表单
onChange
})
}
},
{
label: '自定义ElTransfer',
prop: 'elTransfer',
renderField: (value, onChange) => {
interface Option {
key: number
label: string
disabled: boolean
}
const data: Option[] = []
for (let i = 1; i <= 15; i++) {
data.push({
key: i,
label: `Option ${i}`,
disabled: i % 4 === 0
})
}
return h(ElTransfer as any, {
modelValue: value,
data: [...data],
onChange
})
}
},
{
label: '上传',
prop: 'img',
width: 100,
valueType: 'img',
renderField(value, onChange) {
// 自定义上传
const handleHttpRequest = async ({ file, onError, onSuccess }: any) => {
try {
onSuccess(file)
} catch (error: any) {
onError(error)
}
return file
}
return h(Fragment, [
h(ElImage as any, {
src: value,
previewSrcList: [value],
style: value
? {
width: '60px',
marginRight: '10px'
}
: {}
}),
h(
ElUpload,
{
action: '',
httpRequest: handleHttpRequest,
onChange: async (data: any) => {
const value = await fileToDataURL(data.raw)
// 手动调用 renderField 的onChange 回调把值传给表单
onChange(value)
}
},
() => h(ElButton, () => '点击上传')
)
])
}
},
{
label: '原生表单(返回VNode)',
prop: 'data',
fieldProps: {
placeholder: '请输入原生表单值'
},
renderField: (value, onChange) => {
return h('input', {
// 原生表单需要手动添加 value值
value: value,
// 返回VNode时,需要手动调用 renderField 的onChange 回调把值传给表单
onChange: (e: Event) => {
const value = (e.target as HTMLInputElement)?.value
onChange(value)
},
style: {
border: '1px solid #ccc',
width: '200px',
padding: '0 10px'
}
})
}
},
{
label: '原生标签(返回字符串)',
prop: 'data1',
fieldProps: {
placeholder: '请输入原生表单值',
onChange(e: Event) {
const value = (e?.target as HTMLInputElement)?.value
return value
}
},
renderField: value => {
return h('div', null, value as string)
}
}
]
const handleChange = (values: FieldValues, prop: PlusColumn) => {
console.log(values, prop, 'change')
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
自定义表单项 (jsx/tsx)
注意
jsx/tsx 的使用需要将 vue 单文件组件的script
的属性 lang
设置为jsx
或者tsx
,jsx
中值使用单花括号{}
绑定。
<script lang="tsx" setup></script>
jsx/tsx
的支持本质是jsx/tsx
解析出来是VNode
, 使用 renderField 函数自定义表单项。
<template>
<div>
<PlusForm v-model="state" :rules="rules" :columns="columns" :row-props="{ gutter: 20 }" />
</div>
</template>
<script lang="tsx" setup>
import { ref } from 'vue'
import type { PlusColumn } from 'plus-pro-components'
import { ElInput } from 'element-plus'
const state = ref({
status: '0',
name: '默认值',
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const rules = {
name: [
{
required: true,
message: '请输入名称'
}
],
tag: [
{
required: true,
message: '请输入标签'
}
]
}
const columns: PlusColumn[] = [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
tooltip: '名称最多显示6个字符',
renderField: value => {
return <div style="color: green;">{value}</div>
}
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
],
renderField: value => {
return <ElInput modelValue={value as string} />
}
}
]
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
自定义表单 label (插槽)
注意
插槽 的优先级低于 renderLabel,高于 label。
PlusForm
组件会自动根据配置项的 prop
生成对应的插槽,例如下面的配置项,则会自动生成两个名称叫做 [ plus-label-name
]和 [ plus-label-status
] 的两个插槽,插槽的生成规则就是 固定 key 值 [ plus-label- ] 然后加上 配置项的 prop
。
import { PlusColumn } from 'plus-pro-components'
const tableConfig: PlusColumn[] = [
{
label: '名称',
// 自动生成对应的插槽 'plus-label-name'
prop: 'name'
},
{
label: '状态',
// 自动生成对应的插槽 'plus-label-status'
prop: 'status'
}
]
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<PlusForm v-model="state" :rules="rules" :columns="columns" :row-props="{ gutter: 20 }">
<!--这里的plus-label-name 插槽没有生效,因为它的优先级低于renderLabel函数 -->
<template #plus-label-name="{ label }">
<span style="color: red">{{ label }}</span>
</template>
<template #plus-label-status="{ label }">
<span style="color: green">{{ label }}</span>
</template>
</PlusForm>
</div>
</template>
<script lang="ts" setup>
import { ref, h } from 'vue'
import type { PlusColumn } from 'plus-pro-components'
const state = ref({
status: '0',
name: '默认值',
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const rules = {
name: [
{
required: true,
message: '请输入名称'
}
],
tag: [
{
required: true,
message: '请输入标签'
}
]
}
const columns: PlusColumn[] = [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
tooltip: '名称最多显示6个字符',
renderLabel: value => {
return h('div', {}, value as string)
}
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
}
]
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
自定义表单 label (renderLabel)
注意
renderLabel 的优先级高于插槽。
自定义表单项的核心方法是定义 renderLabel
方法, renderLabel
方法需要返回一个 VNode 或 String
<template>
<div>
<PlusForm
v-model="state"
label-width="140px"
:rules="rules"
:columns="columns"
:row-props="{ gutter: 20 }"
/>
</div>
</template>
<script lang="ts" setup>
import { h, ref } from 'vue'
import type { PlusColumn } from 'plus-pro-components'
import { ElButton } from 'element-plus'
const state = ref({
status: '0',
name: '默认值',
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const rules = {
name: [
{
required: true,
message: '请输入名称'
}
],
tag: [
{
required: true,
message: '请输入标签'
}
]
}
const columns: PlusColumn[] = [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
tooltip: '名称最多显示6个字符',
renderLabel: value => {
return h('div', {}, value as string)
}
},
{
label: '自定义label',
prop: 'customName',
renderLabel: () => {
return ''
}
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
],
renderLabel: value => h(ElButton, null, () => value as string)
}
]
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
自定义表单 label (jsx/tsx)
注意
jsx/tsx 的使用需要将 vue 单文件组件的script
的属性 lang
设置为jsx
或者tsx
,jsx
中值使用单花括号{}
绑定。
<script lang="tsx" setup></script>
jsx/tsx
的支持本质是jsx/tsx
解析出来是VNode
, 使用 renderField 函数自定义表单项。
<template>
<div>
<PlusForm v-model="state" :rules="rules" :columns="columns" :row-props="{ gutter: 20 }" />
</div>
</template>
<script lang="tsx" setup>
import { ref } from 'vue'
import type { PlusColumn } from 'plus-pro-components'
import { ElButton } from 'element-plus'
const state = ref({
status: '0',
name: '',
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
})
const rules = {
name: [
{
required: true,
message: '请输入名称'
}
],
tag: [
{
required: true,
message: '请输入标签'
}
]
}
const columns: PlusColumn[] = [
{
label: '名称',
width: 120,
prop: 'name',
valueType: 'copy',
tooltip: '名称最多显示6个字符',
renderLabel: label => {
return <div style="color: green;">{label}</div>
}
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
],
renderLabel: label => {
return <ElButton>{label}</ElButton>
}
}
]
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
自定义表单下一行的内容
支持 renderExtra 渲染函数,支持 jsx/tsx,支持插槽,插槽的生成规则就是 固定 key 值 [ plus-extra- ] 然后加上 配置项的 prop
。
<template>
<el-card>
<PlusForm
v-model="state"
label-width="100px"
:columns="columns"
:rules="rules"
@submit="mySubmit"
>
<!--这里的plus-extra-content 插槽没有生效,因为它的优先级低于renderExtra函数 -->
<template #plus-extra-content>
<el-input v-model="state.name" placeholder="自定义下一行内容" />
</template>
<template #plus-extra-status="column">
<div style="display: flex">
<label style="width: 100px; color: #606266; margin-right: 12px">
没有的{{ column.label }}:
</label>
<el-input v-model="state.extra" placeholder="自定义下一行内容" />
</div>
</template>
</PlusForm>
</el-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
const state = ref<FieldValues>({
status: '0',
content: '',
extra: ''
})
const columns: PlusColumn[] = [
{
label: '请假原因',
width: 120,
prop: 'content',
valueType: 'textarea',
renderExtra: () => `请假原因示例:1.隔壁着火; 2.喝喜酒; 3.漏水;`
},
{
label: '状态',
width: 120,
prop: 'status',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
}
]
}
]
const rules = {
content: [
{
required: true,
message: '请输入原因'
}
]
}
const mySubmit = () => {
console.log(state.value)
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
自定义表单每一项子项的内容
v0.0.7注意
仅当PlusColumn的
valueType
为checkbox
|radio
|select
|plus-radio
之一时生效。当PlusColumn的
valueType
为select
时,不建议定义label
【无法回显】当
fieldSlot
函数 和fieldChildrenSlot
函数同时出现的时候,fieldSlot
函数的优先级更高。
PlusColumn提供 fieldChildrenSlot 函数可整体自定义表单每一项的子项,PlusColumn的options
中提供 fieldSlot 函数可单独自定义表单每一项子项的内容。
<template>
<el-card>
<PlusForm
v-model="state"
label-width="180px"
:columns="columns"
@change="handleChange"
@submit="handleSubmit"
@submit-error="handleSubmitError"
@reset="handleReset"
/>
</el-card>
</template>
<script lang="ts" setup>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { ref, h } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
const state = ref<FieldValues>({
checkbox: ['0'],
checkboxAll: ['0'],
checkboxMixed: ['0'],
radio: '0',
radioAll: '0',
radioMixed: '0',
select: '0',
selectAll: '0',
selectMixed: '0',
plusRadio: '0',
plusRadioAll: '0',
plusRadioMixed: '0'
})
const columns: PlusColumn[] = [
{
label: '单个自定义checkbox',
prop: 'checkbox',
valueType: 'checkbox',
options: [
{
label: '四六级',
value: '0',
fieldSlot: ({ label }) => {
return `单个自定义1-${label}`
}
},
{
label: '计算机二级证书',
value: '1',
fieldSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-warning )' } }, `单个自定义1-${label}`)
}
},
{
label: '普通话证书',
value: '2',
fieldSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-info)' } }, `单个自定义1-${label}`)
}
}
]
},
{
label: '单个自定义radio',
prop: 'radio',
valueType: 'radio',
options: [
{
label: '诗',
value: '0',
fieldSlot: ({ label }) => {
return `单个自定义1-${label}`
}
},
{
label: '远方',
value: '1',
fieldSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-warning )' } }, `单个自定义1-${label}`)
}
},
{
label: '美食',
value: '2',
fieldSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-info)' } }, `单个自定义1-${label}`)
}
}
]
},
{
label: '单个自定义select',
prop: 'select',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'var(--el-color-warning )',
fieldSlot: ({ label, color }) => {
return h('div', { style: { color } }, `${label}`)
}
},
{
label: '已解决',
value: '1',
color: 'var(--el-color-success )',
fieldSlot: ({ label, color }) => {
return h('div', { style: { color } }, `${label}`)
}
},
{
label: '解决中',
value: '2',
color: 'var(--el-color-info )',
fieldSlot: ({ label, color }) => {
return h('div', { style: { color } }, `${label}`)
}
}
]
},
{
label: '单个自定义plus-radio',
prop: 'plusRadio',
valueType: 'plus-radio',
options: [
{
label: '诗',
value: '0',
fieldSlot: ({ label }) => {
return `单个自定义1-${label}`
}
},
{
label: '远方',
value: '1',
fieldSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-warning )' } }, `单个自定义1-${label}`)
}
},
{
label: '美食',
value: '2',
fieldSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-info)' } }, `单个自定义1-${label}`)
}
}
]
},
{
label: '整体自定义checkbox',
prop: 'checkboxAll',
valueType: 'checkbox',
fieldChildrenSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-success)' } }, `整体自定义-${label}`)
},
options: [
{
label: '四六级',
value: '0'
},
{
label: '计算机二级证书',
value: '1'
},
{
label: '普通话证书',
value: '2'
}
]
},
{
label: '整体自定义radio',
prop: 'radioAll',
valueType: 'radio',
fieldChildrenSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-success)' } }, `整体自定义-${label}`)
},
options: [
{
label: '诗',
value: '0'
},
{
label: '远方',
value: '1'
},
{
label: '美食',
value: '2'
}
]
},
{
label: '整体自定义select',
prop: 'selectAll',
valueType: 'select',
fieldChildrenSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-warning)' } }, `整体自定义-${label}`)
},
options: [
{
label: '未解决',
value: '0',
color: 'var(--el-color-warning )'
},
{
label: '已解决',
value: '1',
color: 'var(--el-color-success )'
},
{
label: '解决中',
value: '2',
color: 'var(--el-color-info )'
}
]
},
{
label: '整体自定义plus-radio',
prop: 'plusRadioAll',
valueType: 'plus-radio',
fieldChildrenSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-success)' } }, `整体自定义-${label}`)
},
options: [
{
label: '诗',
value: '0'
},
{
label: '远方',
value: '1'
},
{
label: '美食',
value: '2'
}
]
},
{
label: '混合checkbox',
prop: 'checkboxMixed',
valueType: 'checkbox',
// 整体自定义优先级低于单个自定义
fieldChildrenSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-success)' } }, `整体自定义-${label}`)
},
options: [
{
label: '四六级',
value: '0',
// 单个自定义优先级高于 整体自定义
fieldSlot: ({ label }) => {
return `单个自定义-${label}`
}
},
{
label: '计算机二级证书',
value: '1'
},
{
label: '普通话证书',
value: '2'
}
]
},
{
label: '混合自定义radio',
prop: 'radioMixed',
valueType: 'radio',
// 整体自定义优先级低于单个自定义
fieldChildrenSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-success)' } }, `整体自定义-${label}`)
},
options: [
{
label: '诗',
value: '0',
// 单个自定义优先级高于 整体自定义
fieldSlot: ({ label }) => {
return `单个自定义-${label}`
}
},
{
label: '远方',
value: '1'
},
{
label: '美食',
value: '2'
}
]
},
{
label: '混合自定义select',
prop: 'selectMixed',
valueType: 'select',
fieldChildrenSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-success)' } }, `${label}`)
},
options: [
{
label: '未解决',
value: '0',
color: 'var(--el-color-warning )',
fieldSlot: ({ label, color }) => {
return h('div', { style: { color } }, `${label}`)
}
},
{
label: '已解决',
value: '1',
color: 'var(--el-color-success )'
},
{
label: '解决中',
value: '2',
color: 'var(--el-color-info )'
}
]
},
{
label: '混合自定义plus-radio',
prop: 'plusRadioMixed',
valueType: 'plus-radio',
// 整体自定义优先级低于单个自定义
fieldChildrenSlot: ({ label }) => {
return h('div', { style: { color: 'var(--el-color-success)' } }, `整体自定义-${label}`)
},
options: [
{
label: '诗',
value: '0',
// 单个自定义优先级高于 整体自定义
fieldSlot: ({ label }) => {
return `单个自定义-${label}`
}
},
{
label: '远方',
value: '1'
},
{
label: '美食',
value: '2'
}
]
}
]
const handleChange = (values: FieldValues, prop: PlusColumn) => {
console.log(values, prop, 'change')
}
const handleSubmit = (values: FieldValues) => {
console.log(values, 'Submit')
}
const handleSubmitError = (err: any) => {
console.log(err, 'err')
}
const handleReset = () => {
console.log('handleReset')
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
自定义 tooltip icon
v0.0.3使用tooltip-icon
插槽 可自定义 tooltip icon。
<template>
<el-card>
<PlusForm v-model="state" label-width="100px" :columns="columns">
<template #tooltip-icon>
<el-icon :size="16" style="margin-left: 10px"><Warning /></el-icon>
</template>
</PlusForm>
</el-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
import { Warning } from '@element-plus/icons-vue'
const state = ref<FieldValues>({
input: '文本'
})
const columns: PlusColumn[] = [
{
label: '输入框',
prop: 'input',
valueType: 'input',
tooltip: '自动补全输入框'
}
]
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
所有内置的表单类型
<template>
<el-card>
<PlusForm
v-model="state"
label-width="140px"
:columns="columns"
@change="handleChange"
@submit="handleSubmit"
@submit-error="handleSubmitError"
@reset="handleReset"
/>
</el-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { PlusColumn, FieldValues } from 'plus-pro-components'
interface RestaurantItem {
value: string
link: string
}
interface Option {
key: number
label: string
disabled: boolean
}
const state = ref<FieldValues>({
autocomplete: 'vue',
cascader: ['0', '0-0', '0-0-0'],
'cascader-multiple': [
['0', '0-0', '0-0-0'],
['0', '0-0', '0-0-1'],
['0', '0-0', '0-0-2']
],
checkbox: ['0'],
'color-picker': 'rgba(255, 69, 0, 0.68)',
year: '2024',
years: ['2024', '2005'],
month: '2024-02',
date: '2024-03-05',
dates: ['2024-03-05', '2024-03-06'],
datetime: '2024-03-19 00:00:00',
week: '2024-03-19',
datetimerange: ['2024-03-07 00:00:00', '2024-03-09 00:00:00'],
daterange: ['2024-02-29', '2024-03-29'],
monthrange: ['2024-03', '2024-05'],
select: '0',
'select-multiple': ['0', '1'],
input: '单行文本',
textarea: '多行文本',
'input-number': 4,
rate: 3,
switch: true,
radio: '0',
slider: 50,
'time-picker': '2024-03-18 09:55:31',
'time-select': '09:55:31',
text: '文本',
transfer: [1, 2, 3, 4, 5, 6],
'plus-radio': 1,
'plus-date-picker': ['2024-03-18 09:55:31', '2024-03-20 09:55:31'],
'plus-input-tag': ['tag', 'tag1'],
'select-v2': 'Option 1',
'select-v2-multiple': ['Option 1', 'Option 2']
})
const generateData = () => {
const data: Option[] = []
for (let i = 1; i <= 15; i++) {
data.push({
key: i,
label: `Option ${i}`,
disabled: i % 4 === 0
})
}
return data
}
const restaurants = ref<RestaurantItem[]>([])
const createFilter = (queryString: string) => {
return (restaurant: RestaurantItem) => {
return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
}
}
const loadAll = () => {
return [
{ value: 'vue', link: 'https://github.com/vuejs/vue' },
{ value: 'element', link: 'https://github.com/ElemeFE/element' },
{ value: 'cooking', link: 'https://github.com/ElemeFE/cooking' },
{ value: 'mint-ui', link: 'https://github.com/ElemeFE/mint-ui' },
{ value: 'vuex', link: 'https://github.com/vuejs/vuex' },
{ value: 'vue-router', link: 'https://github.com/vuejs/vue-router' },
{ value: 'babel', link: 'https://github.com/babel/babel' }
]
}
restaurants.value = loadAll()
const cascaderOptions = [
{
value: '0',
label: '陕西',
children: [
{
value: '0-0',
label: '西安',
children: [
{
value: '0-0-0',
label: '新城区'
},
{
value: '0-0-1',
label: '高新区'
},
{
value: '0-0-2',
label: '灞桥区'
}
]
}
]
},
{
value: '1',
label: '山西',
children: [
{
value: '1-0',
label: '太原',
children: [
{
value: '1-0-0',
label: '小店区'
},
{
value: '1-0-1',
label: '古交市'
},
{
value: '1-0-2',
label: '万柏林区'
}
]
}
]
}
]
const columns: PlusColumn[] = [
{
label: 'autocomplete',
width: 120,
prop: 'autocomplete',
valueType: 'autocomplete',
tooltip: '自动补全输入框',
fieldProps: {
fetchSuggestions: (queryString: string, cb: any) => {
const results = queryString
? restaurants.value.filter(createFilter(queryString))
: restaurants.value
// call callback function to return suggestions
cb(results)
}
}
},
{
label: 'cascader',
prop: 'cascader',
valueType: 'cascader',
options: cascaderOptions
},
{
label: 'cascader-multiple',
prop: 'cascader-multiple',
valueType: 'cascader',
options: cascaderOptions,
fieldProps: {
props: { multiple: true }
}
},
{
label: 'checkbox',
prop: 'checkbox',
valueType: 'checkbox',
options: [
{
label: '四六级',
value: '0'
},
{
label: '计算机二级证书',
value: '1'
},
{
label: '普通话证书',
value: '2'
}
]
},
{
label: 'color-picker',
prop: 'color-picker',
valueType: 'color-picker'
},
{
label: 'year',
prop: 'year',
valueType: 'date-picker',
fieldProps: {
type: 'year'
}
},
{
label: 'years',
prop: 'years',
valueType: 'date-picker',
fieldProps: {
type: 'years'
}
},
{
label: 'month',
prop: 'month',
valueType: 'date-picker',
fieldProps: {
type: 'month'
}
},
{
label: 'date',
prop: 'date',
valueType: 'date-picker',
fieldProps: {
type: 'date'
}
},
{
label: 'dates',
prop: 'dates',
valueType: 'date-picker',
fieldProps: {
type: 'dates'
}
},
{
label: 'datetime',
prop: 'datetime',
valueType: 'date-picker',
fieldProps: {
type: 'datetime'
}
},
{
label: 'week',
prop: 'week',
valueType: 'date-picker',
fieldProps: {
type: 'week'
}
},
{
label: 'datetimerange',
prop: 'datetimerange',
valueType: 'date-picker',
fieldProps: {
type: 'datetimerange'
}
},
{
label: 'daterange',
prop: 'daterange',
valueType: 'date-picker',
fieldProps: {
type: 'daterange'
}
},
{
label: 'monthrange',
prop: 'monthrange',
valueType: 'date-picker',
fieldProps: {
type: 'monthrange'
}
},
{
label: 'select',
width: 120,
prop: 'select',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
]
},
{
label: 'select-multiple',
width: 120,
prop: 'select-multiple',
valueType: 'select',
options: [
{
label: '未解决',
value: '0',
color: 'red'
},
{
label: '已解决',
value: '1',
color: 'blue'
},
{
label: '解决中',
value: '2',
color: 'yellow'
},
{
label: '失败',
value: '3',
color: 'red'
}
],
fieldProps: {
multiple: true
}
},
{
label: 'input',
width: 120,
prop: 'input'
},
{
label: 'input-number',
prop: 'input-number',
valueType: 'input-number',
fieldProps: { precision: 2, step: 2 }
},
{
label: 'textarea',
prop: 'textarea',
valueType: 'textarea',
fieldProps: {
maxlength: 10,
showWordLimit: true,
autosize: { minRows: 2, maxRows: 4 }
}
},
{
label: 'rate',
width: 200,
prop: 'rate',
valueType: 'rate'
},
{
label: 'switch',
width: 100,
prop: 'switch',
valueType: 'switch'
},
{
label: 'radio',
prop: 'radio',
valueType: 'radio',
options: [
{
label: '诗',
value: '0'
},
{
label: '远方',
value: '1'
},
{
label: '美食',
value: '2'
}
]
},
{
label: 'slider',
prop: 'slider',
valueType: 'slider'
},
{
label: 'time-picker',
prop: 'time-picker',
valueType: 'time-picker'
},
{
label: 'time-select',
prop: 'time-select',
valueType: 'time-select'
},
{
label: 'text',
prop: 'text',
valueType: 'text'
},
{
label: 'transfer',
prop: 'transfer',
valueType: 'transfer',
fieldProps: {
data: generateData()
}
},
{
label: 'divider',
prop: 'divider',
valueType: 'divider'
},
{
label: 'tree-select',
prop: 'tree-select',
valueType: 'tree-select',
fieldProps: {
data: cascaderOptions
}
},
{
label: 'plus-radio',
prop: 'plus-radio',
valueType: 'plus-radio',
options: [
{ label: '选项一', value: 1 },
{ label: '选项二', value: 2 }
]
},
{
label: 'plus-date-picker',
prop: 'plus-date-picker',
valueType: 'plus-date-picker'
},
{
label: 'plus-input-tag',
prop: 'plus-input-tag',
valueType: 'plus-input-tag'
},
{
label: 'select-v2 ',
prop: 'select-v2',
// v0.2.21 新增
valueType: 'select-v2',
options: () => {
const initials = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
return Array.from({ length: 1000 }).map((_, idx) => ({
value: `Option ${idx + 1}`,
label: `${initials[idx % 10]}${idx}`
}))
}
},
{
label: 'select-v2-multiple',
prop: 'select-v2-multiple',
// v0.2.21 新增
valueType: 'select-v2',
options: () => {
const initials = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
return Array.from({ length: 1000 }).map((_, idx) => ({
value: `Option ${idx + 1}`,
label: `${initials[idx % 10]}${idx}`
}))
},
fieldProps: {
multiple: true
}
}
]
const handleChange = (values: FieldValues, prop: PlusColumn) => {
console.log(values, prop, 'change')
}
const handleSubmit = (values: FieldValues) => {
console.log(values, 'Submit')
}
const handleSubmitError = (err: any) => {
console.log(err, 'err')
}
const handleReset = () => {
console.log('handleReset')
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
Form API
Form Attributes
名称 | 说明 | 类型 | 默认值 | 是否必须 |
---|---|---|---|---|
model-value / v-model | 表单绑定值 (尽量使用 ref 定义绑定的值,以避免 reactive 带来的响应式丢失问题) | object FieldValues | 否 | |
defaultValues | 点击重置按钮时 赋值给表单 | object FieldValues | {} | 否 |
columns | 表单配置信息 | array PlusColumn[] | [] | 否 |
rowProps | el-row 的 props | object RowProps | 否 | |
colProps | el-col 属性 | object ColProps | 否 | |
labelWidth | el-form 的 labelWidth | string | 80px | 否 |
labelPosition | el-form 的 labelPosition | string | left | 否 |
labelSuffix | el-form 的 labelSuffix | string | : | 否 |
hasErrorTip | 是否需要校验 message 提示 | boolean | true | 否 |
hasFooter | 是否需要底部按钮 | boolean | true | 否 |
footerAlign | 底部按钮对齐方式 | string | left | 否 |
hasReset | 是否需要底部按钮 重置 | boolean | true | 否 |
hasLabel | 是否显示 label,值为false 时labelWidth 会被设置为0 ,labelSuffix 会被设置为'' | boolean | true | 否 |
submitLoading | 确定按钮 loading | boolean | false | 否 |
rules | 表单校验 | object FormRules | {} | 否 |
group | 分组表单配置 | false / (array PlusFormGroupRow[] ) | false | 否 |
cardProps v0.1.1 | 分组表单 el-card 的 props,group 存在时生效 ,优先级低于group 配置的每一项的cardProps PlusFormGroupRow | object ElCardProps | false | 否 |
submitText | 提交按钮文字 | string | 提交 | 否 |
resetText | 重置按钮文字 | string | 重置 | 否 |
prevent v0.1.13 | 阻止 el-form 的默认提交表单行为 | boolean | false | 否 |
collapseTransition v0.1.15 | 是否需要表单变化动画,在PlusSearch和PlusStepsForm 组件中效果明显 | boolean | true | 否 |
collapseDuration v0.1.15 | 表单变化动画持续时长(单位:ms) | number | 300 | 否 |
clearable v0.1.18 | 表单内所有表单项的是否可清除,会显示清除图标,优先级低于配置项的 PlusColumn[fieldProps]里的clearable | boolean | true | 否 |
... | ... | ... | ... | ... |
...
表示同时支持所有ElForm Attributes
提示
ElForm 的 model 参数已在组件内部处理,一般不需要传。
Form Events
名称 | 说明 | 类型 |
---|---|---|
submit | 点击提交按钮校验通过触发的事件 | function |
change | 表单变化触发的事件 | function |
reset | 点击重置按钮触发的事件 | function |
submitError | 点击提交按钮校验不通过触发的事件 | function |
... | ... | ... |
...
表示同时支持所有ElForm Events
提示
如 el-form 的 validate,如下示例
示例:
模板中
<PlusForm @validate="handleValidate" />
setup 中
const handleValidate = (prop: FormItemProp, isValid: boolean, message: string) => {
console.log(dropdownItem)
}
2
3
Form Slots
插槽名 | 说明 | 作用域插槽参数 |
---|---|---|
default | 表单的内容 默认是 PlusFormItem 组件 | |
footer | 表单底部按钮 | {handleReset,handleSubmit} |
tooltip-icon v0.0.3 | tooltip icon | |
group-header | 分组表单头部,配置 group 字段时生效。 | {title,columns,icon,index} v0.1.17 新增index |
plus-field-* | 自定义表单项,组件会自动根据配置项的 prop 生成对应的插槽 | {prop,label,fieldProps,valueType,column} |
plus-label-* | 自定义表单项 label,组件会自动根据配置项的 prop 生成对应的插槽 | {prop,label,fieldProps,valueType,column} |
plus-extra-* | 自定义渲染 el-form-item 下一行额外的内容,组件会自动根据配置项的 prop 生成对应的插槽 | {column} |
Exposes
名称 | 说明 | 类型 |
---|---|---|
formInstance | el-form 实例 | object |
handleReset | 表单默认的重置方法,同时会清空校验 | function |
handleSubmit | 表单默认的提交方法,有校验的话,校验成功返回true ,失败返回false | function |
拿到 formInstance 后支持所有ElForm 方法
提示
如 validate
,validateField
等