1.前言
现在的客户端与以往的有些区别,比如登录页面的输入框,大都废弃了之前的登录框样式,而采用只存在底边的输入框。还有就是输入框的错误信息都会采用浮层的形式提示,而不是瀑布流的方式去提示。
在借鉴了其他客户端的方式,做了一些调整,这些调整都需要对antdv
组件进行从新修正。因为我是用的是electron-vue
+antdv
的架构去构建的应用。
2.修改输入框的默认样式
先看一下效果图如下:
2.1 修改border仅保留底边
我们要做的事情就是:
- 修改输入框为只有底边
- 当输入框获取悬浮事件时,改变border的颜色
- 当输入框获取焦点事件时,修改边框的颜色
2.1.1 去除输入框其他边框
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;
}
这样就可以实现只保留底边。
2.1.2 鼠标移动时改变边框
那么我们现在来做进一步的优化,就是当我们移动到上面时去改变边框的颜色,这个也简单只要设置ant-input
的hover
事件就行了。
.ant-input:hover,.ant-input-number:hover {
border-bottom: 1px solid #bebebe !important;
}
鼠标悬浮时,改变边框的颜色。
2.1.3 输入框获取焦点时改变输入框的颜色
我本来以为这个也简单,当输入框获取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
,这样获取焦点事件的权重比悬浮事件的权重要高,当鼠标离开输入框,在回到输入框时,也不会去覆盖获取焦点的颜色。
2.2 修改antdv表单校验提示
我发现现在大多数客户端的校验提示信息都不是瀑布流的方式(即在输入框下面留了一大片空白,来显示错误信息),而是通过浮层的方式去处理这个问题。
传统的方式:
这种在客户端尤其是只有底边的的情况下,会有很大的空白,页面显得大而宽泛。需要给登陆页面设置较大的宽和高。而正常的登陆基本上就是用户名密码登陆按钮这些,不需要太多留白。在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>
好了,基本上就这么多了,如果有问题,欢迎留言!