|
|
<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: { // #ifdef MP-TOUTIAO
virtualHost: false, // #endif
// #ifndef MP-TOUTIAO
virtualHost: true // #endif
}, 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>
|