在表单label栏目实现标题序号、红色标示语、扩展项。效果图如下:
扩展项与序号:
点击规范后:
红色标示语:
先贴上需要修改的文件form-item.vue的代码:
<template>
<div class="cube-form-item border-bottom-1px" ref="formItem" :class="itemClass">
<template v-if="!isBtnField">
<!-- label块 -->
<slot name="label">
<div class="cube-form-label" v-show="fieldValue.label"><span>{{fieldValue.label}}</span></div>
</slot>
<!-- 验证模块 -->
<cube-validator
class="cube-form-field"
v-if="hasRules"
ref="validator"
v-model="originValid"
:disabled="validatorDisabled"
:model="validatorModel"
:model-key="validatorModelKey"
:rules="fieldValue.rules"
:messages="fieldValue.messages"
@input="validatorChangeHandler"
@validating="validatingHandler"
@validated="validatedHandler"
@msg-click="msgClick"
>
<!-- 开启验证下的组件 -->
<slot>
<component :is="componentName" v-model="modelValue" v-bind="fieldValue.props" v-on="fieldValue.events"></component>
</slot>
</cube-validator>
<!-- 不开启验证下的组件 -->
<div class="cube-form-field" v-else>
<slot>
<component :is="componentName" v-model="modelValue" v-bind="fieldValue.props" v-on="fieldValue.events"></component>
</slot>
</div>
</template>
<!-- 按钮标题 -->
<cube-button v-bind="fieldValue.props" v-on="fieldValue.events" v-else>{{fieldValue.label}}</cube-button>
</div>
</template>
<script>
import { processField } from './fields/index'
import { resetTypeValue, cb2PromiseWithResolve, debounce } from '../../common/helpers/util'
import CubeValidator from '../validator/validator.vue'
import LAYOUTS from './layouts'
import { getResetValueByType } from './fields/reset'
import mixin from './mixin'
import components from './components'
components.CubeValidator = CubeValidator
const COMPONENT_NAME = 'cube-form-item'
const EVENT_FOCUSIN = 'focusin'
const EVENT_FOCUSOUT = 'focusout'
export default {
name: COMPONENT_NAME,
mixins: [mixin],
props: {
field: {
type: Object,
default() {
/* istanbul ignore next */
return {}
}
}
},
data() {
const validatorModelKey = 'value'
const modelKey = this.field.modelKey
const modelValue = modelKey ? this.form.model[modelKey] : null
return {
validatorDisabled: false,
validatorModelKey,
modelValue: modelValue,
validatorModel: {
[validatorModelKey]: modelValue
}
}
},
computed: {
fieldValue() {
return processField(this.field)
},
hasRules() {
return Object.keys(this.fieldValue.rules || {}).length > 0
},
isBtnField() {
return this.fieldValue.type === 'button'
},
itemClass() {
const rules = this.fieldValue.rules
return {
// only handle required rule for now
'cube-form-item_required': rules && rules.required,
'cube-form-item_btn': this.isBtnField,
'cube-form-item_validating': this.validating,
'cube-form-item_pending': this.pending,
'cube-form-item_valid': this.valid,
'cube-form-item_invalid': this.invalid
}
},
modelVal() {
return this.form.model[this.fieldValue.modelKey]
},
componentName() {
const fieldValue = this.fieldValue
const component = fieldValue.component
if (component) {
return component
}
const type = fieldValue.type
const cubeType = `cube-${type}`
if (components[cubeType]) {
return cubeType
}
return type
}
},
watch: {
modelVal(newModel) {
if (this.modelValue !== newModel) {
this.modelValue = newModel
}
},
modelValue: {
handler(newModel) {
// update form model
this.form.model[this.fieldValue.modelKey] = newModel
this.updateValidatorModel()
},
sync: true
},
originValid(newVal) {
this.lastOriginValid = newVal
}
},
beforeCreate() {
this.form = this.$parent.form
},
created() {
this.form.addField(this)
this.getValidatorModel = (modelValue) => {
this.pending = false
return modelValue
}
},
mounted() {
this.initDebounce()
this.initFocusEvents()
},
methods: {
initDebounce() {
let debounceTime = this.fieldValue.debounce
if (debounceTime === true) {
debounceTime = 200
}
if ((!debounceTime && debounceTime !== 0) || debounceTime < 0 || this.fieldValue.trigger === 'blur') return
this.getValidatorModel = debounce((modelValue) => {
this.syncValidatorValue()
// this.validate()
return modelValue
}, debounceTime, false, this.validatorModel[this.validatorModelKey])
},
focusInHandler() {
this.focused = true
},
focusOutHandler() {
this.focused = false
this.updateValidatorModel()
// this.validate()
},
initFocusEvents() {
if (this.fieldValue.trigger === 'blur') {
const formItem = this.$refs.formItem
formItem.addEventListener(EVENT_FOCUSIN, this.focusInHandler, false)
formItem.addEventListener(EVENT_FOCUSOUT, this.focusOutHandler, false)
this.getValidatorModel = (modelValue) => {
if (this.focused) {
return this.validatorModel[this.validatorModelKey]
} else {
this.pending = false
this.form.updatePending()
return modelValue
}
}
}
},
removeFocusEvents() {
const formItem = this.$refs.formItem
formItem.removeEventListener(EVENT_FOCUSIN, this.focusInHandler, false)
formItem.removeEventListener(EVENT_FOCUSOUT, this.focusOutHandler, false)
},
updateValidatorModel() {
this.pending = true
this.validatorModel[this.validatorModelKey] = this.getValidatorModel(this.modelValue)
if (this.pending) {
this.form.setPending(this.pending)
this.originValid = undefined
}
},
syncValidatorValue() {
this.pending = false
this.validatorModel[this.validatorModelKey] = this.modelValue
this.form.updatePending()
},
validatorChangeHandler() {
// disabled or true to true no update validity
if (this.validatorDisabled || (this.originValid && this.lastOriginValid)) {
return
}
this.updateValidity()
},
validatingHandler() {
this.validating = true
this.form.setValidating(true)
},
validatedHandler() {
this.validating = false
this.form.updateValidating()
},
updateValidity() {
const validator = this.$refs.validator
if (validator) {
// sync update validaty
this.form.updateValidity(this.fieldValue.modelKey, validator.valid, validator.result, validator.dirty)
}
},
validate(cb) {
const promise = cb2PromiseWithResolve(cb)
if (promise) {
cb = promise.resolve
}
const validator = this.$refs.validator
if (validator) {
validator.validate(() => {
this.validatorDisabled = true
this.updateValidity()
cb && cb()
this.$nextTick(() => {
this.validatorDisabled = false
})
})
} else {
cb && cb()
}
return promise
},
reset() {
const fieldValue = this.fieldValue
if (fieldValue.modelKey) {
const defValue = getResetValueByType(fieldValue.type)
this.validatorDisabled = true
resetTypeValue(this, 'modelValue', defValue)
// need to sync validator value too, because of trigger blur or debounce
this.syncValidatorValue()
this.$refs.validator && this.$refs.validator.reset()
this.$nextTick(() => {
this.validatorDisabled = false
})
}
this.validating = false
this.pending = false
},
msgClick() {
/* istanbul ignore if */
if (this.form.layout !== LAYOUTS.STANDARD) {
return
}
/* istanbul ignore next */
this.$createToast && this.$createToast({
type: 'warn',
txt: this.$refs.validator.msg,
time: 1000
}).show()
}
},
beforeDestroy() {
this.removeFocusEvents()
this.form.destroyField(this)
this.form = null
},
components
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@require "../../common/stylus/variable.styl"
@require "../../common/stylus/mixin.styl"
.cube-form-item
position: relative
display: flex
align-items: center
padding: 0 15px
&:last-child
&::after
display: none
.cube-checkbox-group, .cube-radio-group
background-color: transparent
.cube-checkbox, .cube-radio
padding-left: 0
padding-right: 0
.cube-form-item_btn
margin: 15px 0
&::after
display: none
.cube-form-label
display: flex
align-items: center
word-wrap: break-word
word-break: break-word
.cube-form-item_required
.cube-form-label
&::before
content: "*"
display: block
margin-top: 1px
margin-right: .3em
color: $form-label-required-color
</style>
1.先实现标题序号的功能,我们可以通过css来实现,在babel那里加一个伪类放在标题前面,然后通过在cube-form-item这个类名的css中添加counter-increment属性递增一个计数器值,得到标题的下标,然后在伪类上使用content将下标显示出来。为了控制是否显示,还需要传入一个属性来控制,我这边是定义了一个showNumber 类型为boolean,默认为false,不显示,这个属性需要在form.vue 和 form-group.vue 中一层层传递过来。
showNumber: {
type: Boolean,
default () {
return false
}
},
在标题部分修改代码,定义一个显示序号的class名,我是定为cube-form-title,当showNumber为true时,显示序号:
<div class="cube-form-label" v-show="fieldValue.label">
<span :class="showNumber? 'cube-form-title': ''">{{fieldValue.label}}</span>
</div>
因为表单项是遍历出来的,所以可以在cube-form-item中添加一个counter-increment,定一个变量cube-form-index用来记录下标:
.cube-form-item
counter-increment: cube-form-index
position: relative
display: flex
align-items: center
padding: 0 15px
然后添加一个css
.cube-form-title
&::before
content: counter(cube-form-index) '.'
order: -1
white-space: nowrap
margin-right: 10px
添加序号的功能就实现了。
2.添加红色标语,因为有字体的颜色改动,所以为了方便,我们将标题从text改为html就可以直接传入标签。
<div class="cube-form-label" v-show="fieldValue.label">
<span :class="showNumber? 'cube-form-title': ''" v-html="fieldValue.label"></span>
</div>
使用的时候就可以直接传入html格式了
{
type: 'select',
modelKey: 'baseHsjg',
label: '核实结果 <span style="color:red">(此项为核实人的状态)</span>',
props: {
options: []
},
rules: {
required: true
}
},
3.扩展项,由于业务上是点击按钮后出弹窗显示一个提示页面,但是经过考虑,为了更灵活,决定不只使用button,而是使用component标签实现,由于在做红色标语的时候使用v-html 会导致标签里面的元素会被覆盖,所以要改动一下结构,在里面再加一个行内元素标签span用于装label,然后使用slot和component标签实现一个自定义组件。为了接收label的组件属性、事件、对象,所以要定义一些参数用于控制自定义组件,我定的是labelComponent、labelProps、labelEvents:
<slot name="label">
<div class="cube-form-label" v-show="fieldValue.label">
<span :class="showNumber? 'cube-form-title': ''">
<span v-html="fieldValue.label"></span>
<slot>
<component :is="fieldLabelComponent" v-bind="fieldValue.labelProps" v-on="fieldValue.labelEvents"></component>
</slot>
</span>
</div>
</slot>
还需要判断这个组件是否有传入,所以我将他封装成一个函数,判断后return
/*
* 判断LabelComponent属性是否有效
*/
fieldLabelComponent () {
const fieldValue = this.fieldValue
const component = fieldValue.labelComponent
if (component) {
return component
} else {
return false
}
}
使用的时候的schema:
{
type: 'select',
modelKey: `modelKey`,
label: '核实结果',
labelComponent: popUpButton,
labelProps: {
data: '暂无',
value: '规范',
title: '暂无',
styles: 'color:red;padding-left:10px',
type: 'text',
click: this.showLawerBtn
},
props: {
},
rules: {
required: true
}
}
labelProps 是回传到自定义组件 popUpButton 上的属性,传的是什么由popUpButton自定义组件决定。