123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- <template>
- <view
- class="tn-form-item-class tn-form-item"
- :class="{
- 'tn-border-solid-bottom': elBorderBottom,
- 'tn-form-item__border-bottom--error': validateState === 'error' && showError('border-bottom')
- }"
- >
- <view
- class="tn-form-item__body"
- :style="{
- flexDirection: elLabelPosition == 'left' ? 'row' : 'column'
- }"
- >
- <!-- 处理微信小程序中设置属性的问题,不设置值的时候会变成true -->
- <view
- class="tn-form-item--left"
- :style="{
- width: wLabelWidth,
- flex: `0 0 ${wLabelWidth}`,
- marginBottom: elLabelPosition == 'left' ? 0 : '10rpx'
- }"
- >
- <!-- 块对齐 -->
- <view v-if="required || leftIcon || label" class="tn-form-item--left__content"
- :style="[leftContentStyle]"
- >
- <!-- nvue不支持伪元素before -->
- <view v-if="leftIcon" class="tn-form-item--left__content__icon">
- <view :class="[`tn-icon-${leftIcon}`]" :style="leftIconStyle"></view>
- </view>
- <!-- <view
- class="tn-form-item--left__content__label"
- :style="[elLabelStyle, {
- 'justify-content': elLabelAlign === 'left' ? 'flex-satrt' : elLabelAlign === 'center' ? 'center' : 'flex-end'
- }]"
- >
- {{label}}
- </view> -->
- <view
- class="tn-form-item--left__content__label"
- :style="[elLabelStyle]"
- >
- {{label}}
- </view>
- <text v-if="required" class="tn-form-item--left__content--required">*</text>
- </view>
- </view>
-
- <view class="tn-form-item--right tn-flex">
- <view class="tn-form-item--right__content">
- <view class="tn-form-item--right__content__slot">
- <slot></slot>
- </view>
- <view v-if="$slots.right || rightIcon" class="tn-form-item--right__content__icon tn-flex">
- <view v-if="rightIcon" :class="[`tn-icon-${rightIcon}`]" :style="rightIconStyle"></view>
- <slot name="right"></slot>
- </view>
- </view>
- </view>
- </view>
-
- <view
- v-if="validateState === 'error' && showError('message')"
- class="tn-form-item__message"
- :style="{
- paddingLeft: elLabelPosition === 'left' ? elLabelWidth + 'rpx' : '0'
- }"
- >
- {{validateMessage}}
- </view>
- </view>
- </template>
- <script>
- import Emitter from '../../libs/utils/emitter.js'
- import schema from '../../libs/utils/async-validator.js'
- // 去除警告信息
- schema.warning = function() {}
-
- export default {
- mixins: [Emitter],
- name: 'tn-form-item',
- inject: {
- tnForm: {
- default() {
- return null
- }
- }
- },
- props: {
- // label提示语
- label: {
- type: String,
- default: ''
- },
- // 绑定的值
- prop: {
- type: String,
- default: ''
- },
- // 是否显示表单域的下划线边框
- borderBottom: {
- type:Boolean,
- default: true
- },
- // label(标签名称)的位置
- // left - 左边
- // top - 上边
- labelPosition: {
- type: String,
- default: ''
- },
- // label的宽度
- labelWidth: {
- type: Number,
- default: 0
- },
- // label的对齐方式
- // left - 左对齐
- // top - 上对齐
- // right - 右对齐
- // bottom - 下对齐
- labelAlign: {
- type: String,
- default: ''
- },
- // label 的样式
- labelStyle: {
- type: Object,
- default() {
- return {}
- }
- },
- // 左侧图标
- leftIcon: {
- type: String,
- default: ''
- },
- // 右侧图标
- rightIcon: {
- type: String,
- default: ''
- },
- // 左侧图标样式
- leftIconStyle: {
- type: Object,
- default() {
- return {}
- }
- },
- // 右侧图标样式
- rightIconStyle: {
- type: Object,
- default() {
- return {}
- }
- },
- // 是否显示必填项的*,不做校验用途
- required: {
- type: Boolean,
- default: false
- }
- },
- computed: {
- // 处理微信小程序label的宽度
- wLabelWidth() {
- // 如果用户设置label为空字符串(微信小程序空字符串最终会变成字符串的'true'),意味着要将label的位置宽度设置为auto
- return this.elLabelPosition === 'left' ? (this.label === 'true' || this.label === '' ? 'auto' : this.elLabelWidth + 'rpx') : '100%'
- },
- // 是否显示错误提示
- showError() {
- return type => {
- if (this.errorType.indexOf('none') >= 0) return false
- else if (this.errorType.indexOf(type) >= 0) return true
- else return false
- }
- },
- // label的宽度(默认值为90)
- elLabelWidth() {
- return this.labelWidth != 0 ? this.labelWidth : (this.parentData.labelWidth != 0 ? this.parentData.labelWidth : 90)
- },
- // label的样式
- elLabelStyle() {
- return Object.keys(this.labelStyle).length ? this.labelStyle : (Object.keys(this.parentData.labelStyle).length ? this.parentData.labelStyle : {})
- },
- // label显示位置
- elLabelPosition() {
- return this.labelPosition ? this.labelPosition : (this.parentData.labelPosition ? this.parentData.labelPosition : 'left')
- },
- // label对齐方式
- elLabelAlign() {
- return this.labelAlign ? this.labelAlign : (this.parentData.labelAlign ? this.parentData.labelAlign : 'left')
- },
- // label下划线
- elBorderBottom() {
- return this.borderBottom !== '' ? this.borderBottom : (this.parentData.borderBottom !== '' ? this.parentData.borderBottom : true)
- },
- leftContentStyle() {
- let style = {}
- if (this.elLabelPosition === 'left') {
- switch(this.elLabelAlign) {
- case 'left':
- style.justifyContent = 'flex-start'
- break
- case 'center':
- style.justifyContent = 'center'
- break
- default:
- style.justifyContent = 'flex-end'
- break
- }
- }
-
- return style
- }
- },
- data() {
- return {
- // 默认值
- initialValue: '',
- // 是否校验成功
- validateState: '',
- // 校验失败提示信息
- validateMessage: '',
- // 错误的提示方式(参考form组件)
- errorType: ['message'],
- // 当前子组件输入的值
- fieldValue: '',
- // 父组件的参数
- // 由于再computed中无法得知this.parent的变化,所以放在data中
- parentData: {
- borderBottom: true,
- labelWidth: 90,
- labelPosition: 'left',
- labelAlign: 'left',
- labelStyle: {},
- }
- }
- },
- watch: {
- validateState(val) {
- this.broadcastInputError()
- },
- "tnForm.errorType"(val) {
- this.errorType = val
- this.broadcastInputError()
- }
- },
- mounted() {
- // 组件创建完成后,保存当前实例到form组件中
- // 支付宝、头条小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用\
- this.parent = this.$t.$parent.call(this, 'tn-form')
- if (this.parent) {
- // 遍历parentData属性,将parent中同名的属性赋值给parentData
- Object.keys(this.parentData).map(key => {
- this.parentData[key] = this.parent[key]
- })
- // 如果没有传入prop或者tnForm为空(单独使用form-item组件的时候),就不进行校验
- if (this.prop) {
- // 将本实例添加到父组件中
- this.parent.fields.push(this)
- this.errorType = this.parent.errorType
- // 设置初始值
- this.initialValue = this.fieldValue
- // 添加表单校验,这里必须要写在$nextTick中,因为tn-form的rules是通过ref手动传入的
- // 不在$nextTick中的话,可能会造成执行此处代码时,父组件还没通过ref把规则给tn-form,导致规则为空
- this.$nextTick(() => {
- this.setRules()
- })
- }
- }
- },
- beforeDestroy() {
- // 组件销毁前,将实例从tn-form的缓存中移除
- // 如果当前没有prop的话表示当前不进行删除
- if (this.parent && this.prop) {
- this.parent.fields.map((item, index) => {
- if (item === this) this.parent.fields.splice(index, 1)
- })
- }
- },
- methods: {
- // 向input组件发出错误事件
- broadcastInputError() {
- this.broadcast('tn-input', 'on-form-item-error', this.validateState === 'error' && this.showError('border'))
- },
- // 设置校验规则
- setRules() {
- let that = this
- // 从父组件tn-form拿到当前tn-form-item需要验证 的规则
- // let rules = this.getRules()
- // if (rules.length) {
- // this.isRequired = rules.some(rule => {
- // // 如果有必填项,就返回,没有的话,就是undefined
- // return rule.required
- // })
- // }
-
- // blur事件
- this.$on('on-form-blur', that.onFieldBlur)
- // change事件
- this.$on('on-form-change', that.onFieldChange)
- },
- // 从form的rules属性中取出当前form-item的校验规则
- getRules() {
- let rules = this.parent.rules
- rules = rules ? rules[this.prop] : []
-
- // 返回数值形式的值
- return [].concat(rules || [])
- },
- // blur事件时进行表单认证
- onFieldBlur() {
- this.validation('blur')
- },
- // change事件时进行表单认证
- onFieldChange() {
- this.validation('change')
- },
- // 过滤出符合要求的rule规则
- getFilterRule(triggerType = '') {
- let rules = this.getRules()
- // 整体验证表单时,triggerType为空字符串,此时返回所有规则进行验证
- if (!triggerType) return rules
- // 某些场景可能的判断规则,可能不存在trigger属性,故先判断是否存在此属性
- // 历遍判断规则是否有对应的事件,比如blur,change触发等的事件
- // 使用indexOf判断,是因为某些时候设置的验证规则的trigger属性可能为多个,比如['blur','change']
- return rules.filter(rule => rule.trigger && rule.trigger.indexOf(triggerType) !== -1)
- },
- // 校验数据
- validation(trigger, callback = ()=>{}) {
- // 校验之前先获取需要校验的值
- this.fieldValue = this.parent.model[this.prop]
- // blur和change是否有当前方式的校验规则
- let rules = this.getFilterRule(trigger)
- // 判断是否有验证规则,如果没有规则,也调用回调方法,否则父组件tn-form会因为
- // 对count变量的统计错误而无法进入上一层的回调
- if (!rules || rules.length === 0) {
- return callback('')
- }
- // 设置当前为校验中
- this.validateState = 'validating'
- // 调用async-validator的方法
- let validator = new schema({
- [this.prop]: rules
- })
- validator.validate({
- [this.prop]: this.fieldValue
- }, {
- firstFields: true
- }, (errors, fields) => {
- // 记录状态和报错信息
- this.validateState = !errors ? 'success' : 'error'
- this.validateMessage = errors ? errors[0].message : ''
-
- callback(this.validateMessage)
- })
- },
-
- // 清空当前item信息
- resetField() {
- this.parent.model[this.prop] = this.initialValue
- // 清空错误标记
- this.validateState = 'success'
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .tn-form-item {
- display: flex;
- flex-direction: column;
- padding: 20rpx 0;
- font-size: 28rpx;
- color: $tn-font-color;
- box-sizing: border-box;
- line-height: $tn-form-item-height;
-
- &__border-bottom--error:after {
- border-color: $tn-color-red;
- }
-
- &__body {
- display: flex;
- flex-direction: row;
- }
-
- &--left {
- display: flex;
- flex-direction: row;
- align-items: center;
-
- &__content {
- display: flex;
- flex-direction: row;
- position: relative;
- align-items: center;
- padding-right: 18rpx;
- flex: 1;
-
- &--required {
- position: relative;
- right: 0;
- vertical-align: middle;
- color: $tn-color-red;
- }
-
- &__icon {
- color: $tn-font-sub-color;
- margin-right: 8rpx;
- }
-
- &__label {
- // display: flex;
- // flex-direction: row;
- // align-items: center;
- // flex: 1;
- }
- }
- }
-
- &--right {
- flex: 1;
-
- &__content {
- display: flex;
- flex-direction: row;
- align-items: center;
- flex: 1;
-
- &__slot {
- flex: 1;
- /* #ifndef MP */
- display: flex;
- flex-direction: row;
- align-items: center;
- /* #endif */
- }
-
- &__icon {
- margin-left: 10rpx;
- color: $tn-font-sub-color;
- font-size: 30rpx;
- }
- }
- }
-
- &__message {
- font-size: 24rpx;
- line-height: 24rpx;
- color: $tn-color-red;
- margin-top: 12rpx;
- }
- }
- </style>
|