Vue2 Element 模仿秀-输入框篇(Input)
前方
这里文章主要分享Element 对 input 输入框在组件封装。但是本文没有把关于 input 的输入框所有组件都模仿一遍,原因有二:1、官方介绍 input 类组件中包括了其它组件相对较复杂的组件,比如:select 组件、autocomplete 组件,在这里我打算把它分开来模仿。2、如果都放在一篇文章中,代码会很多,不便于阅读理解。
文件结构
├── index.html
├── mixins
│ └── emitter.js
├── utils
│ ├── calcTextareaHeight.js
│ └── merge.js
├── assets
│ ├── fonts
│ │ ├── yunkus-icons.ttf
│ │ └── yunksu-icons.woff
│ └── icon.css
├── main.js
├── router
│ └── index.js # 路由配置文件
└── components
├── Home.vue # 大的框架结构组件
└── Input.vue
下面就是三个主要的文件代码,一个是 Hom.vue 另一个是 Input.vue ,还有一个 merge.js。其它两个文件的代码就没有把它们帖出来了,不过已经在文章最后面把它们的链接帖出来。
效果演示:
第一个 textarea 是默认 textarea,第二个是默认一行,多行自适应高度,第三个就是设置了一个区间,在这里区间内 textarea 为自适应,超过这 textarea 的最大高度时,就会出现滚动条。
Home.vue
<template>
<div class="container demo-input">
<div class="panel panel-default">
<div class="panel-heading">基本用法</div>
<div class="panel-body">
<yk-input v-model="input1" placeholder="请输入内容" clearable></yk-input>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">禁用状态</div>
<div class="panel-body">
<yk-input v-model="input1" disabled placeholder="请输入内容"></yk-input>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">属性方式</div>
<div class="panel-body">
<yk-input v-model="input2" prefix-icon="yk-icon-search" placeholder="请输入日期"></yk-input>
<yk-input v-model="input2" suffix-icon="yk-icon-date" placeholder="请输入日期"></yk-input>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">slot 方式</div>
<div class="panel-body">
<yk-input v-model="input2" placeholder="请输入日期"><i slot="prefix" class="yk-input-icon yk-icon-search"></i></yk-input>
<yk-input v-model="input3" placeholder="请输入日期"><i slot="suffix" class="yk-input-icon yk-icon-date"></i></yk-input>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">文本域</div>
<div class="panel-body">
<yk-input type="textarea" :rows="2" placeholder="请输入内容" v-model="textarea"></yk-input>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">文本域(autosize)</div>
<div class="panel-body">
<yk-input type="textarea" placeholder="请输入内容" autosize v-model="textarea2"></yk-input>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">文本域(:autosize="{ minRows: 2, maxRows: 4}")</div>
<div class="panel-body">
<yk-input type="textarea" placeholder="请输入内容" :autosize="{ minRows: 2, maxRows: 4}" v-model="textarea3"></yk-input>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">复合型输入框</div>
<div class="panel-body">
<div>
<yk-input placeholder="请输入内容" v-model="input8">
<template slot="prepend">Http://</template>
</yk-input>
</div>
<div style="margin-top: 15px;">
<yk-input placeholder="请输入内容" v-model="input9">
<template slot="append">.com</template>
</yk-input>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">输入框尺寸</div>
<div class="panel-body">
<div class="demo-input-size">
<yk-input
placeholder="请输入内容"
suffix-icon="yk-icon-date"
v-model="input4">
</yk-input>
<yk-input
size="medium"
placeholder="请输入内容"
suffix-icon="yk-icon-date"
v-model="input5">
</yk-input>
<yk-input
size="small"
placeholder="请输入内容"
suffix-icon="yk-icon-date"
v-model="input6">
</yk-input>
<yk-input
size="mini"
placeholder="请输入内容"
suffix-icon="yk-icon-date"
v-model="input7">
</yk-input>
</div>
</div>
</div>
</div>
</template>
<script>
import YkInput from "@/components/Input";
import "@/assets/icon.css";
export default {
name: "Home",
components: { YkInput },
data() {
return {
input1: "",
input2: "",
input3: "",
input4: "",
input5: "",
input6: "",
input7: "",
input8: "",
input9: "",
textarea: "",
textarea2: "",
textarea3: ""
};
}
};
</script>
Input.vue
<template>
<!-- v-bind="$props" 绑定 props 中的所有属性 -->
<div :class="[
type === 'textarea'?'yk-textarea-box':'yk-input-box',
size ? 'yk-input-' + size : '',
{'is-disabled': inputDisabled,
'yk-input-group-box': $slots.prepend || $slots.append, // 如果 $slots.prepend 或者 $slots.append 有值
'yk-input-group-append-box': $slots.append,
'yk-input-group-prepend-box': $slots.prepend,
'yk-input-box-prefix': $slots.prefix || prefixIcon,
'yk-input-box-suffix': $slots.suffix || suffixIcon
}
]"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
<template v-if="type !== 'textarea'">
<!-- 前置元素 -->
<div class="yk-input-group-prepend" v-if="$slots.prepend">
<slot name="prepend"></slot>
</div>
<input
class="yk-input"
@input="handleInput"
@change="handleChange"
v-bind="$props"
:value="currentValue"
type="text"
:disabled="inputDisabled">
<!-- 前置内容 -->
<span class="yk-input-prefix" v-if="$slots.prefix || prefixIcon">
<slot name="prefix"></slot>
<i class="yk-input-icon"
v-if="prefixIcon"
:class="prefixIcon">
</i>
</span>
<!-- 后置内容 -->
<span class="yk-input-suffix" v-if="$slots.suffix || showClear || suffixIcon">
<span class="yk-input-suffix-inner">
<template v-if="!showClear">
<slot name="suffix"></slot>
<i class="yk-input-icon"
v-if="suffixIcon"
:class="suffixIcon">
</i>
</template>
<i v-else class="yk-input-icon yk-icon-circle-close yk-input-clear" @click="clear"></i>
</span>
</span>
<!-- 后置元素 -->
<div class="yk-input-group-append" v-if="$slots.append">
<slot name="append"></slot>
</div>
</template>
<textarea v-else
:style="textareaStyle"
class="yk-textarea"
:value="currentValue"
v-bind="$props"
ref="textarea"
:disabled="inputDisabled"
@input="handleInput"
@change="handleChange"
></textarea>
</div>
</template>
<script>
import Emitter from "@/mixins/emitter";
// calcTextareaHeight 模块实现 textarea 高度自适应
import calcTextareaHeight from "@/utils/calcTextareaHeight";
// merge 模块实现了简单的对象合并
import merge from "@/utils/merge";
export default {
name: "YkInput",
componentName: "YkInput",
mixins: [Emitter],
props: {
value: String,
type: {
type: String,
default: "text"
},
placeholder: String,
disabled: Boolean,
clearable: {
type: Boolean,
default: false
},
suffixIcon: String,
prefixIcon: String,
size: String,
resize: String,
autosize: {
type: [Boolean, Object],
default: false
}
},
data() {
return {
// 初始化当前值
currentValue: this.value,
hovering: false, // 这个属性到于鼠标悬浮时显示一些元素
focused: false,
textareaCalcStyle: {} // 这个用于保存 textarea 的一些属性,比如最小高度,高度
};
},
methods: {
handleInput(event) {
const value = event.target.value;
this.$emit("input", value);
this.setCurrentValue(value);
},
handleChange(event) {
this.$emit("change", event.target.value);
},
setCurrentValue(value) {
this.currentValue = value;
// this.$nextTick(_ => {
this.resizeTextarea();
// });
},
clear() {
this.$emit("input", "");
this.$emit("change", "");
this.setCurrentValue("");
this.focus();
},
resizeTextarea() {
// if (this.$isServer) return;
const { autosize, type } = this;
// 如果不是 textarea 直接返回,不再执行后面的代码
if (type !== "textarea") return;
// 如果没有设置autosize
if (!autosize) {
this.textareaCalcStyle = {
// 求出 textarea 的最小高度
minHeight: calcTextareaHeight(this.$refs.textarea).minHeight
};
return;
}
// 如果存在 autosize 则会继续往下执行
const minRows = autosize.minRows;
const maxRows = autosize.maxRows;
// 通过 calcTextareaHeight 来计算出 textarea 的最小高度以及最大高度
this.textareaCalcStyle = calcTextareaHeight(
this.$refs.textarea,
minRows,
maxRows
);
}
},
computed: {
textareaStyle() {
// 合并样式
return merge({}, this.textareaCalcStyle, { resize: this.resize });
},
inputDisabled() {
return this.disabled || false;
},
showClear() {
return (
this.clearable &&
this.currentValue !== "" &&
(this.focused || this.hovering)
);
}
},
mounted() {
this.resizeTextarea();
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scope>
.demo-input .yk-input-box {
margin-bottom: 15px;
}
.demo-input .yk-input-box {
width: 180px;
margin-right: 15px;
}
.demo-input .yk-textarea-box {
width: 414px;
}
.demo-input .yk-input-group-box {
width: 100%;
}
.yk-input-box {
position: relative;
font-size: 14px;
display: inline-block;
width: 100%;
}
.yk-input {
-webkit-appearance: none;
background-color: #fff;
background-image: none;
border-radius: 4px;
border: 1px solid #dcdfe6;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #606266;
display: inline-block;
font-size: inherit;
height: 40px;
line-height: 1;
outline: 0;
padding: 0 15px;
-webkit-transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
width: 100%;
}
.yk-input-box.is-active .yk-input,
.yk-input:focus {
border-color: #409bc2;
outline: 0;
}
.yk-input-box.is-disabled .yk-input {
background-color: #f5f7fa;
border-color: #e4e7ed;
color: #c0c4cc;
cursor: not-allowed;
}
.yk-input-box.is-disabled .yk-input::-webkit-input-placeholder {
color: #ccc;
}
.yk-input-box .yk-input:hover {
border-color: #c0c4cc;
}
.yk-input-box.is-disabled .yk-input:hover {
border-color: #dcdfe6;
}
.yk-input-suffix {
right: 5px;
-webkit-transition: all 0.3s;
transition: all 0.3s;
pointer-events: none;
}
.yk-input-prefix,
.yk-input-suffix {
position: absolute;
top: 0;
color: #c0c4cc;
height: 100%;
text-align: center;
-webkit-transition: all 0.3s;
transition: all 0.3s;
}
.yk-input-box .yk-input-clear {
color: #c0c4cc;
font-size: 14px;
line-height: 16px;
cursor: pointer;
-webkit-transition: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
transition: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.yk-input-box .yk-input-clear:hover {
color: #909399;
}
.yk-input-suffix-inner {
pointer-events: all;
}
.yk-input-prefix {
left: 5px;
transition: all 0.3s;
}
.yk-input-icon {
height: 100%;
width: 25px;
text-align: center;
-webkit-transition: all 0.3s;
transition: all 0.3s;
line-height: 40px;
}
.yk-input-icon:after {
content: "";
height: 100%;
width: 0;
display: inline-block;
vertical-align: middle;
}
.yk-input-prefix {
position: absolute;
left: 5px;
top: 0;
color: #c0c4cc;
}
.yk-input-box-prefix .yk-input {
padding-left: 30px;
}
.yk-textarea-box {
display: inline-block;
width: 100%;
vertical-align: bottom;
font-size: 14px;
}
.yk-textarea {
display: block;
resize: vertical;
padding: 5px 15px;
line-height: 1.5;
box-sizing: border-box;
width: 100%;
font-size: inherit;
color: #606266;
background-color: #fff;
background-image: none;
border: 1px solid #dcdfe6;
border-radius: 4px;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.yk-input-group-box {
line-height: normal;
display: inline-table;
width: 100%;
border-collapse: separate;
}
.yk-input-group-box > .yk-input {
vertical-align: middle;
display: table-cell;
}
.yk-input-group-append,
.yk-input-group-prepend {
background-color: #f5f7fa;
color: #909399;
vertical-align: middle;
display: table-cell;
position: relative;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 20px;
width: 1px;
white-space: nowrap;
}
.yk-input-group-prepend {
border-right: 0;
}
.yk-input-group-append {
border-left: 0;
}
.yk-input-group-append-box .yk-input,
.yk-input-group-prepend {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.yk-input-group-prepend-box .yk-input,
.yk-input-group-append {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.yk-input-medium {
font-size: 14px;
}
.yk-input-small {
font-size: 13px;
}
.yk-input-mini {
font-size: 12px;
}
.yk-input-medium .yk-input {
height: 36px;
}
.yk-input-small .yk-input {
height: 32px;
}
.yk-input-mini .yk-input {
height: 28px;
}
.yk-input-medium .yk-input-icon {
line-height: 36px;
}
.yk-input-small .yk-input-icon {
line-height: 32px;
}
.yk-input-mini .yk-input-icon {
line-height: 28px;
}
</style>
Input 组件中使用到的 merge 模块中的代码非常地简单:
merge.js
export default function (target) {
console.log(arguments[2]);
for (let i = 1, j = arguments.length; i < j; i++) {
let source = arguments[i] || {};
for (let prop in source) {
if (source.hasOwnProperty(prop)) {
let value = source[prop];
if (value !== undefined) {
target[prop] = value;
}
}
}
}
return target;
};
还有两个文件一个是 emitter ,另一个是 calcTextareaHeight ,这两个模板我已经单独到两篇文章中了,你可以前往阅读:《Vue Element emitter.js 详解》、《Vue Element calcTextareaHeight.js 详解》。
-
微信扫一扫,赏我
-
支付宝扫一扫,赏我