接上篇文章,费心劳神好几天的项目框架终于可以用了。现在可以开始写页面了吧?
既然上司说,UI框架我们自己来写,那我们就自己写吧。
虽然答应的时候挺痛快。真到写的时候,首先就不知道从哪里开始下手了
那我们就一点点来。先从组件框架开始一点点做。
首先先排布一下UI框架目录。在于上司聊了许久后,最后决定用这种目录架构:
红色箭头代表业务组件(business components) 存放项目中业务类组件的地方。如头部导航,个人信息卡等
绿色箭头代表业务组件(framework components) 存放项目中基础框架组件的地方。如按钮,输入框,开关等
蓝色箭头是导出文件。统一导出所有组件方便调用(文章后面会讲到)
好的,那我们先从input开始
input组件的编写
我们先看看效果图:
大致就是这个样子。非常简洁的UI,功能也算是够用了。(这里给UI大哥的作品点个赞)
我把它拆分成了这个样子(如图) 每个颜色框内都是不同的slot(具名插槽)
大致代码就是这个样子的存在:
<template>
<div class="input-wrapper">
<div class="input-content">
<div class="input__left">
<slot name="left"></slot> //红色框插槽
</div>
<div class="input__center">
<input type="text" title="">
<div class="input__center__tools">
<i class="iconfont icon-qingchu" v-show="inputValue" @click="clearInputValue"></i>
//清除value的地方
</div>
</div>
<div class="input__right">
<slot name="right"></slot> //input右侧的自定义区。可以放置“获取验证码”之类的操作
</div>
</div>
<div class="input-tip">
<slot name="tip"></slot> //下方提示区域插槽
</div>
</div>
</template>复制代码
css方面选用flex布局。字体/图标大小,元素间距使用rem布局 class命名使用bem方式
CSS:
<style scoped>
.input__left .iconfont {
font-size: 2rem;
}
.input-content {
display: flex;
flex-direction: row;
}
.input__left {
padding-bottom: 0.4rem;
}
.input__left > span {
font-size: 1.5rem;
}
.input__center {
margin-left: .5rem;
flex: 1;
display: flex;
flex-direction: row;
padding-bottom: .3rem;
border-bottom: 1px solid #E9E9E9;
}
.input__center > .input__center__tools > .iconfont {
color: #141414;
cursor: pointer;
font-size: 2rem;
}
.input__center > input {
text-indent: .3rem;
flex: 1;
border: 0;
width: 100%;
height: 100%;
font-size: 1.3rem;
color: #3b3b3b;
outline: none;
}
.input__right {
padding-left: .5rem;
padding-bottom: .3rem;
border-bottom: 1px solid #E9E9E9;
}
.icon-qingchu {
color: #E9E9E9 !important;
}
.input-tip {
margin-top: .3rem;
margin-left: 2.45rem;
font-size: 1rem;
}
.input-tip i {
font-size: 1rem;
}
.input-tip--info {
color: #BFBFBF;
}
.input-tip--success {
color: #5CD18B;
}
.input-tip--warning {
color: #FFC53D;
}
.input-tip--error {
color: #FF7875;
}
</style>
复制代码
OK,这时候我们的UI画完了。还能输入文字...不错哦
这时候遇到了一个问题:之前我们直接v-model就可以双向数据绑定input的value。现在input在组件内包着。那我们如何在父组件绑定子组件内的input呢??
找了半天教程,找到了这样一个操作:
给组件添加 v-model 属性时,默认会把 value 作为组件的属性,然后把 'input' 值作为给组件绑定事件时的事件名
啊哈,这样就好说了。那我们可以这样去写:
<input :type="textType" title="" v-model="inputValue"
@input="$emit('input', inputValue)">
export default{
data() {
return {
inputValue: "" //子组件内绑定一遍。等会要用
}
},
}
复制代码
外部调用:
<zb-input v-model="phoneLogin.phone.value">复制代码
这样就大功告成了。这样父组件调用方就可以绑定到输入框的值了
OK,接下来我们开始做“一键清空”功能
由于我们传出去的值,是走的子组件内的双向绑定的data。所以理论上我们只需要把绑定在data内的变量清空就行了
this.inputValue = '';复制代码
但是这样会有问题,子组件内已经清空,父组件仍然保留着值。
那我们就模仿上面的做法,再$emit一次~~~
clearInputValue() {
this.inputValue = ''; //清空输入框的值
this.$nextTick(function () { //在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM
this.$emit('input', this.inputValue); //执行传出操作
});
},复制代码
这样功能就实现了。如下图( 清除按钮的颜色太浅。动图录制软件看不见~~~ 抱歉)
好的,那接下来实现一下密码“显示”“隐藏“的功能
这个功能也比较有意思。不只是把输入框的type换成password那么简单。还能还原之前传入的input type
首先我们先定义一个props:
inputType: { //输入框类型
default: 'text'
}
canHide: { //是否支持隐藏值
required: false,
default: false
},
复制代码
这个props从父组件接收想要的input类型。如果没有就默认text
然后copy一份同义的变量到data里。保持以prop做参考,data负责内部更改~~~
textType: this.inputType //从props里接收初始值
isHideValue: false //现在是否隐藏输入框值复制代码
然后清除事件:
hideInputValue() {
if (this.textType === this.inputType) { //如果props内和data内相等。说明是没隐藏状态
this.textType = "password"; //让他变为password类型
this.isHideValue = true; //控制隐藏按钮的类名。更换icon
} else {
this.textType = this.inputType; //替换为初始化的type
this.isHideValue = false;
}
}复制代码
按钮方面:
<i class="iconfont"
:class="[{'icon-yanjing_yincang_o':!isHideValue},{'icon-yanjing_xianshi_o':isHideValue}]"
@click="hideInputValue" v-show="canHide && inputValue"></i>复制代码
这样就大功告成啦!!
既然我们做input了,那就做到底呗~~
让他支持一下自定义规则验证
通过和上司商定,暂选了这几个规则支持:
1. lengthRange: Object 支持max,min最大最小值
2. regular: 标准正则表达式
3. required: Boolean 是否必填
数据格式大概是这样的:
regex: {
required:false,
lengthRange: {
max: 11,
min: 1
},
regular: /^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/
}复制代码
然后通过props传进去:
regexObject: { //校验对象
required: false
}
:regexObject="regex"复制代码
然后准备工作做好了,开始准备校验工作了
校验顺序按照队列校验。分别各自返回校验结果。没有指定的放行
是否必填校验:
reg_required(value) {
if (this.regexObject.required) { //如果有required这个字段
return !!value //返回value是否有值
} else {
return null; //代表没填required这个字段
}
},复制代码
Tips:" !! " 常常用来做类型判断,在第一步!(变量)之后再做逻辑取反运算。简单理解就是判断这个变量是否存在值。
等价 " value!=null&&typeof(value)!=undefined&&value!='' "
正则表达式校验:
reg_regular(value) {
if (this.regexObject.regular) { //如果有regular这个字段
return this.regexObject.regular.test(value); //返回校验的结果
} else {
return null; //代表没填regular这个字段
}
},复制代码
长度校验:
reg_lengthRange(value) {
if (this.regexObject.lengthRange) { //如果有lengthRange这个字段
return value.length >= this.regexObject.lengthRange.min //如果value的长度大于等于预定最小值
&& value.length <= this.regexObject.lengthRange.max //如果value的长度小于等于预定最大值
} else {
return null; //代表没填lengthRange这个字段
}
},复制代码
主入口方法
regex(value) {
let val = value.toString(); //统一转成字符串。防止没有length属性
let info = {}; //空对象
info.value = val; //一块把输入框的值传出去
if (this.regexObject) { //如果props传了校验对象
info.required = this.reg_required(val);
info.regular = this.reg_regular(val);
info.lengthRange = this.reg_lengthRange(val);
}
return info;
},复制代码
最后在输入框失焦事件(blur)内调用了一下:
inputReg() {
console.log(this.regex(this.inputValue));
},复制代码
控制台:
自己体会~~~
然后发现了个bug,虽然定义了最大输入范围,但超出只是提示不禁止输入
于是watch监听一下inputValue
inputValue(){
if (this.regexObject && this.regexObject.lengthRange) {
if (this.inputValue.length > this.regexObject.lengthRange.max) { //如果输入框长度大于了既定最大长度
this.inputValue = this.inputValue.slice(0, this.regexObject.lengthRange.max);//从0开始截取。截到最大长度
return false;
}
} else {
return false;
}
}复制代码
因为html5把maxlength属性去掉了.....所以只能字符串截取
这样一个入门级别的Input就做好了!!功能还算是比较实用