vue封装自己的组件库 03.封装input+switch组件



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

参数支持:

参数名称参数描述参数类型默认值
placeholder占位符string
type文本框类型(text/password)stringtext
disabled禁用booleanfalse
clearable是否显示清空按钮booleanfalse
show-password是否显示密码切换按钮booleanfalse
namename属性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>

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

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

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

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

  • :value=“username” 使得我们可以设置变量修改表单的value的值 控制值
  • @input=“username=$event.target.value”/ 使得表单的值改动后会更新我们的变量 值反馈
<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>
  • 注意v-model=“username” 等价于 :value=“username” @input=“username=$event.target.value” 子组件会接受到父组件传的value (注意需要接受)
  • 组件内部需要修改value的值 但不能直接 this.value = e.target.value 无法直接修改父组件传过来的值 这里又用到了v-model的实质 这里通过传递事件的方式 触发@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"

切换显示按钮点击时的颜色

<i
   class="my-input_icon my-icon-visible"
   v-if="showPassword && type == 'password'"
   @click="handlePassword"
   :class="{'my-icon-view-active':passwordVisible}"
   ></i>
.my-input_suffix {
  .my-input_inner {
    padding-right: 30px;
  }
  .my-input_suffix {
    position: absolute;
    right: 10px;
    height: 100%;
    top: 0;
    line-height: 40px;
    text-align: center;
    color: #c0c4cc;
    transition: all 0.3s;
    z-index: 900;
    i {
      color: #c0c4cc;
      font-size: 14px;
      cursor: pointer;
      transition: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
    }
    .my-icon-view-active {
      color: blue;
    }
  }
}

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

附组件全部代码:

<template>
  <div class="my-input" :class="{ 'my-input_suffix': showSuffix }">
    <input
      class="my-input_inner"
      :class="{ 'is-disabled': disabled }"
      :name="name"
      :type="showPassword ? (passwordVisible ? 'text' : 'password') : type"
      :placeholder="placeholder"
      :disabled="disabled"
      :value="value"
      @input="handleInput"
    />
    <span class="my-input_suffix">
      <i
        class="my-input_icon my-icon-cancel"
        v-if="clearable && value"
        @click="clear"
      ></i>
      <i
        class="my-input_icon my-icon-visible"
        v-if="showPassword && type == 'password'"
        @click="handlePassword"
        :class="{'my-icon-view-active':passwordVisible}"
      ></i>
    </span>
  </div>
</template>

<script>
export default {
  name: "my-input",
  data() {
    return {
      // 显示是否显示密码框
      passwordVisible: false
    };
  },
  props: {
    placeholder: {
      type: String,
      default: "",
    },
    type: {
      type: String,
      default: "password",
    },
    name: {
      type: String,
      default: "",
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    value: {
      type: String,
      default: "",
    },
    clearable: {
      type: Boolean,
      default: false,
    },
    showPassword: {
      type: Boolean,
      default: false,
    },
  },
  computed: {
    showSuffix() {
      return this.clearable || this.showPassword
    }
  },
  methods: {
    handleInput(e) {
      this.$emit("input", e.target.value);
    },
    clear() {
      this.$emit("input", "");
    },
    handlePassword () {
      this.passwordVisible = !this.passwordVisible
    }
  },
};
</script>

<style lang="scss" scoped>
.my-input {
  width: 100%;
  position: relative;
  font-size: 14px;
  display: inline-block;
  .my-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 0.2s cubic-bezier(0.645, 045, 0.355, 1);
    width: 100%;

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

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

参数支持:

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

事件支持:

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

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>
 
  props: {
    value: {
      type: Boolean,
      default: false,
    },
  },
  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
-model="active"
ctive-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>

方式1:通过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
      }
    }
  }

方式2: nexttick 解决子组件上传给父组件 父组件反馈子组件

  • 首先在mounted加载后修改颜色值

    methods: {
        setColor() {
            console.log(123);
            console.log(this.activeColor || this.inactiveColor);
            if(this.activeColor || this.inactiveColor){
                let color = this.value?this.activeColor:this.inactiveColor;
                this.$refs.core.style.borderColor = color
                this.$refs.core.style.backgroundColor = color
            }
        }
      },
      mounted() {
          this.setColor()
      }
    

    发现颜色一致[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iHEFfOvz-1658968721486)(switch.gif)]

    因为switch是切换两种颜色 mounted只触发一次

  • 绑定点击事件进行改动

    methods: {
        handleClick() {
          this.$emit("input", !this.value);
          this.setColor()
        },
        setColor() {
            if(this.activeColor || this.inactiveColor){
                let color = this.value?this.activeColor:this.inactiveColor;
                console.log(color);
                this.$refs.core.style.borderColor = color
                this.$refs.core.style.backgroundColor = color
            }
        }
      },
      mounted() {
          this.setColor()
      }
    

    发现新的问题 初始状态switch状态时false 但是组件内查询到按钮事件点击后 switch状态依旧为false 因为状态更新后 父组件没有及时更新给子组件 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U5JYQRvY-1658968721487)(Animation.gif)]

  • 添加this.$nexttick解决问题

    //写法1
    handleClick() {
        this.$emit("input", !this.value);
        this.$nextTick(() => {
            this.setColor();
        });
    },
    //写法2
     handleClick() {
        this.$emit("input", !this.value);
        this.$nextTick().then(() => {
            this.setColor();
        });   
    },
    //写法3
    async handleClick() {
        this.$emit("input", !this.value);
        //   this.$nextTick(() => {
        //     this.setColor();
        //   });
        //   this.$nextTick().then(() => {
        //     this.setColor();
        //   });
        await this.$nextTick();
        this.setColor();
    },
    

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>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值