Skip to content

Commit f909e2d

Browse files
committed
feat(form): add form builder
1 parent d903a19 commit f909e2d

File tree

9 files changed

+760
-0
lines changed

9 files changed

+760
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"consola": "^2.11.0",
3131
"copy-text-to-clipboard": "^2.1.1",
3232
"core-js": "^3.3.2",
33+
"element-form-builder": "^2.0.0",
3334
"element-ui": "^2.12.0",
3435
"json2csv": "^4.5.4",
3536
"lodash": "^4.17.15",

src/common/config/nav.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ const nav = [
4545
path: '/form/basic/100271'
4646
}
4747
]
48+
},
49+
{
50+
key: 'form-builder',
51+
name: '动态表单',
52+
links: [
53+
{
54+
name: '表单生成器',
55+
path: '/form/form-builder'
56+
}
57+
]
4858
}
4959
]
5060
},
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
<template>
2+
<div>
3+
<validation-observer ref="observer" v-slot="{ invalid }">
4+
<el-row :gutter="10">
5+
<el-col :span="12" :xs="24">
6+
<el-form label-width="80px" v-loading="loading">
7+
<el-card v-if="!formConfig.length">
8+
<div class="placeholder"></div>
9+
</el-card>
10+
<form-builder
11+
@command="handleCommand"
12+
:form="form"
13+
:shares="formShares"
14+
:config="formConfig"
15+
:metadata="metadata"
16+
></form-builder>
17+
<app-form-action>
18+
<el-button :disabled="loading" type="primary" @click="save">
19+
提交
20+
</el-button>
21+
</app-form-action>
22+
</el-form>
23+
</el-col>
24+
<el-col :span="12" :xs="24">
25+
<el-card header="表单配置" class="form-builder-card">
26+
<el-input type="textarea" v-model="formConfigJSON" rows="45" />
27+
</el-card>
28+
</el-col>
29+
</el-row>
30+
</validation-observer>
31+
<el-dialog title="提交结果" :visible.sync="showResultModal" width="80%">
32+
<pre>{{ JSON.stringify(formValues, null, 4) }}</pre>
33+
<span slot="footer" class="dialog-footer">
34+
<el-button type="primary" @click="showResultModal = false"
35+
>确 定</el-button
36+
>
37+
</span>
38+
</el-dialog>
39+
</div>
40+
</template>
41+
42+
<style lang="scss" scoped>
43+
.el-card.form-builder-card {
44+
::v-deep .el-card__header {
45+
padding: 10px 20px;
46+
}
47+
}
48+
</style>
49+
50+
<script>
51+
import { useForm, useLoading } from '@fext/vue-use';
52+
import { createFormBuilder } from 'element-form-builder';
53+
import { ElFormAdaptor } from 'element-form-builder/lib/adaptor/element';
54+
import ExampleFormComponents from './components';
55+
56+
export default {
57+
name: 'example-basic-form',
58+
59+
components: {
60+
FormBuilder: createFormBuilder({
61+
components: {
62+
ElFormAdaptor,
63+
64+
...ExampleFormComponents
65+
}
66+
})
67+
},
68+
69+
setup() {
70+
const form = useForm();
71+
const { formValues, setInitialFormValues, updateFormValues } = form;
72+
const { loading, withLoading } = useLoading();
73+
74+
return {
75+
// from form composition
76+
form,
77+
formValues,
78+
setInitialFormValues,
79+
updateFormValues,
80+
81+
// from loading composition
82+
loading,
83+
withLoading
84+
};
85+
},
86+
87+
data() {
88+
return {
89+
showResultModal: false,
90+
91+
metadata: {},
92+
93+
formShares: {
94+
size: 'medium',
95+
96+
props: {
97+
clearable: true
98+
}
99+
},
100+
101+
formConfig: [],
102+
103+
formConfigJSON: JSON.stringify(
104+
[
105+
{
106+
component: 'el-card',
107+
props: {
108+
header: '基础信息'
109+
},
110+
fields: [
111+
{
112+
name: 'channel',
113+
component: 'ExampleChannel',
114+
rules: {
115+
required: true
116+
}
117+
},
118+
{
119+
name: 'name',
120+
component: 'ExampleName',
121+
rules: {
122+
required: true
123+
}
124+
},
125+
{
126+
name: 'comment',
127+
component: 'ElFormAdaptor',
128+
label: '评语',
129+
tip: '一句话评价(使用 FormAdaptor 的自定义字段)',
130+
tooltip: '精彩点评',
131+
rules: {
132+
required: true,
133+
max: 50,
134+
min: 10
135+
},
136+
props: {
137+
placeholder: '不超过 20 个字'
138+
}
139+
}
140+
]
141+
},
142+
{
143+
component: 'el-card',
144+
props: {
145+
header: '高级信息'
146+
},
147+
fields: [
148+
{
149+
name: 'type',
150+
component: 'ExampleType',
151+
defaultValue: 2
152+
},
153+
{
154+
name: 'actor',
155+
component: 'ExampleActorComplex'
156+
},
157+
{
158+
name: 'date',
159+
component: 'ElFormAdaptor',
160+
label: '发行日期',
161+
extend: {
162+
component: 'el-date-picker'
163+
},
164+
props: {
165+
placeholder: '请通过日期选择器'
166+
}
167+
},
168+
{
169+
name: 'description',
170+
component: 'ElFormAdaptor',
171+
label: '描述',
172+
tip: '剧情描述(使用 FormAdaptor 的自定义字段)',
173+
rules: {
174+
max: 180
175+
},
176+
props: {
177+
type: 'textarea',
178+
rows: 5,
179+
placeholder: '不超过 180 个字'
180+
}
181+
}
182+
]
183+
}
184+
],
185+
null,
186+
2
187+
)
188+
};
189+
},
190+
191+
watch: {
192+
formConfigJSON: {
193+
handler(value) {
194+
try {
195+
this.formConfig = JSON.parse(this.formConfigJSON);
196+
} catch (err) {
197+
// this.formConfig = this.formConfig;
198+
}
199+
},
200+
immediate: true
201+
}
202+
},
203+
204+
created() {
205+
this.registerEvents();
206+
this.renderForm();
207+
},
208+
209+
methods: {
210+
delay(timeout = 1000) {
211+
return new Promise(resolve => {
212+
setTimeout(() => {
213+
resolve();
214+
}, timeout);
215+
});
216+
},
217+
218+
getInitialValues() {
219+
return {};
220+
},
221+
222+
registerEvents() {
223+
this.$on('doSomething', options => {
224+
this.$message({
225+
type: 'success',
226+
message: '触发表单事件成功!'
227+
});
228+
});
229+
},
230+
231+
handleCommand(cmd, ...args) {
232+
this.$emit(cmd, ...args);
233+
},
234+
235+
async renderForm() {
236+
return this.withLoading(() => {
237+
return Promise.all([this.getRenderMetadata(), this.getFormValues()]);
238+
});
239+
},
240+
241+
async getRenderMetadata() {
242+
await this.delay();
243+
244+
// fake render metadata
245+
return Promise.resolve({
246+
channels: [
247+
{
248+
id: 1,
249+
code: 'dy',
250+
name: '电影'
251+
},
252+
{
253+
id: 2,
254+
code: 'dm',
255+
name: '动漫'
256+
}
257+
]
258+
}).then(data => {
259+
this.metadata = data;
260+
});
261+
},
262+
263+
async getFormValues() {
264+
await this.delay();
265+
266+
// fake form values
267+
return Promise.resolve({
268+
channel: 2,
269+
name: 'Form Builder',
270+
comment: 'A powerful form builder',
271+
actor: ['Yang', 'Zhang'],
272+
description: 'What a nice tool'
273+
}).then(data => {
274+
const { formValues } = this;
275+
276+
this.updateFormValues({
277+
...formValues,
278+
...data
279+
});
280+
});
281+
},
282+
283+
async save() {
284+
const valid = await this.$refs.observer.validate();
285+
286+
if (!valid) {
287+
this.$message({
288+
type: 'error',
289+
message: '部分表单填写错误,请检查!'
290+
});
291+
return;
292+
}
293+
294+
this.showResultModal = true;
295+
}
296+
}
297+
};
298+
</script>

0 commit comments

Comments
 (0)