开始
- 2019-11-04更新: 增加多个组件的测试.
因代码历史背景,无法使用NodeJS.所以这个版本的是纯js调用,不会涉及到NodeJS.
有时候会遇到想动态配置一下表单,根据动态参数自动生成表单并且可以验证.于是想了想,顺便找了找代码..发现只有使用NodeJS是最简单的.但又无法使用,难受…
只有持续鼓捣…还好…
模板部分
代码其实很简单,就是把模板代码定义在一个js文件(组件)中.然后在HTML中引用即可.
文件名: dyform_item.js
var template=` <div> <el-form-item :label="widget.name" :props="dataModel" :label-width="widget.labelwidth" label-width="120px"> <template v-if="widget.type == 'input'" > <el-input v-if="widget.options.dataType == 'number' || widget.options.dataType == 'integer' || widget.options.dataType == 'float'" :type="widget.options.dataType" v-model="dataModel" v-on:change="childData($event)" :placeholder="widget.options.placeholder" :style="{width: widget.options.width?widget.options.width:'100%'}" ></el-input> <el-input v-else :type="widget.options.dataType" v-model="dataModel" v-on:change="childData($event)" :placeholder="widget.options.placeholder" :style="{width: widget.options.width?widget.options.width:'100%'}" ></el-input> </template> <template v-if="widget.type == 'textarea'"> <el-input type="textarea" :rows="5" v-model="dataModel" :disabled="widget.options && widget.options.disabled" v-on:change="childData($event)" :placeholder="(widget.options && widget.options.placeholder) ?widget.options.placeholder:''" :style="{width: (widget.options && widget.options.width)?widget.options.width:'100%'}" ></el-input> </template> <template v-if="widget.type == 'number'"> <el-input-number v-model="dataModel" :style="{width: (widget.options && widget.options.width)?widget.options.width:'100%'}" :step="(widget.options && widget.options.step)?widget.options.step:1" v-on:change="childData($event)" controls-position="right" ></el-input-number> </template> <template v-if="widget.type == 'radio'"> <el-radio-group v-model="dataModel" v-on:change="childData($event)" :style="{width: widget.options.width?widget.options.width:'100%'}" > <el-radio :style="{display: widget.options.inline ? 'inline-block' : 'block'}" :label="item.value" v-for="(item, index) in (widget.options.remote ? widget.options.remoteOptions : widget.options.options)" :key="index" > <template v-if="widget.options.remote">{{item.label}}</template> <template v-else>{{widget.options.showLabel ? item.label : item.value}}</template> </el-radio> </el-radio-group> </template> <template v-if="widget.type == 'checkbox'"> <el-checkbox-group v-model="checkboxModel" v-on:change="childData($event)" :style="{width: widget.options.width?widget.options.width:'100%'}" > <el-checkbox :style="{display: widget.options.inline ? 'inline-block' : 'block'}" v-for="(item, index) in ((widget.options.remote && widget.options.remoteOptions) ? widget.options.remoteOptions : widget.options.options)" v-model="dataModel" :label="item.value" :key="index" > {{widget.options.remote?item.label:widget.options.showLabel?item.label:item.value}} </el-checkbox> </el-checkbox-group> </template> <template v-if="widget.type == 'time'"> <el-time-picker v-model="timePickerModel" v-on:change="childData($event)" :is-range="(widget.options && widget.options.isRange)?true:false" :placeholder="(widget.options && widget.options.placeholder)?widget.options.placeholder:''" :start-placeholder="(widget.options && widget.options.startPlaceholder)?widget.options.startPlaceholder:''" :end-placeholder="(widget.options && widget.options.endPlaceholder)?widget.options.endPlaceholder:''" :readonly="(widget.options && widget.options.readonly)?true:false" :disabled="(widget.options && widget.options.disabled)?true:false" :editable="(widget.options && widget.options.editable)?true:false" :clearable="(widget.options && widget.options.clearable)?true:false" :arrowControl="(widget.options && widget.options.arrowControl)?true:false" :value-format="(widget.options && widget.options.format)?widget.options.format:''" :style="{width: (widget.options && widget.options.width)?widget.options.width:'100%'}" > </el-time-picker> </template> <template v-if="widget.type=='date'"> <el-date-picker v-model="dataModel" v-on:change="childData($event)" :type="(widget.options&&widget.options.type)?widget.options.type:'date'" :placeholder="(widget.options && widget.options.placeholder)?widget.options.placeholder:''" :start-placeholder="(widget.options && widget.options.startPlaceholder)?widget.options.startPlaceholder:''" :end-placeholder="(widget.options && widget.options.endPlaceholder)?widget.options.endPlaceholder:''" :readonly="(widget.options && widget.options.readonly)?true:false" :disabled="(widget.options && widget.options.disabled)?true:false" :editable="(widget.options && widget.options.editable)?true:false" :clearable="(widget.options && widget.options.clearable)?true:false" :value-format="(widget.options&&widget.options.timestamp) ? 'timestamp' : (widget.options&&widget.options.format)?widget.options.format:null" :format="(widget.options && widget.options.format)?widget.options.format:null" :style="{width: (widget.options && widget.options.width)?widget.options.width:'100%'}" > </el-date-picker> </template> <template v-if="widget.type =='rate'"> <el-rate v-model="dataModel" v-on:change="childData($event)" :max="(widget.options && widget.options.max)?widget.options.max:5" :disabled="(widget.options && widget.options.disabled)?true:false" :allow-half="(widget.options && widget.options.allowHalf)?true:false" ></el-rate> </template> <template v-if="widget.type == 'color'"> <el-color-picker v-model="dataModel" v-on:change="childData($event)" :disabled="(widget.options && widget.options.disabled)?true:false" :show-alpha="(widget.options && widget.options.showAlpha)?true:false" ></el-color-picker> </template> <template v-if="widget.type == 'select'"> <el-select v-model="dataModel" v-on:change="childData($event)" :disabled="widget.options.disabled" :multiple="widget.options.multiple" :clearable="widget.options.clearable" :placeholder="widget.options.placeholder" :style="{width: widget.options.width?widget.options.width:'100%'}" > <el-option v-for="item in (widget.options.remote ? widget.options.remoteOptions : widget.options.options)" :key="item.value" :value="item.value" :label="widget.options.showLabel || widget.options.remote?item.label:item.value"></el-option> </el-select> </template> <template v-if="widget.type=='switch'"> <el-switch v-model="dataModel" v-on:change="childData($event)" :disabled="(widget.options && widget.options.disabled)?true:false" > </el-switch> </template> </el-form-item> </div>`; var dyform_item=Vue.extend({ template:template, props: ['widget'], data () { return { timePickerModel:[], checkboxModel:[], dataModel: this.widget.value, } }, methods: { childData:function(value){ this.$emit('change',this.widget.code,value); } }, watch:{ dataModel:function(newVal,oldVal){ if(widget.type == 'number'){ return parseInt(this.dataModel); } if(widget.options.dataType == 'number' || widget.options.dataType == 'integer' || widget.options.dataType == 'float'){ return widget.options.dataType == 'float'?parseFloat(this.dataModel):parseInt(this.dataModel) } return this.dataModel; } }, created () { }, watch: { } });
使用示例(HTML)
之后就是在HTML里面使用定义好的模板了.
文件名: index.html
<!DOCTYPE html> <html lang="en"> <head> <title>自动生成表单</title> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link href="https://cdn.bootcss.com/element-ui/2.12.0/theme-chalk/index.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script> <script src="https://cdn.bootcss.com/element-ui/2.12.0/index.js"></script> </head> <body> <script type="text/javascript" src="dyform_item.js"></script> <div id="app"> <el-row :gutter="20"> <el-col :span="12"> <el-form ref="form"> <template v-for="item in ExtForm"> <form-item :key="item.key" @change="changeData" :ref="item.code" :widget="item"></form-item> </template> </el-form> </el-col> <el-col :span="10"> <el-table :data="ExtFormData" border style="width: 100%"> <el-table-column prop="name" label="名称" width="180"> </el-table-column> <el-table-column prop="value" label="值"> </el-table-column> </el-table> </el-col> </el-row> </div> <script> var cp = { data: function() { return { ExtForm: {}, ExtFormData: [] } }, components: { 'form-item': dyform_item }, methods: { // 动态加载数据到右侧表格. getDataShow:function() { this.ExtFormData=Object.entries(this.ExtForm).map(data=>{ return { name:data[1].name, value:data[1].value } }) }, // 子组件动态修改数据调用. changeData: function(itemCode, value) { console.log(`[${this.ExtForm[itemCode].name}]更新前的值:${this.ExtForm[itemCode].value}`); this.ExtForm[itemCode].value = value; console.log(`[${this.ExtForm[itemCode].name}]更新后的值:${this.ExtForm[itemCode].value}`); this.getDataShow(); }, // 动态初始化表单 initExtForm: function() { var _this = this; var items = [ { "XMLID": "940bfdfdsaf2218911c81330d", "ITEMCODE": "sCheckBox", "ITEMNAME": "渠道1", "ITEMTYPE": "checkbox", "NOTNULL": "", "DEFDATA": "[{\"label\":\"网络\",\"value\":\"1\"},{\"label\":\"线下\",\"value\":\"2\"},{\"label\":\"活动\",\"value\":\"3\"}]", "ACTIVE_STATE": "Y", "ITEMNO": "5", "DEFTYPE": "json" }, { "XMLID": "940bfdsafd2c226c8303911c81330d", "ITEMCODE": "sName", "ITEMNAME": "姓名2", "ITEMTYPE": "", "NOTNULL": "", "DEFDATA": "", "ACTIVE_STATE": "Y", "ITEMNO": "1", "DEFTYPE": null }, { "XMLID":"4b0f67616dfdsafdas8106fecbbb1d7e", "ITEMCODE": "sSex", "ITEMNAME": "性别3", "ITEMTYPE": "select", "NOTNULL": "", "DEFDATA": "[{\"label\":\"男性\",\"value\":\"1\"},{\"label\":\"女性\",\"value\":\"2\"},{\"label\":\"未知\",\"value\":\"3\"}]", "ACTIVE_STATE": "Y", "ITEMNO": "2", "DEFTYPE": "json" }, { "XMLID": "940bfdsafasd6c8308911c81330d", "ITEMCODE": "sPhone", "ITEMNAME": "电话4", "ITEMTYPE": "number", "NOTNULL": "", "DEFDATA": "", "ACTIVE_STATE": "Y", "ITEMNO": "3", "DEFTYPE": null }, { "XMLID": "940bfdfdsoiwoekskkzdcf08911c81330d", "ITEMCODE": "sRadio", "ITEMNAME": "是否启用5", "ITEMTYPE": "radio", "NOTNULL": "", "DEFDATA": "[{\"label\":\"启用\",\"value\":\"1\"},{\"label\":\"停用\",\"value\":\"2\"},{\"label\":\"未知\",\"value\":\"3\"}]", "ACTIVE_STATE": "Y", "ITEMNO": "3", "DEFTYPE": "json" }, { "XMLID": "2040356f36f9dsaze29fc98da3c", "ITEMCODE": "sZipCode", "ITEMNAME": "邮编6", "ITEMTYPE": "textarea", "NOTNULL": "", "DEFDATA": "", "ACTIVE_STATE": "Y", "ITEMNO": "4", "DEFTYPE": null }, { "XMLID": "2040356f36f9232daewewda3c", "ITEMCODE": "sRegTime", "ITEMNAME": "注册时间7", "ITEMTYPE": "time", "NOTNULL": "", "DEFDATA": "", "ACTIVE_STATE": "Y", "ITEMNO": "4", "DEFTYPE": null }, { "XMLID": "204fdsafdsa2iqkjs98da3c", "ITEMCODE": "sRegDate", "ITEMNAME": "注册日期8", "ITEMTYPE": "date","ITEMSUBTYPE":"date", "NOTNULL": "", "DEFDATA": "", "ACTIVE_STATE": "Y", "ITEMNO": "4", "DEFTYPE": null }, { "XMLID": "204fdsafdsawewzjs98da3c", "ITEMCODE": "sRegDateTo", "ITEMNAME": "有效区间9", "ITEMTYPE": "date","ITEMSUBTYPE":"daterange", "NOTNULL": "", "DEFDATA": "", "ACTIVE_STATE": "Y", "ITEMNO": "4", "DEFTYPE": null }, { "XMLID": "204fd28iueiww98da3c", "ITEMCODE": "sRegRate", "ITEMNAME": "服务评分10", "ITEMTYPE": "rate","ITEMSUBTYPE":"", "NOTNULL": "", "DEFDATA": "", "ACTIVE_STATE": "Y", "ITEMNO": "4", "DEFTYPE": null }, { "XMLID": "2041298siuiqs", "ITEMCODE": "sColorSelect", "ITEMNAME": "颜色选择11", "ITEMTYPE": "color","ITEMSUBTYPE":"", "NOTNULL": "", "DEFDATA": "", "ACTIVE_STATE": "Y", "ITEMNO": "4", "DEFTYPE": null }, { "XMLID": "2041298siuiqs", "ITEMCODE": "sSwitch", "ITEMNAME": "滑块选择12", "ITEMTYPE": "switch","ITEMSUBTYPE":"", "NOTNULL": "", "DEFDATA": "", "ACTIVE_STATE": "Y", "ITEMNO": "4", "DEFTYPE": null } ]; for (var i in items) { var row = items[i]; var optionsSelect = ''; switch (row.DEFTYPE) { case 'json': optionsSelect = JSON.parse(row.DEFDATA); console.log(optionsSelect); break; } var value = _this.ExtFormData[row.ITEMCODE] || ""; switch (row.ITEMTYPE) { case 'select': case 'radio': case 'checkbox': _this.$set(_this.ExtForm, row.ITEMCODE, { notnull: row.NOTNULL, type: row.ITEMTYPE, model: _this.ExtForm[row.ITEMCODE], code: row.ITEMCODE, name: row.ITEMNAME, value: value, options: { options: optionsSelect, showLabel: true } }); break; case 'inputnumber': case 'inputfloat': case 'inputint': _this.$set(_this.ExtForm, row.ITEMCODE, { notnull: row.NOTNULL, type: 'input', model: '0', code: row.ITEMCODE, name: row.ITEMNAME, value: value, options: { dataType: row.ITEMTYPE.replace('input',''), placeholder: '' } }); break; case 'number': _this.$set(_this.ExtForm, row.ITEMCODE, { notnull: row.NOTNULL, type: 'number', model: '0', code: row.ITEMCODE, name: row.ITEMNAME, value: value}); break; case 'time': _this.$set(_this.ExtForm, row.ITEMCODE, { notnull: row.NOTNULL, type: 'time', model: _this.ExtForm[row.ITEMCODE],options:{format:'HH:mm:ss'}, code: row.ITEMCODE, name: row.ITEMNAME, value: value}); break; case 'date': let optionsType=(row.ITEMSUBTYPE)?row.ITEMSUBTYPE:'date'; _this.$set(_this.ExtForm, row.ITEMCODE, { notnull: row.NOTNULL, type: 'date', model: _this.ExtForm[row.ITEMCODE],options:{type:optionsType,format:'yyyy-MM-dd'}, code: row.ITEMCODE, name: row.ITEMNAME, value: value}); break; case 'textarea': case 'rate': case 'color': case 'switch': _this.$set(_this.ExtForm, row.ITEMCODE, { notnull: row.NOTNULL, type: row.ITEMTYPE, model: _this.ExtForm[row.ITEMCODE], code: row.ITEMCODE, name: row.ITEMNAME, value: value}); break; default: _this.$set(_this.ExtForm, row.ITEMCODE, { notnull: row.NOTNULL, type: 'input', model: _this.ExtForm[row.ITEMCODE], code: row.ITEMCODE, name: row.ITEMNAME, value: value, options: { options: { dataType: 'text', placeholder: '' } } }); break; } } } }, created: function() { }, mounted: function() { this.initExtForm(); } } var config = Vue.extend(cp); var c = new config().$mount('#app'); </script> </body> </html>
总结
- 定义通用模板(子组件);
- 在HTML中引用(父组件),并初始化;
- 子组件通过$emit的方式更新父组件中的值,这里使用了一个方法changeData.可以在父组件中自定义需要更新及验证的数据.
- 将dyform_item.js和index.html保存成文件,放在同一个目录.之后用浏览器访问index.html即可看到效果.