模仿element-ui封装vue组件库(三)

魏晓巍

发布于 2020.03.27 16:22 阅读 637 评论 0

五、封装一个element-ui风格的input组件

参数支持:

参数名称 参数描述 参数类型 默认值
placeholder 占位符 string
type 文本框类型(text/password) string text
disabled 禁用 boolean false
clearable 是否显示清空按钮 boolean false
show-password 是否显示密码切换按钮 boolean false
name name属性 string

事件支持:

事件名称 事件描述
blur 失去焦点事件
change 内容改变事件
focus 获取焦点事件

5.1input组件的基本框架和样式以及处理placeholde、type、name、disabled

因为这部分与前面介绍的内容相同且比较简单,所以将这部分放在一起,不多做介绍了。

这里需要注意的是,disabled属性为true时,输入框禁用,并且需要改变样式,之前在button组件封装的时候也用到了相同的方法,获取到值后动态设置组件样式。

input组件的框架以及样式,获取到的数据以及数据处理:

<template>
 <div class="one-input">
   <input
   class="one-input_inner"
   :class="{'is-disabled': disabled}"
   :placeholder="placeholder"
   :type="type"
   :name="name"
   :disabled="disabled">
 </div>
</template>
<script>
export default {
  name: 'oneInput',
  components: {
  },
  props: {
    placeholder: {
      type: String,
      default: ''
    },
    type: {
      type: String,
      default: 'text'
    },
    name: {
      type: String,
      default: ''
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  data () {
    return {}
  },
  methods: {}
}
</script>
<style lang="scss" scoped>
  .one-input{
    width: 100%;
    position: relative;
    font-size: 14px;
    display: inline-block;
    .one-input_inner{
      -webkit-appearance: none;
      background-color: #fff;
      background-image: none;
      border: 1px solid #dcdfe6;
      border-radius: 4px;
      box-sizing: border-box;
      color: #606266;
      display: inline-block;
      font-size: inherit;
      height: 40px;
      line-height: 40px;
      outline: none;
      padding: 0 15px;
      transition: border-color .2s cubic-bezier(.645,045,.355,1);
      width: 100%;

      &:focus{
        outline: none;
        border-color: #409eff;
      }
      // input禁用样式
      &.is-disabled{
        background-color: #f5f7fa;
        border-color: #e4e7ed;
        color: #c0c4cc;
        cursor:not-allowed;
      }
    }
  }
</style>

父组件中传值也是与之前一样的:

 <one-input placeholder="请输入密码" type="password" name="name" disabled=true></one-input>

5.2v-model语法糖——实现双向数据绑定

当我们给一个input标签进行双向数据绑定时,我们需要使用value绑定数据,再使用input事件监听标签内数据的变动,如下:

      <input :value="username" @input="username=$event.target.value"/>

在封装input组件时,这样显然是不合适的,所以这里我们需要使用到v-model语法糖。

显然,我们是不能给我们封装的input组件直接使用v-model绑定数据的,但是由于v-model的特性,他将value值绑定在了组件上,所以,我们组件内部通过接收value值的方式,就可以接收到传入的数据;并且v-model也实现了input事件,在组件内部绑定的input事件作为回调,把value值返回给父组件,这样就实现了input组件的双向绑定了。

父组件中的使用v-model绑定:

<one-input v-model="username"></one-input>

组件内部绑定value值以及实现回调:

//绑定value值和input事件  
 <input
   class="one-input_inner"
   :class="{'is-disabled': disabled}"
   :placeholder="placeholder"
   :type="type"
   :name="name"
   :value="value"
   @input="handleInput"
   :disabled=disabled>
//绑定input事件进行回调
    handleInput (e) {
      this.$emit('input', e.target.value)
    }

5.3实现clearable功能和showPassword功能

当我们在组件中键入clearable属性时,我们希望组件可以有一个一键删除数据得功能。

当input组件的type属性是password时,我们希望在给与show-password属性时,可以有一个显示和隐藏密码的按钮。

实现这个两个功能,除了基本的父子组件传值外,还要添加i标签的icon字体图标,以及实现功能。

 <div class="one-input" :class="{'one-input_suffix':showSuffix}">
   <input
   class="one-input_inner"
   :class="{'is-disabled': disabled}"
   :placeholder="placeholder"
   :type="type"
   :name="name"
   :value="value"
   @input="handleInput"
   :disabled=disabled>
  <span class="one-input_suffix">
   <i class="on-input_icon one-icon-cancel" v-if="clearable && value" @click="clear"></i>
   <i class="on-input_icon one-icon-visible" v-if="showPassword && type=='password'" @click="handlePassword"></i>
 </span>
 </div>

样式:

  .one-input_suffix{
    .one-input_inner{
      padding-right: 30px;
    }
    .one-input_suffix{
      position: absolute;
      right: 10px;
      height: 100%;
      top: 0;
      line-height: 40px;
      text-align: center;
      color: #c0c4cc;
      transition: all .3s;
      z-index: 900;
      i{
        color: #c0c4cc;
        font-size: 14px;
        cursor: pointer;
        transition: color .2s cubic-bezier(.645,.045,.355,1);
      }
    }
  }

5.3.1实现clearable功能

首先获取父组件传递的clearable值,然后给i标签绑定一个点击事件,这个事件触发input事件回调,当点击叉号字体图标时,将父组件的value清空:

    clear () {
      this.$emit('input', '')
    }

5.3.2实现showPassword功能

实现showPassword功能的思路很简单,就是改变input的type类型即可。但是,我们不能直接改变父组件传递过来的type值,但是我们可以使用判断type值的方式,实现type的改变。

首先设置一个布尔类型的变量,并且设置点击事件改变这个变量:

 data () {
    return {
      // 显示是否显示密码框
      passwordVisible: false
    }
  },
methods: {
    handlePassword () {
      this.passwordVisible = !this.passwordVisible
    }
  }

然后我们需要在绑定type值时,进行两重判断。

第一步、判断showPassword是否为真;第二步、如果为真则通过passwordVisible去判断type为text还是password,以此来控制隐藏和现实,否则type值就为传入的type值即可:

:type="showPassword ? (passwordVisible ? 'text' : 'password') : type"

--------------------------------------------------至此,input组件封装完成-------------------------------------------------

附组件全部代码:

<template>
 <div class="one-input" :class="{'one-input_suffix':showSuffix}">
   <input
   class="one-input_inner"
   :class="{'is-disabled': disabled}"
   :placeholder="placeholder"
   :type="showPassword ? (passwordVisible ? 'text' : 'password') : type"
   :name="name"
   :value="value"
   @input="handleInput"
   :disabled=disabled>
  <span class="one-input_suffix">
   <i class="on-input_icon one-icon-cancel" v-if="clearable && value" @click="clear"></i>
   <i class="on-input_icon one-icon-visible" v-if="showPassword && type=='password'" @click="handlePassword"></i>
 </span>
 </div>
</template>
<script>
export default {
  name: 'oneInput',
  components: {
  },
  props: {
    placeholder: {
      type: String,
      default: ''
    },
    type: {
      type: String,
      default: 'text'
    },
    name: {
      type: String,
      default: ''
    },
    disabled: {
      type: Boolean,
      default: false
    },
    value: {
      type: String,
      default: ''
    },
    clearable: {
      type: Boolean,
      default: false
    },
    showPassword: {
      type: Boolean,
      default: false
    }
  },
  watch: {},
  computed: {
    showSuffix () {
      return this.clearable || this.showPassword
    }
  },
  data () {
    return {
      // 显示是否显示密码框
      passwordVisible: false
    }
  },
  methods: {
    handleInput (e) {
      this.$emit('input', e.target.value)
    },
    clear () {
      this.$emit('input', '')
    },
    handlePassword () {
      this.passwordVisible = !this.passwordVisible
    }
  }
}
</script>
<style lang="scss" scoped>
  .one-input{
    width: 100%;
    position: relative;
    font-size: 14px;
    display: inline-block;
    .one-input_inner{
      -webkit-appearance: none;
      background-color: #fff;
      background-image: none;
      border: 1px solid #dcdfe6;
      border-radius: 4px;
      box-sizing: border-box;
      color: #606266;
      display: inline-block;
      font-size: inherit;
      height: 40px;
      line-height: 40px;
      outline: none;
      padding: 0 15px;
      transition: border-color .2s cubic-bezier(.645,045,.355,1);
      width: 100%;

      &:focus{
        outline: none;
        border-color: #409eff;
      }
      // input禁用样式
      &.is-disabled{
        background-color: #f5f7fa;
        border-color: #e4e7ed;
        color: #c0c4cc;
        cursor:not-allowed;
      }
    }
  }
  .one-input_suffix{
    .one-input_inner{
      padding-right: 30px;
    }
    .one-input_suffix{
      position: absolute;
      right: 10px;
      height: 100%;
      top: 0;
      line-height: 40px;
      text-align: center;
      color: #c0c4cc;
      transition: all .3s;
      z-index: 900;
      i{
        color: #c0c4cc;
        font-size: 14px;
        cursor: pointer;
        transition: color .2s cubic-bezier(.645,.045,.355,1);
      }
    }
  }
</style>

六、封装一个element-ui风格的switch组件

参数支持:

参数名 参数描述 参数类型 默认值
v-model 双向绑定 布尔类型 false
name name属性 string text
activeColor 自定义的激活颜色 string #1ec63b
inactiveColor 自定义的不激活颜色 string #dd001b

事件支持:

事件名称 事件描述
change change时触发的事件

6.1switch组件的基本框架和样式

switch组件基本框架:

<template>
  <div class="one-switch">
    <span class="on-switch_core">
      <span class="one-switch_button"></span>
    </span>
  </div>
</template>

switch组件样式:

<style lang="scss" scoped>
  .one-switch{
    display: inline-block;
    align-items: center;
    position: relative;
    font-size: 14px;
    line-height: 20px;
    vertical-align: middle;
    .one-switch_core{
    margin: 0;
    display: inline-block;
    position: relative;
    width: 40px;
    height: 20px;
    border: 1px solid #dcdfe6;
    outline: none;
    border-radius: 10px;
    box-sizing: border-box;
    background: #dcdfe6;
    cursor: pointer;
    transition: border-color .3s,background-color .3s;
    vertical-align: middle;
    .one-switch_button{
      position:absolute;
      top: 1px;
      left: 1px;
      border-radius: 100%;
      transition: all .3s;
      width: 16px;
      height: 16px;
      background-color: #fff;
      }
    }
  }
</style>

6.2实现switch组件的数据双向绑定

 实现switch组件数据双向绑定和input组件相同,使用v-model语法糖即可。

在父组件种通过v-model绑定数据,在组件内部获取value属性,并且定义一个回调函数与父组件通信,改变父组件中的绑定值即可。

父组件:

<one-switch v-model="active" ></one-switch>

子组件,点击时改变is-checked类状态,触发滑块滑动:

<div class="one-switch" :class="{'is-checked':value}" @click="handleClick">
  <span class="one-switch_core">
    <span class="one-switch_button"></span>
  </span>
</div>

  methods: {
    handleClick () {
      this.$emit('input', !this.value)
    }
  }

滑动样式:

  // 选中样式
  .is-checked {
    .one-switch_core{
      border-color: #409eff;
      background-color: #409eff;
      .one-switch_button {
        transform: translateX(20px);
      }
    }
  }

6.3实现switch组件颜色自定义

自定义switch组件的颜色,首先需要传入颜色的值,在子组件中获取后,使用ref获取节点,将背景颜色改变为对应颜色即可。

父组件传递色彩参数:

     <one-switch
     v-model="active"
     active-color="#13ce66"
     inactive-color="#ff4949"
     ></one-switch>

子组件中定义ref="core"以确定节点:

 <div class="one-switch" :class="{'is-checked':value}" @click="handleClick">
    <span class="one-switch_core" ref="core">
      <span class="one-switch_button"></span>
    </span>
  </div>

通过mouted钩子和watch监听,在刚进入页面以及value改变时对颜色进行改变:

  mounted () {
    // 修改开关颜色
    if (this.activeColor || this.inactiveColor) {
      var color = !this.value ? this.activeColor : this.inactiveColor
      this.$refs.core.style.borderColor = color
      this.$refs.core.style.backgroundColor = color
    }
  },
  watch: {
    'value' (e) {
    // 修改开关颜色
      if (this.activeColor || this.inactiveColor) {
        var color = !e ? this.activeColor : this.inactiveColor
        this.$refs.core.style.borderColor = color
        this.$refs.core.style.backgroundColor = color
      }
    }
  }

6.4name属性支持

用户在使用switch组件的时候,实质上是当成表单元素来使用的。因此可能会用到组件的name属性。所以需要在switch组件中添加一个checkbox,并且当值改变的时候,也需要设置checkbox的value值。

加入input标签:

<template>
  <div class="one-switch" :class="{'is-checked':value}" @click="handleClick">
    <span class="one-switch_core" ref="core">
      <span class="one-switch_button"></span>
    </span>
    <input type="checkbox" class="one-switch_input" :name="name" ref="input">
  </div>
</template>

设置标签样式,因为input标签只作为name绑定使用,所以将其隐藏起来:

  // 隐藏input标签
  .one-switch_input{
    position:absolute;
    width: 0;
    height: 0;
    opacity: 0;
    margin: 0;
  }

我们在页面加载和点击时修改input的checked值,保证可以和value值同步:

  mounted () {
    // 修改开关颜色
    if (this.activeColor || this.inactiveColor) {
      var color = !this.value ? this.activeColor : this.inactiveColor
      this.$refs.core.style.borderColor = color
      this.$refs.core.style.backgroundColor = color
    }
    // 控制checkbox的值,input值同步value值
    this.$refs.input.checked = this.value
  },
  methods: {
    handleClick () {
      this.$emit('input', !this.value)
      // 控制checkbox的值,input值同步value值
      this.$refs.input.checked = this.value
    }
  }

--------------------------------------------------------------至此,switch组件封装完成--------------------------------------------------

附switch组件代码:

<template>
  <div class="one-switch" :class="{'is-checked':value}" @click="handleClick">
    <span class="one-switch_core" ref="core">
      <span class="one-switch_button"></span>
    </span>
    <input type="checkbox" class="one-switch_input" :name="name" ref="input">
  </div>
</template>
<script>
export default {
  name: 'oneSwitch',
  components: {
  },
  props: {
    value: {
      type: Boolean,
      defualt: false
    },
    activeColor: {
      type: String,
      defualt: ''
    },
    inactiveColor: {
      type: String,
      defualt: ''
    },
    name: {
      type: String,
      defualt: ''
    }
  },
  mounted () {
    // 修改开关颜色
    if (this.activeColor || this.inactiveColor) {
      var color = !this.value ? this.activeColor : this.inactiveColor
      this.$refs.core.style.borderColor = color
      this.$refs.core.style.backgroundColor = color
    }
    // 控制checkbox的值,input值同步value值
    this.$refs.input.checked = this.value
  },
  watch: {
    'value' (e) {
    // 修改开关颜色
      if (this.activeColor || this.inactiveColor) {
        var color = !e ? this.activeColor : this.inactiveColor
        this.$refs.core.style.borderColor = color
        this.$refs.core.style.backgroundColor = color
      }
    }
  },
  data () {
    return {}
  },
  methods: {
    handleClick () {
      this.$emit('input', !this.value)
      // 控制checkbox的值,input值同步value值
      this.$refs.input.checked = this.value
    }
  }
}
</script>
<style lang="scss" scoped>
  .one-switch{
    display: inline-block;
    align-items: center;
    position: relative;
    font-size: 14px;
    line-height: 20px;
    vertical-align: middle;
    .one-switch_core{
    margin: 0;
    display: inline-block;
    position: relative;
    width: 40px;
    height: 20px;
    border: 1px solid #dcdfe6;
    outline: none;
    border-radius: 10px;
    box-sizing: border-box;
    background: #dcdfe6;
    cursor: pointer;
    transition: border-color .3s,background-color .3s;
    vertical-align: middle;
    .one-switch_button{
      position:absolute;
      top: 1px;
      left: 1px;
      border-radius: 100%;
      transition: all .3s;
      width: 16px;
      height: 16px;
      background-color: #fff;
      }
    }
  }
  // 选中样式
  .is-checked {
    .one-switch_core{
      border-color: #409eff;
      background-color: #409eff;
      .one-switch_button {
        transform: translateX(20px);
      }
    }
  }
  // 隐藏input标签
  .one-switch_input{
    position:absolute;
    width: 0;
    height: 0;
    opacity: 0;
    margin: 0;
  }
</style>