Demand background

The demand meeting was held, and the product said that there are a lot of forms required this time, there are 19 at present, and the later forms may be added or modified. As the head of front-end development this time, I saw such demand, and KNEW that the front end should not be exhausted, first of all, the form is in the majority, but also change, and later maintenance also makes people haggard.

So I propose to do dynamic forms, do a form configuration system, in the system to configure form types, form fields, and form management. Later, the requirements were reviewed and the system part was developed by the back end. The front end was responsible for dynamically generating forms, displaying submitted forms, and processing the forms.

Data interface design

The form-type interface is not mentioned, which is simpler. I agreed with the back end to create a work order interface, this interface is the back end to tell the front end, need to generate a form.

Prepare to create the form interface (with field explanation) :
  • idThe ID of the form field
  • nameThe name of the form field (stored in the database)
  • typeType of form field (select_Item dropdown, String single line text, Multiple multiple line text, INTEGER single line number, images upload image)
  • titleChinese name of form field (field name of dynamic form)
  • prompt_msgPlaceholder copy for form fields
  • selectObjWhen type is select_item, selectObj has a value, which defaults to null
{
	"code": 0."msg": "success"."data": {
		"list": [{
			"id": 10."name": "check_type"."type": "select_item"."title": "Audit Type"."prompt_msg": "Please fill in the type of audit"."selectObj": [{
				"id": 1."item": "Pre-audit"
			}, {
				"id": 2."item": "Patient Review"}]."val": null."rank": 0
		}, {
			"id": 16."name": "bank_branch_info"."type": "string"."title": "Branch Information"."prompt_msg": "Please fill in the branch information"."selectObj": null."val": null."rank": 0
		},  {
			"id": 19."name": "project_content"."type": "multiple"."title": "Project Content"."prompt_msg": "Please fill in the contents of the project"."selectObj": null."val": null."rank": 0
		}, {
			"id": 22."name": "project_extension_time"."type": "integer"."title": "Project extension time"."prompt_msg": "Please fill in the item extension time"."selectObj": null."val": null."rank": 0
		}, {
			"id": 24."name": "images"."type": "images"."title": "Image"."prompt_msg": "Please upload a picture"."selectObj": null."val": null."rank": 0}}}]Copy the code

Render the form through Vue dynamic components

Now that I’m ready to create a form interface document, how do I render a dynamic form? The dynamic form has five element types, and five element components are created according to this category.

1. Upload the image component

The Uploader component is used here.

<template>
    <div class="default images">
        <div class="lable">{{ item.title }}</div>
        <div v-if="item.val === null" class="content">
            <Uploader 
                :max-num="8"
                :user-imgs="project_image"
                @change="onUploadProject"
            />
        </div>
        <div v-else class="img-wrap">
            <img v-for="(it, idx) in item.val" :src="it" :key="idx" @click="preview(idx, item.val)">
        </div>
    </div>
</template>
Copy the code
2. Multi-line input box components

The default multi-line input box is 3 lines

<template>
    <div v-if="item"  class="default multiple">
        <div class="lable">{{ item.title }}</div>
        <template>
            <textarea
                rows="3" 
                :placeholder="item.prompt_msg" 
                v-model="value" 
                :value="it.item">
        </template>
    </div>
</template>
Copy the code
3. Select components from the drop-down list box

Elder-ui’s el-select is used

<template>
    <div v-if="item" class="default select_item">
        <div class="lable select-lable">{{ item.title }}</div>
        <div class="content">
            <el-select
                v-model="value" 
                placeholder="Please select type" 
                class="el-select-wrap" 
                size="mini"
                @change="onChangeFirstValue"
            >
                <el-option
                    v-for="it in item.selectObj"
                    :key="it.id"
                    :label="it.item"
                    :value="it.item">
                </el-option>
            </el-select>
        </div>
    </div>
</template>
Copy the code

The other two numeric single-line input box components and text single-line input box components are similar to the multi-line input box components.

The components are created for the convenience of unified management of custom components. Import and export components in the form of export default composition.

// Single-line text input box component
export { default as String } from './string.vue'  

// Single-line numeric input box component
export { default as Integer } from './integer.vue' 

// Multi-line text input box component
export { default as Multiple } from './multiple.vue' 

// Drop down list selector component
export { default as Select_item } from './select_item.vue' 

// Upload the image component
export { default as Images } from './images.vue' 
Copy the code

Then the dynamic form page is uniformly introduced and rendered in the form of Vue dynamic component, and is attribute is the dynamic component name.

<template>
    <div class="g-container">
        <component 
            v-for="(item, number) in freedomConfig" 
            :key="item.name"
            :is="item.type" 
            :item="item" 
            :number="number" 
            @changeComponent="changeComponentHandle"
        ></component>
    </div>
</template>

<script>
    import * as itemElements from '.. /.. /components/itemElement'
    
    export default {
        components: itemElements,
    }
</script>
Copy the code

With that done, the dynamic form is displayed. Forms are dynamically generated, so how do you validate forms and summarize form data?

Form data summary

When rendering the component dynamically, the number parameter is passed, which is used to identify the number of the component in the dynamic form, which is convenient for later data filling and data saving.

The default value property value is null, listen for value, emit when value changes to tell parent component that data has changed, please save.

data() {
    return {
        value: ''
    }
},
watch: {
    value(v, o) {
        this.throttleHandle(() => {
            this.$emit('changeComponent', {
                number: this.number,
                value: this.$data.value
            })
        })
    }
},
Copy the code

But where is the data stored? How do I keep it? Let the back end give a form all the fields of the interface, take the data to store in the data, every time the data update to find whether there is this field, if there is a value assigned to save. When you commit later, you commit this object.

Form validation

At the time of submission, it is expected that the user can complete the form before invoking the submission interface. If the front end is required to verify whether the form is completed or not, the toast of the response will be gently prompted to prevent the form submission.

this.checkFrom(freedomConfig, preWordorderData).then(canSubmit= > {
    canSubmit && postSubmitWorkorder(preWordorderData).then(res= > {
        if (res.code === 0) {
            showLoading()
            this.$router.push(`/detail/${res.data.id}`)}})})Copy the code

CheckFrom is our checkFrom method, loop through the pre-created form, check the data to see if the field has a value, if not, give toast prompt. And return a promise.resolve(false). Resolve (true) is returned if all validations pass. This makes checkFrom an asynchronous function.

It should be noted that the value of the drop-down box is a number greater than 0, and the attribute value of the uploaded picture is an array.

A dynamic form is created, validated, and consolidated. A lot of times scenarios that require a lot of code are easy to think about, but abstracting a component requires more consideration.