【Eelectron-vue】构建桌面应用(15)- 修改antdv输入框默认样式及表单校验提示信息_逆风飞翔的猿的博客-程序员秘密

技术标签: 修改antdv输入框样式  Electron  自定义表单提示信息位置  antdv输入框无边框  自定义表单校验  

1.前言

现在的客户端与以往的有些区别,比如登录页面的输入框,大都废弃了之前的登录框样式,而采用只存在底边的输入框。还有就是输入框的错误信息都会采用浮层的形式提示,而不是瀑布流的方式去提示。

在借鉴了其他客户端的方式,做了一些调整,这些调整都需要对antdv组件进行从新修正。因为我是用的是electron-vue+antdv的架构去构建的应用。

2.修改输入框的默认样式

先看一下效果图如下:
在这里插入图片描述

2.1 修改border仅保留底边

我们要做的事情就是:

  1. 修改输入框为只有底边
  2. 当输入框获取悬浮事件时,改变border的颜色
  3. 当输入框获取焦点事件时,修改边框的颜色

2.1.1 去除输入框其他边框

antdv的处理方式和普通的输入框不一样,我们可以先设置一下输入框的border,因为是antdv提供的组件,所以我们需要F12查看输入框绑定的样式,然后去覆盖该样式的border
在这里插入图片描述
如上图所示,我们直接去覆盖ant-inputant-input-number的样式。

.ant-input,
.ant-input-number {
    
  width: 100%;
  border: none !important;
  border-bottom: 1px solid #e9e3e3 !important;
}

这样就可以实现只保留底边。

2.1.2 鼠标移动时改变边框

那么我们现在来做进一步的优化,就是当我们移动到上面时去改变边框的颜色,这个也简单只要设置ant-inputhover事件就行了。

.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="[email protected]/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>

好了,基本上就这么多了,如果有问题,欢迎留言!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/chenqk_123/article/details/109216158

智能推荐

android 使用xml layer-list绘制阴影_liuhaha1015的博客-程序员秘密

&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;layer-list xmlns:android="http://schemas.android.com/apk/res/android"&gt; &lt;item&gt; &lt;shape android:shape="rectangle"&gt; &lt;padding android:bottom="2dp" ...

[总线]I2C通信_源自1994的博客-程序员秘密

I2C总线SCL:时钟SDA:数据接线主设备和从设备SDA脚——SDA脚SCL脚——SCL脚传输数据I2C有设备地址开始:SCL为高电平时,SDA由高电平向低电平跳变(下降沿)结束:SCL为高电平时,SDA由低电平向高电平跳变(上升沿)...

Objective-C 面向对象初体验_T__Mad的博客-程序员秘密

1.类的声明[email protected]开始,@end结束1-2变量和方法不能混在一起,实例变量必须放在{}内1-3没有static、const的实例变量。在.m文件中可以声明static静态属性:属于类,不属于对象。同样,也没有const方法,没有友元方法。1-4“-”声明的方法是protect,“+”声明是public,如果一个方法在.h中没有声明,但

百度API制作地图上的拆线图(经纬度)与标注!_Rundy_Deng的博客-程序员秘密

百度API制作地图上的拆线图(经纬度)与标注!&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=utf-8&quot; pageEncoding=&quot;utf-8&quot;%&amp;gt;&amp;lt;c:set var=&quot;contextPath&quot; value=&quot;${pageContext.request.contextPath}&quot;&amp;g

软件质量可视化与软件测试_aibo4482的博客-程序员秘密

最紧我正在天津大学软件学院书记王赞手下做一个关于软件质量可视化的项目,此项目将软件以直观可视化的方式直观的展现出来。用可视化化的元素表现软件中类之间的耦合度(LCOM),类之间继承深度(DIT),类之间的重复度(CBO),类中方法数量(WMC),以及类中注释比例。下面简单阐述一下软件可视化的重要性以及和软件测试的关系。其实关于软件质量分析可视化的问题已经在十年前的一篇名为Visu...

随便推点

oracle12c r2静默安装,Oracle 12c 静默安装(脚本自动化)_weixin_42304279的博客-程序员秘密

Storyboard中segue(转场)使用源引:http://www.2cto.com/kf/201210/161737.html 一.视图切换类型介绍在storyboard中,segue有几种不同的类型,在iphone和ipad的开发中,s ...BZOJ2819 Nim(DFS序)题目:单点修改.树链查询. 可以直接用树链剖分做.. 修改是O(QlogN),查询是O(QlogNlogN),Q...

细节(一)Android中Toast不显示问题_GoAndSeeSee的博客-程序员秘密

Toast.makeText(mActivity, "发送消息不能为空", Toast.LENGTH_SHORT).show(); 大多数是因为后面的.show()方法没有写。另外需要注意的是:子线程不能更新UI、如果有需要那么就post到Handler去处理更新UI

大咖|英特尔中国研究院院长宋继强:我们是如何与李宇春打造全球第一支三维人脸特效的音乐视频的_大数据文摘的博客-程序员秘密

上个月底,李宇春一支据说筹备时间长达6个月的mv正式发布。这首基于英特尔人工智能技术的MV,将电子曲风的浪漫情歌与尖端科技结合,一上线即火遍全网。在11月15日刚刚结束的2017英特尔人工智能大会上,英特尔中国研究院院长宋继强揭秘了这支有三维人脸特效的音乐视频是如何实现的。此外,英特尔全球副总裁兼中国区总裁杨旭本次大会上宣告了英特尔在人工智能领域的三大战略方向:创新技术、广泛合作、

MySQL数据库-数据的排序操作(order by) 命令演示_数据库添加数据并排序_HXH.py的博客-程序员秘密

排序order by 字段** asc从小到大排列,即升序**desc从大到小排序,即降序查询年龄在18到34岁之间的男性,按照年龄从小到到排序select * from students where (age between 18 and 34) and gender=1;select * from students where (age between 18 and 34) a...

tcpdf把php转换html,TP5下利用TCPDF类把HTML页面,数据库数据转PDF格式_Lucy-露西娅的博客-程序员秘密

1.composer下载TCPDF插件https://packagist.org/packages/tecnickcom/tcpdf2.把TCPDF类引入到 vendor目录下3.thinkphp5下新建Application/common/common/function.phpSetCreator(PDF_CREATOR);$pdf-&gt;SetAuthor("作者");$pdf-&gt;Se...

推荐文章

热门文章

相关标签