zoukankan      html  css  js  c++  java
  • 练手项目之会议室预订

    练手项目之会议室预订

    项目需求

    1. 实现用户登录功能。
    2. 登录后,登录用户能够批量提交预定。
    3. 登录后,登录用户可以通过点击来选择预定或者取消预定。
    4. 登录后,登录用户预定和其他用户预定以颜色区分。
    5. 登录后,对其他用户的预定不可更改。

    数据模型和 URL

    数据 Model

    数据层一共有三张表:用户表、会议室表,预定信息表。

    其中预定信息表中的预定时间由 choices 指定固定的时间点,只能在指定值范围内取值。

    from django.db import models
    
    # Create your models here.
    from django.contrib.auth.models import AbstractUser
    
    """
    ban ban11111
    panpan panpan1111
    root root1111
    """
    class UserInfo(AbstractUser):
        tel = models.CharField(max_length=32)
    
    
    class Room(models.Model):
        """
        会议室表
        """
        caption = models.CharField(max_length=32)
        num = models.IntegerField()
    
        def __str__(self):
            return self.caption
    
    
    class Book(models.Model):
        """
        预定信息表
        """
        user = models.ForeignKey("UserInfo")
        room = models.ForeignKey("Room")
        date = models.DateField()
    
        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'),
        )
    
        time_id = models.IntegerField(choices=time_choices)
    
        class Meta:
            unique_together = (
                ('room', 'date', 'time_id')
            )
    
        def __str__(self):
            return str(self.user) + "预定了" + str(self.room)
    
    

    URL

    展示预定信息
    url(r'^index/', views.index),

    登录视图
    url(r'^login/', views.login),

    预定功能逻辑对应的 url
    url(r'^book/', views.book),

    业务逻辑功能

    登录

    模板层

    <form action="" method="post">
        {% csrf_token %}
        用户名:<input type="text" name="user">
        密码: <input type="password" name="pwd">
        <input type="submit">
    </form>
    

    视图函数

    def login(request):
        if request.method == "POST":
            user = request.POST.get("user")
            pwd = request.POST.get("pwd")
    
            user = auth.authenticate(username=user,password=pwd)
            if user:
                auth.login(request, user)
                return redirect('/index/')
    
        return render(request, "login.html")
    

    主页展示

    模板层

    在模板中定义一些 style 样式,在显示不同用户预定信息颜色不同时能用到。

    time_choices 是在数据 model 中定义的,可以在前端模板中使用。其中 choice.0 是键, choice.1 是值。

    前端只是构建一个架子,真实的表结构在后端进行 HTML 代码构建,从后端发来的 HTML 代码直接在前端渲染, 很多业务逻辑都是这么做的。

     <style>
    	.active{
    		background-color: green!important;
    		color: white;
    	}
    	.another_active{
    		background-color: #2b669a;
    		color: white;
    	}
    
    	.td_active{
    		background-color: lightblue;
    		color: white;
    	}
    </style>
    </head>
    <body>
    <h3>会议室预定</h3>
    
    <table class="table table-bordered table-striped">
        <thead>
            <tr>
                <th>会议室/时间</th>
                {% for time_choice in time_choices %}
                    <th>{{ time_choice.1 }}</th>
                {% endfor %}
            </tr>
        </thead>
        <tbody>
            {{ htmls|safe }}
        </tbody>
    </table>
    <button class="btn btn-success pull-right keep">保存</button>
    

    视图函数层

    由表的结构可知后端需要进行两层循环:第一层循环是遍历会议室列表,第二层循环是遍历时间,形成会议室和时间相对应的表样式。

    首先,需要判断会议室相对应的时间点是否有人预定。需要判断两个点:会议室 id 和时间 id ,都相同时表示被预定。

    然后,如果该单元格被预定,判断是否为当前登录人预定的,以显示不同的颜色。样式类可以直接在后端 HTML 代码中指定,在前端会自动渲染。需要注意的是,在构建 HTML 标签过程中,要会通过标签属性,如 id, name 等传值,这是经常用到的方式。

    最后,构建 HTML 代码的时候要注意标签的 end 标签的位置放置正确。

    def index(request):
        date = datetime.datetime.now().date()
        time_choices = Book.time_choices
        room_list = Room.objects.all()
        book_list = Book.objects.all()
    
        htmls = ""
        # 遍历会议室列表,构建预定表格
        for room in room_list:
            htmls += "<tr><td>{}({})</td>".format(room.caption, room.num)
    
            for time_choice in time_choices:
                booked = False
                for book in book_list:
                    # 这个单元格已经被预定
                    if book.room_id == room.pk and book.time_id == time_choice[0]:
                        booked = True
                        break
    
                if booked:
                    if request.user.pk == book.user.pk:
                        htmls += "<td class='active item' room_id={} time_id={}>{}</td>".format(room.pk, time_choice[0], book.user.username)
                    else:
                        htmls += "<td class='another_active item' room_id={} time_id={}>{}</td>".format(room.pk, time_choice[0], book.user.username)
                else:
                    htmls += "<td class='item' room_id={} time_id={}></td>".format(room.pk, time_choice[0])
    
            # 遍历完之后算是完整一行,再加上</tr>标签
            htmls += "</tr>"
    
        return render(request, "index.html", locals())
    

    前端业务逻辑

    到目前为止,已经可以在前端显示已经预定的信息。而我们所需要的通过点击对应单元格来更改预定、取消的样式显示是通过前端 js 代码实现的。

    首先,需要为每个单元格绑定事件,同时获取当前单元格的信息,即 room_id, time_id 。而且需要构建发送到后端的数据结构。其中包括要增加的预定信息和要删除的预定信息两个部分。

    所有可能出现的业务逻辑有三种:

    1. 取消预定
      适用于取消登录用户自己已经预定的会议室。

      样式上,去掉被预定的样式。

      数据上,将 time_id 和 room_id 加入要返回的数据中。

    2. 临时取消预定
      这是登录用户在选择预定之后反悔将其取消时。

      样式上,去掉选择样式。

      数据上,由于已被选中,所以已经将该预定信息添加到了数据中,需要将在数据中对应的部分删除。

    3. 添加预定

      两种情况:

      1. 该room还没有被预订:给列表赋值要预定的时间id
      2. 该room已有预订:给列表添加上要预定的时间id

      就是一个是添加,一个是直接赋值。这种情况以后会经常碰到,已经存在了就是添加否则就是赋值,要掌握。

    
    // 以字典的形式保存往后台发送到的数据
    var POST_DATA={
       // 存储的形式为:room_id:[time_id1,time_id2,...]
      "ADD":{},
      "DEL":{},
    };
    
    // 为td绑定事件
    function BindTD() {
    	$(".item").click(function () {
    		// 获取每个td的相关属性
    		var room_id = $(this).attr("room_id");
    		var time_id = $(this).attr("time_id");
    		// console.log(room_id);
    
    		// 取消预订
    		if ($(this).hasClass("active")){
    			$(this).removeClass("active").empty();
    
    			if(POST_DATA.DEL[room_id]){
    				POST_DATA.DEL[room_id].push(time_id);
    			}else {
    				POST_DATA.DEL[room_id] = [time_id,]
    			}
    		}// 临时取消预订
    		else if($(this).hasClass("td_active")){
    			$(this).removeClass("td_active");
    			// POST_DATA.ADD[room_id].pop();
    			// 删除数组中的指定元素
    			POST_DATA.ADD[room_id] = $.grep(POST_DATA.ADD[room_id], function (value) {
    				return value != time_id;
    			})
    		}
    		else { // 添加预订
    			$(this).addClass("td_active");
    
    			// 两种情况:
    			// 1. 该room还没有被预订:给列表赋值要预定的时间id
    			// 2. 该room已有预订:给列表添加上要预定的时间id
    			if(POST_DATA.ADD[room_id]){
    				POST_DATA.ADD[room_id].push(time_id);
    			}else {
    				POST_DATA.ADD[room_id] = [time_id,]
    			}
    		}
    
    	});
    }
    
    BindTD();
    

    批量处理预定

    前端逻辑

    日历插件的使用

    预定涉及到日期,所以我们选择一个日期插件来处理。

    首先在里面 HTML 代码中引入。

    <div class="calender pull-right">
          <div class='input-group' style=" 230px;">
                <input type='text' class="form-control" id='datetimepicker11' placeholder="请选择日期"/>
                <span class="input-group-addon">
                    <span class="glyphicon glyphicon-calendar">
                    </span>
                </span>
    
          </div>
    </div>
    

    然后,在 script 中加入使用代码:

    // 日历插件
    
    $('#datetimepicker11').datetimepicker({
    			minView: "month",
    			language: "zh-CN",
    			sideBySide: true,
    			format: 'yyyy-mm-dd',
    			startDate: new Date(),
    			bootcssVer: 3,
    			autoclose: true,
    		}).on('changeDate', book_query);
    
     function book_query(e) {
    	 CHOOSE_DATE=e.date.yuan("yyyy-MM-dd");
    	 location.href="/index/?book_date="+CHOOSE_DATE;
     }
    

    最后,我们格式化日期,使之变成我们想要的格式,能够为我们所用。

    // 日期格式化方法
    Date.prototype.yuan = 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;
    	};
    
    向后端发送数据

    通过 AJAX 异步向后端发送数据和处理返回的结果。

    向后端发送的数据有预定的时间、会议室信息和日期信息。以 JSON 方式发送。

    获取后端返回的数据后判断是否操作成功,并以不同的方式显示结果。即预定成功改变其单元格颜色,如果点击了已经预定的会议室会发出警告。

    
    // 日期
    
     if (location.search.slice(11)){
    	   CHOOSE_DATE = location.search.slice(11)
    	  }
      else {
    	   CHOOSE_DATE = new Date().yuan('yyyy-MM-dd');
    	   }
    
    $(".keep").click(function () {
    
    	$.ajax({
    		url: '/book/',
    		type: 'POST',
    		dataType: "json",
    		data: {
    			post_data: JSON.stringify(POST_DATA),
    			choose_date: CHOOSE_DATE,
    
    		},
    		success: function (data) {
    			// console.log(data);
    
    			if (data.state){
    				// 预订成功
    				location.href = "";
    			}else {
    				alert("预订的房间已经被预订");
    				location.href = "";
    			}
    		}
    	});
    
    });
    

    视图函数层

    因为从前端获取了两种数据:要添加的预定和要取消的预定,所以在后端需要批量创建保存新数据和批量删除数据

    res = {"state": True, "msg": None}

    在后端经常以字典的形式往前端返回数据,其中一个字段表示处理结果,另一个字段代表要返回的数据。

    同样,后端往前端也是以 JSON 格式传数据。

    import json
    def book(request):
    
        # print(request.POST)
        post_data = json.loads(request.POST.get("post_data"))
        choose_date = request.POST.get("choose_date")
    
        res = {"state": True, "msg": None}
    
        try:
            # 添加预订
            book_list = []
            for room_id, time_id_list in post_data["ADD"].items():
    
                for time_id in time_id_list:
                    book_obj = Book(user=request.user, room_id=room_id, time_id=time_id, date=choose_date)
                    book_list.append(book_obj)
    
            # 一次性创建并保存到数据库中多条数据
            Book.objects.bulk_create(book_list)
    
            # 批量删除预订
            from django.db.models import Q
    
            remove_book = Q()
            for room_id,time_id_list in post_data["DEL"].items():
                temp = Q()
                for time_id in time_id_list:
                    temp.children.append(("room_id", room_id))
                    temp.children.append(("time_id", time_id))
                    temp.children.append(("user_id", request.user.pk))
                    temp.children.append(("date", choose_date))
                    remove_book.add(temp, "OR")
    
            if remove_book:
                Book.objects.filter(remove_book).delete()
    
    
        except Exception as e:
            res["state"] = False
            res["msg"] = str(e)
    
    
        return HttpResponse(json.dumps(res))
    

    效果演示

    当前登录用户为 "ban" ,登录人预定信息与别人预定显示不同。
    mrbs1

    点击单元格,不同的情况显示不同。

    RMBS2

    提交预定信息。

    RMBS3

    GitHub 地址:https://github.com/protea-ban/oldboy/tree/master/9day86/MRBS

  • 相关阅读:
    Python读写ini文件
    MySQL 让主键id 从1开始自增
    python 找字符串中所有包含字符的下标
    centos7防火墙命令
    如何将npm升级到最新版本
    将 Npm 的源替换成淘宝的源
    MySQL 时间格式化/ MySQL DATE_FORMAT
    Python中crypto模块进行AES加密和解密
    windows环境下python3安装Crypto
    Nginx+PHPSTORM+Xdebug 配置
  • 原文地址:https://www.cnblogs.com/banshaohuan/p/9949489.html
Copyright © 2011-2022 走看看