效果展示
当然,以上用法均可以混合使用
使用
attributes
参数名 | 描述 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
v-model | 绑定value | string / number | — | — |
clear-able | 展示清空图标 | boolean | — | false |
showPassword | 展示密码切换图标 | boolean | — | false |
showWordLimit | 展示字数限制 | boolean | — | false |
suffixIcon | 尾部图标 | string | — | — |
prefixIcon | 头部图标 | string | — | — |
showExt | 展示提示信息 | boolean | — | false |
extIcon | 提示信息的图标 | string | — | 叉图标 |
extInfo | 提示信息文本 | string | — | — |
readonly | 只读 | boolean | — | false |
maxlength | 文本最大长度 | string / number | — | — |
minlength | 文本最小长度 | string / number | — | — |
placeholder | 占位文本 | string | — | — |
disabled | 禁用 | boolean | — | false |
autofocus | 自动获取焦点 | boolean | — | false |
type | 输入框类型 | string | text / password | text |
id | 输入框id | string | — | — |
设置其他的input框原生属性也会有效,但是编译器不会给出提示,由于目前用不到那么多原生属性所以并没有设置。
slots
槽名 | 描述 |
---|---|
prefix | input头部内容,强制限制了高度和input框高相等、边界为none. |
suffix | input尾部内容,强制限制了高度和input框高相等、边界为none. |
events
事件名 | 描述 | 回调参数 |
---|---|---|
input | 输入内容时触发 | value: string | number |
blur | input失去焦点时触发 | event:Event |
focus | input获取焦点时触发 | event:Event |
change | 在input内容发生了变化 且 (input失去焦点或者按下enter之后) | event:Event |
click | 在input被点击时触发 | event:Event |
clear | 点击了清空图标后触发 | — |
methods
方法名 | 描述 | 参数 |
---|---|---|
focus | 使得input获取焦点 | — |
blur | 使得input失去焦点 | — |
select | 选择input的文本 | — |
正文
实现思路
想要实现图标包裹在input内部的效果可以考虑:
- 将input作为组件最外层元素,图标相对定位,但是由于input为单标签元素,不能作为图标的父元素提供定位基准
- 将组件最外层元素设置为一个div,input、图标等作为div内部元素
于是组件结构为:
<div class="sss-input-outer">
<!-- 头部slot-->
<div class="sss-input-prefix-slot"><slot name="prefix"></slot></div>
<!-- 头部icon-->
<div class="sss-input-prefix-icon-box"></div>
<!-- input-->
<input class="sss-input-inner">
<!-- 尾部icon-->
<div class="sss-input-suffix-icon-box"></div>
<!-- 尾部slot-->
<div class="sss-input-suffix-slot"><slot name="suffix"></slot></div>
<!-- 文字提示-->
<div class="sss-input-ext"></div>
</div>
这个组件不会涉及太多东西,就是为了美观css会多一点
html
<template>
<div class="sss-input-outer" :class="computedClass"
@click="$emit('click',$event)">
<div class="sss-input-prefix">
<slot name="prefix"></slot>
</div>
<span v-if="prefixIcon" class="iconfont sss-input-left-icon" :class="prefixIcon"></span>
<input class="sss-input-inner"
ref="inputInner"
v-bind="$attrs"
@input="$emit('input',$event.target.value)"
@focus="$emit('focus', $event)"
@blur="$emit('blur', $event)"
@change="$emit('change', $event)"
:readonly="readonly"
:maxlength="maxlength"
:minlength="minlength"
:placeholder="placeholder"
:disabled="disabled"
:step="step"
:autofocus="autofocus"
:type="type"
:id="id"
:value="value"
>
<div v-if="clearAble||showPassword||suffixIcon||showWordLimit" class="sss-input-right-icon-box">
<!-- 清空-->
<span v-if="clearAble && this.value"
class="iconfont icon-yuanxingdacha sss-input-icon-clear"
@click="__clearContext"></span>
<!-- 字数限制-->
<i v-if="this.value.length > this.maxlength" class="iconfont icon-icon--jinggao"
style="color: red;font-size: 12px"></i>
<span v-if="showWordLimit && maxlength && (this.type==='text'||this.type==='textarea')"
style="font-size: 10px;color: gray">
{{ this.value?.length || 0 }}/{{ this.maxlength }}
</span>
<!-- 密码显示-->
<span v-if="showPassword && this.value && this.type==='password' "
class="iconfont icon-yanjing4 sss-input-icon-password"
@click="__showContext"></span>
<!-- 尾部icon-->
<span v-if="suffixIcon" ref="suffixIcon" class="iconfont" :class="suffixIcon"></span>
</div>
<div class="sss-input-suffix">
<slot name="suffix"></slot>
</div>
<transition name="sssInputFade">
<label v-if="showExt" class="sss-input-ext" :for="id" ref="sss-input-ext">
<span class="iconfont" :class="extIcon"></span>
{{ this.extInfo }}
</label>
</transition>
</div>
</template>
js
<script>
export default {
name: "sss-input",
props: {
value: {
type: String || Number,
default: ""
},
clearAble: {
type: Boolean,
default: false
},
showPassword: {
type: Boolean,
default: false,
},
showWordLimit: {
type: Boolean,
default: false,
},
suffixIcon: {
type: String,
default: undefined
},
prefixIcon: {
type: String,
default: undefined
},
showExt: {type: Boolean, default: false},
extIcon: {default: "icon-yuanxingdacha"},
extInfo: {default: "不符合预期"},
readonly: {
type: Boolean,
default: false
},
maxlength: {},
minlength: {},
placeholder: {},
disabled: {
type: Boolean,
default: false
},
autofocus: {
type: Boolean,
default: false
},
step: {},
type: {default: 'text'},
id: {}
},
computed: {
computedClass() {
return {
disabled: this.disabled
}
}
},
methods: {
__clearContext() {
this.$emit('input', "");
this.$emit('clear');
this.$refs.inputInner.focus();
},
__showContext() {
if (this.$refs.inputInner.type === 'password') {
this.$refs.inputInner.type = 'text';
} else {
this.$refs.inputInner.type = 'password';
}
this.$refs.inputInner.focus();
},
__reverseSuffixIcon() {
this.$refs.suffixIcon.classList.toggle("reverse");
},
__correctSuffixIcon() {
if (this.$refs.suffixIcon.classList.contains('reverse')) {
this.$refs.suffixIcon.classList.remove('reverse');
}
},
focus() {
this.$refs.inputInner.focus();
},
blur() {
this.$refs.inputInner.blur();
},
select() {
this.$refs.inputInner.select();
},
},
}
</script>
css
<style lang="less">
@import "@/assets/style/sss-var.less";
.reverse {
transform: rotate(-180deg);
}
.sss-input-outer {
box-sizing: border-box;
width: 100%;
height: 40px;
border: solid 1px @color-gray;
border-radius: 5px;
display: inline-flex;
flex-flow: row nowrap;
justify-content: center;
align-items: center;
user-select: none;
position: relative;
font-size: @font-size-s;
transition: all .3s;
& * { //继承
color: @color-black1;
font-size: inherit;
box-sizing: border-box;
}
//去除默认样式
& input {
border: none;
padding: 10px;
width: 100%;
display: inline-block;
border-radius: inherit;
&:focus {
outline: none;
}
&::placeholder {
color: @color-gray;
}
&[type="password"]::-ms-reveal {
display: none !important;
}
}
&:hover { //覆盖时 边界变化 且 清空图标出现
border: solid 1px darken(@color-gray, 10%);
.sss-input-icon-clear {
display: inline;
}
}
&:focus-within { //内部元素聚焦时,边界变化 图标展示
border: solid 1px @color-main;
box-shadow: 0 0 5px 0 darken(@color-main, -30%);
.sss-input-icon-password, .sss-input-icon-clear {
display: inline;
}
}
&.disabled {
background: @color-white3;
& * {
color: @color-gray;
}
.sss-input-right-icon-box {
display: none !important;
}
}
}
//左图标
.sss-input-left-icon {
padding-left: 10px;
color: @color-black1;
}
//右图标盒子
.sss-input-right-icon-box {
display: flex;
justify-content: center;
align-items: center;
color: @color-gray;
& * { //内部元素默认边距
padding: 0 6px;
color: inherit;
transition: all .3s;
}
& .sss-input-icon-password, & .sss-input-icon-clear {
display: none;
cursor: pointer;
&:hover {
+ span {
display: inline;
}
display: inline;
&:active {
+ span {
display: inline;
}
}
}
}
& .sss-input-icon-password {
display: inline;
}
}
.sss-input-suffix {
flex: none;
display: flex;
justify-content: center;
align-items: center;
border-left: solid 1px @color-gray;
height: 100%;
background: #f1f2f6;
border-radius: 0 5px 5px 0;
overflow: hidden;
& > * {
height: 100% !important;
border: none !important;
border-radius: 0 !important;
}
&:empty {
display: none;
}
}
.sss-input-prefix {
flex: none;
display: flex;
justify-content: center;
align-items: center;
border-right: solid 1px @color-gray;
height: 100%;
background: #f1f2f6;
border-radius: 5px 0 0 5px;
overflow: hidden;
& > * {
height: 100% !important;
border: none !important;
border-radius: 0 !important;
}
&:empty {
display: none;
}
}
.sss-input-ext {
position: absolute;
bottom: -22px;
left: 1px;
color: red;
font-size: 10px;
& * {
color: red !important;
padding-right: 5px;
&:before {
font-size: 15px;
}
}
display: flex;
justify-content: center;
align-items: center;
}
.sssInputFade-enter-active {
animation: sssFadeDownIn .3s;
}
.sssInputFade-leave-active {
animation: sssFadeIn .3s reverse;
}
</style>
position: absolute;
bottom: -22px;
left: 1px;
color: red;
font-size: 10px;
& * {
color: red !important;
padding-right: 5px;
&:before {
font-size: 15px;
}
}
display: flex;
justify-content: center;
align-items: center;
}
.sssInputFade-enter-active {
animation: sssFadeDownIn .3s;
}
.sssInputFade-leave-active {
animation: sssFadeIn .3s reverse;
}
</style>