在許多大學的教務系統或者第三方教務軟件中,都有一個重要的、不可或缺的功能——課程表,做爲整個教務軟件中使用頻率最高的功能,課程表的好壞直接決定了這個系統的用戶體驗。
然而對於剛剛初學軟件開發的我來講,不管是前端界面仍是後端的代碼彷佛都有一些難度。想着給和我同樣的又剛好也在開發教務系統相關軟件的小夥伴們一些思路,這篇博客就誕生了。
如圖,就是某款教務軟件的課表界面:
前端
一門課是一個實體,由於一門課中含有不少節課,因此會有許多條數據,若是隻有一張數據表,把每節課都從做爲一條單獨的數據寫入表中,無疑,會產生許多冗餘的數據(由於對於某一門課中的每一節課來講,他們的課程名稱、上課班級、任課教師都是相同的),
而且還會出現其餘問題,好比,一門課程是一個對象,同這種方式儲存的數據,一門課程被拆分紅了多個對象,在數據的存取時很不方便。
所以,應該怎麼寫呢?——一門課程建一張數據表(命名爲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代碼來循環,就能減輕複雜度了。
或許在大佬面前這種功能簡單的不值一提,可做爲一個菜雞初學者,實現這個功能的時候確實花費了不少精力,思考了很長的時間,大概想了四五天才想出這個方案。