高级功能
本节介绍 SunlightForm 组件的高级使用方法,包括异步选项加载、图片上传、表单联动等高级功能。
异步选项加载
支持通过函数动态加载下拉选项和级联数据,实现异步数据获取、缓存和搜索过滤。
异步选项加载
展示简单的异步选项加载功能,包括异步下拉选择和异步级联选择。
vue
<template>
<div class="demo-container">
<h3>异步选项加载</h3>
<p>展示简单的异步选项加载功能,包括异步下拉选择和异步级联选择。</p>
<SunlightForm v-model="formData" :columns="columns">
<template #operation>
<el-button type="primary" @click="handleSubmit">提交</el-button>
<el-button @click="handleReset">重置</el-button>
</template>
</SunlightForm>
<!-- 提交结果 -->
<div class="result-container" v-if="showResult">
<h4>提交结果</h4>
<pre class="result-json">{{ JSON.stringify(formData, null, 2) }}</pre>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import { ElMessage } from "element-plus";
// 表单数据
const formData = ref({
// 异步下拉选择
city: "",
// 异步级联选择
address: [],
});
// 表单选项
const formOptions = reactive({
labelWidth: "120px",
});
// 是否显示结果
const showResult = ref(false);
// 模拟城市数据加载
const loadCities = async () => {
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 800));
return [
{ label: "北京", value: "beijing" },
{ label: "上海", value: "shanghai" },
{ label: "广州", value: "guangzhou" },
{ label: "深圳", value: "shenzhen" },
{ label: "杭州", value: "hangzhou" },
];
};
// 模拟级联数据加载
const loadCascaderOptions = async (node: any, resolve: Function) => {
const { level, value } = node;
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 500));
if (level === 0) {
// 加载省份
resolve([
{ label: "北京", value: "beijing" },
{ label: "上海", value: "shanghai" },
{ label: "广东", value: "guangdong" },
]);
} else if (level === 1) {
// 加载城市
const cityMap: Record<string, any[]> = {
beijing: [
{ label: "朝阳区", value: "chaoyang" },
{ label: "海淀区", value: "haidian" },
],
shanghai: [
{ label: "浦东新区", value: "pudong" },
{ label: "徐汇区", value: "xuhui" },
],
guangdong: [
{ label: "广州", value: "guangzhou" },
{ label: "深圳", value: "shenzhen" },
],
};
resolve(cityMap[value] || []);
} else if (level === 2) {
// 加载区县
const districtMap: Record<string, any[]> = {
chaoyang: [
{ label: "三里屯", value: "sanlitun" },
{ label: "国贸", value: "guomao" },
],
haidian: [
{ label: "中关村", value: "zhongguancun" },
{ label: "五道口", value: "wudaokou" },
],
pudong: [
{ label: "陆家嘴", value: "lujiazui" },
{ label: "张江", value: "zhangjiang" },
],
xuhui: [
{ label: "徐家汇", value: "xujiahui" },
{ label: "衡山路", value: "hengshanlu" },
],
guangzhou: [
{ label: "天河区", value: "tianhe" },
{ label: "越秀区", value: "yuexiu" },
],
shenzhen: [
{ label: "南山区", value: "nanshan" },
{ label: "福田区", value: "futian" },
],
};
resolve(districtMap[value] || []);
}
};
// 表单列配置
const columns = reactive([
// 异步下拉选择
{
prop: "city",
label: "城市选择",
type: "select",
span: 12,
placeholder: "请选择城市",
options: loadCities,
clearable: true,
},
// 异步级联选择
{
prop: "address",
label: "详细地址",
type: "cascader",
span: 12,
placeholder: "请选择地址",
load: loadCascaderOptions,
clearable: true,
},
]);
// 提交表单
const handleSubmit = () => {
showResult.value = true;
ElMessage.success("表单提交成功");
console.log("表单数据:", formData.value);
};
// 重置表单
const handleReset = () => {
formData.value = {
city: "",
address: [],
};
showResult.value = false;
ElMessage.info("表单已重置");
};
</script>
1
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
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
隐藏源代码
配置说明
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| type | 表单控件类型 | string | - |
| options | 选项列表或异步加载函数 | Array<any> Function | [] |
| cache | 是否缓存异步选项 | boolean | true |
| load | 异步加载函数(级联选择) | Function | - |
| filterable | 是否支持搜索 | boolean | false |
| debounce | 搜索防抖时间(毫秒) | number | 300 |
| multiple | 是否支持多选 | boolean | false |
| clearable | 是否支持清空 | boolean | false |
图片上传
支持单张或多张图片上传,包括拖拽上传、预览、删除等功能,并支持自定义样式,如圆形、方形、长方形等多种样式。
vue
<template>
<div>
<SunlightForm v-model="formData" :columns="columns" :rules="rules" :formOptions="formOptions">
<template #operation>
<el-button type="primary" @click="handleSubmit">提交</el-button>
<el-button @click="handleReset">重置</el-button>
</template>
</SunlightForm>
<!-- 上传结果预览 -->
<div class="preview-container" v-if="showPreview">
<h4>上传结果预览</h4>
<div class="preview-content">
<!-- 头像预览 -->
<div class="preview-item">
<h5>用户头像</h5>
<div class="avatar-preview">
<el-image
v-if="formData.avatar"
:src="formData.avatar"
fit="cover"
:style="{ borderRadius: '50%', width: '120px', height: '120px' }"
>
<template #error>
<div class="image-error">加载失败</div>
</template>
</el-image>
<div v-else class="no-image">暂无头像</div>
</div>
</div>
<!-- 照片预览 -->
<div class="preview-item">
<h5>用户照片</h5>
<div class="photos-preview">
<el-image
v-for="(photo, index) in formData.photos"
:key="index"
:src="photo.url"
fit="cover"
:preview-src-list="photoPreviewList"
:style="{ borderRadius: '8px', width: '100px', height: '100px', margin: '5px' }"
>
<template #error>
<div class="image-error">加载失败</div>
</template>
</el-image>
<div v-if="formData.photos.length === 0" class="no-image">暂无照片</div>
</div>
</div>
<!-- Banner预览 -->
<div class="preview-item">
<h5>Banner图</h5>
<div class="banner-preview">
<el-image
v-if="formData.banner"
:src="formData.banner"
fit="cover"
:style="{ borderRadius: '4px', width: '300px', height: '100px' }"
>
<template #error>
<div class="image-error">加载失败</div>
</template>
</el-image>
<div v-else class="no-image">暂无Banner图</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from "vue";
import { ElMessage } from "element-plus";
// 表单数据
const formData = ref({
avatar: "",
photos: [],
banner: "",
username: "",
email: "",
description: "",
});
// 表单选项
const formOptions = reactive({
labelWidth: "120px",
size: "default",
});
// 表单验证规则
const rules = reactive({
avatar: [{ required: true, message: "请上传用户头像", trigger: "change" }],
photos: [{ required: true, message: "请上传至少一张照片", trigger: "change" }],
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
email: [
{ required: true, message: "请输入邮箱", trigger: "blur" },
{ type: "email", message: "请输入正确的邮箱格式", trigger: "blur" },
],
});
// 照片预览列表
const photoPreviewList = computed(() => {
return formData.value.photos.map(item => item.url);
});
// 是否显示预览
const showPreview = ref(false);
// 上传接口 - 使用指定接口 https://jsonplaceholder.typicode.com/posts/
const mockUploadApi = async formData => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/", {
method: "POST",
body: formData,
});
const data = await response.json();
return {
fileUrl: `https://picsum.photos/600/300?random=${data.id}`,
};
} catch (error) {
throw error;
}
};
// 表单列配置
const columns = reactive([
{
prop: "username",
label: "用户名",
type: "input",
span: 12,
placeholder: "请输入用户名",
clearable: true,
},
{
prop: "email",
label: "邮箱",
type: "input",
inputType: "email",
span: 12,
placeholder: "请输入邮箱",
clearable: true,
},
{
prop: "avatar",
label: "用户头像",
type: "images",
span: 12,
multiple: false,
limit: 1,
api: mockUploadApi,
fileSize: 3, // 3MB
width: "120px",
height: "120px",
borderRadius: "50%",
drag: false,
customStyle: {
borderColor: "#67C23A",
},
placeholder: "请上传圆形头像",
},
{
prop: "photos",
label: "用户照片",
type: "images",
span: 12,
multiple: true,
limit: 3,
api: mockUploadApi,
fileSize: 5, // 5MB
width: "100px",
height: "100px",
borderRadius: "8px",
drag: true,
customStyle: {
borderColor: "#409EFF",
},
placeholder: "请上传照片(最多3张)",
},
{
prop: "banner",
label: "Banner图",
type: "images",
span: 24,
multiple: false,
limit: 1,
api: mockUploadApi,
fileSize: 5, // 5MB
width: "300px",
height: "100px",
borderRadius: "4px",
drag: true,
customStyle: {
borderColor: "#E6A23C",
},
placeholder: "请上传横幅图片",
},
{
prop: "description",
label: "个人简介",
type: "textarea",
span: 24,
rows: 4,
maxlength: 200,
showWordLimit: true,
placeholder: "请输入个人简介",
},
]);
// 提交表单
const handleSubmit = () => {
console.log("表单数据:", formData.value);
showPreview.value = true;
ElMessage.success("表单提交成功,预览已更新");
};
// 重置表单
const handleReset = () => {
formData.value = {
avatar: "",
photos: [],
banner: "",
username: "",
email: "",
description: "",
};
showPreview.value = false;
ElMessage.info("表单已重置");
};
</script>
<style scoped></style>
1
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
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
隐藏源代码
配置说明
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| type | 表单控件类型 | string | 'images' |
| api | 上传接口 | Function | - |
| multiple | 是否支持多选 | boolean | true |
| limit | 最大上传数量 | number | 5 |
| fileSize | 单个文件大小限制(MB) | number | 5 |
| fileType | 支持的文件类型 | Array<string> | ['image/jpeg', 'image/png', 'image/gif'] |
| height | 图片高度 | string | '150px' |
| width | 图片宽度 | string | '150px' |
| borderRadius | 图片圆角 | string | '8px' |
| drag | 是否支持拖拽上传 | boolean | true |
| customStyle | 自定义样式 | Record<string, any> | {} |
表单联动
通过监听表单数据变化,实现表单控件之间的联动效果。
vue
<template>
<div>
<SunlightForm v-model="formData" :columns="columns" :rules="rules">
<template #operation>
<el-button type="primary" @click="handleSubmit">提交</el-button>
<el-button @click="handleReset">重置</el-button>
</template>
</SunlightForm>
<div class="result-container" v-if="submitResult">
<h4>提交结果</h4>
<pre class="result-json">{{ JSON.stringify(submitResult, null, 2) }}</pre>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from "vue";
import { ElMessage } from "element-plus";
// 表单数据
const formData = ref({
country: "",
city: "",
province: "",
address: "",
});
// 提交结果
const submitResult = ref(null);
// 国家选项
const countryOptions = [
{ label: "中国", value: "china", code: "CN" },
{ label: "美国", value: "usa", code: "US" },
{ label: "日本", value: "japan", code: "JP" },
];
// 城市选项
const cityOptions = reactive({
china: [
{ label: "北京", value: "beijing" },
{ label: "上海", value: "shanghai" },
{ label: "广州", value: "guangzhou" },
{ label: "深圳", value: "shenzhen" },
],
usa: [
{ label: "纽约", value: "newyork" },
{ label: "洛杉矶", value: "losangeles" },
{ label: "芝加哥", value: "chicago" },
],
japan: [
{ label: "东京", value: "tokyo" },
{ label: "大阪", value: "osaka" },
{ label: "京都", value: "kyoto" },
],
});
// 省份/州选项 - 按国家和城市分类
const provinceOptions = reactive({
china: {
beijing: [{ label: "北京市", value: "beijing" }],
shanghai: [{ label: "上海市", value: "shanghai" }],
guangzhou: [{ label: "广东省", value: "guangdong" }],
shenzhen: [{ label: "广东省", value: "guangdong" }],
},
usa: {
newyork: [{ label: "纽约州", value: "newyork" }],
losangeles: [{ label: "加利福尼亚州", value: "california" }],
chicago: [{ label: "伊利诺伊州", value: "illinois" }],
},
japan: {
tokyo: [{ label: "东京都", value: "tokyo" }],
osaka: [{ label: "大阪府", value: "osaka" }],
kyoto: [{ label: "京都府", value: "kyoto" }],
},
});
// 表单列配置
const columns = reactive([
{
prop: "country",
label: "国家",
type: "select",
span: 8,
placeholder: "请选择国家",
options: countryOptions,
setValue: "value",
setLabel: "label",
},
{
prop: "city",
label: "城市",
type: "select",
span: 8,
placeholder: "请选择城市",
options: [],
setValue: "value",
setLabel: "label",
disabled: !formData.value.country,
},
{
prop: "province",
label: "省份/州",
type: "select",
span: 8,
placeholder: "请选择省份/州",
options: [],
setValue: "value",
setLabel: "label",
disabled: !formData.value.city,
},
{
prop: "address",
label: "详细地址",
type: "textarea",
span: 24,
placeholder: "请输入详细地址",
rows: 3,
},
]);
// 表单规则
const rules = reactive({
country: [{ required: true, message: "请选择国家", trigger: "change" }],
city: [{ required: true, message: "请选择城市", trigger: "change" }],
province: [{ required: true, message: "请选择省份/州", trigger: "change" }],
address: [{ required: true, message: "请输入详细地址", trigger: "blur" }],
});
// 监听国家变化,动态更新城市选项
watch(
() => formData.value.country,
newCountry => {
// 更新城市选项
columns[1].options = cityOptions[newCountry] || [];
// 重置城市和省份值
formData.value.city = "";
formData.value.province = "";
// 重置省份选项
columns[2].options = [];
// 更新城市和省份选择器的禁用状态
columns[1].disabled = !newCountry;
columns[2].disabled = true;
if (newCountry) {
ElMessage.info(`已切换国家为:${newCountry}`);
}
},
{ immediate: true }
);
// 监听城市变化,动态更新省份选项
watch(
() => formData.value.city,
newCity => {
const country = formData.value.country;
// 更新省份/州选项
columns[2].options = country && newCity ? provinceOptions[country][newCity] || [] : [];
// 重置省份值
formData.value.province = "";
// 更新省份选择器的禁用状态
columns[2].disabled = !newCity;
if (newCity) {
ElMessage.info(`已切换城市为:${newCity}`);
}
},
{ immediate: true }
);
// 提交表单
const handleSubmit = () => {
submitResult.value = { ...formData.value };
ElMessage.success("表单提交成功");
console.log("表单数据:", formData.value);
};
// 重置表单
const handleReset = () => {
formData.value = {
country: "",
city: "",
province: "",
address: "",
};
submitResult.value = null;
// 重置所有选项和禁用状态
columns[1].options = [];
columns[2].options = [];
columns[1].disabled = true;
columns[2].disabled = true;
ElMessage.info("表单已重置");
};
</script>
1
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
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
隐藏源代码
配置说明
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| listeners | 事件监听器 | Record<string, Function> | {} |
| watch | 监听配置 | Record<string, Function> | {} |
动态表单
支持动态添加和删除表单字段,实现灵活的表单配置。
1. 动态添加/删除字段
根据业务需求,动态增减表单字段,实现灵活的表单配置。
2. 条件显示字段
根据表单数据的变化,动态显示或隐藏相关字段,实现智能表单交互。
3. 动态字段类型
根据选择的字段类型,动态切换表单控件类型,支持多种表单控件的灵活切换。
vue
<template>
<div class="demo-container">
<!-- 动态添加/删除字段演示 -->
<div class="demo-section">
<h4>1. 动态添加/删除字段</h4>
<p class="section-description">根据业务需求,动态增减表单字段,实现灵活的表单配置。</p>
<SunlightForm v-model="formData" :columns="dynamicColumns" :rules="dynamicRules">
<template #operation>
<div class="operation-buttons">
<el-button type="primary" @click="handleSubmit">提交</el-button>
<el-button @click="handleReset">重置</el-button>
<el-button type="success" @click="addField">添加字段</el-button>
<el-button type="danger" @click="removeField" :disabled="dynamicColumns.length <= 2">删除字段</el-button>
</div>
</template>
</SunlightForm>
</div>
<!-- 条件显示字段演示 -->
<div class="demo-section">
<h4>2. 条件显示字段</h4>
<p class="section-description">根据表单数据的变化,动态显示或隐藏相关字段,实现智能表单交互。</p>
<SunlightForm v-model="conditionFormData" :columns="conditionColumns" :rules="conditionRules">
<template #operation>
<el-button type="primary" @click="handleConditionSubmit">提交</el-button>
<el-button @click="handleConditionReset">重置</el-button>
</template>
</SunlightForm>
</div>
<!-- 动态字段类型演示 -->
<div class="demo-section">
<h4>3. 动态字段类型</h4>
<p class="section-description">根据选择的字段类型,动态切换表单控件类型,支持多种表单控件的灵活切换。</p>
<SunlightForm v-model="dynamicTypeFormData" :columns="dynamicTypeColumns" :rules="dynamicTypeRules">
<template #operation>
<el-button type="primary" @click="handleDynamicTypeSubmit">提交</el-button>
<el-button @click="handleDynamicTypeReset">重置</el-button>
</template>
</SunlightForm>
</div>
<!-- 提交结果展示 -->
<div class="result-container" v-if="submitResult">
<h4>提交结果</h4>
<pre class="result-json">{{ JSON.stringify(submitResult, null, 2) }}</pre>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from "vue";
import { ElMessage } from "element-plus";
// 动态表单数据
const formData = ref({});
const fieldCount = ref(2);
// 条件表单数据
const conditionFormData = ref({
type: "person",
name: "",
age: null,
company: "",
position: "",
});
// 动态字段类型表单数据
const dynamicTypeFormData = ref({
fieldType: "input",
fieldValue: "",
});
// 提交结果
const submitResult = ref(null);
// 动态表单列配置
const dynamicColumns = reactive([
{
prop: "field1",
label: "字段1",
type: "input",
span: 12,
placeholder: "请输入字段1",
},
{
prop: "field2",
label: "字段2",
type: "select",
span: 12,
placeholder: "请选择字段2",
options: [
{ label: "选项A", value: "optionA" },
{ label: "选项B", value: "optionB" },
{ label: "选项C", value: "optionC" },
],
},
]);
// 动态表单验证规则
const dynamicRules = reactive({
field1: [{ required: true, message: "请输入字段1", trigger: "blur" }],
field2: [{ required: true, message: "请选择字段2", trigger: "change" }],
});
// 条件表单列配置 - 根据类型显示不同字段
const conditionColumns = computed(() => {
const columns = [
{
prop: "type",
label: "表单类型",
type: "radio",
span: 24,
options: [
{ label: "个人信息", value: "person" },
{ label: "公司信息", value: "company" },
],
placeholder: "请选择表单类型",
},
];
// 根据选择的类型显示对应的字段
if (conditionFormData.value.type === "person") {
// 个人信息字段
columns.push(
{
prop: "name",
label: "姓名",
type: "input",
span: 12,
placeholder: "请输入姓名",
},
{
prop: "age",
label: "年龄",
type: "input",
inputType: "number",
span: 12,
placeholder: "请输入年龄",
config: {
min: 0,
max: 150,
},
}
);
} else if (conditionFormData.value.type === "company") {
// 公司信息字段
columns.push(
{
prop: "company",
label: "公司名称",
type: "input",
span: 12,
placeholder: "请输入公司名称",
},
{
prop: "position",
label: "职位",
type: "select",
span: 12,
placeholder: "请选择职位",
options: [
{ label: "总经理", value: "generalManager" },
{ label: "部门经理", value: "departmentManager" },
{ label: "员工", value: "employee" },
],
}
);
}
return columns;
});
// 条件表单验证规则
const conditionRules = reactive({
type: [{ required: true, message: "请选择表单类型", trigger: "change" }],
name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
age: [{ required: true, message: "请输入年龄", trigger: "blur" }],
company: [{ required: true, message: "请输入公司名称", trigger: "blur" }],
position: [{ required: true, message: "请选择职位", trigger: "change" }],
});
// 动态字段类型列配置
const dynamicTypeColumns = computed(() => {
const columns = [
{
prop: "fieldType",
label: "字段类型",
type: "select",
span: 12,
placeholder: "请选择字段类型",
options: [
{ label: "文本输入", value: "input" },
{ label: "多行文本", value: "textarea" },
{ label: "数字输入", value: "number" },
{ label: "下拉选择", value: "select" },
{ label: "单选按钮", value: "radio" },
{ label: "多选按钮", value: "checkbox" },
],
},
];
// 根据选择的字段类型显示对应的表单控件
switch (dynamicTypeFormData.value.fieldType) {
case "input":
columns.push({
prop: "fieldValue",
label: "文本输入",
type: "input",
span: 12,
placeholder: "请输入文本内容",
});
break;
case "textarea":
columns.push({
prop: "fieldValue",
label: "多行文本",
type: "textarea",
span: 24,
placeholder: "请输入多行文本内容",
rows: 4,
});
break;
case "number":
columns.push({
prop: "fieldValue",
label: "数字输入",
type: "input",
inputType: "number",
span: 12,
placeholder: "请输入数字",
config: {
min: 0,
max: 1000,
step: 1,
},
});
break;
case "select":
columns.push({
prop: "fieldValue",
label: "下拉选择",
type: "select",
span: 12,
placeholder: "请选择一个选项",
options: [
{ label: "选项1", value: "option1" },
{ label: "选项2", value: "option2" },
{ label: "选项3", value: "option3" },
{ label: "选项4", value: "option4" },
],
});
break;
case "radio":
columns.push({
prop: "fieldValue",
label: "单选按钮",
type: "radio",
span: 12,
options: [
{ label: "选项A", value: "optionA" },
{ label: "选项B", value: "optionB" },
{ label: "选项C", value: "optionC" },
],
});
break;
case "checkbox":
columns.push({
prop: "fieldValue",
label: "多选按钮",
type: "checkbox",
span: 12,
options: [
{ label: "选项1", value: "option1" },
{ label: "选项2", value: "option2" },
{ label: "选项3", value: "option3" },
{ label: "选项4", value: "option4" },
],
});
break;
default:
break;
}
return columns;
});
// 动态字段类型验证规则
const dynamicTypeRules = reactive({
fieldType: [{ required: true, message: "请选择字段类型", trigger: "change" }],
fieldValue: [{ required: true, message: "请填写字段值", trigger: "change" }],
});
// 添加字段
const addField = () => {
fieldCount.value++;
const newField = {
prop: `field${fieldCount.value}`,
label: `字段${fieldCount.value}`,
type: fieldCount.value % 2 === 0 ? "input" : "select",
span: 12,
placeholder: `请输入字段${fieldCount.value}`,
options:
fieldCount.value % 2 === 0
? undefined
: [
{ label: "选项1", value: "1" },
{ label: "选项2", value: "2" },
{ label: "选项3", value: "3" },
],
};
// 设置不同类型字段的占位符
if (newField.type === "select") {
newField.placeholder = `请选择字段${fieldCount.value}`;
}
dynamicColumns.push(newField);
// 添加验证规则
dynamicRules[newField.prop] = [{ required: true, message: `请输入字段${fieldCount.value}`, trigger: "blur" }];
// 初始化字段值
formData.value[newField.prop] = "";
ElMessage.success(`已添加字段${fieldCount.value}`);
};
// 删除字段
const removeField = () => {
if (dynamicColumns.length <= 2) {
ElMessage.warning("至少保留2个字段");
return;
}
const removedField = dynamicColumns.pop();
if (removedField) {
// 删除验证规则
delete dynamicRules[removedField.prop];
// 删除字段值
delete formData.value[removedField.prop];
ElMessage.success(`已删除字段${removedField.label}`);
}
};
// 提交动态表单
const handleSubmit = () => {
submitResult.value = { type: "动态添加/删除字段", data: { ...formData.value } };
ElMessage.success("动态表单提交成功");
console.log("动态表单数据:", formData.value);
};
// 重置动态表单
const handleReset = () => {
// 重置到初始状态
dynamicColumns.splice(2);
fieldCount.value = 2;
// 重置验证规则
Object.keys(dynamicRules).forEach(key => {
if (key !== "field1" && key !== "field2") {
delete dynamicRules[key];
}
});
// 重置表单数据
formData.value = {
field1: "",
field2: "",
};
submitResult.value = null;
ElMessage.info("动态表单已重置");
};
// 提交条件表单
const handleConditionSubmit = () => {
submitResult.value = { type: "条件显示字段", data: { ...conditionFormData.value } };
ElMessage.success("条件表单提交成功");
console.log("条件表单数据:", conditionFormData.value);
};
// 重置条件表单
const handleConditionReset = () => {
conditionFormData.value = {
type: "person",
name: "",
age: null,
company: "",
position: "",
};
submitResult.value = null;
ElMessage.info("条件表单已重置");
};
// 提交动态字段类型表单
const handleDynamicTypeSubmit = () => {
submitResult.value = { type: "动态字段类型", data: { ...dynamicTypeFormData.value } };
ElMessage.success("动态字段类型表单提交成功");
console.log("动态字段类型表单数据:", dynamicTypeFormData.value);
};
// 重置动态字段类型表单
const handleDynamicTypeReset = () => {
dynamicTypeFormData.value = {
fieldType: "input",
fieldValue: "",
};
submitResult.value = null;
ElMessage.info("动态字段类型表单已重置");
};
</script>
1
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
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
隐藏源代码
配置说明
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| columns | 动态列配置 | Ref<FormColumn[]> | - |
| v-model | 表单数据 | Ref<Record<string, any>> | {} |
自定义样式
支持自定义表单控件的样式,包括容器样式、组件样式等。
表单数据:
{
"name": "",
"email": "",
"phone": "",
"description": "",
"city": null,
"multiCity": [],
"region": null,
"gender": null,
"hobby": [],
"avatar": [],
"photos": [],
"birthdate": null,
"createTime": null,
"dateRange": [],
"datetimeRange": []
}vue
<template>
<el-card shadow="hover">
<!-- 多功能表单:包含所有类型的表单控件 -->
<SunlightForm v-model="formData" :columns="columns" :form-options="formOptions">
<template #operation>
<el-button type="primary" @click="handleSubmit">提交</el-button>
<el-button @click="handleReset">重置</el-button>
</template>
</SunlightForm>
<div class="result">
<h3>表单数据:</h3>
<pre>{{ JSON.stringify(formData, null, 2) }}</pre>
</div>
</el-card>
</template>
<script setup>
import { ref } from "vue";
import { SunlightForm } from "sunlight-ui";
import { ElMessage } from "element-plus";
// 统一颜色主题配置
const themeStyle = {
borderColor: "#67c23a",
focusBorderColor: "#67c23a",
focusBoxShadow: "0 0 0 3px rgba(103, 194, 58, 0.25)",
};
// 模拟异步数据
async function fetchCityOptions() {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ value: "beijing", label: "北京" },
{ value: "shanghai", label: "上海" },
{ value: "guangzhou", label: "广州" },
{ value: "shenzhen", label: "深圳" },
{ value: "hangzhou", label: "杭州" },
{ value: "nanjing", label: "南京" },
{ value: "wuhan", label: "武汉" },
{ value: "chengdu", label: "成都" },
]);
}, 300);
});
}
async function fetchDepartmentOptions() {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ value: "tech", label: "技术部" },
{ value: "product", label: "产品部" },
{ value: "operation", label: "运营部" },
{ value: "market", label: "市场部" },
{ value: "sales", label: "销售部" },
{ value: "hr", label: "人事部" },
]);
}, 300);
});
}
// 三级联动数据
async function fetchCascaderOptions() {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{
value: "beijing",
label: "北京",
children: [
{
value: "chaoyang",
label: "朝阳区",
children: [
{ value: "chaoyang1", label: "朝阳街道1" },
{ value: "chaoyang2", label: "朝阳街道2" },
],
},
{
value: "haidian",
label: "海淀区",
children: [
{ value: "haidian1", label: "海淀街道1" },
{ value: "haidian2", label: "海淀街道2" },
],
},
],
},
{
value: "shanghai",
label: "上海",
children: [
{
value: "huangpu",
label: "黄浦区",
children: [
{ value: "huangpu1", label: "黄浦街道1" },
{ value: "huangpu2", label: "黄浦街道2" },
],
},
{
value: "pudong",
label: "浦东新区",
children: [
{ value: "pudong1", label: "浦东街道1" },
{ value: "pudong2", label: "浦东街道2" },
],
},
],
},
{
value: "guangdong",
label: "广东",
children: [
{
value: "guangzhou",
label: "广州",
children: [
{ value: "tianhe", label: "天河区" },
{ value: "yuexiu", label: "越秀区" },
],
},
{
value: "shenzhen",
label: "深圳",
children: [
{ value: "nanshan", label: "南山区" },
{ value: "futian", label: "福田区" },
],
},
],
},
]);
}, 300);
});
}
async function fetchGenderOptions() {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ value: "male", label: "男" },
{ value: "female", label: "女" },
]);
}, 300);
});
}
async function fetchHobbyOptions() {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ value: "reading", label: "阅读" },
{ value: "music", label: "音乐" },
{ value: "sports", label: "运动" },
{ value: "travel", label: "旅行" },
{ value: "movie", label: "电影" },
{ value: "game", label: "游戏" },
]);
}, 300);
});
}
// 模拟上传 API
async function mockUploadApi(file) {
return new Promise(resolve => {
setTimeout(() => {
const url = URL.createObjectURL(file.raw || file);
resolve({
fileUrl: url,
fileName: file.name || "image.jpg",
});
}, 500);
});
}
// 表单数据
const formData = ref({
// 单行文本
name: "",
email: "",
phone: "",
// 多行文本
description: "",
// 选择框(单选)
city: null,
// 选择框(多选)
multiCity: [],
// 三级联动
region: null,
// 单选
gender: null,
// 多选
hobby: [],
// 上传单个图片
avatar: [],
// 上传多个图片
photos: [],
// 日期选择器(年月日)
birthdate: null,
// 日期时间选择器(年月日时分秒)
createTime: null,
// 日期范围选择器
dateRange: [],
// 日期时间范围选择器
datetimeRange: [],
});
// 表单配置
const formOptions = {
labelWidth: "120px",
size: "default",
buttonAlign: "center", // 可选值:left, center, right
};
// 列配置:包含所有类型的表单控件
const columns = [
// ========== 单行文本 ==========
{
prop: "name",
label: "姓名",
type: "input",
span: 12,
placeholder: "请输入姓名",
clearable: true,
wrapperStyle: themeStyle,
},
{
prop: "email",
label: "邮箱",
type: "input",
span: 12,
placeholder: "请输入邮箱",
inputType: "email",
clearable: true,
wrapperStyle: themeStyle,
},
{
prop: "phone",
label: "手机号",
type: "input",
span: 12,
placeholder: "请输入手机号",
inputType: "tel",
clearable: true,
wrapperStyle: themeStyle,
},
// ========== 选择框(单选) ==========
{
prop: "city",
label: "城市",
type: "select",
span: 12,
placeholder: "请选择城市",
options: fetchCityOptions,
filterable: true,
clearable: true,
wrapperStyle: themeStyle,
},
// ========== 选择框(多选) ==========
{
prop: "multiCity",
label: "多选城市",
type: "select",
span: 12,
placeholder: "请选择多个城市",
options: fetchCityOptions,
multiple: true,
filterable: true,
clearable: true,
wrapperStyle: themeStyle,
},
// ========== 三级联动 ==========
{
prop: "region",
label: "地区(三级联动)",
type: "cascader",
span: 12,
placeholder: "请选择省/市/区",
options: fetchCascaderOptions,
filterable: true,
clearable: true,
wrapperStyle: themeStyle,
},
// ========== 日期选择器(年月日) ==========
{
prop: "birthdate",
label: "出生日期",
type: "date-picker",
span: 12,
placeholder: "请选择出生日期",
valueFormat: "YYYY-MM-DD",
clearable: true,
config: {
type: "date",
format: "YYYY-MM-DD",
},
wrapperStyle: themeStyle,
},
// ========== 日期时间选择器(年月日时分秒) ==========
{
prop: "createTime",
label: "创建时间",
type: "datetime-picker",
span: 12,
placeholder: "请选择创建时间",
valueFormat: "YYYY-MM-DD HH:mm:ss",
clearable: true,
config: {
type: "datetime",
format: "YYYY-MM-DD HH:mm:ss",
},
wrapperStyle: themeStyle,
},
// ========== 日期范围选择器 ==========
{
prop: "dateRange",
label: "日期范围",
type: "date-picker",
span: 12,
placeholder: "请选择日期范围",
valueFormat: "YYYY-MM-DD",
clearable: true,
config: {
type: "daterange",
format: "YYYY-MM-DD",
startPlaceholder: "开始日期",
endPlaceholder: "结束日期",
},
wrapperStyle: themeStyle,
},
// ========== 日期时间范围选择器 ==========
{
prop: "datetimeRange",
label: "时间范围",
type: "datetime-picker",
span: 12,
placeholder: "请选择时间范围",
valueFormat: "YYYY-MM-DD HH:mm:ss",
clearable: true,
config: {
type: "datetimerange",
format: "YYYY-MM-DD HH:mm:ss",
startPlaceholder: "开始时间",
endPlaceholder: "结束时间",
},
wrapperStyle: themeStyle,
},
// ========== 单选 ==========
{
prop: "gender",
label: "性别",
type: "radio",
span: 12,
options: fetchGenderOptions,
isGroup: true,
wrapperStyle: themeStyle,
},
// ========== 多选 ==========
{
prop: "hobby",
label: "爱好",
type: "checkbox",
span: 24,
options: fetchHobbyOptions,
isGroup: true,
wrapperStyle: themeStyle,
},
// ========== 多行文本 ==========
{
prop: "description",
label: "描述",
type: "textarea",
span: 24,
placeholder: "请输入描述信息",
rows: 4,
maxlength: 200,
showWordLimit: true,
wrapperStyle: themeStyle,
},
// ========== 上传单个图片 ==========
{
prop: "avatar",
label: "头像(单个)",
type: "images",
span: 12,
api: mockUploadApi,
limit: 1,
multiple: false,
drag: true,
height: "150px",
width: "150px",
wrapperStyle: themeStyle,
},
// ========== 上传多个图片 ==========
{
prop: "photos",
label: "照片(多个)",
type: "images",
span: 12,
api: mockUploadApi,
limit: 5,
multiple: true,
drag: true,
height: "120px", // 自定义高度
width: "120px", // 自定义宽度
wrapperStyle: themeStyle,
},
];
// 提交表单
const handleSubmit = () => {
console.log("提交表单数据:", formData.value);
ElMessage.success("提交成功!");
};
// 重置表单
const handleReset = () => {
formData.value = {
name: "",
email: "",
phone: "",
description: "",
city: null,
multiCity: [],
region: null,
gender: null,
hobby: [],
avatar: [],
photos: [],
birthdate: null,
createTime: null,
dateRange: [],
datetimeRange: [],
};
ElMessage.info("表单已重置");
};
</script>
<style scoped>
.description p {
margin: 4px 0;
color: #606266;
}
.result {
margin-top: 30px;
padding: 20px;
background-color: #f5f5f5;
border-radius: 4px;
}
.result h3 {
margin-top: 0;
margin-bottom: 10px;
}
.result pre {
margin: 0;
padding: 10px;
background-color: #fff;
border-radius: 4px;
overflow-x: auto;
}
</style>
1
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
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
隐藏源代码
配置说明
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| wrapperStyle | 容器样式 | Record<string, any> | {} |
| customStyle | 组件样式 | Record<string, any> | {} |
| customClass | 自定义类名 | string | - |
表单事件与方法
表单事件
支持多种表单事件,包括表单提交、重置、数据变化等。
vue
<template>
<div class="demo-container">
<SunlightForm ref="formRef" v-model="formData" :columns="columns" :rules="rules">
<template #operation>
<el-button type="primary" @click="handleSubmit">提交</el-button>
<el-button @click="handleReset">重置</el-button>
<el-button @click="handleValidate">验证</el-button>
</template>
</SunlightForm>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import { ElMessage } from "element-plus";
// 表单引用
const formRef = ref();
// 表单数据
const formData = ref({
name: "",
email: "",
age: null,
});
// 表单规则
const rules = reactive({
name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
email: [
{ required: true, message: "请输入邮箱", trigger: "blur" },
{ type: "email", message: "请输入正确的邮箱格式", trigger: "blur" },
],
age: [
{ required: true, message: "请输入年龄", trigger: "blur" },
{ type: "number", min: 18, max: 100, message: "年龄必须在18-100之间", trigger: "blur" },
],
});
// 事件日志
const eventLogs = ref([]);
// 添加事件日志
const addLog = content => {
const time = new Date().toLocaleTimeString();
eventLogs.value.unshift({ time, content });
// 限制日志数量
if (eventLogs.value.length > 10) {
eventLogs.value.pop();
}
};
// 表单列配置
const columns = reactive([
{
prop: "name",
label: "姓名",
type: "input",
span: 12,
placeholder: "请输入姓名",
listeners: {
blur: () => addLog("姓名输入框失去焦点"),
},
},
{
prop: "email",
label: "邮箱",
type: "input",
span: 12,
placeholder: "请输入邮箱",
listeners: {
blur: () => addLog("邮箱输入框失去焦点"),
},
},
{
prop: "age",
label: "年龄",
type: "input",
span: 12,
placeholder: "请输入年龄",
inputType: "number",
listeners: {
blur: () => addLog("年龄输入框失去焦点"),
},
},
]);
// 提交表单
const handleSubmit = async () => {
try {
const valid = await formRef.value.validate();
if (valid) {
addLog("表单提交成功");
ElMessage.success("表单提交成功");
console.log("表单数据:", formData.value);
}
} catch (error) {
addLog("表单验证失败");
ElMessage.error("表单验证失败");
}
};
// 重置表单
const handleReset = () => {
formRef.value.reset();
addLog("表单重置");
ElMessage.info("表单已重置");
};
// 单独验证
const handleValidate = async () => {
try {
const valid = await formRef.value.validate();
if (valid) {
addLog("表单验证通过");
ElMessage.success("表单验证通过");
}
} catch (error) {
addLog("表单验证失败");
ElMessage.error("表单验证失败");
}
};
</script>
<style scoped>
.demo-container {
}
</style>
1
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
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
隐藏源代码
表单方法
支持通过 ref 调用表单方法,包括验证、重置等功能。
vue
<template>
<div class="demo-container">
<SunlightForm ref="formRef" v-model="formData" :columns="columns" :rules="rules">
<template #operation>
<el-button type="primary" @click="handleSubmit">提交</el-button>
<el-button @click="handleReset">重置</el-button>
<el-button @click="handleValidate">验证</el-button>
<el-button @click="getFormModel">获取表单数据</el-button>
</template>
</SunlightForm>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import { ElMessage } from "element-plus";
// 表单引用
const formRef = ref();
// 表单数据
const formData = ref({
username: "",
password: "",
confirmPassword: "",
});
// 表单规则
const rules = reactive({
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 6, message: "密码长度不能少于6位", trigger: "blur" },
],
confirmPassword: [
{ required: true, message: "请再次输入密码", trigger: "blur" },
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error("请再次输入密码"));
} else if (value !== formData.value.password) {
callback(new Error("两次输入密码不一致"));
} else {
callback();
}
},
trigger: "blur",
},
],
});
// 验证结果
const validateResult = ref(null);
// 表单数据
const formModel = ref(null);
// 表单列配置
const columns = reactive([
{
prop: "username",
label: "用户名",
type: "input",
span: 24,
placeholder: "请输入用户名",
},
{
prop: "password",
label: "密码",
type: "input",
span: 24,
placeholder: "请输入密码",
inputType: "password",
},
{
prop: "confirmPassword",
label: "确认密码",
type: "input",
span: 24,
placeholder: "请再次输入密码",
inputType: "password",
},
]);
// 提交表单
const handleSubmit = async () => {
const valid = await formRef.value.validate();
if (valid) {
ElMessage.success("表单提交成功");
console.log("表单数据:", formData.value);
} else {
ElMessage.error("表单验证失败");
}
validateResult.value = valid;
};
// 重置表单
const handleReset = () => {
formRef.value.reset();
ElMessage.info("表单已重置");
validateResult.value = null;
formModel.value = null;
};
// 验证表单
const handleValidate = async () => {
const valid = await formRef.value.validate();
validateResult.value = valid;
if (valid) {
ElMessage.success("表单验证通过");
} else {
ElMessage.error("表单验证失败");
}
};
// 获取表单数据
const getFormModel = () => {
formModel.value = formRef.value.model;
ElMessage.info("已获取表单数据");
};
</script>
<style scoped>
.demo-container {
}
</style>
1
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
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
隐藏源代码
配置说明
| 事件/方法 | 说明 | 类型 | 返回值 |
|---|---|---|---|
| @submit | 表单提交事件 | Function | - |
| @reset | 表单重置事件 | Function | - |
| @update:modelValue | 表单数据变化事件 | Function | - |
| validate | 表单验证方法 | Function | Promise<boolean> |
| reset | 重置表单方法 | Function | - |
| listeners | 组件事件监听器 | Record<string, Function> | {} |
高级验证
支持自定义验证函数、异步验证和复杂验证规则。
高级表单验证
SunlightForm 支持多种高级表单验证方式,包括自定义验证函数、异步验证和复杂验证规则。
vue
<template>
<div class="demo-container">
<h3>高级表单验证</h3>
<p>SunlightForm 支持多种高级表单验证方式,包括自定义验证函数、异步验证和复杂验证规则。</p>
<SunlightForm
ref="formRef"
v-model="formData"
:columns="columns"
:rules="rules"
>
<template #operation>
<el-button type="primary" @click="handleSubmit">提交</el-button>
<el-button @click="handleReset">重置</el-button>
</template>
</SunlightForm>
<div class="result-container" v-if="submitResult">
<h4>提交结果</h4>
<pre class="result-json">{{ JSON.stringify(submitResult, null, 2) }}</pre>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
// 表单引用
const formRef = ref();
// 表单数据
const formData = ref({
username: '',
email: '',
password: '',
confirmPassword: '',
phone: '',
idCard: '',
customField: '',
asyncField: '',
});
// 提交结果
const submitResult = ref(null);
// 自定义验证函数
const validatePhone = (rule, value, callback) => {
if (!value) {
return callback(new Error('请输入手机号码'));
}
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(value)) {
callback(new Error('请输入正确的手机号码'));
} else {
callback();
}
};
// 身份证验证函数
const validateIdCard = (rule, value, callback) => {
if (!value) {
return callback(new Error('请输入身份证号码'));
}
const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
if (!idCardRegex.test(value)) {
callback(new Error('请输入正确的身份证号码'));
} else {
callback();
}
};
// 自定义字段验证
const validateCustomField = (rule, value, callback) => {
if (!value) {
return callback(new Error('请输入自定义字段'));
}
// 自定义验证逻辑:必须包含字母和数字
const hasLetter = /[a-zA-Z]/.test(value);
const hasNumber = /\d/.test(value);
if (!hasLetter || !hasNumber) {
callback(new Error('自定义字段必须包含字母和数字'));
} else {
callback();
}
};
// 异步验证函数
const validateAsyncField = (rule, value, callback) => {
if (!value) {
return callback(new Error('请输入异步验证字段'));
}
// 模拟异步验证
setTimeout(() => {
if (value === 'async') {
callback(new Error('该值已被使用'));
} else {
callback();
}
}, 1000);
};
// 表单列配置
const columns = reactive([
{
prop: 'username',
label: '用户名',
type: 'input',
span: 12,
placeholder: '请输入用户名',
},
{
prop: 'email',
label: '邮箱',
type: 'input',
span: 12,
placeholder: '请输入邮箱',
},
{
prop: 'phone',
label: '手机号码',
type: 'input',
span: 12,
placeholder: '请输入手机号码',
},
{
prop: 'idCard',
label: '身份证号码',
type: 'input',
span: 12,
placeholder: '请输入身份证号码',
},
{
prop: 'password',
label: '密码',
type: 'input',
span: 12,
placeholder: '请输入密码',
inputType: 'password',
},
{
prop: 'confirmPassword',
label: '确认密码',
type: 'input',
span: 12,
placeholder: '请再次输入密码',
inputType: 'password',
},
{
prop: 'customField',
label: '自定义字段',
type: 'input',
span: 12,
placeholder: '必须包含字母和数字',
},
{
prop: 'asyncField',
label: '异步验证字段',
type: 'input',
span: 12,
placeholder: '异步验证,请勿输入 "async"',
},
]);
// 表单规则
const rules = reactive({
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度在 3 到 20 个字符', trigger: 'blur' },
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' },
],
phone: [
{ required: true, validator: validatePhone, trigger: 'blur' },
],
idCard: [
{ required: true, validator: validateIdCard, trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' },
{ pattern: /^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{6,20}$/, message: '密码必须包含字母和数字', trigger: 'blur' },
],
confirmPassword: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== formData.value.password) {
callback(new Error('两次输入密码不一致'));
} else {
callback();
}
},
trigger: 'blur'
}
],
customField: [
{ required: true, validator: validateCustomField, trigger: 'blur' },
],
asyncField: [
{ required: true, validator: validateAsyncField, trigger: 'blur' },
],
});
// 提交表单
const handleSubmit = async () => {
try {
const valid = await formRef.value.validate();
if (valid) {
submitResult.value = { ...formData.value };
ElMessage.success('表单验证通过,提交成功!');
console.log('表单数据:', formData.value);
}
} catch (error) {
ElMessage.error('表单验证失败,请检查输入!');
}
};
// 重置表单
const handleReset = () => {
formRef.value.reset();
submitResult.value = null;
ElMessage.info('表单已重置');
};
</script>
<style scoped>
.demo-container {
width: 100%;
max-width: 800px;
margin: 0 auto;
}
h3 {
margin-bottom: 20px;
font-size: 18px;
font-weight: 600;
}
h4 {
margin: 20px 0 10px;
font-size: 16px;
font-weight: 600;
}
p {
margin-bottom: 20px;
color: #909399;
}
.result-container {
margin-top: 20px;
padding: 15px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
.result-json {
background-color: #f5f7fa;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 10px;
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-all;
}
</style>
1
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
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
隐藏源代码
配置说明
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| validator | 自定义验证函数 | Function | - |
| asyncValidator | 异步验证函数 | Function | - |
| trigger | 验证触发方式 | string | 'blur' |
| required | 是否必填 | boolean | false |
复杂表单布局
通过组合多种表单控件和布局配置,实现复杂的表单设计。
复杂表单布局
通过组合多种表单控件和布局配置,实现复杂的表单设计。
vue
<template>
<div class="demo-container">
<h3>复杂表单布局</h3>
<p>通过组合多种表单控件和布局配置,实现复杂的表单设计。</p>
<SunlightForm
v-model="formData"
:columns="columns"
:form-options="formOptions"
>
<template #operation>
<el-button type="primary" @click="handleSubmit">提交</el-button>
<el-button @click="handleReset">重置</el-button>
</template>
</SunlightForm>
<div class="result-container" v-if="submitResult">
<h4>提交结果</h4>
<pre class="result-json">{{ JSON.stringify(submitResult, null, 2) }}</pre>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
// 表单数据
const formData = ref({
// 基本信息
name: '',
email: '',
phone: '',
gender: 'male',
age: null,
// 地址信息
province: '',
city: '',
district: '',
address: '',
// 工作信息
company: '',
position: '',
industry: '',
salary: '',
// 其他信息
status: 'active',
tags: [],
description: '',
});
// 提交结果
const submitResult = ref(null);
// 表单配置
const formOptions = reactive({
labelWidth: '120px',
size: 'default',
buttonAlign: 'right',
});
// 表单列配置
const columns = reactive([
// 基本信息
{
prop: 'name',
label: '姓名',
type: 'input',
span: 8,
placeholder: '请输入姓名',
},
{
prop: 'email',
label: '邮箱',
type: 'input',
span: 8,
placeholder: '请输入邮箱',
},
{
prop: 'phone',
label: '手机号码',
type: 'input',
span: 8,
placeholder: '请输入手机号码',
},
{
prop: 'gender',
label: '性别',
type: 'radio',
span: 8,
options: [
{ label: '男', value: 'male' },
{ label: '女', value: 'female' },
{ label: '其他', value: 'other' },
],
},
{
prop: 'age',
label: '年龄',
type: 'input',
span: 8,
inputType: 'number',
placeholder: '请输入年龄',
},
// 地址信息
{
prop: 'province',
label: '省份',
type: 'select',
span: 8,
placeholder: '请选择省份',
options: [
{ label: '北京', value: 'beijing' },
{ label: '上海', value: 'shanghai' },
{ label: '广东', value: 'guangdong' },
{ label: '江苏', value: 'jiangsu' },
],
},
{
prop: 'city',
label: '城市',
type: 'select',
span: 8,
placeholder: '请选择城市',
options: [
{ label: '北京市', value: 'beijing' },
{ label: '上海市', value: 'shanghai' },
{ label: '广州市', value: 'guangzhou' },
{ label: '深圳市', value: 'shenzhen' },
{ label: '杭州市', value: 'hangzhou' },
{ label: '南京市', value: 'nanjing' },
],
},
{
prop: 'district',
label: '区县',
type: 'select',
span: 8,
placeholder: '请选择区县',
options: [
{ label: '朝阳区', value: 'chaoyang' },
{ label: '海淀区', value: 'haidian' },
{ label: '浦东新区', value: 'pudong' },
{ label: '黄浦区', value: 'huangpu' },
],
},
{
prop: 'address',
label: '详细地址',
type: 'textarea',
span: 24,
placeholder: '请输入详细地址',
rows: 3,
},
// 工作信息
{
prop: 'company',
label: '公司名称',
type: 'input',
span: 12,
placeholder: '请输入公司名称',
},
{
prop: 'position',
label: '职位',
type: 'input',
span: 12,
placeholder: '请输入职位',
},
{
prop: 'industry',
label: '行业',
type: 'select',
span: 12,
placeholder: '请选择行业',
options: [
{ label: '互联网', value: 'internet' },
{ label: '金融', value: 'finance' },
{ label: '教育', value: 'education' },
{ label: '医疗', value: 'medical' },
{ label: '制造业', value: 'manufacturing' },
],
},
{
prop: 'salary',
label: '薪资范围',
type: 'select',
span: 12,
placeholder: '请选择薪资范围',
options: [
{ label: '5k以下', value: 'below5k' },
{ label: '5k-10k', value: '5k-10k' },
{ label: '10k-20k', value: '10k-20k' },
{ label: '20k-30k', value: '20k-30k' },
{ label: '30k以上', value: 'above30k' },
],
},
// 其他信息
{
prop: 'status',
label: '状态',
type: 'select',
span: 12,
placeholder: '请选择状态',
options: [
{ label: '激活', value: 'active' },
{ label: '禁用', value: 'disabled' },
{ label: '审核中', value: 'pending' },
],
},
{
prop: 'tags',
label: '标签',
type: 'select',
span: 12,
placeholder: '请选择标签',
options: [
{ label: '技术', value: 'tech' },
{ label: '管理', value: 'management' },
{ label: '设计', value: 'design' },
{ label: '市场', value: 'marketing' },
{ label: '销售', value: 'sales' },
],
multiple: true,
filterable: true,
},
{
prop: 'description',
label: '个人描述',
type: 'textarea',
span: 24,
placeholder: '请输入个人描述',
rows: 4,
showWordLimit: true,
maxlength: 500,
},
]);
// 提交表单
const handleSubmit = () => {
submitResult.value = { ...formData.value };
ElMessage.success('复杂表单提交成功');
console.log('表单数据:', formData.value);
};
// 重置表单
const handleReset = () => {
formData.value = {
name: '',
email: '',
phone: '',
gender: 'male',
age: null,
province: '',
city: '',
district: '',
address: '',
company: '',
position: '',
industry: '',
salary: '',
status: 'active',
tags: [],
description: '',
};
submitResult.value = null;
ElMessage.info('表单已重置');
};
</script>
<style scoped>
.demo-container {
width: 100%;
max-width: 800px;
margin: 0 auto;
}
h3 {
margin-bottom: 20px;
font-size: 18px;
font-weight: 600;
}
h4 {
margin: 20px 0 10px;
font-size: 16px;
font-weight: 600;
}
p {
margin-bottom: 20px;
color: #909399;
}
.result-container {
margin-top: 20px;
padding: 15px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
.result-json {
background-color: #f5f7fa;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 10px;
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-all;
}
</style>
1
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
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
隐藏源代码
配置说明
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| columns | 表单列配置 | FormColumn[] | - |
| span | 所占列数 | number | 24 |
| offset | 偏移列数 | number | 0 |
| formOptions | 表单配置 | FormOptions | {} |