现在的客户端与以往的有些区别,比如登录页面的输入框,大都废弃了之前的登录框样式,而采用只存在底边的输入框。还有就是输入框的错误信息都会采用浮层的形式提示,而不是瀑布流的方式去提示。
在借鉴了其他客户端的方式,做了一些调整,这些调整都需要对antdv组件进行从新修正。因为我是用的是electron-vue+antdv的架构去构建的应用。
先看一下效果图如下:
我们要做的事情就是:
修改输入框为只有底边当输入框获取悬浮事件时,改变border的颜色当输入框获取焦点事件时,修改边框的颜色antdv的处理方式和普通的输入框不一样,我们可以先设置一下输入框的border,因为是antdv提供的组件,所以我们需要F12查看输入框绑定的样式,然后去覆盖该样式的border。 如上图所示,我们直接去覆盖ant-input和ant-input-number的样式。
.ant-input, .ant-input-number { width: 100%; border: none !important; border-bottom: 1px solid #e9e3e3 !important; }这样就可以实现只保留底边。
那么我们现在来做进一步的优化,就是当我们移动到上面时去改变边框的颜色,这个也简单只要设置ant-input的hover事件就行了。
.ant-input:hover,.ant-input-number:hover { border-bottom: 1px solid #bebebe !important; }鼠标悬浮时,改变边框的颜色。
我本来以为这个也简单,当输入框获取focus时,直接改变底边的颜色即可。但是发现并非如此。
.ant-input:focus, .ant-input-number:focus { border: none; border-bottom: 1px solid #12b7f5 !important; }可以发现,并没有生效。看了下网上说的,蓝色边框是输入框的外部轮廓,而不是border的问题。于是就设置输入框的outline:none。并没有生效还是有蓝色边框。
.ant-input:focus, .ant-input-number:focus { border: none; border-bottom: 1px solid #12b7f5 !important; outline: none; }如果不是蓝色边框的慌,又不是轮廓的问题,那么只有一种可能了,那就是box-shadow的问题,于是尝试了修改
.ant-input:focus, .ant-input-number:focus { border: none; border-bottom: 1px solid #12b7f5 !important; box-shadow: none; }确实获取焦点时,所谓的轮廓就没有了,看来是阴影的问题。到此,修改边框功能基本上实现了。只保留底边,鼠标悬浮时会改变底边的颜色,并且获取焦点时也可以去除阴影改变底边颜色。
但是还有一个问题,当获取焦点时同时会触发悬浮的事件,导致只有鼠标离开时才会变成获取焦点时的蓝色。
这个问题是由于权重相同导致的。怎么理解呢?因为我在获取焦点时,给的是!important,而在悬浮时也是设置了!important,所以导致了两个事件的权重一样,那么在输入框获取焦点时,颜色变为蓝色。当你鼠标移出输入框,但是光标还在输入框的情况下,触发了输入框的hover事件,所以输入框底边就会变成灰色。
解决办法就是,删除悬浮时底边颜色的权重!importan,这样获取焦点事件的权重比悬浮事件的权重要高,当鼠标离开输入框,在回到输入框时,也不会去覆盖获取焦点的颜色。
我发现现在大多数客户端的校验提示信息都不是瀑布流的方式(即在输入框下面留了一大片空白,来显示错误信息),而是通过浮层的方式去处理这个问题。
传统的方式: 这种在客户端尤其是只有底边的的情况下,会有很大的空白,页面显得大而宽泛。需要给登陆页面设置较大的宽和高。而正常的登陆基本上就是用户名密码登陆按钮这些,不需要太多留白。在antdv中默认的留白是32px吧,我记得是。所以为每一个表单项多留出32px的空白,就显得这个页面不紧凑。所以我们需要重新去修改一下这种提示信息的位置。
而我们要实现的效果就是这种浮层的形式提示表单的错误信息: 于是研究了一下antdv的表单,看看有没有关于表单校验信息的位置修改方法,研究之后,无果。又尝试能不能使用tooltip来实现,发现并没有触发事件去触发这个tooltip的显示。
于是决定自己写一个仿tooltip校验的信息,写的话,我们需要先理一下思路。
首先这个提示信息应该是浮层,就是说脱离文档流,并且z-index高于其他元素创建一个div,里面包含两个小的div,一个是小三角,一个是提示信息该div应该和input在同一父元素下三角的实现应该是利用border去设置小三角应该偏离一段距离,能对到我们的输入框,输入信息的起始位置整个div应该可以显示隐藏,所以我们需要定义一个属性去控制显示隐藏,以及定义一个msg去显示错误的提示信息好了这个思路我们理清了,那么操作起来就相对简单多了,把复杂的问题拆分成一个个小的问题,人后逐个攻破,就会容易的多了。
首先的第一个难点就是怎么创建一个小三角。如果你做过,当然就不难,如果没做过还是有一点难度的,当然现在网上有很多关于这方面的资料,感兴趣的可以搜一下。
我处理问题的习惯就是,一个新的东西,我会先脱离当前的项目,重新搞一个简单页面或者脚手架测试一下可行性。
<div style="width:300px;height: 300px;background-color: #bfa;"> </div> <div style="position: absolute;top:20px"> <div style="width: 100px; height: 100; background-color: black;"> </div> <!-- <div style="width: 200px;height: 20px;background-color: black;border-radius: 2px;"> </div> --> </div>然后我们将黑色div的都设为零,并且设置其border为一定的大小
<div style="width:300px;height: 300px;background-color: #bfa;"> </div> <div style="position: absolute;top:20px"> <div style="width: 0; height: 0; border: 100px solid; border-color: red black;margin-left: 10px;"> </div> <!-- <div style="width: 200px;height: 20px;background-color: black;border-radius: 2px;"> </div> --> </div>然后将,这样我们只要把其他的边框设置透明就能让它只显示一块了
<div style="width:300px;height: 300px;background-color: #bfa;"> </div> <div style="position: absolute;top:20px"> <div style="width: 0; height: 0; border: 100px solid; border-color: transparent transparent black;margin-left: 10px;"> </div> <!-- <div style="width: 200px;height: 20px;background-color: black;border-radius: 2px;"> </div> --> </div>接着,把border的大小改变一下
<div style="width:300px;height: 300px;background-color: #bfa;"> </div> <div style="position: absolute;top:20px"> <div style="width: 0; height: 0; border: 5px solid; border-color: transparent transparent black;margin-left: 10px;"> </div> <!-- <div style="width: 200px;height: 20px;background-color: black;border-radius: 2px;"> </div> --> </div>这么一个小三角形就出来了,然后打开下面的那个div
<div style="width:300px;height: 300px;background-color: #bfa;"> </div> <div style="position: absolute;top:20px"> <div style="width: 0; height: 0; border: 5px solid; border-color: transparent transparent black;margin-left: 10px;"> </div> <div style="width: 200px;height: 20px;background-color: black;border-radius: 2px;color:white;font-size: 12px;line-height: 20px;padding-left: 5px;"> 请输入正确的用户名 </div> </div>是不是就有内个味了。
好了回到我们的项目中,需要在我们的表单输入框的父节点下,加上我们刚才的代码,并且添加控制显示影藏的属性
<a-form-model-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" prop="username" > <a-input v-model="loginVo.username" placeholder="账号" @change="changeUsername" > <a-icon slot="prefix" type="user" /> </a-input> <div class="custom-tooltip" v-show="showUsername"> <div class="custom-srrow"></div> <div class="custom-info">{{ usernameMsg }}</div> </div> </a-form-model-item>样式定义如下:
.custom-tooltip { position: absolute; top: 20px; z-index: 999; .custom-srrow { width: 0; height: 0; border: 5px solid; border-color: transparent transparent black; margin-left: 10px; } .custom-info { width: 120px; height: 25px; background-color: black; border-radius: 2px; color: white; line-height: 25px; padding-left: 10px; } }这样的话基本就实现了,这种提示信息的展示。但是还有点美中不足的就是,这么写每一个输入框都需要写一份,那最好的办法就是封装成组件,这个我先不打算做,因为还没考虑好,是单独做个组件还是和输入框一起做一个组件,然后引入到form表单中。如果感兴趣大家可以自己尝试一下。
这里不再赘述,并附上所有的代码
<template> <div class="login-container"> <div class="login-top"> <div class="left">xxxxxxx</div> <div class="right"> <span class="window-min" @click="windowMin"> <a-icon type="minus" /> </span> <span class="window-close" @click="windowClose"> <a-icon type="close" /> </span> </div> <img src="~@/assets/logo123.png" class="logo" /> </div> <div class="login-form"> <a-form-model ref="loginForm" :model="loginVo" :rules="rules"> <a-form-model-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" > <a-form-model-item prop="address" :style="{ display: 'inline-block', width: 'calc(60%)' }" > <a-input v-model="loginVo.address" placeholder="IP地址" @change="changeAddress" > <a-icon slot="prefix" type="global" /> </a-input> <div class="custom-tooltip" v-show="showAddress"> <div class="custom-srrow"></div> <div class="custom-info">{{ addressMsg }}</div> </div> </a-form-model-item> <a-form-model-item prop="port" :style="{ display: 'inline-block', width: 'calc(40%)' }" > <a-input-number @change="changePort" v-model="loginVo.port" placeholder="端口" :max="65535" :min="1" /> <div class="custom-tooltip" v-show="showPort"> <div class="custom-srrow"></div> <div class="custom-info">{{ portMsg }}</div> </div> </a-form-model-item> </a-form-model-item> <a-form-model-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" prop="username" > <a-input v-model="loginVo.username" placeholder="账号" @change="changeUsername" > <a-icon slot="prefix" type="user" /> </a-input> <div class="custom-tooltip" v-show="showUsername"> <div class="custom-srrow"></div> <div class="custom-info">{{ usernameMsg }}</div> </div> </a-form-model-item> <a-form-model-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" prop="password" > <a-input @change="changePassword" v-model="loginVo.password" type="password" placeholder="密码" > <a-icon slot="prefix" type="lock" /> </a-input> <div class="custom-tooltip" v-show="showPassword"> <div class="custom-srrow"></div> <div class="custom-info">{{ passwordMsg }}</div> </div> </a-form-model-item> <a-form-model-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" > <a-checkbox @change="rememberPassword" v-model="rememberMe" class="record-password" > 记住密码 </a-checkbox> </a-form-model-item> <a-form-model-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" > <a-button type="primary" @click="onSubmit" class="login-button" >登陆</a-button > </a-form-model-item> </a-form-model> </div> </div> </template> <script> import ipRules from "@/utils/ipRules"; import { formItemLayout, formTailLayout } from "@/utils/const"; import { message } from "ant-design-vue"; const { session } = require("electron"); export default { name: "login-page", data() { return { loginVo: { username: "", password: "", address: localStorage.getItem("address"), port: localStorage.getItem("port"), type: "login", }, showUsername: false, showPassword: false, showAddress: false, showPort: false, rememberMe: false, addressMsg: "请输入你的IP地址", usernameMsg: "请输入你的账户", passwordMsg: "请输入你的密码", portMsg: "请输入你的服务端口", formItemLayout, formTailLayout, rules: {}, }; }, created() { console.log("localStorage", localStorage); }, methods: { windowMin() { this.$electron.ipcRenderer.send("window-min"); }, windowClose() { this.$electron.ipcRenderer.send("window-close"); }, onSubmit() { if (this.loginVo.address === "" || this.loginVo.address === null) { this.showAddress = true; return; } if (this.loginVo.port === "" || this.loginVo.port === null) { this.showPort = true; return; } if (this.loginVo.username === "" || this.loginVo.username === null) { this.showUsername = true; return; } if (this.loginVo.password === "" || this.loginVo.password === null) { this.showPassword = true; return; } this.$electron.ipcRenderer.send("login", this.loginVo); this.$electron.ipcRenderer.once("login-message", (event, arg) => { let result = JSON.parse(arg); console.log("登陆结果:", result); if (result.type === "0") { localStorage.setItem("address", this.loginVo.address); localStorage.setItem("port", this.loginVo.port); localStorage.setItem("user", JSON.stringify(result)); if (rememberMe) { localStorage.setItem("username", this.loginVo.username); localStorage.setItem("password", this.loginVo.password); } this.$router.push("/home"); } else { this.$message.error(result.msg); } }); }, rememberPassword() {}, changeAddress() { if (ipRules.test(this.loginVo.address)) { this.showAddress = false; } else { this.showAddress = true; this.addressMsg = "你输入的IP地址规范"; } }, changePort() { if (this.loginVo.port === "" || this.loginVo.port === null) { this.showPort = true; } else { this.showPort = false; } }, changeUsername() { if (this.loginVo.username === "" || this.loginVo.username === null) { this.showUsername = true; } else { this.showUsername = false; } }, changePassword() { if (this.loginVo.password === "" || this.loginVo.password === null) { this.showPassword = true; } else { this.showPassword = false; } }, }, }; </script> <style lang="scss"> .login-container { width: 100vw; height: 100vh; -webkit-app-region: drag; //无边框下设置窗口可拖拽 font-size: 14px; .login-top { position: relative; width: 100%; height: 70px; background-color: #f3eff1; color: gray; .left { float: left; height: 30px; line-height: 30px; padding-left: 10px; } .right { float: right; .window-min, .window-close { width: 30px; height: 30px; line-height: 30px; display: inline-block; text-align: center; -webkit-app-region: no-drag; //事件处可以禁用拖拽区域 } .window-min:hover { background-color: rgb(209, 207, 207); } .window-close:hover { background-color: red; } } .logo { position: absolute; top: 40px; left: 160px; width: 70px; } } .login-form { width: 70vw; height: 150px; position: absolute; left: 15vw; top: 120px; // 去除input 默认的蓝色边框 .ant-input:focus, .ant-input-number:focus { border: none; border-bottom: 1px solid #12b7f5 !important; box-shadow: none; } .ant-row { margin-bottom: 0px; font-size: 12px; -webkit-app-region: no-drag; //事件处可以禁用拖拽区域 } .ant-input, .ant-input-number { width: 100%; border: none !important; border-bottom: 1px solid #e9e3e3 !important; border-radius: 0px; font-size: 12px; } .ant-input-number { height: 31px; } .ant-input-prefix { left: 0px; } .ant-input:hover,.ant-input-number:hover { border-bottom: 1px solid #bebebe ; } .login-button { border: none; display: block; width: 100%; background-color: #c22064; color: white; } .record-password { font-size: 12px; color: #b8b7b7; } } .custom-tooltip { position: absolute; top: 20px; z-index: 999; .custom-srrow { width: 0; height: 0; border: 5px solid; border-color: transparent transparent black; margin-left: 10px; } .custom-info { width: 120px; height: 25px; background-color: black; border-radius: 2px; color: white; line-height: 25px; padding-left: 10px; } } } </style>好了,基本上就这么多了,如果有问题,欢迎留言!