如何實現動態表單功能

前言

客戶提出問題“手機端(app\小程序等)每次修改表單的字段名或者新增其它表單時,每次都需要重新審覈,由於表單修改很頻繁且審覈時間又很長,導致程序經常使用中斷,體驗不好。”;根據客戶的問題,總結了兩種方法,都是不需要修改到手機端,所以不用經過審覈。

Tips:$.load(url)和iframe也能實現,但第一個要解決跨域問題,第二個需要每次更新引用的html,操作起來都不方便,這裏就不做過多分析。

實現過程

根據是否能操作DOM,分成兩種實現方式:

第一種:能操作DOM情況;首先調用接口獲取json數據(爲了方便演示,這裏使用本地json,數據會包含html+css+js),然後使用$('#id').html(json)進行渲染;

let apiData = {
    "signPage": "sign3",
    "flowRunState": "",
    "canRollback": 0,
    "moduleId": "7571dc058ff54a528fa08bc4dd22bc20",
    "mobilecontent": "<div class=\"weui-cells weui-cells_form sini-form-body\"><div class=\"weui-cell\"><div class=\"weui-cell__hd\"><div class=\"weui-label\" id=\"name_s20190522182959430\">編號</div></div><div id=\"value_s20190522182959430\" class=\"weui-cell__bd\"><input id=\"s20190522182959430\" name=\"s20190522182959430\" class=\"weui-input \" placeholder=\"請輸入編號\"/></div></div><div class=\"weui-cell\"><div class=\"weui-cell__hd\"><div class=\"weui-label\" id=\"name_s20190524100310477\">所在部門</div></div><div id=\"value_s20190524100310477\" class=\"weui-cell__bd\"><input id=\"s20190524100310477\" name=\"s20190524100310477\" class=\"weui-input \" placeholder=\"請輸入所在部門\"/></div></div><div class=\"weui-cell\"><div class=\"weui-cell__hd\"><div class=\"weui-label\" id=\"name_s20190524100451474\">招聘崗位</div></div><div id=\"value_s20190524100451474\" class=\"weui-cell__bd\"><input type=\"hidden\" id=\"s20190524100451474\" name=\"s20190524100451474\"/><textarea id=\"s201905241004514741\" name=\"s201905241004514741\" rows=\"\" readonly=\"readonly\" class=\"weui-textarea required\" οnclick=\"opensaleSelectView(&#39;s201905241004514741&#39;,&#39;s20190524100451474&#39;,1)\" placeholder=\"請選擇招聘崗位\"></textarea></div></div><div class=\"weui-cell\"><div class=\"weui-cell__hd\"><div class=\"weui-label\" id=\"name_s2019052218311894\">招聘人數</div></div><div id=\"value_s2019052218311894\" class=\"weui-cell__bd\"><input id=\"s2019052218311894\" name=\"s2019052218311894\" class=\"weui-input required num1\" type=\"text\" maxlength=\"8\" placeholder=\"請輸入招聘人數\"/></div></div><div class=\"weui-cell\"><div class=\"weui-cell__hd\"><div class=\"weui-label\" id=\"name_s20190522183138590\">招聘原因</div></div><div id=\"value_s20190522183138590\" class=\"weui-cell__bd\"><input id=\"s20190522183138590\" name=\"s20190522183138590\" class=\"weui-input required\" type=\"text\" maxlength=\"100\" placeholder=\"請輸入招聘原因\"/></div></div><div class=\"weui-cell\"><div class=\"weui-cell__hd\"><div class=\"weui-label\" id=\"name_s20190522183222762\">到崗時間</div></div><div id=\"value_s20190522183222762\" class=\"weui-cell__bd\"><input id=\"s20190522183222762\" name=\"s20190522183222762\" οnclick=\"weui_selectCalendar(&#39;s20190522183222762&#39;);\" class=\"weui-input required \" type=\"text\" placeholder=\"請輸入到崗時間\"/></div></div><div class=\"weui-cell\"><div class=\"weui-cell__hd\"><div class=\"weui-label\" id=\"name_s20191003120034101\">崗位工資</div></div><div id=\"value_s20191003120034101\" class=\"weui-cell__bd\"><input id=\"s20191003120034101\" name=\"s20191003120034101\" class=\"weui-input required num1\" type=\"text\" maxlength=\"8\" placeholder=\"請輸入崗位工資\"/></div></div><div class=\"weui-cell\"><div class=\"weui-cell__hd\"><div class=\"weui-label\" id=\"name_s20190522183303859\">任職要求</div></div><div id=\"value_s20190522183303859\" class=\"weui-cell__bd\"><textarea id=\"s20190522183303859\" name=\"s20190522183303859\" maxlength=\"50\" class=\"weui-textarea required\" rows=\"\" placeholder=\"請輸入任職要求\"></textarea></div></div><div class=\"weui-cell\"><div class=\"weui-cell__hd\"><div class=\"weui-label\" id=\"name_s20190522183239822\">崗位職責</div></div><div id=\"value_s20190522183239822\" class=\"weui-cell__bd\"><textarea id=\"s20190522183239822\" name=\"s20190522183239822\" maxlength=\"50\" class=\"weui-textarea required\" rows=\"\" placeholder=\"請輸入崗位職責\"></textarea></div></div><div class=\"weui-cell\"><div class=\"weui-cell__hd\"><div class=\"weui-label\" id=\"name_s20190522183325556\">\n\t\t\t\t\t\t特殊技能</div></div><div id=\"value_s20190522183325556\" class=\"weui-cell__bd\"><textarea id=\"s20190522183325556\" name=\"s20190522183325556\" maxlength=\"50\" class=\"weui-textarea required\" rows=\"\" placeholder=\"請輸入特殊技能\"></textarea></div></div></div><script type=\"text/javascript\">\n\t\tfx.getFormData(function( formdata ,isRead ){\n\t  fx.registerFormVerify(function(){\n\t\tvar reg=/^\\d+$/; \n\t\tvar isverify = true;\n\t\tvar fee = $(\"#s2019052218311894\").val();\n\t\tvar oldFee = $(\"#s20191003120034101\").val();\n\t\tvar position = $(\"#s20190524100451474\").val();\n\t\tvar error =\"\";\n\t\t\n\t\tif(!isNotBlank(position)){\n\t\t\terror = \"招聘崗位不能爲空\";\n\t\t\tisverify = false;\n\t\t}\n\t\t\n\t\tif(!reg.test(fee)){\n\t\t\terror = \"招聘人數只能輸入正整數\";\n\t\t\tisverify = false;\n\t\t}\n\t\tif(!reg.test(oldFee)){\n\t\t\terror = \"崗位工資只能輸入正整數\";\n\t\t\tisverify = false;\n\t\t}\n\t\tif( !isverify ){\n\t\t  fx.toast(error, \"cancel\")\n\t\t}\n\t\treturn isverify\n\t  })\n\t  \n\t  })\n\tfunction isNotBlank(value) {\n\t\treturn (value != null && value != undefined && value.trim()!=\"\")\n\t}\n\t\n\tfunction opensaleSelectView( nameInput , idInput , opra ){\n\t//name 顯示傳入去 \n\t//id 傳入id\n\t//sale_customer_name 回顯\n\t//id 回顯\n\t//child 多個沒用\n\tfx.openSelectView(\"fisheries/org/getDepartmentPosition\",{},\"選擇崗位\",nameInput,idInput , \"name\",\"id\",\"child\" ,opra,function( dataArrayList ){\n      if(dataArrayList.length>0){\n\t\t  console.log(dataArrayList[0]['department_id'])\n\t  } \n\t  //顯示名\n   },function( itemValue ){ \n       \n          return itemValue.name\n\t\t  //返回展開數據\n   },function (itemValue) {\n\t   let resArray = []\n               if ( fx.isNotNull( itemValue.child )&&fx.isArray( itemValue.child )&&itemValue.child.length > 0 ){\n                  //返回展開數據\n\t\t\t\t  resArray = itemValue.child\n                }\n               if ( fx.isNotNull( itemValue.position )&&fx.isArray( itemValue.position )&&itemValue.position.length > 0 ){\n                  //\n\t\t\t\t  resArray =resArray.concat(itemValue.position )\n                }\n              \n\t\t\t  return resArray\n\t\t\t  //回調函數\n   },function (itemValue){\n\t\n   }\n   \n   )\n   }\n   \n   \n  /*fx.selectPersonnel(  nameInput , idInput ,opra, \"sale_customer_name\",\"id\",\"child\" ,function( dataArrayList ){\n      if(dataArrayList.length>0){\n\t\t  let valueTemp = dataArrayList[0]\n\t      fx.setBindProp(\"sale_selected_customer_name\",valueTemp.sale_customer_name)\n\t      fx.setBindProp(\"sale_selected_customer_id\",valueTemp.id)\n\t  } \n  } )*/</script>"
}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <style>
        /*html, body {
            height: 100%;
            padding: 0;
            margin: 0;
        }*/
    </style>
</head>

<body>
    <div id="mobilecontent"></div>
    <!-- <div class="getUrl"></div> -->
    <!-- <iframe src="http://jeecg-boot.mydoc.io/" frameborder="0" width="100%" height="100%"></iframe> -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="api.json"></script>
    <script>
    $(function() {
        // console.log(apiData.mobilecontent);
        $('#mobilecontent').html(apiData.mobilecontent);
        // $('.getUrl').load('flowDealJob.html');
    });
    </script>
</body>

</html>

第二種:不能操作DOM情況,這裏以uniApp框架爲例,其它框架同理;

1. 調用接口獲取json數據,爲了方便演示,這裏使用本地json;

“label”用於判斷什麼組件,“type”用於判斷組件類型,“name”組件name屬性,“title”字段名,“placeholder”默認提示信息,“maxLength”輸入最長長度,“required”是否必填,“requiredMessage”必填提示信息,“ruleType”校驗規則的類型,“checkRule”校驗規則的長度,“ruleMessage”校驗規則的提示信息,“option”選擇項內容,“mode”選擇的類型(單選、多列、日期等),“checked”是否選中等;可根據實際情況需要,和組件的屬性來對接口返回字段增改刪;

// 動態表單數據
module.exports = {
	"errcode": 0,
	"data": [{
			"sort": 1,
			"label": "input",
			"type": "text",
			"name": "sqrxm",
			"title": "申請單位名稱",
			"placeholder": "請填寫申請單位名稱",
			"maxLength": 20,
			"required": true,
			"requiredMessage": "請填寫申請單位名稱",
			"ruleType": "string",
			"checkRule": "1,50",
			"ruleMessage": "申請單位名稱必須爲1-50個字符!"
		},
		{
			"sort": 2,
			"label": "input",
			"type": "text",
			"name": "sqrdz",
			"title": "申請單位地址",
			"placeholder": "請填寫申請單位地址",
			"maxLength": 20,
			"required": true,
			"requiredMessage": "請填寫申請單位地址",
			"ruleType": "string",
			"checkRule": "1,50",
			"ruleMessage": "申請單位地址必須爲1-50個字符!"
		},
		{
			"sort": 3,
			"label": "select",
			"type": "",
			"name": "township",
			"title": "所屬鎮區",
			"required": false,
			"ruleType": "notcheck",
			"checkRule": "",
			"ruleMessage": "不需要",
			"option": ['請選擇', '東區', '石岐區', '古鎮', '五桂山', '其他'],
			"mode": "selector",
			"index": 0
		},
		{
			"sort": 4,
			"label": "select",
			"type": "",
			"name": "sqrzjlx",
			"title": "申請單位證件類型",
			"placeholder": "請選擇申請單位證件類型",
			"required": true,
			"requiredMessage": "請選擇申請單位證件類型",
			"ruleType": "notnull",
			"checkRule": "",
			"ruleMessage": "請選擇申請單位證件類型",
			"option": ['請選擇', '統一社會信用代碼', '工商營業執照', '組織機構代碼', '登記證', '其他'],
			"mode": "selector",
			"index": 0
		},
		{
			"sort": 5,
			"label": "select",
			"type": "",
			"name": "dlxzq",
			"title": "多列選擇器",
			"placeholder": "",
			"required": true,
			"requiredMessage": "",
			"ruleType": "notnull",
			"checkRule": "",
			"ruleMessage": "請選擇城市",
			"option": [
				['中國', '伊朗'],
				['廣東省', '山東省'],
				['中山市', '北京', '上海', '廣州']
			],
			"mode": "multiSelector",
			"multiIndex": [0, 0, 0]
		},
		{
			"sort": 6,
			"label": "select",
			"type": "",
			"name": "rqxzq",
			"title": "日期選擇器",
			"placeholder": "",
			"required": true,
			"requiredMessage": "",
			"ruleType": "notnull",
			"checkRule": "",
			"ruleMessage": "請選擇日期",
			"option": null,
			"mode": "date",
			"index": 0
		},
		{
			"sort": 7,
			"label": "radio",
			"type": "",
			"name": "radio",
			"title": "單選框",
			"placeholder": "",
			"required": false,
			"ruleType": "notcheck",
			"checkRule": "",
			"ruleMessage": "不需要",
			"option": [{
					"value": "radio1",
					"text": "選項1"
				},
				{
					"value": "radio2",
					"text": "選項2"
				}
			]
		},
		{
			"sort": 8,
			"label": "checkbox",
			"type": "",
			"name": "checkbox",
			"title": "複選框",
			"placeholder": "",
			"required": false,
			"ruleType": "notcheck",
			"checkRule": "",
			"ruleMessage": "不需要",
			"option": [{
					"value": "checkbox1",
					"text": "選項1"
				},
				{
					"value": "checkbox2",
					"text": "選項2"
				},
				{
					"value": "checkbox3",
					"text": "選項3"
				}
			]
		},
		{
			"sort": 9,
			"label": "switch",
			"type": "",
			"name": "switch",
			"title": "開關選擇器",
			"placeholder": "",
			"maxLength": 0,
			"required": false,
			"ruleType": "notcheck",
			"checkRule": "",
			"ruleMessage": "不需要",
			"checked": "checked"
		},
		{
			"sort": 10,
			"label": "slider",
			"type": "",
			"name": "slider",
			"title": "滑動選擇器",
			"placeholder": "",
			"maxLength": 0,
			"required": false,
			"ruleType": "notcheck",
			"checkRule": "",
			"ruleMessage": "不需要",
			"option": {
				"min": 5,
				"max": 100,
				"step": 5,
				"value": 50
			}
		},
		{
			"sort": 11,
			"label": "textarea",
			"type": "",
			"name": "sqsy",
			"title": "申請事由",
			"placeholder": "請填寫申請事由",
			"maxLength": 2000,
			"required": false,
			"ruleType": "notcheck",
			"checkRule": "",
			"ruleMessage": "不需要"
		}
	]
}

2. 循環json數據(v-for="(item, index) in portData"),根據每一項內容判斷顯示的組件和組件屬性,爲了便於擴充,建議把所有表單常用的組件都進行判斷;以後無論什麼組件,直接獲取接口渲染;

下面實現了組件“input”、“select”(多種)、“radio”、“checkbox”、“switch”、“slider”、“textarea”;

<template>
	<view class="form-box">
		<form @submit="formSubmit">
			<view v-for="(item, index) in portData" :key="item.sort">
				<!-- input類型 -->
				<view class="uni-form-item uni-column" v-if="item.label == 'input'">
					<view class="title">{{item.title}}(文本框)</view>
					<input class="uni-input" :name="item.name" :placeholder="item.placeholder" :maxlength="item.maxLength" />
				</view>
				<!-- select類型 -->
				<!-- 普通選擇器 -->
				<view class="uni-form-item uni-column" v-if="item.label == 'select' && item.mode == 'selector'">
					<view class="title">{{item.title}}(普通選擇器)</view>
					<picker @change="bindPickerChange" :mode="item.mode" :value="item.index" :range="item.option" :name="item.name"
					 :data-index="index">
						<view class="uni-input">{{item.option[item.index]}}</view>
					</picker>
				</view>
				<!-- #ifndef MP-ALIPAY -->
				<!-- 多列選擇器(支付寶需要用piker-view實現) -->
				<view class="uni-form-item uni-column" v-if="item.label == 'select' && item.mode == 'multiSelector'">
					<view class="title">{{item.title}}</view>
					<picker :mode="item.mode" @columnchange="bindMultiPickerColumnChange" :value="item.multiIndex" :range="item.option"
					 :name="item.name" :data-index="index">
						<view class="uni-input">{{item.option[0][item.multiIndex[0]]}},{{item.option[1][item.multiIndex[1]]}},{{item.option[2][item.multiIndex[2]]}}</view>
					</picker>
				</view>
				<!-- #endif -->
				<!-- #ifdef MP-ALIPAY -->
				<view class="uni-form-item uni-column" v-if="item.label == 'select' && item.mode == 'multiSelector'">
					<view class="title" @click="togglePicker">{{country}}, {{province}}, {{city}}</view>
					<picker-view class="picker-view" v-if="visible" :indicator-style="indicatorStyle" :value="item.multiIndex" :name="item.name" :data-index="index" @change="bindChange">
						<picker-view-column>
							<view class="item" v-for="(item,index) in item.option[0]" :key="index">{{item}}</view>
						</picker-view-column>
						<picker-view-column>
							<view class="item" v-for="(item,index) in item.option[1]" :key="index">{{item}}</view>
						</picker-view-column>
						<picker-view-column>
							<view class="item" v-for="(item,index) in item.option[2]" :key="index">{{item}}</view>
						</picker-view-column>
					</picker-view>
				</view>	
				<!-- #endif -->
				<!-- 日期選擇器 -->
				<view class="uni-form-item uni-column" v-if="item.label == 'select' && item.mode == 'date'">
					<view class="title">{{item.title}}</view>
					<picker :mode="item.mode" :value="date" :start="startDate" :end="endDate" @change="bindDateChange" :name="item.name">
						<view class="uni-input">{{date}}</view>
					</picker>
				</view>
				<!-- radio類型 -->
				<view class="uni-form-item uni-column" v-if="item.label == 'radio'">
					<view class="title">{{item.title}}</view>
					<radio-group :name="item.name">
						<label v-for="(radioData, index) in item.option" :key="index">
							<!-- 加color爲了解決小程序顏色差異 -->
							<radio color="#007aff" :value="radioData.value" /><text>{{radioData.text}}</text>
						</label>
					</radio-group>
				</view>
				<!-- checkbox類型 -->
				<view class="uni-form-item uni-column" v-if="item.label == 'checkbox'">
					<view class="title">{{item.title}}</view>
					<checkbox-group :name="item.name">
						<label v-for="(checkboxData, index) in item.option" :key="index">
							<!-- 加color爲了解決小程序顏色差異 -->
							<checkbox color="#007aff" :value="checkboxData.value" /><text>{{checkboxData.text}}</text>
						</label>
					</checkbox-group>
				</view>
				<!-- switch類型 -->
				<view class="uni-form-item uni-column" v-if="item.label == 'switch'">
					<view class="title">{{item.title}}</view>
					<!-- 加color爲了解決小程序顏色差異 -->
					<switch color="#007aff" name="switch" :checked="item.checked" />
				</view>
				<!-- slider類型 -->
				<view class="uni-form-item uni-column" v-if="item.label == 'slider'">
					<view class="title">{{item.title}}</view>
					<!-- 加activeColor爲了解決小程序顏色差異 -->
					<slider activeColor="#007aff" :min="item.option.min" :max="item.option.max" :step="item.option.step" :value="item.option.value"
					 :name="item.name" show-value></slider>
				</view>
				<!-- textarea類型 -->
				<view class="uni-form-item uni-column" v-if="item.label == 'textarea'">
					<view class="title">{{item.title}}(多文本框)</view>
					<textarea :placeholder="item.placeholder" auto-height />
					</view>
			</view>
			<view class="uni-btn-v">
				<button form-type="submit" type="primary">保存</button>
			</view>
		</form>
	</view>
</template>
<script>
	// 用來計算當前日期、開始日期和結束日期
	function getDate(type) {
		const date = new Date();	
		let year = date.getFullYear();
		let month = date.getMonth() + 1;
		let day = date.getDate();
	
		if (type === 'start') {
			// 有效期開始的日期
			year = year - 60;
		} else if (type === 'end') {
			// 有效期結束的日期
			year = year + 2;
		}
		month = month > 9 ? month : '0' + month;;
		day = day > 9 ? day : '0' + day;
	
		return `${year}-${month}-${day}`;
	}
	let graceChecker = require('../../../common/js/graceChecker.js');
	export default {
		data() {
			return {
				portData: null,
				date: getDate({
					format: true
				}),
				startDate: getDate('start'),
				endDate: getDate('end'),
				country: '中國',
				province: '廣東省',
				city: '中山市',
				/**
				 * 解決動態設置indicator-style不生效的問題
				 */
                visible: false,
                indicatorStyle: `height: ${Math.round(uni.getSystemInfoSync().screenWidth/(750/100))}px;`
			}
		},
		onLoad() {
			const res = require('../../../common/js/json/form.js'); // 獲取表單json數據
			this.portData = res.data;
		},
		methods: {
			// 普通選擇器
			bindPickerChange: function(e) {
				let index = e.target.dataset.index; // 獲取該類型屬於portData的那一個index				
				this.portData[index].index = e.target.value; // 替換該類型的index達到選擇更換
			},
			// 多列選擇器
			bindMultiPickerColumnChange: function(e) {
				console.log('修改的列爲:' + e.detail.column + ',值爲:' + e.detail.value);
				let index = e.target.dataset.index;
				this.portData[index].multiIndex[e.detail.column] = e.detail.value;
				// 強制重新渲染
				this.$forceUpdate();
			},
			// 支付寶小程序切換picker-view
			togglePicker: function(e) {
				this.visible = !this.visible;				
			},
			bindChange: function(e) {
				const val = e.detail.value;
				let country_index = val[0]; // 獲取國家的index
				let province_index = val[1]; // 獲取省份的index
				let city_index = val[2]; // 獲取城市的index
				let index = e.target.dataset.index;
				this.country = this.portData[index].option[0][country_index];
				this.province = this.portData[index].option[1][province_index];
				this.city = this.portData[index].option[2][city_index];
			},
			// 日期選擇器
			bindDateChange: function(e) {
				this.date = e.detail.value
			},
			// 表單提交
			formSubmit: function(e) {
				// console.log('form發生了submit事件,攜帶數據爲:' + JSON.stringify(e.detail.value));
				// 定義表單規則
				let rule = [];
				this.portData.map((item, index) => {
					rule.push({
						name: item.name,
						ruleType: item.ruleType,
						checkRule: item.checkRule,
						required: item.required,
						ruleMessage: item.ruleMessage
					})
				});
				// 進行表單檢查
				let formdata = e.detail.value;
				let checkRes = graceChecker.check(formdata, rule);
				if (checkRes) {
				    uni.showToast({title:"驗證通過!", icon:"none"});
				} else {
				    uni.showToast({ title: graceChecker.error, icon: "none" });
				}
			}
		}
	}
</script>

<style>
	.form-box {
		background-color: #f4f5f6;
		padding: 0 30rpx;
	}
	.uni-form-item .title {
		padding: 20rpx 0;
	}
	.uni-form-item input {
		width: 100%;
		background-color: #fff;
		padding: 8rpx;
		font-size: 24rpx;
		box-sizing: border-box;
	}
	.uni-form-item textarea {
		background-color: #fff;
		font-size: 24rpx;
		width: 100%;
		padding: 8rpx 0;
	}
	.uni-input-placeholder {
		font-size: 24rpx;
	}
	.uni-btn-v {
		margin-top: 40rpx;
	}
	/* .picker-view {
		position: absolute;
		bottom: 0;
		width: 100%;
		margin-left: -30rpx;
		background-color: #fff;
	} */
</style>

3. 最終效果。

 

 

關注公衆號,瞭解更多實例分享:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章