在上一篇博客中,我们完成了请假系统的数据库部分设计,现在让我们来实现后端和前端的功能。
十三 填写假单
我们打开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">
© 2020 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 Vacation
def createvacationapply(username,category,startdate,startdateMorning,enddate,enddateMorning,reason,timesum,approveuser,approvedate,state):
result = 'Fail'
if timesum == 0:
result = 'Fail'
return result
if startdateMorning == 'Morning':
startdateMorning = True
else:
startdateMorning = False
if enddateMorning == 'Morning':
enddateMorning = True
else:
enddateMorning = False
if startdate == '':
result = 'Fail'
return result
if enddate == '':
result = 'Fail'
return result
startdateinfo = 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.py
from util.timesheet.timesheetutil import createvacationapply
class 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.supervisor
result = createvacationapply(username,category,startdate,startdateMorning,
enddate,enddateMorning,reason,timesum,
approveuser,datetime.date.today(),'WaitForApprove')
resultpath = gettemplatepath('result.html')
self.render(resultpath, result=result)
# main.py
routelist = [
# ...
(r"/createvacationapply", CreateVacationApply),
# ...
]
这个的handler的get方法中,会从TimeSheetEvent中获得所有category是Vacation的事件传入前端,以便用户选择假期种类;post方法则是简单的从表单中拿到数据后丢入createvacationapply函数,创建假单。
十四 查看假单
我们已经实现了填写假单功能,下面让我们来看一下查看假单。这个功能比较简单,就是把当前用户提过的所有假单都列出来。
我们依然打开timesheetutil.py,实现viewvacationapply函数:
# timesheetutil.py
def viewvacationapply(username):
vacationlist = []
vacationapplys = session.query(Vacation).filter(Vacation.username == username)
for vacationapply in vacationapplys:
vacationdict = {}
vacationdict['id'] = vacationapply.id
event = session.query(TimeSheetEvent).filter(TimeSheetEvent.eventcode == vacationapply.vacationcategory).first()
if type(event) is TimeSheetEvent:
vacationdict['category'] = event.nickname
else:
vacationdict['category'] = vacationapply.vacationcategory
vacationdict['state'] = vacationapply.state
vacationdict['startdate'] = vacationapply.startdate
if vacationapply.startdateMorning == True:
vacationdict['starttime'] = '9:00'
else:
vacationdict['starttime'] = '13:00'
vacationdict['enddate'] = vacationapply.enddate
if vacationapply.enddateMorning == True:
vacationdict['endtime'] = '9:00'
else:
vacationdict['endtime'] = '13:00'
vacationdict['timesum'] = vacationapply.timesum
vacationdict['username'] = username
vacationlist.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.py
from util.timesheet.timesheetutil import viewvacationapply
class 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.py
routelist = [
# ...
(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">
© 2020 Tornado考勤系统
</footer>
<!-- ============================================================== -->
<!-- End footer -->
<!-- ============================================================== -->
</div>
{% end %}
最后的效果如下:
在这期博客中,我们实现了假单系统中的两个功能:填写假单和查看假单。在下篇博客中,将继续实现假单系统的第三个功能:审批假单,希望大家继续关注~