| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 | <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 && !required}"				:style="{width:localLabelWidth,justifyContent: localLabelAlign}">				<text v-if="required" 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(默认70)	 * @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的宽度			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: '',				userRules: null,				localLabelAlign: 'left',				localLabelWidth: '70px',				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()				// 将需要校验的子组件加入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))			},			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 ? 70 : 'auto')))				// }				// return '70px'			},			// 处理 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>
 |