码迷,mamicode.com
首页 > 其他好文 > 详细

会议室预定(可作为插件使用)

时间:2018-03-02 20:51:50      阅读:1739      评论:0      收藏:0      [点我收藏+]

标签:查询   数字   UI   ssd   init   oss   options   primary   注销   

会议室预定(小项目)

该项目仍旧是用Django框架完成的,此项目的重点在于前端页面中有关预定的操作

  首先建表,这里用的表较少,一共三张表,表结构如下:

from django.db import models
class UserInfo(models.Model):
    name = models.CharField(verbose_name=用户姓名, max_length=32)
    password = models.CharField(verbose_name=密码, max_length=32)

class MeetingRoom(models.Model):
    title = models.CharField(verbose_name=会议室, max_length=32)

class Booking(models.Model):
    user = models.ForeignKey(verbose_name=用户, to=UserInfo)
    room = models.ForeignKey(verbose_name=会议室, to=MeetingRoom)
    booking_date = models.DateField(verbose_name=预定日期)
    time_choices = (
        (1, 8:00),
        (2, 9:00),
        (3, 10:00),
        (4, 11:00),
        (5, 12:00),
        (6, 13:00),
        (7, 14:00),
        (8, 15:00),
        (9, 16:00),
        (10, 17:00),
        (11, 18:00),
        (12, 19:00),
        (13, 20:00),
    )
    booking_time = models.IntegerField(verbose_name=预定时间段, choices=time_choices)
    class Meta:
        unique_together = (
            (booking_date, booking_time, room)
        )

 

 

接下来分配路由(项目较为简单,所以并没有写注册的页面,这里是直接将用户数据录入数据库了,若想使项目更完善,可自行添加注册功能。)

from django.conf.urls import url
from django.contrib import admin
from meet import views
urlpatterns = [
    url(r^admin/, admin.site.urls),
    url(r^login/$, views.login),
    url(r^index/$, views.index),
    url(r^booking/$, views.booking),
    url(r^log_out/$, views.log_out),
]

 

然后是静态文件static的配置

STATIC_URL = /static/
STATICFILES_DIRS=[
    os.path.join(BASE_DIR, meet,static),#别名所指的实际文件夹路径
]

 

  这里我们用到两个插件,分别是datetimepicker和sweetalert2,前者是在前端页面对Date进行扩展的时间工具,后者是对alert进行美化的一共工具,如不想使用后者,直接用alert即可。

从网上下载两个插件,放入static下。

技术分享图片

 

   登录、注销功能  

    url(r^login/$, views.login),
    url(r^log_out/$, views.log_out),
技术分享图片
#注销功能
def log_out(request):
    del request.session[user_info]
    return redirect(/index/)


def login(request):
    """
    用户登录
    """
    if request.method == "GET":
        form = LoginForm()
        return render(request, login.html, {form: form})
    else:
        form = LoginForm(request.POST)
        if form.is_valid():
            rmb = form.cleaned_data.pop(rmb)#一周免登陆选项
            user = models.UserInfo.objects.filter(**form.cleaned_data).first()
            if user:
                request.session[user_info] = {id: user.id, name: user.name}
                if rmb:#若勾选了一周免登陆选项
                    request.session.set_expiry(60 * 60 * 24 * 30)
                return redirect(/index/)
            else:
                form.add_error(password, 密码错误)
                return render(request, login.html, {form: form})
        else:
            return render(request, login.html, {form: form})
注销、登录的views

上面用到了form组件如下:

技术分享图片
from django.forms import Form
from django.forms import fields
from django.forms import widgets

class LoginForm(Form):
    name = fields.CharField(
        required=True,
        error_messages={required: 用户名不能为空},
        widget=widgets.TextInput(attrs={class: form-control, placeholder: 用户名, id: name})
    )
    password = fields.CharField(
        required=True,
        error_messages={required: 密码不能为空},
        widget=widgets.PasswordInput(attrs={class: form-control, placeholder: 密码, id: password})
    )
    #一周免登陆选项
    rmb = fields.BooleanField(required=False, widget=widgets.CheckboxInput(attrs={value: 1}))
LoginForm
技术分享图片
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static ‘bootstrap-3.3.7-dist/css/bootstrap.min.css‘ %}">
    <style>


    </style>
</head>

<body>

<div style="width: 500px;margin: 50px auto;padding-top: 180px;">
    <form class="form-horizontal" method="post" novalidate>
        {% csrf_token %}
        <div class="form-group">
            <label for="name" class="col-sm-2 control-label">用户名:</label>
            <div class="col-sm-10">
                {{ form.name }}
                {{ form.errors.name.0 }}
            </div>
        </div>
        <div class="form-group">
            <label for="password" class="col-sm-2 control-label">密码:</label>
            <div class="col-sm-10">
                {{ form.password }}
                {{ form.errors.password.0 }}
            </div>
        </div>
        <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
                <div class="checkbox">
                    <label>
                       {{ form.rmb }} 一周内免登录
                    </label>
                </div>
            </div>
        </div>
        <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
                <button type="submit" class="btn btn-primary">登录</button>
            </div>
        </div>
    </form>

</div>
</body>
</html>
login.html

之后用于验证登陆与否的装饰器:

技术分享图片
#验证登陆与否的装饰器
def auth(func):
    def inner(request, *args, **kwargs):
        user_info = request.session.get(user_info)
        if not user_info:
            return redirect(/login/)
        return func(request, *args, **kwargs)
    return inner
装饰器auth

 

登录功能较为简单,不做详述,接下来我们做首页

 

  我们的预定功能就在首页中,所以首页是重中之重。

难点:index.html中的js:tbody的生成、datetimepicker插件的使用、前后端发送的时间格式的转换、后端录入数据库的操作

    url(r^index/$, views.index),
    url(r^booking/$, views.booking),
#views.py中:
import json
import datetime
from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse
from meet import models
from meet.form import *
from django.db.models import Q
from django.db.utils import IntegrityError

@auth def index(request): """ 会议室预定首页 :param request: :return: """ #拿到所有的时间段 time_choices = models.Booking.time_choices user_info = request.session.get(user_info) name=user_info[name] return render(request, index.html, {time_choices: time_choices,name:name})
技术分享图片
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static ‘bootstrap-3.3.7-dist/css/bootstrap.min.css‘ %}">
    <link rel="stylesheet" href="{% static ‘datetimepicker/bootstrap-datetimepicker.min.css‘ %}">
    <link rel="stylesheet" href="{% static ‘sweetalert2/sweetalert2.css‘ %}">

    {#    <link rel="stylesheet" href="{% static ‘mycss/index.css‘ %}">#}
    <style>

    body {
        font-size: 14px;
    }

    .shade {
        position: fixed;
        z-index: 1040;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: #999;
        filter: alpha(opacity=50);
        -moz-opacity: 0.5;
        opacity: 0.5;
    }

    .loading {
        position: fixed;
        z-index: 1050;
        top: 40%;
        left: 50%;
        height: 32px;
        width: 32px;
        margin: 0 0 0 -16px;
        background: url(/static/img/loading.gif);
    }


    .clearfix{
        padding: 10px 0;

    }
    .input-group{
        width: 230px;
        float:left;
    }
    .save-btn{
        padding: 0 5px;float: left
    }

    table > tbody td {
        height: 80px;
        width: 80px;
        text-align: center;
        vertical-align: middle;

    }

    table > tbody td.chosen {
        background-color: #ebccd1;
    }

    table > tbody td.selected {
        background-color:#d58512 ;
    }
    .mycolor{
        background-color: #EEE685;
    }
    .unable{

        color: #002a80;
        opacity: 0.5;
    }


    </style>

</head>
<body>

<div class="container">
<div class="panel panel-primary">
  <div class="panel-heading">
          <h1 class="text-center">会议室预定</h1>
  </div>
  <div class="panel-body">
        <div class="clearfix">
        <div style="float: left;color: red" id="errors"></div>
        <div class=‘input-group‘>
{#            时间插件#}
            <input type=‘text‘ class="form-control" id=‘datetimepicker11‘ placeholder="请选择日期"/>
            <span class="input-group-addon">
                <span class="glyphicon glyphicon-calendar">
                </span>
            </span>
        </div>
        <div class="save-btn">
            <a id="save"  class="btn btn-primary">保存</a>
        </div>
        <div class="pull-right">
            <b>hello {{ name }} </b>&nbsp;&nbsp;&nbsp;&nbsp;<a href="/log_out/">注销</a>
        </div>
    </div>

    <table class="table table-bordered  table-striped" style="border:1px solid red">
        <thead>
        <tr>
            <th>会议室</th>
{#            拿到从后端发过来的所有时间段#}
            {% for choice in time_choices %}
                <th>{{ choice.1 }}</th>
            {% endfor %}
        </tr>
        </thead>
        <tbody id="tBody">
{#        tbody中的内容包含未预定信息和预定信息,且需要实时更新,所以这里用后端传递的方式获取#}
        </tbody>
    </table>
</div>
  </div>
</div>




<!-- 遮罩层开始 -->
<div id=‘shade‘ class=‘shade hide‘></div>
<!-- 遮罩层结束 -->
<!-- 加载层开始 -->
<div id=‘loading‘ class=‘loading hide‘></div>
<!-- 加载层结束 -->


<script src="{% static ‘js/jquery-3.2.1.min.js‘ %}"></script>
<script src="{% static ‘js/jquery.cookie.js‘ %}"></script>
<script src="{% static ‘bootstrap-3.3.7-dist/js/bootstrap.js‘ %}"></script>
<script src="{% static ‘datetimepicker/bootstrap-datetimepicker.min.js‘ %}"></script>
<script src="{% static ‘datetimepicker/bootstrap-datetimepicker.zh-CN.js‘ %}"></script>
<script src="{% static ‘sweetalert2/sweetalert2.js‘ %}"></script>
<script>
//插件中自带,直接复制粘贴:
    // 对Date的扩展,将 Date 转化为指定格式的String
    // 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
    // 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
    // 例子:
    // (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
    // (new Date()).Format("yyyy-M-d h:m:s.S")      ==> 2006-7-2 8:9:4.18
    Date.prototype.Format = function (fmt) { //author: meizz
        var o = {
            "M+": this.getMonth() + 1, //月份
            "d+": this.getDate(), //
            "h+": this.getHours(), //小时
            "m+": this.getMinutes(), //
            "s+": this.getSeconds(), //
            "q+": Math.floor((this.getMonth() + 3) / 3), //季度
            "S": this.getMilliseconds() //毫秒
        };
        if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
        for (var k in o)
            if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
        return fmt;
    };

//自定义的全局变量:
    SELECTED_ROOM = {del: {}, add: {}};
    CHOSEN_DATE = new Date().Format(yyyy-MM-dd);//转成字符串格式后的今日日期
//网页加载完成后执行的js脚本内容:
    $(function () {
        initDatepicker();//初始化日期插件
{#        初始化房间信息,将今日日期发给后端,利用ajax从后台获得房间预订信息#}
        initBookingInfo(new Date().Format(yyyy-MM-dd));
        bindTdEvent();//绑定预定会议室事件
        bindSaveEvent();//保存按钮
    });
//处理csrftoken:
    function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }
    $.ajaxSetup({
        beforeSend: function (xhr, settings) {
            if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", $.cookie(csrftoken));
            }
        }
    });
//初始化日期插件内容:
    function initDatepicker() {
        $(#datetimepicker11).datetimepicker({
            minView: "month",//最小可视是到月份,即最小选择是到day
            language: "zh-CN",
            sideBySide: true,
            format: yyyy-mm-dd,
            bootcssVer: 3,//bootstrap3必写
            startDate: new Date(),//起始日为今日
            autoclose: true,//自动关闭,不需要可删
        }).on(changeDate, changeDate);//绑定改日期后的事件
    }
//绑定的改日期后的事件:
    function changeDate(ev) {
        CHOSEN_DATE = ev.date.Format(yyyy-MM-dd);//日期变为选择后的日期
        initBookingInfo(CHOSEN_DATE);//初始化预定信息

    }
//初始化房间信息(利用ajax从后台获得房间预订信息)
    function initBookingInfo(date) {
        SELECTED_ROOM = {del: {}, add: {}};

        $(#shade,#loading).removeClass(hide);//遮罩层
        $.ajax({
            url: /booking/,
            type: get,
            data: {date: date},//字符串转义后的今日日期
            dataType: JSON,
            success: function (arg) {
                $(#shade,#loading).addClass(hide);//遮罩层去除
                if (arg.code === 1000) {//表示后台操作成功
                    $(#tBody).empty();
                    $.each(arg.data, function (i, item) {
                        var tr = document.createElement(tr);//此为js操作,等同于jQuery的$(‘<tr>‘)
                        $.each(item, function (j, row) {
                            var td = $(<td>);
                            $(td).text(row.text).attr(class,everytd);

                            $.each(row.attrs, function (k, v) {
                                $(td).attr(k, v);
                            });
                            if (row.chosen) {
                                $(td).addClass(chosen);
                            }
                            $(tr).append(td)
                        });
                        $(#tBody).append(tr);
                    })
                } else {
                    alert(arg.msg);
                }
            },
            error: function () {
                $(#shade,#loading).addClass(hide);
                alert(请求异常);
            }
        })
    }

    /*
     绑定预定会议室事件,事件委派
     */
    function bindTdEvent() {
        $(#tBody).on(click, td[time-id][disable!="true"], function () {

            var roomId = $(this).attr(room-id);
            var timeId = $(this).attr(time-id);

            //var item = {‘roomId‘: $(this).attr(‘room-id‘), ‘timeId‘: $(this).attr(‘time-id‘)};
            // 取消原来的预定:
            if ($(this).hasClass(chosen)) {
                $(this).removeClass(chosen).empty();

                //SELECTED_ROOM[‘del‘].push(item);
                if (SELECTED_ROOM.del[roomId]) {
                    SELECTED_ROOM.del[roomId].push(timeId);
                } else {
                    SELECTED_ROOM.del[roomId] = [timeId];
                }

            } else if ($(this).hasClass(selected)) {
                $(this).removeClass(selected);
                // 取消选择
                var timeIndex = SELECTED_ROOM.add[roomId].indexOf(timeId);
                if (timeIndex !== -1) {
                    SELECTED_ROOM.add[roomId].splice(timeIndex, 1);
                }
            } else {
                $(this).addClass(selected);
                // 选择
                if (SELECTED_ROOM.add[roomId]) {
                    SELECTED_ROOM.add[roomId].push(timeId);
                } else {
                    SELECTED_ROOM.add[roomId] = [timeId];
                }
            }
        })
    }

    /*
     保存按钮
     */
    function bindSaveEvent() {
        $(#errors).text(‘‘);

        $(#save).click(function () {
            $(#shade,#loading).removeClass(hide);
            $.ajax({
                url: /booking/,
                type: POST,
                data: {date: CHOSEN_DATE, data: JSON.stringify(SELECTED_ROOM)},
                dataType: JSON,
                success: function (arg) {
                    $(#shade,#loading).addClass(hide);
                    if (arg.code === 1000) {
                        initBookingInfo(CHOSEN_DATE);

                    } else {
                        $(#errors).text(arg.msg);
                    }
                    swal(
                      保存成功,
                      会议室预定状态已刷新,
                      success
                    )
                }
            });
        });

    }


//鼠标悬浮变色功能(美化)
    $(document).ready(function(){
        $(body).on(mouseover,.everytd,function () {
            $(this).addClass(mycolor)
        })
        $(body).on(mouseout,.everytd,function () {
            $(this).removeClass(mycolor)
        })
    });

</script>



</body>
</html>
index.html(重点在js代码!!!)
技术分享图片
#装饰器
def auth_json(func):
    def inner(request, *args, **kwargs):
        user_info = request.session.get(user_info)
        if not user_info:
            return JsonResponse({status: False, msg: 用户未登录})
        return func(request, *args, **kwargs)
    return inner

@auth_json
def booking(request):
    """
    获取会议室预定情况以及预定会议室
    :param request:
    :param date:
    :return:
    """
    ret = {code: 1000, msg: None, data: None}
    current_date = datetime.datetime.now().date()#年月日
    if request.method == "GET":
        try:
            fetch_date = request.GET.get(date)#拿到前端传过来的转义过的字符串格式的日期
            fetch_date = datetime.datetime.strptime(fetch_date, %Y-%m-%d).date()#转义成时间格式
            if fetch_date < current_date:
                raise Exception(放下过往,着眼当下)
            #拿到当日的预定信息
            booking_list = models.Booking.objects.filter(booking_date=fetch_date).select_related(user,room).order_by(booking_time)

            booking_dict = {}#构建方便查询的大字典
            for item in booking_list:#item是每一个预定对象
                if item.room_id not in booking_dict:#对象的room_id没在字典内
                    booking_dict[item.room_id] = {item.booking_time: {name: item.user.name, id: item.user.id}}
                else:#对象的room_id在字典内
                    if item.booking_time not in booking_dict[item.room_id]:#但是还有预定信息没在字典内
                        booking_dict[item.room_id][item.booking_time] = {name: item.user.name, id: item.user.id}
            """
            {
                room_id:{
                    time_id:{‘user.name‘:esfsdfdsf,‘user.id‘:1},
                    time_id:{‘user.name‘:esfsdfdsf,‘user.id‘:1},
                    time_id:{‘user.name‘:esfsdfdsf,‘user.id‘:1},
                }
            }
            """

            room_list = models.MeetingRoom.objects.all()#数组【所有房间对象】

            booking_info = []
            for room in room_list:
                temp = [{text: room.title, attrs: {rid: room.id}, chosen: False}]
                for choice in models.Booking.time_choices:
                    v = {text: ‘‘, attrs: {time-id: choice[0], room-id: room.id}, chosen: False}
                    if room.id in booking_dict and choice[0] in booking_dict[room.id]:#说明已有预定信息
                        v[text] = booking_dict[room.id][choice[0]][name]#预订人名
                        v[chosen] = True
                        if booking_dict[room.id][choice[0]][id] != request.session[user_info][id]:
                            v[attrs][disable] = true
                            v[attrs][class] = unable#不可对别人预定的房间进行操作
                    temp.append(v)
                booking_info.append(temp)
            ret[data] = booking_info
        except Exception as e:
            ret[code] = 1001
            ret[msg] = str(e)
        return JsonResponse(ret)
    else:
        try:
            #拿到预定的日期并进行转义
            booking_date = request.POST.get(date)
            booking_date = datetime.datetime.strptime(booking_date, %Y-%m-%d).date()
            if booking_date < current_date:
                raise Exception(放下过往,着眼当下)

            #SELECTED_ROOM = {del: {roomId:timeId}, add: {roomId:timeId}};
            booking_info = json.loads(request.POST.get(data))
            for room_id, time_id_list in booking_info[add].items():
                if room_id not in booking_info[del]:
                    continue
                for time_id in list(time_id_list):
                    #同时点了增加和删除,即用户在选择之后反悔了。。
                    if time_id in booking_info[del][room_id]:
                        booking_info[del][room_id].remove(time_id)
                        booking_info[add][room_id].remove(time_id)

            add_booking_list = []
            for room_id, time_id_list in booking_info[add].items():
                for time_id in time_id_list:
                    obj = models.Booking(
                        user_id=request.session[user_info][id],
                        room_id=room_id,
                        booking_time=time_id,
                        booking_date=booking_date
                    )
                    add_booking_list.append(obj)
            models.Booking.objects.bulk_create(add_booking_list)#批量添加,增加数据库效率

            remove_booking = Q()
            for room_id, time_id_list in booking_info[del].items():
                for time_id in time_id_list:
                    temp = Q()
                    temp.connector = AND
                    temp.children.append((user_id, request.session[user_info][id],))
                    temp.children.append((booking_date, booking_date,))
                    temp.children.append((room_id, room_id,))
                    temp.children.append((booking_time, time_id,))
                    remove_booking.add(temp, OR)
            if remove_booking:
                models.Booking.objects.filter(remove_booking).delete()
        except IntegrityError as e:
            ret[code] = 1011
            ret[msg] = 会议室已被预定

        except Exception as e:
            ret[code] = 1012
            ret[msg] = 预定失败:%s % str(e)

    return JsonResponse(ret)
预定功能,重点!

 

 最后生成的页面例子:

注:淡色为别人预定,不可操作;深色为自己预定,可退订;咖色为选中,还未提交。

 技术分享图片

 

会议室预定(可作为插件使用)

标签:查询   数字   UI   ssd   init   oss   options   primary   注销   

原文地址:https://www.cnblogs.com/zhuminghui/p/8494655.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!