【化学药品管理平台——Servlet+Jsp实现 0101】环境搭建和前端页面
前言
从17年暑假学习JavaWeb到现在,终于断断续续学完了各种基础,和几个框架。由于平时需要顾着科研,学习技术的时间非常少,所以过程比较艰难。
时间都是挤出来的。没有错,时间是有的,只是看大家愿不愿意去挤。
博主本人是生命科学学院的在读研究生,于是第一个项目就写了一个药品管理的网页应用。模仿的是一个在高校用的比较多,目前感觉开发的不错的一个药品管理方面的网站。
为了将学习过的技术投入应用,这个管理平台我写了三个版本,除了本篇的Servlet+Jsp,还有后面用SSH和SSM框架写的两个版本。
项目源码连接(旧的源码有很多瑕疵)
本项目将分三个方面讲解:前端页面,后台用户,后台库存。同时准备写三个篇幅。
开发环境
服务器:apache-tomcat-7.0.52
数据库:MySQL Server 5.5
IDE:Eclipse
数据库
本来应该放在下一篇讲,因为我先讲前端,要涉及到后端传来的一些对象,所以列一个数据库表结构。此项目只有两个表,一个user用户,一个agents药品,一对多的关系。
前端页面
主页home.jsp
前端总共六个jsp页面,本篇只介绍head.jsp和agents_list.jsp,其他在后面的篇幅中会有讲解。
主要页面是agents_list.jsp,分为三部分,页面顶端,左侧显示其他信息部分(暂时没有开发),和右侧入库功能及展示药品信息部分。通过设置div的float属性来设计页面布局。页面样式主要由bootstrap完成。页面功能和和特效由Javascript和Jquery来实现。
前端页面所有可能引入的指令标签、外部样式表和外部js如下。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery.validate.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/bootstrap.min.js"></script>
<link href="${pageContext.request.contextPath }/css/bootstrap.min.css" rel="stylesheet">
<link href="${pageContext.request.contextPath }/css/agents_list.css" rel="stylesheet">
</head>
一、页面顶端
先说页面顶端吧,一个head.jsp,动态包含在其他几个页面中。head.jsp包含四个按钮,分别连接到首页、库存展示、登陆功能和退出功能。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- bootstrap导航条 -->
<ul class="nav nav-tabs">
<li style="position: relative; left:30%">
<a href="${pageContext.request.contextPath }/home.jsp">首页</a>
</li>
<!-- 想要安排两个相邻的按钮,可以将其相对位置设置为一样的 -->
<li style="position: relative; left:30%">
<a href="${pageContext.request.contextPath }/agents?method=findAgentsByPage&currPage=1">库存</a>
</li>
<!-- 登录和注册 -->
<c:if test="${empty user }">
<li style="position: relative; left:70%"><a href="${pageContext.request.contextPath }/user?method=loginUI">登录</a></li>
<li style="position: relative; left:70%"><a href="${pageContext.request.contextPath }/user?method=registUI">注册</a></li>
</c:if>
<!-- 用户名和退出登录 -->
<c:if test="${not empty user }">
<li style="position: relative; left:68%"><a href="javascript:void(0);">${user.uname }_${user.ugrade }:您好</a></li>
<li style="position: relative; left:68%"><a href="${pageContext.request.contextPath }/user?method=logout">退出</a></li>
</c:if>
</ul>
</body>
</html>
四个按钮是用bootstrap的li标签写的,用relative属性设定相对位置,设定相同参数的相对位置,则会有两个按钮先后排列的效果。
首页即普通a标签连接,取出部署的应用程序名,设置绝对路径。
库存a标签是一个servlet,同时传递了要执行的方法参数method和一个currPage参数,其作用会在讲后台的时候介绍。
下面是jstl的c:if标签判断session域中是否有EL表达式获取的user对象,即是否登陆过。若未登陆,则最后两个按钮完成登陆和注册功能;若已登陆,则完成显示用户名和退出登陆的功能。
二、入库
入库及药品展示都在agents_list.jsp中,代码部分从前往后分别是动态包含页面顶端,一个div左侧页面,一个div右侧页面。下面主要看右侧页面的药品入库部分的代码。
入库的div首先在agents_list.css设置为隐藏(display:none),点击入库按钮触发点击事件,可以让入库表单下滑出来。点击表单中的取消按钮,可以让入库表单上滑隐藏。
入库表单隐藏
入库表单滑出
<!-- jsp代码:隐藏的入库表单 -->
<div id="agentsLoad" style="float: left; width: 76%;">
<form id="formId" action="${pageContext.request.contextPath }/agents?method=add" method="post">
<div class="form-group">
<table style="border-collapse:separate; border-spacing:6px;">
<tr>
<th>品名<input type="text" class="form-control" name="aname"/></th>
<th>品牌<input type="text" class="form-control" name="aboard"/></th>
<th>货号<input type="text" class="form-control" name="aNo"/></th>
<th>CAS<input type="text" class="form-control" name="aCAS"/></th>
<th>供应商<input type="text" class="form-control" name="asup"/></th>
</tr>
<tr>
<th>数量<input type="text" class="form-control" name="count"/></th>
<th>单位<input type="text" class="form-control" name="aunit"/></th>
<th>规格<input type="text" class="form-control" name="aspec"/></th>
<th>存放地<input type="text" class="form-control" name="astore"/></th>
<th>
<input type="submit" class="btn btn-default" value="提交" style="margin: 20px 0 0 0"/>
<input type="button" class="btn btn-default" value="取消" onclick="fadeOut()" style="margin: 20px 0 0 2px"/>
</th>
</tr>
</table>
</div>
</form>
</div>
/* css代码:隐藏入库表单 */
#agentsLoad{
display: none;
background-color: white;
position: absolute;
top: 108px;
z-index: 100;
}
<script type="text/javascript">
/* js代码:入库表单的滑入滑出 */
function fadeIn(){
$("#agentsLoad").slideDown("slow");
}
function fadeOut(){
$("#agentsLoad").slideUp("slow");
}
</script>
三、药品展示
药品展示由单页的药品数据列表和分页显示按钮组成,先说药品列表。
(1)药品列表
用table显示药品,th为表头,tr为行。同样,利用jstl的c:forEach标签和El,循环显示多列后台传来的药品数据。首先获取一个参数为agents的list,然后将var设为单个agents。再将agents的每个属性传入每个tr的td中。
<!-- 药品显示列表 -->
<table class="table" id="atable">
<thead>
<tr>
<th>品名</th>
<th>图片</th>
<th>品牌</th>
<th>货号</th>
<th>CAS</th>
<th>规格</th>
<th>数量</th>
<th>单位</th>
<th>存放地</th>
<th>供应商</th>
<th>入库人</th>
<th>更新时间</th>
<th>损耗</th>
</tr>
</thead>
<!-- jstl的forEach语句展示EL获得从后台传来的数据 -->
<tbody>
<c:forEach items="${ab.list }" var="a">
<tr>
<td>${a.aname }</td>
<!-- 鼠标悬停放大图片,以及点击添加图片 -->
<td class="picsTd">
<div id="picsMagnify">
<img width="100%" alt="点击此处添加图片" src="${pageContext.request.contextPath }/${a.aimg}" onclick="picsAdd(this)">
<!-- 此处放置一个隐藏的input标签,准备给点击事件传递agents的aname和aid属性 -->
<input name="${a.aname }" value="${a.aid }" style="display: none;" />
</div>
</td>
<td>${a.aboard}</td>
<td>${a.aNo}</td>
<td>${a.aCAS}</td>
<td>${a.aspec}</td>
<td>${a.count}</td>
<td>${a.aunit}</td>
<td>${a.astore}</td>
<td>${a.asup}</td>
<td>${a.user.uname}</td>
<td>${a.adate}</td>
<!-- 点击修改药品损耗情况 -->
<td>
<button type="button" class="btn btn-default" name="${a.aid }" value="${a.count}" onclick="showDiv(this)" >损耗</button>
<%-- <button type="button" name="${a.aname }" value="${a.aid }" onclick="picsAdd(this)">添加图片</button> --%>
</td>
</tr>
</c:forEach>
<tbody>
</table>
(2)放大添加图片
.picsTd中的图片展示有一个细节,即鼠标悬停可放大图片,点击可添加图片。
.picsTd{position:relative; width:45px; height:45px}
#picsMagnify{
width: 45px;
height: 30px;
background-color: #FFFFFF;
position:absolute;
left:0px;
top:0px;
z-index:10;
}
#picsMagnify:hover{
background-color: #FFFFFF;
width:300px; height:200px;
left:-50px;
top:-50px;
z-index:100;
box-shadow:0px 0px 5px 0px #000
}
.picsTd设置列表图片的div大小,#picsMagnify设置鼠标非悬停状态的图片大小,#picsMagnify:hover设置鼠标悬停状态图片大小。想要图片悬停前后缩放比例不变的办法就是,设置<img width="100%">,然后上级标签固定了width或height尺寸,所以#picsMagnify和#picsMagnify:hover其实只需要设置width或者height就行。设置z-index可以使放大后的图片显示在页面顶层。
点击放大后的图片可以添加或更新图片。在<img>后面设置一个隐藏的<input>,通过点击事件来传递input中的参数到图片添加弹出层#picsDiv。
<td class="picsTd">
<div id="picsMagnify">
<img width="100%" alt="点击此处添加图片" src="${pageContext.request.contextPath }/${a.aimg}" onclick="picsAdd(this)">
<!-- 此处放置一个隐藏的input标签,准备给点击事件传递agents的aname和aid属性 -->
<input name="${a.aname }" value="${a.aid }" style="display: none;" />
</div>
</td>
<!-- 图片添加弹出层 -->
<div id="picsDiv">
<form id="picsForm" action="${pageContext.request.contextPath }/addImage" method="post" enctype="multipart/form-data">
<div id="picsInner">
<!-- js修改html内容区 -->
</div>
<input type="file" name="aimg" />
<div class="btn_div">
<input type="submit" class="btn btn-default" value="确定" />
<input type="button" class="btn btn-default" value="取消" onclick="picsCl()" />
</div>
</form>
</div>
<script type="text/javascript">
function picsAdd(obj) {
/* 显示隐藏的图片添加区 */
document.getElementById("bg").style.display ="block";
document.getElementById("picsDiv").style.display ="block";
/* 通过点击事件获得的this对象,来回显当前agents对象的aname和aid*/
document.getElementById("picsInner").innerHTML = "<input type='text' class='form-control' name='aname' value='"+obj.nextElementSibling.name+"' readonly='readonly'/><br/>";
document.getElementById("picsInner").innerHTML += "<input type='hidden' class='form-control' name='aid' value='"+obj.nextElementSibling.value+"'/>";
}
/* 取消图片添加弹出层 */
function picsCl(){
document.getElementById("bg").style.display ="none";
document.getElementById("picsDiv").style.display ="none";
}
</script>
点击事件获取this当前对象,然后获取当前对象的下一个兄弟元素nextElementSibling,修改弹出层#picsDiv的html内容,将兄弟元素的属性赋给弹出层#picsDiv的元素。至此,弹出层效果如下。
(3)弹出层
对于弹出层的设计,没有使用bootstrap框架,而是简单地用css做的。因为一开始的想法是,不论前端后端,都从最基本的功能开始设计,然后再用框架做,后来还是想把重心放在后端,弹出层这里就没有再进行优化了。
看下面css代码,#bg是弹出层背景,#picsDiv和#show分别是图片添加和损耗修改弹出层表单。
#bg{
display: none;
position: absolute;
top: 0%;
left: 0%;
width: 100%;
height: 100%;
background-color: black;
z-index:1001;
-moz-opacity: 0.3; /* 提供给mozilla firefox的css属性 */
opacity:.30; /* 兼容IE FF */
filter: alpha(opacity=30); /* IE相应的是 filter:alpha */
}
/* 图片添加弹出层表单 */
#picsDiv{
display: none;
position: absolute;
top: 25%;
left: 32%;
width: 33%;
height: auto;
padding: 8px;
border-radius:12px;
border: 2px solid #ddd;
background-color: white;
z-index:1002;
overflow: auto;
}
/* 损耗修改弹出层表单 */
#show{
display: none;
position: absolute;
top: 25%;
left: 32%;
width: 33%;
height: auto;
padding: 8px;
border-radius:12px;
border: 2px solid #ddd;
background-color: white;
z-index:1002;
overflow: auto;
}
初始的时候,三个部分的display属性都是none,即背景和表单都不显示。当发生相应的点击事件之后,其display属性都变为block,即显示出来。而且,由于z-index属性,背景和表单覆盖在了整个页面的顶端,且表单在最顶端,背景占了整个屏幕。这样就达到了弹出层的效果。点击表单取消按钮,display属性重新变为none。
<script type="text/javascript">
function picsAdd(obj) {
/* 显示隐藏的图片添加区 */
document.getElementById("bg").style.display ="block";
document.getElementById("picsDiv").style.display ="block";
/* 此处代码省略 */
}
/* 取消图片添加弹出层 */
function picsCl(){
document.getElementById("bg").style.display ="none";
document.getElementById("picsDiv").style.display ="none";
}
</script>
(4)损耗功能
列表单行最后一列td是损耗按钮,点击可以对入库的药品数量进行减操作。同样,点击事件将agents的aid和count传入到损耗更新弹出层#show。弹出层将显示该agents的已有数量,和将损耗的数量。
<td>
<button type="button" class="btn btn-default" name="${a.aid }" value="${a.count}" onclick="showDiv(this)" >损耗</button>
</td>
<div id="show">
损耗<hr />
<form id="showForm" action="${pageContext.request.contextPath }/agents?method=update" method="post">
<div id="cal">
<!-- js修改html内容区 -->
</div>
<div class="btn_div">
<input type="submit" class="btn btn-default" value="确定" />
<input type="button" class="btn btn-default" value="取消" onclick="hideDiv()" />
<input type="reset" class="btn btn-default" value="清空" />
</div>
</form>
</div>
这里的损耗数量用validate做了一个表单验证,控制其输入范围在0到已有数量中。这里有一个问题,如果简单的直接设置一个range验证,页面刷新后,第一次点击某个agents的损耗按钮之后,在弹出层点击取消的话,之后不论点击哪一个损耗按钮,出现的表单验证范围都是对于第一个agents的。
所以,在损耗弹出层点击取消按钮之后,需要取消当前的表单验证。试了很多方法,只有下面这种成功了。即先对#showForm建立表单验证,再给#dif添加验证规则。当点击取消按钮的时候,将#dif的验证规则remove掉。这样一来,每次弹出的损耗表单验证就会是对应当前对象的。
<script type="text/javascript">
function showDiv(obj) {
/* 显示隐藏的损耗修改区 */
document.getElementById("bg").style.display ="block";
document.getElementById("show").style.display ="block";
/* 通过点击事件获得的this对象,来回显当前agents对象的名称和数量*/
document.getElementById("cal").innerHTML = "已有数量<input type='text' class='form-control' name='oCount' value='"+obj.value+"' readonly='readonly'/><br/>";
document.getElementById("cal").innerHTML += "损耗数量<input type='text' class='form-control' id='dif' name='dif' /><br/>";
document.getElementById("cal").innerHTML += "<input type='hidden' name='oAid' value='"+obj.name+"'/>";
/* 先建立表单验证 */
$("#showForm").validate({
rules:{
},
messages:{
}
});
/* 再添加验证规则 */
$("#dif").rules("add",{
range:[0, obj.value],
messages:{
range:"请输入{0}~{1}的数量",
}
})
}
/* 取消损耗更新弹出层 */
function hideDiv(){
document.getElementById("bg").style.display ="none";
document.getElementById("show").style.display ="none";
/* 取消弹出层的同时,移除表单验证规则,若不移除,则一直是第一次点击打开的agents对象对应的表单验证规则 */
$("#dif").rules("remove");
}
</script>
(5)分页查询功能
分页查询由所有页码和上一页下一页按钮组成。上一页下一页比较简单,先判断后台传来的ab.currPage是否等于1或是否等于ab.totalPage,即当前页是否是首页或最后一页。若是首页或最后一页,则将首页和最后一页的a标签设置为死链接,不可点击;若不是则可点击完成正常跳转功能。
对于所有页码,若页数过多,则设置forEach语句的begin=ab.currPage-5、end=ab.currPage+4,即只显示当前页前五页到后四页的页码。
之后,判断每个页码是否是当前页,若是则不可点击,若不是则可完成按页查询功能。
<!--分页查询 -->
<div style="width:380px;margin:0 auto;margin-top:20px;">
<ul class="pagination" style="text-align:center; margin-top:10px;">
<!-- 判断当前页是否是首页 -->
<c:if test="${ab.currPage == 1 }">
<li class="disabled">
<a href="javascript:void(0)" aria-label="Previous"><span aria-hidden="true">«</span></a>
</li>
</c:if>
<c:if test="${ab.currPage != 1 }">
<li>
<a href="${pageContext.request.contextPath}/agents?method=findAgentsByPage&currPage=${ab.currPage-1}" aria-label="Previous"><span aria-hidden="true">«</span></a>
</li>
</c:if>
<!-- 展示所有页码 -->
<c:forEach begin="${ab.currPage-5>0?ab.currPage-5:1 }" end="${ab.currPage+4>ab.totalPage?ab.totalPage:ab.currPage+4 }" var="n">
<!-- 判断是否是当前页 -->
<c:if test="${ab.currPage==n }">
<li class="active"><a href="javascript:void(0)">${n }</a></li>
</c:if>
<c:if test="${ab.currPage!=n }">
<li><a href="${pageContext.request.contextPath}/agents?method=findAgentsByPage&currPage=${n}">${n }</a></li>
</c:if>
</c:forEach>
<!-- 判断是否是最后一页 -->
<c:if test="${ab.currPage == ab.totalPage }">
<li class="disabled">
<a href="javascript:void(0)" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</c:if>
<c:if test="${ab.currPage != ab.totalPage }">
<li>
<a href="${pageContext.request.contextPath}/agents?method=findAgentsByPage&currPage=${ab.currPage+1}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</c:if>
</ul>
</div>
<!-- 分页结束-->
结尾
至此,本项目前端部分介绍完了。不知道这种将页面分节式介绍大家好不好理解,看博客的时候,可以先下载此项目代码,将代码先熟悉一遍,再看此文。
内容不多,但是讲解起来还挺需要考量的。博主着急先将第一篇博客写完发布出来,也是想激励自己加快进度写后面的篇幅。希望大家多多支持我在技术方面的努力,比不上科班出身的同行,但要执着追求。各位有什么看法和意见,请多留言。