接上篇文章, 费心劳神好几天的项目框架终于可以用了. 现在可以开始写页面了吧?
既然上司说, 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 方式
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 就做好了!! 功能还算是比较实用
来源: https://juejin.im/post/5b800d6851882542c20f24d5