前言
近期为公司内部开发了一个会议室预约的小工具,作用是可以显示某一时间段的预约信息,并且对预约信息进行增删改查。其中,前端使用html、css 以及原生js,后端使用Servlet,Tomcat以及Highgo Database。以下是整个的设计、开发的过程。
一 、绘制表格
1.表头
行:表头为一周的时间,周一至周日
列:表头为早八点到下午六点半,每半个小时为一个可预约的时间段。
2.绘制表格
设置单元格id 为“_”+行号+“—”+列号形式,其中行号、列号都从1开始。
function createTable() {
var div = document.getElementById("tableContainer");
var timeArr = ['08:00-08:30','08:30-09:00','09:00-09:30','09:30-10:00','10:00-
10:30','10:30-11:00','11:00-11:30','13:00-13:30','13:30-14:00','14:00-
14:30','14:30-15:00','15:00-15:30','15:30-16:00','16:00-
16:30','16:30-17:00','17:00-17:30','17:30-18:00','18:00-18:30'];
var result = '';
result+= "<table id='table'>";
result += '<tr id = "title">'
+ '<th>'
+ '<div class="head-div">'
+ '<div class="head-date">日期</div>'
+ '<div class="head-time">时间</div>'
+ '</div>'
+ '</th>'
+ '<th></th>'
+ '<th></th>'
+ '<th></th>'
+ '<th></th>'
+ '<th></th>'
+ '<th></th>'
+ '<th></th>'
+ '</tr>';
for(var i=0; i<timeArr.length;i++) {
result += "<tr>";
result +='<td>'+ timeArr[i]+ '</td>';
for(var j=0;j<7;j++) {
var a = '_'+ (i+1)+'-'+(j+1);
result+='<td id = "'+a+' onclick="cellOnclick(this)"
ondblclick="celldbClick(this)" onmouseover="cellOver(this)"
onmouseout = "cellMove(this)"></td>';
}
result +="</tr>";
}
result+="</table>";
div.innerHTML = result;
}
3.显示一周时间
时间格式为星期+日期,setDate(date)设置日期,addDate(date,n)方法使日期一次增加,其中,date为当日日期,n为增加的天数。formatDate(date)来规定日期显示的格式,因为date.getDate()返回的天数值如果小于10则不补零,不符合日期的标准格式,因此,天数值小于10要补零。
后因需求变更,需要将可选择时间段放置在标题栏附近,显示从上周开始共计五周的时间,此时间不带年份,因此将formatDate(date)方法优化为可以带年份也可以不带年份的format(date,flag),如果日期格式为年、月、日形式,将flag设为true,如果日期格式为月、日,将flag设为false.
function setDate(date) {
if(localStorage.getItem("current") == "current") {
date = addDate(date,0);
}
if(localStorage.getItem("last") == "last") {
date = addDate(date,-7);
}
if(localStorage.getItem("next") == "next") {
date = addDate(date,7);
}
if(localStorage.getItem("three") == "three") {
date = addDate(date,14);
}
if(localStorage.getItem("four") == "four") {
date = addDate(date,21);
}
// set one week time
var currentDay = date.getDay();
if(currentDay == 0) {
date = addDate(date,-7);
}else{
date = addDate(date,(-currentDay));
}
for(var i=1;i<cellLength;i++) {
cells[i].innerHTML = formatDate(addDate(date,1),true);
}
};
function addDate(date,n) {
date.setDate(date.getDate() + n);
return date;
};
function formatDate(date,flag) {
var year = date.getFullYear()+'.';
var month = (date.getMonth()+1)+'.';
var day = date.getDate();
if(day<10){
day = '0'+ day;
}
if(flag) {
var week =date.getDay();
switch (week) {
case 0:
week="星期日";
break;
case 1 :
week="星期一";
break;
case 2 :
week="星期二";
break;
case 3 :
week="星期三";
break;
case 4 :
week="星期四";
break;
case 5 :
week="星期五";
break;
case 6:
week="星期六";
break;
}
return week + '</br>' + year + month + day;
} else {
var days = month + day;
return days;
}
};
二、预约信息表格设计
因为页面主要以表格为主,且表格所占空间比较大,因此预约时所需填写的内容以弹窗形式展示,双击表格可以进行预约信息的增删改。预约信息为:会议时间,会议主题,参会人数,预约人,设备,招待,备注。
1. 会议时间:开始时间为所选单元格的开始时间,结束时间可选
2. 会议主题:必填
3. 参会人数:必填,且必须填写正整数,如果填写非正整数字符,给出错误提示
4. 预约人:不可填,根据登录信息确定
5. 设备、招待、备注:选填
如果必填内容全部填写完整且符合要求,则进行体交,否则给出提示,不予提交。在弹窗显示的同时,出现遮罩层,避免用户点击页面其他部分。
function isNumber() {
var isNum = /^[1-9]*$/;
var number = document.getElementById("number");
if(!isNum.test(number.value)) {
document.getElementById("alarm").style.visibility = "visible";
document.getElementById("alarm").innerHTML = "请填写数字!";
number.value = "";
}else {
document.getElementById("alarm").style.visibility = "hidden";
document.getElementById("alarm").innerHTML = "";
}
}
三、会议结束时间选择
因为会议结束时间是可选的,且如果某一时段被选中则该时段以后的时间不可选择。因此,在每次出现弹窗时设置会议结束时间下拉框的范围。下拉框的范围为当前选中单元格的结束时间至离它最近的非空单元格的开始时间。因此,before参数为当前选中的单元格的结束时间,end 参数为离它最近的非空单元格的开始时间。
function updateOption (before,after) {
var select = document.getElementById('time');
select.length = 0;
var optionArr =
['08:30','09:00','09:30','10:00','10:30','11:00','11:30','13:30','14:00','14:30','15:00','15:30','16:00','16:30','17:00','17:30','18:00','18:30','19:00'];
var start = optionArr.indexOf(before);
var end = optionArr.indexOf(after);
var newOption = optionArr.slice(start,end);
for(var i=0; i<newOption.length; i++) {
select.options[i] = new Option(newOption[i],newOption[i]);
}
}
var h = t.parentNode.rowIndex+1;
for(var i = h;i<=table.rows.length;i++) {
var cell = document.getElementById('_'+i+'-'+cellIndex);
if(cell == null) {
continue;
}
if(cell.innerHTML != "") {
end = cell.parentNode.firstChild.innerHTML.substr(6,11);
console.log("cell end : " + end);
break;
}
}
四、数据库设计
根据需求将数据库结构设置如下:
五、登录
点击登录按钮,弹出登录对话框。分别填写用户名和密码,其中用户名为邮箱地址。如果登录成功,登录按钮显示“退出”,并在登录按钮前显示用户真实姓名和邮箱地址,如果用户名/密码错误,则给出提示并等待用户重新登录。
通过ajax将用户名、密码作为参数传到Servlet中进行验证,如果验证通过返回用户真实姓名+邮箱地址,失败返回“fail”。
function loginCheck() {
var email = document.getElementById("email").value;
var password = document.getElementById("password").value;
$.ajax({
type:"post",
url:address + '/LoginServlet',
dataType:"text",
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data:{"email":email,"password":password},
success: function(data) {
console.log("data-> " + data);
if(data != "") {
if(data == "fail") {
document.getElementById("login_error").style.visibility = "visible";
document.getElementById("login_error").innerHTML = "用户名或者密码不
对!";
}else{
document.getElementById('loginForm').style.display='none';
console.log("login success");
document.getElementById("loginbtn").innerHTML = "退出";
document.getElementById("loginbtn").style.width = "70px";
document.getElementById("userInfo").innerHTML = data;
localStorage.setItem("user",email);
localStorage.setItem("password",password);
localStorage.setItem("name",data);
window.location.reload(true);
}
}
},
error:function (status,statusText) {
console.log("submit error" + status + statusText);
}
});
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException
{
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=utf-8");
PrintWriter out = null;
out = response.getWriter();
String result = "fail";
String email = request.getParameter("email");
String password = request.getParameter("password");
LdapADHelper ad = new LdapADHelper(email,password);
if(ad.authentication()) {
result = ad.GetADInfo("sn", "mail", email) + " " + email;
}else {
result = "fail";
}
out.write(result);
out.flush();
out.close();
}
public boolean authentication() {
this.host = "192.168.100.100";
this.url = new String("ldap://" + host);
Hashtable<String, String> HashEnv = new Hashtable<>();
HashEnv.put(Context.SECURITY_AUTHENTICATION, "simple"); // LDAP访问安全级别
HashEnv.put(Context.SECURITY_PRINCIPAL, adminName); // AD User
HashEnv.put(Context.SECURITY_CREDENTIALS, adminPassword); // AD Password
HashEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
HashEnv.put(Context.PROVIDER_URL, url);
try {
ctx = new InitialLdapContext(HashEnv, null);
//DirContext authContext = new InitialDirContext(HashEnv);
System.out.println("authentication ok");
return true;
} catch (NamingException e) {
e.printStackTrace();
System.err.println("Throw Exception : " + e);
return false;
}
}
六、数据增删改查
在预约系统中,数据的增删改查即为新建预约记录,删除预约记录,修改预约记录和查询预约记录。
1、新建预约记录
双击单元格,弹出预约信息表格,填写完毕后点击提交,使用ajax传送后台所需信息。为了区分增、删、改、查,特别设置参数flag,分别赋值“insert”,"update","delete","query"等。如果插入数据成功,返回“success”,失败返回“fail”,并在页面给出相应提示。因为,ajax传到后台的数据均为字符串类型的值,所以,要对这些值根据数据库设计的数据类型进行数据类型转换。
$.ajax({
type:"post",
url:address+ '/RequestServlet',
dataType:"text",
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data: {flag:"insert","subject":subject.value,"name":
name.innerHTML,"number":number.value,"equipment":equipment.value,
"remark":remark.value,"date":date,"startTime":
startTime,"endTime":endTime.value,"email":email,
"reception":reception.value,"roomNumber": roomNumber},
success: function(data) {
if(data == "success") {
popup("提交成功!");
document.getElementById("yes").onclick = alertConfirm;
}else {
popup("提交失败!");
}
},
error:function (status,statusText) {
popup("提交失败!");
}
});
if(flag.equals("insert")) {
PrintWriter out = null;
out = response.getWriter();
String result = null;
String subject = request.getParameter("subject");
String number = request.getParameter("number");
String name = request.getParameter("name");
String equipment = request.getParameter("equipment");
String remark = request.getParameter("remark");
String date = request.getParameter("date");
String startTime = request.getParameter("startTime");
String endTime = request.getParameter("endTime");
String email = request.getParameter("email");
String reception = request.getParameter("reception");
String roomNumber = request.getParameter("roomNumber");
int tempNumber = Integer.parseInt(number);
java.sql.Date day = parseDate(date);
Object[] objects = {subject,name,tempNumber,equipment,remark,day,
startTime,endTime,email,reception,roomNumber};
if(DBUtil.update(insertSql, objects)) {
result = "success";
logger.debug("result: " + result);
}else {
result = "fail";
logger.debug("result: " + result);
}
out.write(result);
out.flush();
out.close();
}
public static boolean update(String sql, Object[] objects) {
connection = getConnection();
try {
preparedStatement = connection.prepareStatement(sql);
for(int i=0;i<objects.length;i++) {
preparedStatement.setObject(i+1, objects[i]);
}
preparedStatement.executeUpdate();
return true;
}catch (Exception e) {
e.printStackTrace();
return false;
}finally {
try {
close(connection,preparedStatement);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
2、查询预约记录
在页面刷新的时候,对数据进行查询,将查询到的数据显示在单元格中。按照会议室编号以周为单位进行查询,并将所得数据放入List,返回给前台。使用showData(date)方法将数据展示出来。在展示数据过程中,如果一个会议所需的时间段大于1,就要将多个时间段的单元格进行合并。通过开始时间和结束时间所在的单元格是否是一样的来判断是否需要合并。如果会议的预约人是当前登录用户的话,此预约信息的背景为蓝色,其他人的预约信息的背景为黄色。在单元格内,信息只显示会议主题,且会议主题可显示的字符长度为7,大于7的字符以“...”形式显示,其他的详细信息,在鼠标hover时以提示框的形式显示。
$.ajax({
type:"post",
url:address + '/RequestServlet',
dataType:"json",
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data:{flag:"query","roomNumber": roomNumber,"startDay":startDay,"endDay": endDay},
success: function(data) {
if(!data) {
popup("数据查询失败");
window.location.reload();
}
showData(data);
},
error:function (status,statusText) {
popup("数据查询失败" + statusText);
}
});
function showData(data) {
var head = document.getElementById("title").getElementsByTagName("th");
var id = "";
var row = 0;
var text = document.getElementById("userInfo").innerHTML;
var index = text.indexOf(" ");
var user = text.substr((index+1),text.length);
var shortSubject = "";
for(var j=0;j<data.length;j++) {
var column = 0;
var time = data[j].date;
console.log("time-> "+time);
var startTime = data[j].startTime;
var endTime = data[j].endTime;
var startCell = 0;
var endCell = 0;
var diffrenceCell = 0;
startCell = getStartTime(startTime);
endCell = getEndTime(endTime);
diffrenceCell = endCell-startCell;
for(var i=1;i<head.length;i++) {
var headInner = head[i].innerHTML;
var headTime = headInner.substr(7,headInner.length);
if(time == headTime) {
column = i;
}
}
var rowValue = document.getElementById("_"+ startCell+"-" + column);
console.log("column:::::" + column);
//servlet return null
if(rowValue == null) {
continue;
}
var tipId = "tip" + "_"+ startCell+"-" + column;
if(data[j].subject.length>7) {
shortSubject = data[j].subject.substr(0,7) + "...";
}else {
shortSubject = data[j].subject;
}
rowValue.innerHTML = '<div class="tooltip">'
+'<div>'
+ '<span>'+shortSubject+'</span>'
+'</div>'
+'<div class="tooltiptext" id='+tipId+'>'
+ '<span>主题:</span>'
+ '<span>'+data[j].subject+'</span>'
+ '<br>'
+ '<span>预约人:</span>'
+ '<span>'+data[j].name+'</span>'
+ '<br>'
+ '<span>人数:</span>'
+ '<span>'+data[j].number+'</span>'
+ '<br>'
+ '<span>设备:</span>'
+ '<span>'+data[j].equipment+'</span>'
+ '<br>'
+ '<span>招待:</span>'
+ '<span>'+data[j].reception+'</span>'
+ '<br>'
+ '<span>备注:</span>'
+ '<span>'+data[j].remark+'</span>'
+'</div>'
+ '</div>';
if(diffrenceCell>0) {
for(var i=1;i<=diffrenceCell;i++) {
id = "_"+ (startCell+i) + "-" + column;
rowValue.rowSpan++;
$('#' + id).remove();
}
}
//set background color
var loginButton = document.getElementById("loginbtn").innerHTML;
if(loginButton == "登录") {
rowValue.style.background = "#FFFF99";
}
if(loginButton == "退出") {
if(data[j].email == user) {
rowValue.style.background = "#66CCFF";
}else {
rowValue.style.background = "#FFFF99";
}
}
}
}
if (flag.equals("query")) {
String roomNumber = request.getParameter("roomNumber");
String startDay = request.getParameter("startDay");
String endDay = request.getParameter("endDay");
java.sql.Date day1 = parseDate(startDay);
java.sql.Date day2 = parseDate(endDay);
PrintWriter out = null;
Gson gson = new Gson();
String json = "";
out = response.getWriter();
List<BookingInfoDO> list = new ArrayList<>();
Object[] objects = {roomNumber,day1,day2};
list = DBUtil.query(queryAllSql,objects);
json = gson.toJson(list);
out.write(json);
out.flush();
out.close();
}
3、修改和删除会议记录
双击已预约的单元格,对预约记录进行修改或删除。修改/删除操作只能针对预约人为当前登录用户的记录进行,不可以修改/删除他人的预约记录。如果对他人预约记录进行修改/删除会给出提示。在新增预约时不会出现删除按钮。
修改时,首先向后台发送查询请求,将返回的数据显示在预约表格中,然后,在用户修改完成后点击提交按钮即可。修改成功或失败都会给出提示。因为,提交动作同时兼顾新建和更新操作,因此,会设置参数对这两个操作进行区分,以防更新出错。
$.ajax({
type:"post",
url:address + '/RequestServlet',
dataType:"json",
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data:{"flag": "queryOne","updateDate":date,"updateStartTime":startTime,
"userName":userName,"roomNumber":roomNumber},
success: function(data) {
if(!data) {
popup("查询失败");
window.location.reload();
}
updateOption(data[0].endTime,end);
document.getElementById('form').style.display='block';
flag = true;
subject.value = data[0].subject;
number.value = data[0].number;
name.innerHTML = data[0].name;
equipment.value = data[0].equipment;
remark.value = data[0].remark;
reception.value = data[0].reception;
document.getElementById("delete").style.visibility = "visible";
},
error:function (status,statusText) {
popup("查询失败" + statusText);
}
});
if(flag.equals("queryOne")) {
String updateDate = request.getParameter("updateDate");
String updateStartTime = request.getParameter("updateStartTime");
String roomNumber = request.getParameter("roomNumber");
String userName = request.getParameter("userName");
java.sql.Date day = parseDate(updateDate);
Object[] objects = {day,updateStartTime,userName,roomNumber};
PrintWriter out = null;
Gson gson = new Gson();
String json = "";
out = response.getWriter();
List<BookingInfoDO> list = new ArrayList<>();
list = DBUtil.query(querySql,objects);
json = gson.toJson(list);
out.write(json);
out.flush();
out.close();
}
点击删除按钮,会弹出提示框,提示是否确认删除。如果点击“确定”,则进行删除动作,点击“取消”,则关闭预约表格,回到当前页面。确认删除后,向后台发送日期、开始时间、结束时间及会议室编号,用以唯一确定要删除的数据。后台根据所传数据在数据库里删除指定数据。如果删除成功,返回“success”,失败返回“fail”。数据删除成功和失败在页面均有提示。
function deleteBtnOnclick() {
var message = "确定删除?";
showMsg(message,deleteConfirm,deleteCancel);
}
function showMsg(message,yesCallBack,noCallBack) {
if(message){
document.getElementById("delete_text").innerHTML = message;
}
// show dialog
document.getElementById("delete_alert").style.display = "block";
// confirm button
var confirm = document.getElementById("delete_confirm");
confirm.addEventListener('click',yesCallBack);
// cancel button
var cancel = document.getElementById("delete_cancel");
cancel.addEventListener('click',noCallBack);
}
function deleteConfirm() {
document.getElementById('delete_alert').style.display = 'none';
document.getElementById("form").style.display = 'none';
var select = document.getElementById("selection");
var index = select.selectedIndex;
var roomNumber = "1";
if(index == 1) {
roomNumber = "2";
}
if (index == 2) {
roomNumber = "3";
}
var head = document.getElementById("title").getElementsByTagName("th");
var date = head[cellIndex].innerHTML.substr(7,head[cellIndex].innerHTML.length);
var time = document.getElementById('_'+rowIndex+'-'+cellIndex).parentNode.firstChild;
var startTime =time.innerHTML.substr(0,5);
var endTime = time.innerHTML.substr(6,time.innerHTML.length);
$.ajax({
type:"post",
url:address + '/RequestServlet',
dataType:"text",
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data: {"flag":"delete","deleteDate":date,"deleteStartTime":startTime,
"deleteEndTime":endTime,"roomNumber":roomNumber},
success: function(data) {
if(data == "success") {
console.log("delete success");
popup("删除成功!");
}else {
console.log("delete error!" );
popup("删除失败!");
}
},
error:function (status,statusText) {
console.log("submit error" + status + statusText);
popup("删除失败!");
}
});
}
function deleteCancel() {
document.getElementById('delete_alert').style.display = 'none';
document.getElementById("form").style.display = 'none';
return;
}
if (flag.equals("delete")) {
PrintWriter out = null;
out = response.getWriter();
String result = "fail";
String deleteDate = request.getParameter("deleteDate");
String deleteStartTime = request.getParameter("deleteStartTime");
String roomNumber = request.getParameter("roomNumber");
java.sql.Date day = parseDate(deleteDate);
Object[] objects = {day,deleteStartTime,roomNumber};
if(DBUtil.update(deleteSql, objects)) {
result = "success";
}else {
result = "fail";
}
out.write(result);
out.flush();
out.close();
}
七、提示框设计
根据需求,提示框分为两种:
1.提示:只有提示作用,不需要选择
2.确认:根据选择结果执行操作,比如点击删除按键后的弹窗
function popup(msg) {
var alertWindow = document.getElementById("alert");
alertWindow.style.display = "block";
var text = document.getElementById("alert_text");
text.innerHTML = msg;
}