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

  • 相关阅读:
    POJ 1251 Jungle Roads
    1111 Online Map (30 分)
    1122 Hamiltonian Cycle (25 分)
    POJ 2560 Freckles
    1087 All Roads Lead to Rome (30 分)
    1072 Gas Station (30 分)
    1018 Public Bike Management (30 分)
    1030 Travel Plan (30 分)
    22. bootstrap组件#巨幕和旋转图标
    3. Spring配置文件
  • 原文地址:https://www.cnblogs.com/banshaohuan/p/9949489.html
Copyright © 2011-2022 走看看