封装表单项组件

1,363 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

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

感谢评论区大佬的点拨。

希望看完的朋友可以给个赞,鼓励一下