在许多大学的教务系统或者第三方教务软件中,都有一个重要的、不可或缺的功能——课程表,做为整个教务软件中使用频率最高的功能,课程表的好坏直接决定了这个系统的用户体验。
然而对于刚刚初学软件开发的我来讲,不管是前端界面仍是后端的代码彷佛都有一些难度。想着给和我同样的又刚好也在开发教务系统相关软件的小伙伴们一些思路,这篇博客就诞生了。
如图,就是某款教务软件的课表界面:前端
一门课是一个实体,由于一门课中含有不少节课,因此会有许多条数据,若是只有一张数据表,把每节课都从做为一条单独的数据写入表中,无疑,会产生许多冗余的数据(由于对于某一门课中的每一节课来讲,他们的课程名称、上课班级、任课教师都是相同的),
而且还会出现其余问题,好比,一门课程是一个对象,同这种方式储存的数据,一门课程被拆分红了多个对象,在数据的存取时很不方便。
所以,应该怎么写呢?——一门课程建一张数据表(命名为Course表),一节课建一张数据表(命名为Course_info表)。这样,一门课程与一节课就是多对一的实体关系。
数据库
首先声明一下关于时间表示的关键字段:后端
对于同一门课程来讲,不一样的周次、不一样的时段,上课的教室都会不同,也就是说,对于同一门课程的每节课,周次、时段、教室这三个值中任意两个随第三个值的变化而变化,而且每节课这三个值都至少会有一个改变;而对于同一门课程来讲,课程名、任课教师、班级这三个值都是同样的。
因此,这两张数据表该分别储存什么内容,就显而易见了——把不变量储存进Course表,变化量储存到Course_info表中。
Course表的字段:
Course_info表的字段:
Course表数据:
Course_info表数据:数组
大学的课程,一大节课,会连续持续几小节(请看本文的第一张图片),因此数据库中,每一节课存大节、仍是小节,是个问题。session
对Course_info表作以下修改,加入持续长度:
修改后的数据:app
(代码以ThinkPHP为例)this
// 获取本学生id $studentId = session('studentId'); //查询本学期本本学生的全部课程,(Score表就是学生与课程、成绩的关联表) $score = new Score(); $getScore = $score->where(['course_id'=>$courseIds, 'student_id'=>$studentId])->select(); //创建课程表数组 $coursetable = array('1' => array('0','0','0','0','0','0','0','0') , '2' => array('0','0','0','0','0','0','0','0') , '3' => array('0','0','0','0','0','0','0','0') , '4' => array('0','0','0','0','0','0','0','0') , '5' => array('0','0','0','0','0','0','0','0') , '6' => array('0','0','0','0','0','0','0','0') , '7' => array('0','0','0','0','0','0','0','0') , '8' => array('0','0','0','0','0','0','0','0') , '9' => array('0','0','0','0','0','0','0','0') , '10' => array('0','0','0','0','0','0','0','0') , '11' => array('0','0','0','0','0','0','0','0')) ; //储存本学期本学生全部课程的course_id $scoreIds = []; foreach ($getScore as $score) { array_push($scoreIds, $score->course_id); } //对于每一门课程,查询全部的课(单位:节) $courseinfos = Courseinfo::where(['course_id'=>$scoreIds, 'week'=>$week])->select(); //把每节课填入课表 foreach ($courseinfos as $key => $acourseinfo) { { $coursetable[$acourseinfo->begin][$acourseinfo->weekday] = $acourseinfo; } } //传入课程表 $this->assign('coursetable',$coursetable);
所以,只须要在前端也加一个7*11的循环便可spa
//经过循环生成表格 //$i为表格的行,值为小节(begin),$j为表格的列,值为星期几(weekday) for ($i=0; $i < 11; $i++) { for ($j=0; $j < 7; $j++) { if (课程表中存在 weekday==$j 而且 begin==$i 的值) { 此单元格中显示此课程的信息 } } }
仍是这张图,能够看到,当课程持续两小节或者三小节时,连续两、三个单元格都会显示这节课的信息,而实际上,数据库中只存了第一小节的信息。所以须要合并单元格。利用HTML表格的rowspan标签,能够很方便的合并单元格。
合并前代码:3d
<table border="1"> <tr> <th>星期一</th> <th>星期二</th> <th rowspan="1">合并前</th> </tr> <tr> <td>123</td> <td>456</td> <th>合并前</th> </tr> <tr> <td>789</td> <td>012</td> <th>合并前</th> </tr> </table>
当rowspan=1时不进行合并,至关于不写。效果以下code
合并后代码:
<table border="1"> <tr> <th>星期一</th> <th>星期二</th> <th rowspan="3">合并前</th> </tr> <tr> <td>123</td> <td>456</td> </tr> <tr> <td>789</td> <td>012</td> </tr> </table>
而当rowspan被改为3的时候,就能够发现,最右面的单元格被合并了。
然而,其实说合并,这只是一种表面的说法,细心的你必定发现了,在第二行、第三行分别少了一个<td>标签
与其说rowspan是合并单元格,不如说:rowspan改变了原有单元格的高度。
那么问题来了,为何下面的行要少一个<td>?若是在上一行使用了rowspan的状况下,下面几行仍然保持不变,那么会出现什么现象呢?
<table border="1"> <tr> <th>星期一</th> <th>星期二</th> <th rowspan="3">合并后</th> </tr> <tr> <td>123</td> <td>456</td> <td>234</td> </tr> <tr> <td>789</td> <td>012</td> <td>567</td> </tr> </table>
因此,就须要动态的判断了。
咱们知道,一个单元格有两种状态:显示、不显示(不显示能够不写这一行,也能够用CSS来定义一个样式“disappear”,当class等于这个样式时,单元格就不显示了。而一节课有三种状态:持续2小节、3小节、4小节。因此对于每个单元格,都须要判断它上面的单元格有没有长度等于2的课、它上面的上面的单元格有没有长度为3的课、它上面的上面的上面的单元格有没有长度为四的课——以上三种状况有任意一种知足,这个单元格就是隐藏状态,反之为显示状态
但还有一种状况须要考虑,那就是数组下标不能超出范围。好比,对因而第一行的单元格(第一小节),若是请求它上方的单元格(第零小节),会超出数组范围,致使报错。因此,第一节、第二节、第三节须要单独对待,其余的小节因为确保不会超出索引,因此可使用循环。
因为本人的实际项目中的代码比较凌乱,就不贴出来了,仅提供实现此功能的逻辑,供你们参考。
在我看来,最大的困难,一是在于数据库方面,数据如何储存、如何创建实体关系、一节课按大节仍是按小节储存...这些都是须要考虑的问题。而数据库的结构是一个系统的基石,不合理的结构会大大增长开发的难度,甚至可能因为数据库结构不合理致使项目失败。
另外一个问题就是前端输出信息时的判断了,为了防止使用rowspan致使单元格错误的问题,须要根据前几行的数据来判断当前单元格是否显示,若是上面有课,就不显示,这样就完美解决了问题。惟一的不足就是,若是不借助后端语言,仅仅用HTML来写循环和判断,会很是很是麻烦,这是我没有列出代码的缘由——若是能使用JS或者在前端直接使用PHP代码来循环,就能减轻复杂度了。
或许在大佬面前这种功能简单的不值一提,可做为一个菜鸡初学者,实现这个功能的时候确实花费了不少精力,思考了很长的时间,大概想了四五天才想出这个方案。