在上一篇博客中,我们完成了请假系统的数据库部分设计,现在让我们来实现后端和前端的功能。
十三 填写假单
我们打开base_nav.html,在其中添加新的展开栏,用于放置请假相关功能:
<!--base_nav.html--><!--之前代码略,总之就是左侧导航栏继续往下加就是--><li><a href="#" data-toggle="collapse" data-target="#vacationmanage"><i class="fa fa-calendar m-r-10" aria-hidden="true"></i>假期管理</a><ul id="vacationmanage" class="collapse"><li><a href="/createvacationapply" class="waves-effect"><i class="fa fa-child m-r-10" aria-hidden="true"></i>填写假单</a></li><li><a href="/viewvacationapply" class="waves-effect"><i class="fa fa-eye m-r-10" aria-hidden="true"></i>查看假单</a></li><li><a href="/approvevacationapply" class="waves-effect"><i class="fa fa-check m-r-10" aria-hidden="true"></i>审批假单</a></li></ul></li><!--之后代码略-->
我们在这里实现三个功能:填写假单、查看假单和审批假单。填写假单顾名思义是让用户可以提交假单;查看假单可以让用户查看已经填写的假单;而审批假单可以让用户的上级领导对所有下属的假单进行审批。
首先让我们来看看填写假单的前端实现,再复习一下填写假单的界面:
在这个页面中,我们提供一个下拉框用于选择假别,两个calendar和上下午的选择框用于选择假期的起止日期,在选完开始时间和结束时间后,会将假期总天数计算出来。可见,我们假期的最小单位是半天。
在template目录下新建createvacationapply.html,输入以下代码:
<!--createvacationapply.html--><!--content块部分--><div class="page-wrapper"><!-- ============================================================== --><!-- Container fluid --><!-- ============================================================== --><div class="container-fluid"><!-- ============================================================== --><!-- Bread crumb and right sidebar toggle --><!-- ============================================================== --><div class="row page-titles"><div class="col-md-6 col-8 align-self-center"><h3 class="text-themecolor m-b-0 m-t-0">填写假单</h3><ol class="breadcrumb"><li class="breadcrumb-item"><a href="/">Home</a></li><li class="breadcrumb-item active">填写假单</li></ol></div></div><!-- ============================================================== --><!-- End Bread crumb and right sidebar toggle --><!-- ============================================================== --><!-- ============================================================== --><!-- Start Page Content --><!-- ============================================================== --><!-- Row --><div class="row"><!-- Column --><div class="col-lg-8 col-xlg-9 col-md-7"><div class="card"><div class="card-block"><form class="form-horizontal form-material" action="/createvacationapply" method="post" ><div class="form-group"><label class="col-md-12">假别</label><div class="col-md-12"><select class="form-control form-control-line" name="vacationcategory" id="vacationcategory">{% for category in vacationcategory %}<option value="{{ category.eventcode }}">{{ category.nickname }}</option>{% end %}</select></div></div><div class="form-group"><label class="col-md-12">开始时间</label><div class="col-md-12"><input type="date" name="startdate" id="startdate" onchange="getTimeSum()" required=true /> <select class="form-control form-control-line" name="startdateMorning" id="startdateMorning" onchange="getTimeSum()"><option value="Morning">上午</option><option value="Afternoon">下午</option></select></div></div><div class="form-group"><label class="col-md-12">结束时间</label><div class="col-md-12"><input type="date" name="enddate" id="enddate" onchange="getTimeSum()" required=true /> <select class="form-control form-control-line" name="enddateMorning" id="enddateMorning" onchange="getTimeSum()"><option value="Afternoon">下午</option><option value="Morning">上午</option></select></div></div><div class="form-group"><label class="col-md-12">时间合计</label><div class="col-md-12"><input type="text" readonly=true id="timesum" name="timesum" ></input> 天</div></div><div class="form-group"><label class="col-md-12">请假原因</label><div class="col-md-12"><textarea rows=10 class="form-control" required=true name="reason" id="reason"></textarea></div></div><div class="form-group"><div class="col-sm-12"><button type="submit" class="btn btn-success">创建</button></div></div></form></div></div></div><!-- Column --></div><!-- Row --><!-- ============================================================== --><!-- End PAge Content --><!-- ============================================================== --></div><!-- ============================================================== --><!-- End Container fluid --><!-- ============================================================== --><!-- ============================================================== --><!-- footer --><!-- ============================================================== --><footer class="footer text-center">© Tornado考勤系统</footer><!-- ============================================================== --><!-- End footer --><!-- ============================================================== --></div><script>function getTimeSum(){var strstartdate = document.getElementById("startdate").value;var strenddate = document.getElementById("enddate").value;var startdateMorning = document.getElementById("startdateMorning").value;var enddateMorning = document.getElementById("enddateMorning").value;var startdate = new Date(strstartdate.split('-')[0],strstartdate.split('-')[1],strstartdate.split('-')[2]);var enddate = new Date(strenddate.split('-')[0],strenddate.split('-')[1],strenddate.split('-')[2]);var timesum;if (startdate != "" && enddate != ""){if (startdate == enddate){if (startdateMorning == "Morning" && enddateMorning == "Morning"){timesum = 0.5;}else if (startdateMorning == "Afternoon" && enddateMorning == "Morning"){timesum = 0;}else if (startdateMorning == "Afternoon" && enddateMorning == "Afternoon"){timesum = 0.5;}else{timesum = 1;}}else if (startdate > enddate){timesum = 0;}else{timesum = (enddate - startdate) / 86400000;if (startdateMorning == "Morning" && enddateMorning == "Morning"){timesum += 0.5;}else if (startdateMorning == "Afternoon" && enddateMorning == "Morning"){;}else if (startdateMorning == "Afternoon" && enddateMorning == "Afternoon"){timesum += 0.5;}else{timesum += 1;}}}document.getElementById("timesum").value = timesum;}</script><!--后面略-->
这个表单值得注意的有以下几点:
假期类别从之前的考勤事件中来;我们使用type="date"来生成日历控件,其id分别为startdate和enddate,并且指定了οnchange="getTimeSum()",表明当这个元素变化时要触发getTimeSum函数来计算假期总天数;在id为startdateMorning和enddateMorning的两个下拉框中,onchange也为getTimeSum(),因为我们需要上下午改变时也计算假期总天数;在最后,我们补了一块<script>块,这是因为如果一个页面是继承的其他模板,则所有代码都必须放进某个block中才能生效,因此我们就把这个函数一并放入了content块。
现在再让我们来看看这个getTimeSum函数。在这个函数开头,我们通过document.getElementById拿到了startdate、enddate、startdateMorning和enddateMorning的值,并且把startdate和enddate转换成了javascript的Date类型以便之后的计算;之后就是对这几个值进行分类讨论,得到正确的timesum的值;最后,再将计算好的值赋给timesum控件。
下面让我们看一下对应的后端代码,还是先写util函数:打开timesheetutil.py,建立createvacationapply函数:
# util/timesheet/timesheetutil.py# ...from database.tblvacation import Vacationdef createvacationapply(username,category,startdate,startdateMorning,enddate,enddateMorning,reason,timesum,approveuser,approvedate,state):result = 'Fail'if timesum == 0:result = 'Fail'return resultif startdateMorning == 'Morning':startdateMorning = Trueelse:startdateMorning = Falseif enddateMorning == 'Morning':enddateMorning = Trueelse:enddateMorning = Falseif startdate == '':result = 'Fail'return resultif enddate == '':result = 'Fail'return resultstartdateinfo = startdate.split('-')startdate_date = datetime.date(int(startdateinfo[0]),int(startdateinfo[1]),int(startdateinfo[2]))enddateinfo = enddate.split('-')enddate_date = datetime.date(int(enddateinfo[0]), int(enddateinfo[1]), int(enddateinfo[2]))check_vacation = session.query(Vacation).filter(and_(Vacation.username==username,Vacation.startdate==startdate_date,Vacation.enddate==enddate_date)).first()if type(check_vacation) is Vacation:result = '当前日期已有假单申请'else:newvacationapply = Vacation(username=username,vacationcategory=category,startdate=startdate_date,startdateMorning=startdateMorning,enddate=enddate_date,enddateMorning=enddateMorning,reason=reason,timesum=timesum,approveuser=approveuser,approvedate=approvedate,state=state,applydate=datetime.date.today())result = insertdata(newvacationapply)return result
这个函数没什么说的,就是根据输入参数在Vacation表里建立一笔数据。要注意的就是如果timesum是0的话返回fail,并且在建立数据之前先查一下相同日期是否已经有了假单申请。
打开timesheet_app.py,实现CreateVacationApply的handler:
# timesheet_app.pyfrom util.timesheet.timesheetutil import createvacationapplyclass CreateVacationApply(BaseHandler):def get(self):createvacationapplypath = gettemplatepath('createvacationapply.html')vacationcategory = session.query(TimeSheetEvent).filter(TimeSheetEvent.eventcategory == 'Vacation')self.render(createvacationapplypath, vacationcategory=vacationcategory)def post(self):username = ''bytes_user = self.get_secure_cookie('currentuser')if type(bytes_user) is bytes:username = str(bytes_user, encoding='utf-8')category = self.get_argument('vacationcategory')startdate = self.get_argument('startdate')startdateMorning = self.get_argument('startdateMorning')enddate = self.get_argument('enddate')enddateMorning = self.get_argument('enddateMorning')timesum = self.get_argument('timesum')reason = self.get_argument('reason')currentuser = session.query(User).filter(User.username == username).first()approveuser = currentuser.supervisorresult = createvacationapply(username,category,startdate,startdateMorning,enddate,enddateMorning,reason,timesum,approveuser,datetime.date.today(),'WaitForApprove')resultpath = gettemplatepath('result.html')self.render(resultpath, result=result)# main.pyroutelist = [# ...(r"/createvacationapply", CreateVacationApply),# ...]
这个的handler的get方法中,会从TimeSheetEvent中获得所有category是Vacation的事件传入前端,以便用户选择假期种类;post方法则是简单的从表单中拿到数据后丢入createvacationapply函数,创建假单。
十四 查看假单
我们已经实现了填写假单功能,下面让我们来看一下查看假单。这个功能比较简单,就是把当前用户提过的所有假单都列出来。
我们依然打开timesheetutil.py,实现viewvacationapply函数:
# timesheetutil.pydef viewvacationapply(username):vacationlist = []vacationapplys = session.query(Vacation).filter(Vacation.username == username)for vacationapply in vacationapplys:vacationdict = {}vacationdict['id'] = vacationapply.idevent = session.query(TimeSheetEvent).filter(TimeSheetEvent.eventcode == vacationapply.vacationcategory).first()if type(event) is TimeSheetEvent:vacationdict['category'] = event.nicknameelse:vacationdict['category'] = vacationapply.vacationcategoryvacationdict['state'] = vacationapply.statevacationdict['startdate'] = vacationapply.startdateif vacationapply.startdateMorning == True:vacationdict['starttime'] = '9:00'else:vacationdict['starttime'] = '13:00'vacationdict['enddate'] = vacationapply.enddateif vacationapply.enddateMorning == True:vacationdict['endtime'] = '9:00'else:vacationdict['endtime'] = '13:00'vacationdict['timesum'] = vacationapply.timesumvacationdict['username'] = usernamevacationlist.append(vacationdict)return vacationlist
这里我们把Vacation表中的startdateMorning和enddateMorning根据True和False分别转换为9:00和13:00,即如果start/enddateMorning是True的话,在页面会显示为9:00,如果为False,会显示为13:00。我们会把每个记录转换为一个字典,再把这些字典放进一个list中返回出去。
在timesheet_app.py中实现ViewVacationApply:
# timesheet_app.pyfrom util.timesheet.timesheetutil import viewvacationapplyclass ViewVacationApply(BaseHandler):def get(self):username = ''bytes_user = self.get_secure_cookie('currentuser')if type(bytes_user) is bytes:username = str(bytes_user, encoding='utf-8')vacationlist = viewvacationapply(username)viewvacationapplypath = gettemplatepath('viewvacationapply.html')self.render(viewvacationapplypath,vacationlist=vacationlist)# main.pyroutelist = [# ...(r"/viewvacationapply",ViewVacationApply),# ...]
它所对应的前端页面也很简单,就是把后端传出的vacationlist渲染出来即可:
<!--viewvacationapply.html-->{% block content %}<div class="page-wrapper"><!-- ============================================================== --><!-- Container fluid --><!-- ============================================================== --><div class="container-fluid"><!-- ============================================================== --><!-- Bread crumb and right sidebar toggle --><!-- ============================================================== --><div class="row page-titles"><div class="col-md-6 col-8 align-self-center"><h3 class="text-themecolor m-b-0 m-t-0">查看假单</h3><ol class="breadcrumb"><li class="breadcrumb-item"><a href="/">Home</a></li><li class="breadcrumb-item active">查看假单</li></ol></div></div><!-- ============================================================== --><!-- End Bread crumb and right sidebar toggle --><!-- ============================================================== --><!-- ============================================================== --><!-- Start Page Content --><!-- ============================================================== --><!-- Row --><div class="row"><!-- Column --><div class="col-lg-8 col-xlg-9 col-md-7">{% for vacation in vacationlist %}<div class="card"><div class="card-block"><div class="form-group"><label class="col-md-12">假单ID: {{ vacation['id'] }}</label><label class="col-md-12">假别: {{ escape(vacation['category']) }}</label><label class="col-md-12">{{ vacation['startdate'] }} {{ vacation['starttime'] }} - {{ vacation['enddate'] }} {{ vacation['endtime'] }}</label><label class="col-md-12">总计 {{ escape(vacation['timesum']) }} 天</label><label class="col-md-12">状态: {{ escape(vacation['state']) }}</label></div></div></div>{% end %}</div><!-- Column --></div><!-- Row --><!-- ============================================================== --><!-- End PAge Content --><!-- ============================================================== --></div><!-- ============================================================== --><!-- End Container fluid --><!-- ============================================================== --><!-- ============================================================== --><!-- footer --><!-- ============================================================== --><footer class="footer text-center">© Tornado考勤系统</footer><!-- ============================================================== --><!-- End footer --><!-- ============================================================== --></div>{% end %}
最后的效果如下:
在这期博客中,我们实现了假单系统中的两个功能:填写假单和查看假单。在下篇博客中,将继续实现假单系统的第三个功能:审批假单,希望大家继续关注~