You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							631 lines
						
					
					
						
							15 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							631 lines
						
					
					
						
							15 KiB
						
					
					
				| <template> | |
| 	<view class="uni-forms-item" | |
| 		:class="['is-direction-' + localLabelPos ,border?'uni-forms-item--border':'' ,border && isFirstBorder?'is-first-border':'']"> | |
| 		<slot name="label"> | |
| 			<view class="uni-forms-item__label" :class="{'no-label':!label && !isRequired}" | |
| 				:style="{width:localLabelWidth,justifyContent: localLabelAlign}"> | |
| 				<text v-if="isRequired" class="is-required">*</text> | |
| 				<text>{{label}}</text> | |
| 			</view> | |
| 		</slot> | |
| 		<!-- #ifndef APP-NVUE --> | |
| 		<view class="uni-forms-item__content"> | |
| 			<slot></slot> | |
| 			<view class="uni-forms-item__error" :class="{'msg--active':msg}"> | |
| 				<text>{{msg}}</text> | |
| 			</view> | |
| 		</view> | |
| 		<!-- #endif --> | |
| 		<!-- #ifdef APP-NVUE --> | |
| 		<view class="uni-forms-item__nuve-content"> | |
| 			<view class="uni-forms-item__content"> | |
| 				<slot></slot> | |
| 			</view> | |
| 			<view class="uni-forms-item__error" :class="{'msg--active':msg}"> | |
| 				<text class="error-text">{{msg}}</text> | |
| 			</view> | |
| 		</view> | |
| 		<!-- #endif --> | |
| 	</view> | |
| </template> | |
|  | |
| <script> | |
| 	/** | |
| 	 * uni-fomrs-item 表单子组件 | |
| 	 * @description uni-fomrs-item 表单子组件,提供了基础布局已经校验能力 | |
| 	 * @tutorial https://ext.dcloud.net.cn/plugin?id=2773 | |
| 	 * @property {Boolean} required 是否必填,左边显示红色"*"号 | |
| 	 * @property {String } 	label 				输入框左边的文字提示 | |
| 	 * @property {Number } 	labelWidth 			label的宽度,单位px(默认65) | |
| 	 * @property {String } 	labelAlign = [left|center|right] label的文字对齐方式(默认left) | |
| 	 * 	@value left		label 左侧显示 | |
| 	 * 	@value center	label 居中 | |
| 	 * 	@value right	label 右侧对齐 | |
| 	 * @property {String } 	errorMessage 		显示的错误提示内容,如果为空字符串或者false,则不显示错误信息 | |
| 	 * @property {String } 	name 				表单域的属性名,在使用校验规则时必填 | |
| 	 * @property {String } 	leftIcon 			【1.4.0废弃】label左边的图标,限 uni-ui 的图标名称 | |
| 	 * @property {String } 	iconColor 		【1.4.0废弃】左边通过icon配置的图标的颜色(默认#606266) | |
| 	 * @property {String} validateTrigger = [bind|submit|blur]	【1.4.0废弃】校验触发器方式 默认 submit | |
| 	 * 	@value bind 	发生变化时触发 | |
| 	 * 	@value submit 提交时触发 | |
| 	 * 	@value blur 	失去焦点触发 | |
| 	 * @property {String } 	labelPosition = [top|left] 【1.4.0废弃】label的文字的位置(默认left) | |
| 	 * 	@value top	顶部显示 label | |
| 	 * 	@value left	左侧显示 label | |
| 	 */ | |
|  | |
| 	export default { | |
| 		name: 'uniFormsItem', | |
| 		options: { | |
| 			virtualHost: true | |
| 		}, | |
| 		provide() { | |
| 			return { | |
| 				uniFormItem: this | |
| 			} | |
| 		}, | |
| 		inject: { | |
| 			form: { | |
| 				from: 'uniForm', | |
| 				default: null | |
| 			}, | |
| 		}, | |
| 		props: { | |
| 			// 表单校验规则 | |
| 			rules: { | |
| 				type: Array, | |
| 				default () { | |
| 					return null; | |
| 				} | |
| 			}, | |
| 			// 表单域的属性名,在使用校验规则时必填 | |
| 			name: { | |
| 				type: [String, Array], | |
| 				default: '' | |
| 			}, | |
| 			required: { | |
| 				type: Boolean, | |
| 				default: false | |
| 			}, | |
| 			label: { | |
| 				type: String, | |
| 				default: '' | |
| 			}, | |
| 			// label的宽度 ,默认 80 | |
| 			labelWidth: { | |
| 				type: [String, Number], | |
| 				default: '' | |
| 			}, | |
| 			// label 居中方式,默认 left 取值 left/center/right | |
| 			labelAlign: { | |
| 				type: String, | |
| 				default: '' | |
| 			}, | |
| 			// 强制显示错误信息 | |
| 			errorMessage: { | |
| 				type: [String, Boolean], | |
| 				default: '' | |
| 			}, | |
| 			// 1.4.0 弃用,统一使用 form 的校验时机 | |
| 			// validateTrigger: { | |
| 			// 	type: String, | |
| 			// 	default: '' | |
| 			// }, | |
| 			// 1.4.0 弃用,统一使用 form 的label 位置 | |
| 			// labelPosition: { | |
| 			// 	type: String, | |
| 			// 	default: '' | |
| 			// }, | |
| 			// 1.4.0 以下属性已经废弃,请使用  #label 插槽代替 | |
| 			leftIcon: String, | |
| 			iconColor: { | |
| 				type: String, | |
| 				default: '#606266' | |
| 			}, | |
| 		}, | |
| 		data() { | |
| 			return { | |
| 				errMsg: '', | |
| 				isRequired: false, | |
| 				userRules: null, | |
| 				localLabelAlign: 'left', | |
| 				localLabelWidth: '65px', | |
| 				localLabelPos: 'left', | |
| 				border: false, | |
| 				isFirstBorder: false, | |
| 			}; | |
| 		}, | |
| 		computed: { | |
| 			// 处理错误信息 | |
| 			msg() { | |
| 				return this.errorMessage || this.errMsg; | |
| 			} | |
| 		}, | |
| 		watch: { | |
| 			// 规则发生变化通知子组件更新 | |
| 			'form.formRules'(val) { | |
| 				// TODO 处理头条vue3 watch不生效的问题 | |
| 				// #ifndef MP-TOUTIAO | |
| 				this.init() | |
| 				// #endif | |
| 			}, | |
| 			'form.labelWidth'(val) { | |
| 				// 宽度 | |
| 				this.localLabelWidth = this._labelWidthUnit(val) | |
|  | |
| 			}, | |
| 			'form.labelPosition'(val) { | |
| 				// 标签位置 | |
| 				this.localLabelPos = this._labelPosition() | |
| 			}, | |
| 			'form.labelAlign'(val) { | |
|  | |
| 			} | |
| 		}, | |
| 		created() { | |
| 			this.init(true) | |
| 			if (this.name && this.form) { | |
| 				// TODO 处理头条vue3 watch不生效的问题 | |
| 				// #ifdef MP-TOUTIAO | |
| 				this.$watch('form.formRules', () => { | |
| 					this.init() | |
| 				}) | |
| 				// #endif | |
|  | |
| 				// 监听变化 | |
| 				this.$watch( | |
| 					() => { | |
| 						const val = this.form._getDataValue(this.name, this.form.localData) | |
| 						return val | |
| 					}, | |
| 					(value, oldVal) => { | |
| 						const isEqual = this.form._isEqual(value, oldVal) | |
| 						// 简单判断前后值的变化,只有发生变化才会发生校验 | |
| 						// TODO  如果 oldVal = undefined ,那么大概率是源数据里没有值导致 ,这个情况不哦校验 ,可能不严谨 ,需要在做观察 | |
| 						// fix by mehaotian 暂时取消 && oldVal !== undefined ,如果formData 中不存在,可能会不校验 | |
| 						if (!isEqual) { | |
| 							const val = this.itemSetValue(value) | |
| 							this.onFieldChange(val, false) | |
| 						} | |
| 					}, { | |
| 						immediate: false | |
| 					} | |
| 				); | |
| 			} | |
|  | |
| 		}, | |
| 		// #ifndef VUE3 | |
| 		destroyed() { | |
| 			if (this.__isUnmounted) return | |
| 			this.unInit() | |
| 		}, | |
| 		// #endif | |
| 		// #ifdef VUE3 | |
| 		unmounted() { | |
| 			this.__isUnmounted = true | |
| 			this.unInit() | |
| 		}, | |
| 		// #endif | |
| 		methods: { | |
| 			/** | |
| 			 * 外部调用方法 | |
| 			 * 设置规则 ,主要用于小程序自定义检验规则 | |
| 			 * @param {Array} rules 规则源数据 | |
| 			 */ | |
| 			setRules(rules = null) { | |
| 				this.userRules = rules | |
| 				this.init(false) | |
| 			}, | |
| 			// 兼容老版本表单组件 | |
| 			setValue() { | |
| 				// console.log('setValue 方法已经弃用,请使用最新版本的 uni-forms 表单组件以及其他关联组件。'); | |
| 			}, | |
| 			/** | |
| 			 * 外部调用方法 | |
| 			 * 校验数据 | |
| 			 * @param {any} value 需要校验的数据 | |
| 			 * @param {boolean} 是否立即校验 | |
| 			 * @return {Array|null} 校验内容 | |
| 			 */ | |
| 			async onFieldChange(value, formtrigger = true) { | |
| 				const { | |
| 					formData, | |
| 					localData, | |
| 					errShowType, | |
| 					validateCheck, | |
| 					validateTrigger, | |
| 					_isRequiredField, | |
| 					_realName | |
| 				} = this.form | |
| 				const name = _realName(this.name) | |
| 				if (!value) { | |
| 					value = this.form.formData[name] | |
| 				} | |
| 				// fixd by mehaotian 不在校验前清空信息,解决闪屏的问题 | |
| 				// this.errMsg = ''; | |
|  | |
| 				// fix by mehaotian 解决没有检验规则的情况下,抛出错误的问题 | |
| 				const ruleLen = this.itemRules.rules && this.itemRules.rules.length | |
| 				if (!this.validator || !ruleLen || ruleLen === 0) return; | |
|  | |
| 				// 检验时机 | |
| 				// let trigger = this.isTrigger(this.itemRules.validateTrigger, this.validateTrigger, validateTrigger); | |
| 				const isRequiredField = _isRequiredField(this.itemRules.rules || []); | |
| 				let result = null; | |
| 				// 只有等于 bind 时 ,才能开启时实校验 | |
| 				if (validateTrigger === 'bind' || formtrigger) { | |
| 					// 校验当前表单项 | |
| 					result = await this.validator.validateUpdate({ | |
| 							[name]: value | |
| 						}, | |
| 						formData | |
| 					); | |
|  | |
| 					// 判断是否必填,非必填,不填不校验,填写才校验 ,暂时只处理 undefined  和空的情况 | |
| 					if (!isRequiredField && (value === undefined || value === '')) { | |
| 						result = null; | |
| 					} | |
|  | |
| 					// 判断错误信息显示类型 | |
| 					if (result && result.errorMessage) { | |
| 						if (errShowType === 'undertext') { | |
| 							// 获取错误信息 | |
| 							this.errMsg = !result ? '' : result.errorMessage; | |
| 						} | |
| 						if (errShowType === 'toast') { | |
| 							uni.showToast({ | |
| 								title: result.errorMessage || '校验错误', | |
| 								icon: 'none' | |
| 							}); | |
| 						} | |
| 						if (errShowType === 'modal') { | |
| 							uni.showModal({ | |
| 								title: '提示', | |
| 								content: result.errorMessage || '校验错误' | |
| 							}); | |
| 						} | |
| 					} else { | |
| 						this.errMsg = '' | |
| 					} | |
| 					// 通知 form 组件更新事件 | |
| 					validateCheck(result ? result : null) | |
| 				} else { | |
| 					this.errMsg = '' | |
| 				} | |
| 				return result ? result : null; | |
| 			}, | |
| 			/** | |
| 			 * 初始组件数据 | |
| 			 */ | |
| 			init(type = false) { | |
| 				const { | |
| 					validator, | |
| 					formRules, | |
| 					childrens, | |
| 					formData, | |
| 					localData, | |
| 					_realName, | |
| 					labelWidth, | |
| 					_getDataValue, | |
| 					_setDataValue | |
| 				} = this.form || {} | |
| 				// 对齐方式 | |
| 				this.localLabelAlign = this._justifyContent() | |
| 				// 宽度 | |
| 				this.localLabelWidth = this._labelWidthUnit(labelWidth) | |
| 				// 标签位置 | |
| 				this.localLabelPos = this._labelPosition() | |
| 				this.isRequired = this.required | |
| 				// 将需要校验的子组件加入form 队列 | |
| 				this.form && type && childrens.push(this) | |
|  | |
| 				if (!validator || !formRules) return | |
| 				// 判断第一个 item | |
| 				if (!this.form.isFirstBorder) { | |
| 					this.form.isFirstBorder = true; | |
| 					this.isFirstBorder = true; | |
| 				} | |
|  | |
| 				// 判断 group 里的第一个 item | |
| 				if (this.group) { | |
| 					if (!this.group.isFirstBorder) { | |
| 						this.group.isFirstBorder = true; | |
| 						this.isFirstBorder = true; | |
| 					} | |
| 				} | |
| 				this.border = this.form.border; | |
| 				// 获取子域的真实名称 | |
| 				const name = _realName(this.name) | |
| 				const itemRule = this.userRules || this.rules | |
| 				if (typeof formRules === 'object' && itemRule) { | |
| 					// 子规则替换父规则 | |
| 					formRules[name] = { | |
| 						rules: itemRule | |
| 					} | |
| 					validator.updateSchema(formRules); | |
| 				} | |
| 				// 注册校验规则 | |
| 				const itemRules = formRules[name] || {} | |
| 				this.itemRules = itemRules | |
| 				// 注册校验函数 | |
| 				this.validator = validator | |
| 				// 默认值赋予 | |
| 				this.itemSetValue(_getDataValue(this.name, localData)) | |
| 				this.isRequired = this._isRequired() | |
|  | |
| 			}, | |
| 			unInit() { | |
| 				if (this.form) { | |
| 					const { | |
| 						childrens, | |
| 						formData, | |
| 						_realName | |
| 					} = this.form | |
| 					childrens.forEach((item, index) => { | |
| 						if (item === this) { | |
| 							this.form.childrens.splice(index, 1) | |
| 							delete formData[_realName(item.name)] | |
| 						} | |
| 					}) | |
| 				} | |
| 			}, | |
| 			// 设置item 的值 | |
| 			itemSetValue(value) { | |
| 				const name = this.form._realName(this.name) | |
| 				const rules = this.itemRules.rules || [] | |
| 				const val = this.form._getValue(name, value, rules) | |
| 				this.form._setDataValue(name, this.form.formData, val) | |
| 				return val | |
| 			}, | |
|  | |
| 			/** | |
| 			 * 移除该表单项的校验结果 | |
| 			 */ | |
| 			clearValidate() { | |
| 				this.errMsg = ''; | |
| 			}, | |
|  | |
| 			// 是否显示星号 | |
| 			_isRequired() { | |
| 				// TODO 不根据规则显示 星号,考虑后续兼容 | |
| 				// if (this.form) { | |
| 				// 	if (this.form._isRequiredField(this.itemRules.rules || []) && this.required) { | |
| 				// 		return true | |
| 				// 	} | |
| 				// 	return false | |
| 				// } | |
| 				return this.required | |
| 			}, | |
|  | |
| 			// 处理对齐方式 | |
| 			_justifyContent() { | |
| 				if (this.form) { | |
| 					const { | |
| 						labelAlign | |
| 					} = this.form | |
| 					let labelAli = this.labelAlign ? this.labelAlign : labelAlign; | |
| 					if (labelAli === 'left') return 'flex-start'; | |
| 					if (labelAli === 'center') return 'center'; | |
| 					if (labelAli === 'right') return 'flex-end'; | |
| 				} | |
| 				return 'flex-start'; | |
| 			}, | |
| 			// 处理 label宽度单位 ,继承父元素的值 | |
| 			_labelWidthUnit(labelWidth) { | |
|  | |
| 				// if (this.form) { | |
| 				// 	const { | |
| 				// 		labelWidth | |
| 				// 	} = this.form | |
| 				return this.num2px(this.labelWidth ? this.labelWidth : (labelWidth || (this.label ? 65 : 'auto'))) | |
| 				// } | |
| 				// return '65px' | |
| 			}, | |
| 			// 处理 label 位置 | |
| 			_labelPosition() { | |
| 				if (this.form) return this.form.labelPosition || 'left' | |
| 				return 'left' | |
|  | |
| 			}, | |
|  | |
| 			/** | |
| 			 * 触发时机 | |
| 			 * @param {Object} rule 当前规则内时机 | |
| 			 * @param {Object} itemRlue 当前组件时机 | |
| 			 * @param {Object} parentRule 父组件时机 | |
| 			 */ | |
| 			isTrigger(rule, itemRlue, parentRule) { | |
| 				//  bind  submit | |
| 				if (rule === 'submit' || !rule) { | |
| 					if (rule === undefined) { | |
| 						if (itemRlue !== 'bind') { | |
| 							if (!itemRlue) { | |
| 								return parentRule === '' ? 'bind' : 'submit'; | |
| 							} | |
| 							return 'submit'; | |
| 						} | |
| 						return 'bind'; | |
| 					} | |
| 					return 'submit'; | |
| 				} | |
| 				return 'bind'; | |
| 			}, | |
| 			num2px(num) { | |
| 				if (typeof num === 'number') { | |
| 					return `${num}px` | |
| 				} | |
| 				return num | |
| 			} | |
| 		} | |
| 	}; | |
| </script> | |
|  | |
| <style lang="scss"> | |
| 	.uni-forms-item { | |
| 		position: relative; | |
| 		display: flex; | |
| 		/* #ifdef APP-NVUE */ | |
| 		// 在 nvue 中,使用 margin-bottom error 信息会被隐藏 | |
| 		padding-bottom: 22px; | |
| 		/* #endif */ | |
| 		/* #ifndef APP-NVUE */ | |
| 		margin-bottom: 22px; | |
| 		/* #endif */ | |
| 		flex-direction: row; | |
| 
 | |
| 		&__label { | |
| 			display: flex; | |
| 			flex-direction: row; | |
| 			align-items: center; | |
| 			text-align: left; | |
| 			font-size: 14px; | |
| 			color: #606266; | |
| 			height: 36px; | |
| 			padding: 0 12px 0 0; | |
| 			/* #ifndef APP-NVUE */ | |
| 			vertical-align: middle; | |
| 			flex-shrink: 0; | |
| 			/* #endif */ | |
| 
 | |
| 			/* #ifndef APP-NVUE */ | |
| 			box-sizing: border-box; | |
| 
 | |
| 			/* #endif */ | |
| 			&.no-label { | |
| 				padding: 0; | |
| 			} | |
| 		} | |
| 
 | |
| 		&__content { | |
| 			/* #ifndef MP-TOUTIAO */ | |
| 			// display: flex; | |
| 			// align-items: center; | |
| 			/* #endif */ | |
| 			position: relative; | |
| 			font-size: 14px; | |
| 			flex: 1; | |
| 			/* #ifndef APP-NVUE */ | |
| 			box-sizing: border-box; | |
| 			/* #endif */ | |
| 			flex-direction: row; | |
| 
 | |
| 			/* #ifndef APP || H5 || MP-WEIXIN || APP-NVUE */ | |
| 			// TODO 因为小程序平台会多一层标签节点 ,所以需要在多余节点继承当前样式 | |
| 			&>uni-easyinput, | |
| 			&>uni-data-picker { | |
| 				width: 100%; | |
| 			} | |
| 
 | |
| 			/* #endif */ | |
| 
 | |
| 		} | |
| 
 | |
| 		& .uni-forms-item__nuve-content { | |
| 			display: flex; | |
| 			flex-direction: column; | |
| 			flex: 1; | |
| 		} | |
| 
 | |
| 		&__error { | |
| 			color: #f56c6c; | |
| 			font-size: 12px; | |
| 			line-height: 1; | |
| 			padding-top: 4px; | |
| 			position: absolute; | |
| 			/* #ifndef APP-NVUE */ | |
| 			top: 100%; | |
| 			left: 0; | |
| 			transition: transform 0.3s; | |
| 			transform: translateY(-100%); | |
| 			/* #endif */ | |
| 			/* #ifdef APP-NVUE */ | |
| 			bottom: 5px; | |
| 			/* #endif */ | |
| 
 | |
| 			opacity: 0; | |
| 
 | |
| 			.error-text { | |
| 				// 只有 nvue 下这个样式才生效 | |
| 				color: #f56c6c; | |
| 				font-size: 12px; | |
| 			} | |
| 
 | |
| 			&.msg--active { | |
| 				opacity: 1; | |
| 				transform: translateY(0%); | |
| 			} | |
| 		} | |
| 
 | |
| 		// 位置修饰样式 | |
| 		&.is-direction-left { | |
| 			flex-direction: row; | |
| 		} | |
| 
 | |
| 		&.is-direction-top { | |
| 			flex-direction: column; | |
| 
 | |
| 			.uni-forms-item__label { | |
| 				padding: 0 0 8px; | |
| 				line-height: 1.5715; | |
| 				text-align: left; | |
| 				/* #ifndef APP-NVUE */ | |
| 				white-space: initial; | |
| 				/* #endif */ | |
| 			} | |
| 		} | |
| 
 | |
| 		.is-required { | |
| 			// color: $uni-color-error; | |
| 			color: #dd524d; | |
| 			font-weight: bold; | |
| 		} | |
| 	} | |
| 
 | |
| 
 | |
| 	.uni-forms-item--border { | |
| 		margin-bottom: 0; | |
| 		padding: 10px 0; | |
| 		// padding-bottom: 0; | |
| 		border-top: 1px #eee solid; | |
| 
 | |
| 		/* #ifndef APP-NVUE */ | |
| 		.uni-forms-item__content { | |
| 			flex-direction: column; | |
| 			justify-content: flex-start; | |
| 			align-items: flex-start; | |
| 
 | |
| 			.uni-forms-item__error { | |
| 				position: relative; | |
| 				top: 5px; | |
| 				left: 0; | |
| 				padding-top: 0; | |
| 			} | |
| 		} | |
| 
 | |
| 		/* #endif */ | |
| 
 | |
| 		/* #ifdef APP-NVUE */ | |
| 		display: flex; | |
| 		flex-direction: column; | |
| 
 | |
| 		.uni-forms-item__error { | |
| 			position: relative; | |
| 			top: 0px; | |
| 			left: 0; | |
| 			padding-top: 0; | |
| 			margin-top: 5px; | |
| 		} | |
| 
 | |
| 		/* #endif */ | |
| 
 | |
| 	} | |
| 
 | |
| 	.is-first-border { | |
| 		/* #ifndef APP-NVUE */ | |
| 		border: none; | |
| 		/* #endif */ | |
| 		/* #ifdef APP-NVUE */ | |
| 		border-width: 0; | |
| 		/* #endif */ | |
| 	} | |
| </style>
 | |
| 
 |