| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 | <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>
 |