小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
TIP 👉 疾风知劲草,岁寒见后凋。范晔《后汉书》
前言
在我们日常项目开发中,我们经常会写一些表单项,所以封装了这款表单项组件。表单项组件
属性
1. columns 总分栏数
- 值为数值类型
- 默认值: 12
2. size 表单项大小,即表单项所占的分栏数
- 值为数值类型(数值范围:1 到 columns 的值)
- 默认值: 4
- 说明: 当 columns 为 12 时,size值范围:1 到 12;当 columns 为24时,size值范围:1 到 24; 当 columns 为 12 时,size为 12,表示该表单项占一整行;当 columns 为 24 时,size为 24,表示该表单项占一整行; 当 columns 为 12 时,如果想要一行布局 3 项, 则 size 应设置为 4 ( 12 / 3 = 4 )
3. required 表单项标签是否显示必填样式
- 值为布尔类型
- 默认值:false
4. label 表单项标签名称
- 值为字符串类型
- 默认值:""
- 说明:当有label类型的插槽时忽略此设置
5. labelSize 表单项标签长度
- 值为数值类型
- 默认值:5,表示5个字符的长度
6. labelColon 表单项标签名称后是否加冒号
- 值为布尔类型
- 默认值:true
7. error 错误信息数组
- 值为字符串数组类型
- 示例:["{{label}}不能为空", "手机号码格式错误"]
- 说明:当错误信息不为空时,表单项右侧会出现错误图标,鼠标悬浮在错误图标上时会显示错误信息
插槽
1. 默认插槽
- 默认插槽用于插入表单项输入框、下拉框、日期选择器等组件 示例:
<FormItem label="地址">
<input v-model="bill.address">
</FormItem>
2. label插槽
- label 插槽用于自定义表单项标签 示例:
<FormItem label="地址">
<template slot="label">
<Icon name="address"></Icon>
<span>地址:</span>
</template>
<input v-model="bill.address">
</FormItem>
实现FormItem.vue
<template>
<div class="form-item" :class="formItemClass" :style="formItemStyle">
<label class="form-item-label" :class="'label-size-' + labelSize">
<slot name="label"><i v-if="required" class="required">*</i><span>{{label}}{{labelColon ? ':' : ''}}</span></slot>
</label>
<div class="form-item-value"><slot name="default"></slot></div>
<div class="error">
<div class="error-wrap" v-if="error && error.length > 0">
<div class="icon-wrapper"><Icon name="warn"></Icon></div>
<div class="tip-wrapper"><div class="tip" v-html="errorMsg"></div></div>
</div>
</div>
</div>
</template>
<script>
// 错误信息中 {{label}} 格式参数的正则
const labelParamReg = /{{label}}/g
export default {
name: 'FormItem',
props: {
// 总分栏数
columns: {
type: Number,
default: 12
},
/** 表单项大小
* 1) 当 columns 为 12 时,size值范围:1 到 12;当 columns 为24时,size值范围:1 到 24;
* 2) 当 columns 为 12 时,size为 12,表示该表单项占一整行;当 columns 为 24 时,size为 24,表示该表单项占一整行
* 3) 当 columns 为 12 时,如果想要一行布局 3 项, 则 size 应设置为 4 ( 12 / 3 = 4 )
*/
size: {
type: Number,
default: 4
},
// 表单项标签是否显示必填样式
required: {
type: Boolean,
default: false
},
// 表单项标签名称
label: String,
// 表单项标签长度(默认值:5,表示5个字符的宽度)
labelSize: {
type: Number,
default: 5
},
// 标签名称后是否加冒号
labelColon: {
type: Boolean,
default: true
},
/* 错误信息 */
error: Array
},
computed: {
// 错误信息html字符串(多个错误信息用<br>换行)
errorMsg () {
let msg = ''
if (this.error && this.error instanceof Array) {
this.error.forEach((errMsg, index) => {
if (errMsg) {
// 将错误信息中 {{label}} 参数替换为当前表单项的 label 属性值
let resultMsg = errMsg.replace(labelParamReg, this.label || '')
msg += index === 0 ? resultMsg : '<br>' + resultMsg
}
})
}
return msg
},
// 计算 formItem 的样式
formItemStyle () {
let style = {}
style.width = (100 / this.columns * this.size) + '%'
return style
},
formItemClass () {
let classList = []
// classList.push(`col-${this.columns}-${this.size}`)
if (this.error && this.error.length > 0) {
classList.push('error')
}
return classList
}
}
}
</script>
<style lang="scss" scoped px2rem="false">
$error-icon-width: 18px;
.form-item {
.error {
position: relative;
flex: none;
margin-left: 3px;
width: $error-icon-width;
height: $form-item-height;
&:hover {
.error-wrap .tip-wrapper {
display: block;
}
}
.error-wrap {
display: block;
width: 100%;
height: 100%;
.icon-wrapper {
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
color: $color-danger;
/* background-image: url("../../../assets/img/error-icon.png") no-repeat center center; */
}
.tip-wrapper {
display: none;
position: absolute;
width: 200px;
padding-top: 1px;
top: $form-item-height;
left: -173px;
z-index: 9;
animation: showTip 0.5s;
.tip {
position: relative;
padding: .5em;
color: $color-danger;
border: 1px solid #E2D0D0;
border-radius: 3px;
background-color: #ffeaea;
// box-shadow: 0 1px 4px rgba(0,0,0,.5);
box-shadow: 0 1px 6px 0 rgba(0,0,0,.1);
line-height: 1.5;
&:before{
content: "";
position: absolute;
top: -.36em;
right: 1em;
padding: .35em;
background: inherit;
border: inherit;
border-right: 0;
border-bottom: 0;
transform: rotate(45deg);
}
}
}
}
}
}
@keyframes showTip {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>
index.js
/**
* 表单项组件
* @author 青莲使者
* @date 2021/10/14
*/
import FormItem from './FormItem.vue'
export default FormItem
感谢评论区大佬的点拨。