uni-forms.vue 10.8 KB
Newer Older
DCloud_JSON's avatar
DCloud_JSON 已提交
1
<template>
DCloud_JSON's avatar
DCloud_JSON 已提交
2 3
	<view class="uni-forms">
		<form>
DCloud_JSON's avatar
DCloud_JSON 已提交
4 5 6 7 8 9
			<slot></slot>
		</form>
	</view>
</template>

<script>
DCloud_JSON's avatar
DCloud_JSON 已提交
10 11 12 13 14 15 16 17 18 19 20 21 22
	import Validator from './validate.js';
	import {
		deepCopy,
		getValue,
		isRequiredField,
		setDataValue,
		getDataValue,
		realName,
		isRealName,
		rawData,
		isEqual
	} from './utils.js'

DCloud_JSON's avatar
DCloud_JSON 已提交
23
	// #ifndef VUE3
DCloud_JSON's avatar
DCloud_JSON 已提交
24
	// 后续会慢慢废弃这个方法
DCloud_JSON's avatar
DCloud_JSON 已提交
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
	import Vue from 'vue';
	Vue.prototype.binddata = function(name, value, formName) {
		if (formName) {
			this.$refs[formName].setValue(name, value);
		} else {
			let formVm;
			for (let i in this.$refs) {
				const vm = this.$refs[i];
				if (vm && vm.$options && vm.$options.name === 'uniForms') {
					formVm = vm;
					break;
				}
			}
			if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
			formVm.setValue(name, value);
		}
	};
	// #endif
	/**
	 * Forms 表单
	 * @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据
	 * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
	 * @property {Object} rules	表单校验规则
DCloud_JSON's avatar
DCloud_JSON 已提交
48
	 * @property {String} validateTrigger = [bind|submit|blur]	校验触发器方式 默认 submit
DCloud_JSON's avatar
DCloud_JSON 已提交
49 50
	 * @value bind		发生变化时触发
	 * @value submit	提交时触发
DCloud_JSON's avatar
DCloud_JSON 已提交
51
	 * @value blur	  失去焦点时触发
DCloud_JSON's avatar
DCloud_JSON 已提交
52 53 54 55 56 57 58 59 60 61 62 63 64
	 * @property {String} labelPosition = [top|left]	label 位置 默认 left
	 * @value top		顶部显示 label
	 * @value left	左侧显示 label
	 * @property {String} labelWidth	label 宽度,默认 65px
	 * @property {String} labelAlign = [left|center|right]	label 居中方式  默认 left
	 * @value left		label 左侧显示
	 * @value center	label 居中
	 * @value right		label 右侧对齐
	 * @property {String} errShowType = [undertext|toast|modal]	校验错误信息提示方式
	 * @value undertext	错误信息在底部显示
	 * @value toast			错误信息toast显示
	 * @value modal			错误信息modal显示
	 * @event {Function} submit	提交时触发
DCloud_JSON's avatar
DCloud_JSON 已提交
65
	 * @event {Function} validate	校验结果发生变化触发
DCloud_JSON's avatar
DCloud_JSON 已提交
66 67 68
	 */
	export default {
		name: 'uniForms',
DCloud_JSON's avatar
DCloud_JSON 已提交
69 70 71 72
		emits: ['validate', 'submit'],
		options: {
			virtualHost: true
		},
DCloud_JSON's avatar
DCloud_JSON 已提交
73 74 75 76 77
		props: {
			// 即将弃用
			value: {
				type: Object,
				default () {
DCloud_JSON's avatar
DCloud_JSON 已提交
78
					return null;
DCloud_JSON's avatar
DCloud_JSON 已提交
79 80
				}
			},
DCloud_JSON's avatar
DCloud_JSON 已提交
81
			// vue3 替换 value 属性
DCloud_JSON's avatar
DCloud_JSON 已提交
82 83 84
			modelValue: {
				type: Object,
				default () {
DCloud_JSON's avatar
DCloud_JSON 已提交
85 86 87 88 89 90 91 92
					return null;
				}
			},
			// 1.4.0 开始将不支持 v-model ,且废弃 value 和 modelValue
			model: {
				type: Object,
				default () {
					return null;
DCloud_JSON's avatar
DCloud_JSON 已提交
93 94 95 96 97 98 99 100 101
				}
			},
			// 表单校验规则
			rules: {
				type: Object,
				default () {
					return {};
				}
			},
DCloud_JSON's avatar
DCloud_JSON 已提交
102 103 104 105 106 107
			//校验错误信息提示方式 默认 undertext 取值 [undertext|toast|modal]
			errShowType: {
				type: String,
				default: 'undertext'
			},
			// 校验触发器方式 默认 bind 取值 [bind|submit]
DCloud_JSON's avatar
DCloud_JSON 已提交
108 109
			validateTrigger: {
				type: String,
DCloud_JSON's avatar
DCloud_JSON 已提交
110
				default: 'submit'
DCloud_JSON's avatar
DCloud_JSON 已提交
111
			},
DCloud_JSON's avatar
DCloud_JSON 已提交
112
			// label 位置,默认 left 取值  top/left
DCloud_JSON's avatar
DCloud_JSON 已提交
113 114 115 116
			labelPosition: {
				type: String,
				default: 'left'
			},
DCloud_JSON's avatar
DCloud_JSON 已提交
117
			// label 宽度
DCloud_JSON's avatar
DCloud_JSON 已提交
118 119 120 121
			labelWidth: {
				type: [String, Number],
				default: ''
			},
DCloud_JSON's avatar
DCloud_JSON 已提交
122
			// label 居中方式,默认 left 取值 left/center/right
DCloud_JSON's avatar
DCloud_JSON 已提交
123 124 125 126 127 128 129 130 131
			labelAlign: {
				type: String,
				default: 'left'
			},
			border: {
				type: Boolean,
				default: false
			}
		},
DCloud_JSON's avatar
DCloud_JSON 已提交
132 133 134 135 136
		provide() {
			return {
				uniForm: this
			}
		},
DCloud_JSON's avatar
DCloud_JSON 已提交
137 138
		data() {
			return {
DCloud_JSON's avatar
DCloud_JSON 已提交
139 140 141
				// 表单本地值的记录,不应该与传如的值进行关联
				formData: {},
				formRules: {}
DCloud_JSON's avatar
DCloud_JSON 已提交
142 143 144
			};
		},
		computed: {
DCloud_JSON's avatar
DCloud_JSON 已提交
145 146 147 148 149
			// 计算数据源变化的
			localData() {
				const localVal = this.model || this.modelValue || this.value
				if (localVal) {
					return deepCopy(localVal)
DCloud_JSON's avatar
DCloud_JSON 已提交
150
				}
DCloud_JSON's avatar
DCloud_JSON 已提交
151
				return {}
DCloud_JSON's avatar
DCloud_JSON 已提交
152 153 154
			}
		},
		watch: {
DCloud_JSON's avatar
DCloud_JSON 已提交
155 156 157 158 159 160 161 162 163
			// 监听数据变化 ,暂时不使用,需要单独赋值
			// localData: {},
			// 监听规则变化
			rules: {
				handler: function(val, oldVal) {
					this.setRules(val)
				},
				deep: true,
				immediate: true
DCloud_JSON's avatar
DCloud_JSON 已提交
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
			}
		},
		created() {
			// #ifdef VUE3
			let getbinddata = getApp().$vm.$.appContext.config.globalProperties.binddata
			if (!getbinddata) {
				getApp().$vm.$.appContext.config.globalProperties.binddata = function(name, value, formName) {
					if (formName) {
						this.$refs[formName].setValue(name, value);
					} else {
						let formVm;
						for (let i in this.$refs) {
							const vm = this.$refs[i];
							if (vm && vm.$options && vm.$options.name === 'uniForms') {
								formVm = vm;
								break;
							}
						}
						if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
						formVm.setValue(name, value);
					}
				}
			}
			// #endif

DCloud_JSON's avatar
DCloud_JSON 已提交
189 190 191 192 193
			// 子组件实例数组
			this.childrens = []
			// TODO 兼容旧版 uni-data-picker ,新版本中无效,只是避免报错
			this.inputChildrens = []
			this.setRules(this.rules)
DCloud_JSON's avatar
DCloud_JSON 已提交
194 195 196
		},
		methods: {
			/**
DCloud_JSON's avatar
DCloud_JSON 已提交
197 198 199
			 * 外部调用方法
			 * 设置规则 ,主要用于小程序自定义检验规则
			 * @param {Array} rules 规则源数据
DCloud_JSON's avatar
DCloud_JSON 已提交
200
			 */
DCloud_JSON's avatar
DCloud_JSON 已提交
201 202 203 204 205
			setRules(rules) {
				// TODO 有可能子组件合并规则的时机比这个要早,所以需要合并对象 ,而不是直接赋值,可能会被覆盖
				this.formRules = Object.assign({}, this.formRules, rules)
				// 初始化校验函数
				this.validator = new Validator(rules);
206
			},
DCloud_JSON's avatar
DCloud_JSON 已提交
207

208
			/**
DCloud_JSON's avatar
DCloud_JSON 已提交
209 210 211 212
			 * 外部调用方法
			 * 设置数据,用于设置表单数据,公开给用户使用 , 不支持在动态表单中使用
			 * @param {Object} key
			 * @param {Object} value
213
			 */
DCloud_JSON's avatar
DCloud_JSON 已提交
214 215
			setValue(key, value) {
				let example = this.childrens.find(child => child.name === key);
DCloud_JSON's avatar
DCloud_JSON 已提交
216
				if (!example) return null;
DCloud_JSON's avatar
DCloud_JSON 已提交
217 218 219 220 221 222 223 224 225 226 227 228 229
				this.formData[key] = getValue(key, value, (this.formRules[key] && this.formRules[key].rules) || [])
				return example.onFieldChange(this.formData[key]);
			},

			/**
			 * 外部调用方法
			 * 手动提交校验表单
			 * 对整个表单进行校验的方法,参数为一个回调函数。
			 * @param {Array} keepitem 保留不参与校验的字段
			 * @param {type} callback 方法回调
			 */
			validate(keepitem, callback) {
				return this.checkAll(this.formData, keepitem, callback);
DCloud_JSON's avatar
DCloud_JSON 已提交
230 231 232
			},

			/**
DCloud_JSON's avatar
DCloud_JSON 已提交
233 234 235 236
			 * 外部调用方法
			 * 部分表单校验
			 * @param {Array|String} props 需要校验的字段
			 * @param {Function} 回调函数
DCloud_JSON's avatar
DCloud_JSON 已提交
237
			 */
DCloud_JSON's avatar
DCloud_JSON 已提交
238 239 240
			validateField(props = [], callback) {
				props = [].concat(props);
				let invalidFields = {};
DCloud_JSON's avatar
DCloud_JSON 已提交
241
				this.childrens.forEach(item => {
DCloud_JSON's avatar
DCloud_JSON 已提交
242 243 244 245 246
					const name = realName(item.name)
					if (props.indexOf(name) !== -1) {
						invalidFields = Object.assign({}, invalidFields, {
							[name]: this.formData[name]
						});
DCloud_JSON's avatar
DCloud_JSON 已提交
247 248
					}
				});
DCloud_JSON's avatar
DCloud_JSON 已提交
249 250
				return this.checkAll(invalidFields, [], callback);
			},
DCloud_JSON's avatar
DCloud_JSON 已提交
251

DCloud_JSON's avatar
DCloud_JSON 已提交
252 253 254 255 256 257 258
			/**
			 * 外部调用方法
			 * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果
			 * @param {Array|String} props 需要移除校验的字段 ,不填为所有
			 */
			clearValidate(props = []) {
				props = [].concat(props);
DCloud_JSON's avatar
DCloud_JSON 已提交
259
				this.childrens.forEach(item => {
DCloud_JSON's avatar
DCloud_JSON 已提交
260 261 262 263 264 265 266
					if (props.length === 0) {
						item.errMsg = '';
					} else {
						const name = realName(item.name)
						if (props.indexOf(name) !== -1) {
							item.errMsg = '';
						}
DCloud_JSON's avatar
DCloud_JSON 已提交
267 268 269 270 271
					}
				});
			},

			/**
DCloud_JSON's avatar
DCloud_JSON 已提交
272 273 274 275 276
			 * 外部调用方法 ,即将废弃
			 * 手动提交校验表单
			 * 对整个表单进行校验的方法,参数为一个回调函数。
			 * @param {Array} keepitem 保留不参与校验的字段
			 * @param {type} callback 方法回调
DCloud_JSON's avatar
DCloud_JSON 已提交
277
			 */
DCloud_JSON's avatar
DCloud_JSON 已提交
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
			submit(keepitem, callback, type) {
				for (let i in this.dataValue) {
					const itemData = this.childrens.find(v => v.name === i);
					if (itemData) {
						if (this.formData[i] === undefined) {
							this.formData[i] = this._getValue(i, this.dataValue[i]);
						}
					}
				}

				if (!type) {
					console.warn('submit 方法即将废弃,请使用validate方法代替!');
				}

				return this.checkAll(this.formData, keepitem, callback, 'submit');
DCloud_JSON's avatar
DCloud_JSON 已提交
293
			},
DCloud_JSON's avatar
DCloud_JSON 已提交
294 295 296 297 298

			// 校验所有
			async checkAll(invalidFields, keepitem, callback, type) {
				// 不存在校验规则 ,则停止校验流程
				if (!this.validator) return
DCloud_JSON's avatar
DCloud_JSON 已提交
299
				let childrens = []
DCloud_JSON's avatar
DCloud_JSON 已提交
300
				// 处理参与校验的item实例
DCloud_JSON's avatar
DCloud_JSON 已提交
301
				for (let i in invalidFields) {
DCloud_JSON's avatar
DCloud_JSON 已提交
302
					const item = this.childrens.find(v => realName(v.name) === i)
DCloud_JSON's avatar
DCloud_JSON 已提交
303 304 305 306 307
					if (item) {
						childrens.push(item)
					}
				}

DCloud_JSON's avatar
DCloud_JSON 已提交
308
				// 如果validate第一个参数是funciont ,那就走回调
DCloud_JSON's avatar
DCloud_JSON 已提交
309 310 311 312 313
				if (!callback && typeof keepitem === 'function') {
					callback = keepitem;
				}

				let promise;
DCloud_JSON's avatar
DCloud_JSON 已提交
314
				// 如果不存在回调,那么使用 Promise 方式返回
DCloud_JSON's avatar
DCloud_JSON 已提交
315 316 317 318 319 320 321 322 323
				if (!callback && typeof callback !== 'function' && Promise) {
					promise = new Promise((resolve, reject) => {
						callback = function(valid, invalidFields) {
							!valid ? resolve(invalidFields) : reject(valid);
						};
					});
				}

				let results = [];
DCloud_JSON's avatar
DCloud_JSON 已提交
324 325 326 327 328 329 330 331 332 333 334
				// 避免引用错乱 ,建议拷贝对象处理
				let tempFormData = JSON.parse(JSON.stringify(invalidFields))
				// 所有子组件参与校验,使用 for 可以使用  awiat
				for (let i in childrens) {
					const child = childrens[i]
					let name = realName(child.name);
					const result = await child.onFieldChange(tempFormData[name]);
					if (result) {
						results.push(result);
						// toast ,modal 只需要执行第一次就可以
						if (this.errShowType === 'toast' || this.errShowType === 'modal') break;
DCloud_JSON's avatar
DCloud_JSON 已提交
335 336
					}
				}
DCloud_JSON's avatar
DCloud_JSON 已提交
337 338


DCloud_JSON's avatar
DCloud_JSON 已提交
339 340 341 342 343
				if (Array.isArray(results)) {
					if (results.length === 0) results = null;
				}
				if (Array.isArray(keepitem)) {
					keepitem.forEach(v => {
DCloud_JSON's avatar
DCloud_JSON 已提交
344 345 346 347 348
						let vName = realName(v);
						let value = getDataValue(v, this.localData)
						if (value !== undefined) {
							tempFormData[vName] = value
						}
DCloud_JSON's avatar
DCloud_JSON 已提交
349 350 351
					});
				}

DCloud_JSON's avatar
DCloud_JSON 已提交
352
				// TODO submit 即将废弃
DCloud_JSON's avatar
DCloud_JSON 已提交
353 354 355
				if (type === 'submit') {
					this.$emit('submit', {
						detail: {
DCloud_JSON's avatar
DCloud_JSON 已提交
356
							value: tempFormData,
DCloud_JSON's avatar
DCloud_JSON 已提交
357 358 359 360 361 362 363
							errors: results
						}
					});
				} else {
					this.$emit('validate', results);
				}

DCloud_JSON's avatar
DCloud_JSON 已提交
364 365 366 367
				// const resetFormData = rawData(tempFormData, this.localData, this.name)
				let resetFormData = {}
				resetFormData = rawData(tempFormData, this.name)
				callback && typeof callback === 'function' && callback(results, resetFormData);
DCloud_JSON's avatar
DCloud_JSON 已提交
368 369 370 371 372 373 374 375 376 377

				if (promise && callback) {
					return promise;
				} else {
					return null;
				}

			},

			/**
DCloud_JSON's avatar
DCloud_JSON 已提交
378 379
			 * 返回validate事件
			 * @param {Object} result
DCloud_JSON's avatar
DCloud_JSON 已提交
380
			 */
DCloud_JSON's avatar
DCloud_JSON 已提交
381 382
			validateCheck(result) {
				this.$emit('validate', result);
DCloud_JSON's avatar
DCloud_JSON 已提交
383
			},
DCloud_JSON's avatar
DCloud_JSON 已提交
384 385 386 387 388 389 390
			_getValue: getValue,
			_isRequiredField: isRequiredField,
			_setDataValue: setDataValue,
			_getDataValue: getDataValue,
			_realName: realName,
			_isRealName: isRealName,
			_isEqual: isEqual
DCloud_JSON's avatar
DCloud_JSON 已提交
391 392 393 394
		}
	};
</script>

DCloud_JSON's avatar
DCloud_JSON 已提交
395 396 397
<style lang="scss">
	.uni-forms {}
</style>