效果图
前言
作为一名学习服务端开发方向的学生来说,虽然自己有学过前端,但并不算深入,而且个人觉得写前端比较繁琐,再加上自己的审美一般,所以真写不出能让自己满意的界面。对于大多数后端程序员来说,应该宁愿拿现成的静态资源模板来改,也不愿意自己从无到有去写吧?即使,有了解过一些前端框架,写出让人觉得舒服的界面还是比较费劲。
上面登录表单,看起来还算比较舒服,是我JavaWeb课程的一个小作业,我并没有用老师提供的静态页面,因为太难看了。。哈哈哈。
下面我站在前端角度,分析一下如何实现这样的一个登录表单。当然我也会简要说明一下所需要的后端接口。
详情参看代码,代码中写了简要注释,标出了要注意的细节。
分析
1、输入框(包括绿色的ok提示,红色的error提示)、按钮等等,这些组件,我们当然不会自己手写啦,手写也很难做出这么好看。我选用了 bootstrap 这个比较容易入手的前端框架,说实话,我也没有专门学过,直接去官网看文档就行了,然后复制粘贴,再自己微调一下样式。自己在写的时候,也要注意用上 bootstrap 的响应式特点,让界面能够适应手机设备。
2、从上面的 gif 可以看出来,这个登录表单的验证做的非常 严格和细致。我们来分析一下这个表单验证的逻辑,究竟 细致 到什么程度?
验证规则
(1)邮箱不能为空、邮箱必须符合正则表达式
(2)异步请求后端来验证 邮箱,如果邮箱存在,后端会返回账户的头像URL,前端会更新顶部的圆形头像
(3)密码不能为空
(4)异步验证验证码
验证触发
当某个输入框失去焦点时,验证该表单项是否符合 相应的规则(可能不止一条,比如说邮箱)。只要有任意一条规则不通过,点击“登录”按钮就无法提交表单。
信息提示
信息提示包括两种:绿色的OK提示,红色的error提示。两种提示 各自 包括了3个细节:文字提示、图标提示、输入框的边框变色。当然,这些样式都来自于 bootstrap,但是我们需要根据验证触发和验证来控制样式的切换。
即使用 jquery 来写,这样复杂繁琐的逻辑验证,写起来也会非常棘手,因为细节太多了!!!这个时候我们就要用到了一款基于jquery的插件了: jquery.validation.min.js 。这个插件可以直接去官网下载,我这里在网上找到一个别人的修改版,在原有 jquery.validation 增加了一些常用的验证规则。当然,干这个的,肯定是比较强的前端工程师。学后端的我,目前没有这个能力。
后端接口
1、验证邮箱是否存在,存在返回用户头像:/account/photo
2、生成验证码:/account/captcha/generate
3、验证验证码是否正确:/account/captcha/validate
4、登录:/account/login
接口都比较简单,传参我也省略了。学过的SpringBoot应该很容易写出来。学习需要,我是用Maven徒手整合SSM来写的。用起来真没有SpringBoot舒服。
资料
boostrap官网:https://v3.bootcss.com/css/
jquery.validation 增强版:https://download.csdn.net/download/qq_43290318/13109073
jquery.validation 的使用详解:https://blog.csdn.net/wangxiaoan1234/article/details/77466720
代码
login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String contextPath = request.getContextPath();
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- 不设置的话,手机端不会进行响应式布局 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登录</title>
<!-- 引入Bootstrap核心样式文件(必须) -->
<link rel="stylesheet" href="lib/bootstrap/css/bootstrap.min.css">
<!-- 你自己的样式或其他文件 -->
<link rel="stylesheet" href="css/login.css">
<!--站点图标-->
<!-- ... -->
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-4 col-sm-offset-4 panel panel-default login-box">
<div class="panel-body">
<img class="img-circle photo" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605206616385&di=5a3e31c19b07f422adf49c37505f5126&imgtype=0&src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F202007%2F07%2F20200707113705_VFJvw.thumb.400_0.jpeg"
alt="头像">
<form id="loginForm" onSubmit="return false;">
<!--
关于bootstrap输入框提示
父容器div的has-feedback不能少,has-success和has-error选其一
-->
<div class="form-group has-feedback">
<label for="email">邮箱</label>
<!-- 错误提示信息 -->
<span class="error-msg"></span>
<input type="email" class="form-control" id="email" name="email" placeholder="邮箱">
<!-- 图标,打勾或者打叉。glyphicon-ok和glyphicon-remove -->
<span class="glyphicon form-control-feedback"></span>
</div>
<div class="form-group clear-float has-feedback">
<label for="password">密码</label>
<span class="error-msg"></span>
<input type="password" class="form-control" id="password" name="password" placeholder="密码">
<span class="glyphicon form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<label for="captcha">验证码</label>
<span class="glyphicon glyphicon-refresh refresh" onClick="refreshCaptcha()"></span>
<span class="error-msg"></span>
<input type="text" maxlength="4" class="form-control captcha" id="captcha" name="captcha"
placeholder="验证码" >
<span class="glyphicon form-control-feedback captcha_icon"></span>
</div>
<!-- 注意表单中必须有type="submit"的按钮,否则表单验证通过后,无法进入回调函数submitHandler() -->
<button type="submit" class="btn btn-success login-btn">登 录</button>
<button type="button" class="btn btn-link to-register">还没有账号?去注册 >></button>
</form>
</div>
</div>
</div>
</div>
<script src="lib/jquery/jquery.min.js"></script>
<script src="lib/jquery/jquery.validation.min.js"></script>
<!-- 引入所有的Bootstrap的JS插件 -->
<script src="lib/bootstrap/js/bootstrap.min.js"></script>
<script src="js/login.js"></script>
</body>
</html>
login.css
下面的css都比较基础,主要我学习web前端选修课时,学的也不算很深入,都是一些基础的东西。。虽是如此,但是因为不熟,调样式的时候踩了不少坑。
.clear-float {
clear: both;
}
.login-box {
/* border: 1px solid #000; */
padding: 0 16px;
border-radius: 8px;
margin-top: 100px;
box-shadow: 0 0 10px #ddd;
}
.photo {
/* 一定要设置为块状元素,否则margin auto不生效 */
display: block;
width: 100px;
height: 100px;
border: 1px solid #eee;
padding: 4px;
box-shadow: 0 0 10px #ddd;
background-color: #fff;
margin: 0 auto;
}
.login-btn {
width: 100%;
/* 设置line-height比设置height更好 */
line-height: 24px;
/* height: 24px; */
font-size: 17px;
margin: 8px 0;
}
.to-register {
float: right;
padding-right: 0;
}
.error-msg {
/* 不设置block,margin不起作用 */
display: block;
float: right;
color: #a94442;
font-weight: bold;
}
.captcha {
background-image: url(/account/captcha/generate);
background-repeat: no-repeat;
background-position: right;
/* 如果不加!important,就无法生效 */
padding-right: 120px !important;
}
.refresh {
margin-left: 4px;
cursor: pointer;
}
/* 复写.form-control的right属性,因为验证码输入框右端的提示图标遮挡住验证码 */
.captcha_icon {
/* 如果不加!important,就无法生效 */
right: 100px !important;
}
login.js(重头戏)
// 点击刷新图标,刷新验证码
function refreshCaptcha() {
var url = '/account/captcha/generate?rand=' + Math.random();
$('#captcha').css({
'background-image': 'url(' + url + ')'
});
$('#captcha').val('');
var parentDiv = $('#captcha').parents('div.has-feedback');
var iconSpan = parentDiv.children('span.glyphicon');
var msgSpan = parentDiv.children('span.error-msg');
// 父亲div移除样式
parentDiv.removeClass('has-success has-error');
// 图标设置移除样式
iconSpan.removeClass('glyphicon-ok glyphicon-remove');
msgSpan.html('');
}
$(function() {
// 登录表单验证
$("#loginForm").validate({
// 表单验证成功通过后的回调
submitHandler: function(form) {
//console.log(form);
//form.submit();
// 异步提交表单
// Ajax提交数据
var email = $('#email').val();
var password = $('#password').val();
var captcha = $('#captcha').val();
$.ajax({
url: '/account/login', // 提交到controller的url路径
type: "POST", // 提交方式
data: {
email: email,
password: password,
captcha: captcha
},
dataType: "json", // 服务器端返回的数据类型
success: function (res) {
if (res.code === 2000) {
window.location.href = 'index.jsp';
} else {
alert('密码错误');
location.reload();
}
}
});
},
// 错误提示
errorPlacement: function(error, element) { // 错误信息,input表单项
// 找到父亲div
var parentDiv = element.parents('div.has-feedback');
// 找到显示msg的span
var msgSpan = parentDiv.children('span.error-msg');
// 找到图标的span
var iconSpan = parentDiv.children('span.glyphicon');
// 设置错误信息
msgSpan.html('').append(error);
// 父亲div添加样式has-error
parentDiv.addClass('has-error');
// 图标设置样式glyphicon-remove
iconSpan.addClass('glyphicon-remove');
},
// 成功时,移除
success: function(element) {
// 找到父亲div
var parentDiv = element.parents('div.has-feedback');
// 找到显示msg的span
var msgSpan = parentDiv.children('span.error-msg');
// 找到图标的span
var iconSpan = parentDiv.children('span.glyphicon');
// 设置错误信息
msgSpan.html('');
// 父亲div移除样式has-error,并添加样式has-success
parentDiv.removeClass('has-error');
parentDiv.addClass('has-success');
// 图标设置样式glyphicon-remove,并添加样式glyphicon-ok
iconSpan.removeClass('glyphicon-remove');
iconSpan.addClass('glyphicon-ok');
},
ignore: ".ignore",
// 表单验证规则
rules: {
email: { // input的name属性
required: true,
email: true,
//isMobile: true
remote: {
cache: false,
async: true,
type: 'GET',
url: '/account/photo',
data: { // 请求所需的参数列表
email: function() {
return $('#email').val();
}
},
// 由于remote需要的返回值是布尔值(false表示不通过),而实际返回值是一个封装对象
// 所以需要dataFilter对返回的封装对象进行预处理,并给remote返回所需的布尔值
dataFilter: function(jsonStr, type) {
var res = JSON.parse(jsonStr);
console.log(typeof(res));
console.log(res);
console.log(res.code);
var isOk = (res.code === 2000);
console.log(isOk);
// 如果email存在且data部分不为null(有头像),显示用户的头像
if (isOk && res.data != null) {
$('img.photo').attr('src', res.data);
}
return isOk;
}
}
},
password: {
required: true
},
captcha: {
required: true,
remote: {
cache: false,
async: true,
type: 'GET',
url: '/account/captcha/validate',
data: { // 提交给服务端的数据(键值对)
captcha: function() {
return $('#captcha').val();
}
},
dataFilter: function(jsonStr, type) {
var res = JSON.parse(jsonStr);
var isCorrect = (res.code === 2000);
if (!isCorrect) { // 验证码错误
refreshCaptcha();
}
return isCorrect;
}
}
}
},
messages: { // 与验证规则一一对应的消息提示
email: {
required: '邮箱不能为空',
email: '邮箱格式错误',
remote: '该邮箱尚未注册'
},
password: {
required: "密码不能为空"
},
captcha: {
required: "验证码不能为空",
remote: "验证码错误,已经刷新"
}
},
onkeyup: function(element, event) {
var name = $(element).attr("name");
if (name == "captcha") {
//不可去除,当是验证码输入必须失去焦点才可以验证(错误刷新验证码)
return false;
}
}
});
});