[題目解析] 第 50 屆全國技能競賽青少年組網頁設計考題分析

本篇為 (Moudle 2) 提供競賽選手使用,作為題目作答之技巧研究解析練習,不代表考場之實際考題之內容準確性。相關連結資源如下:

  1. 公告與題目:青少年組-JJ17 網頁設計(製作)-1090724 更新之下載點
  2. 完整代碼:https://github.com/summer10920/WebCompetition_th50_j17
  3. 線上示意:https://webcomp.lokiwebs.com/th50_j17/

moudle2


版型設計

題目沒有提供網頁版型,需由選手自行完成並列入視覺美術評分作業,請依序自行開發完成版型設計(妥善利用考場素材包發揮),同時本節將使用 Bootstrap 技術快速完成開發作業。

本節將先完成以下三筆檔案動作:

  1. index.html: 作為一般前台使用
  2. admin.html: 做為後台使用,提供管理者(與玩家)能對留言板修改刪除之後台管理。
  3. plugin/style.css: 提供此兩項網頁之 css 外觀設計。

小技巧:我們先使用 *.html 為副檔名,這樣使用 VSCode 進行編寫時能得到 Emmet 輔助提示與編寫,最後進行 PHP 設計時再改回*.php 副檔名即可。

此外前置作業需使用 bootstrap 2.2.2 相關檔案,請自行準備放入指定位置,並記得網頁 header 內宣告

  1. plugin/bootstrap.min.css: bootstrap 樣式表
  2. plugin/bootstrap.min.js: bootstrap 動態模組
  3. plugin/jquery.min.js: bootstrap 驅動工具
  4. img/glyphicons-halflings-white.png: 樣式表圖片

宣告方式如下(放入需使用 bootstrap 之網頁):

index.html
<head>
...
<title>電競網站</title>
<link rel="stylesheet" href="plugin/bootstrap.min.css">
<link rel="stylesheet" href="plugin/style.css">
<script src="plugin/jquery.min.js"></script>
<script src="plugin/bootstrap.min.js"></script>
</head>

前台設計

  • 根據題目基本要求:具備選單區、留言區主題(含增加留言)、競賽分配主題(含玩家參賽導引)、後台登入
  • 本解析視覺設計部分:採用 one page 設計並將相關題目要求列入選單,並規劃頁首與廣告區,使體驗完整提高。

初始化外觀

進行以下 CSS 優化:

  • 漸層的背景,建議電競風格為主(螢光與暗黑色)
  • 統一將文字調整為黑體,採 1rem 為基準大小、加粗統一、文字置中(另需要則個別調整)、灰黑文字色
  • 使用 border-box 減少空間計算問題
  • 另外由於 BS 2.2 預設窄版為 940px(但部分 BS Component 不支援),因此使用 !important 強制覆蓋且能自由調整寬度。
style.css
/*環境調整*/
* {
font-family: Arial, "微軟正黑體";
text-align: center;
}
section, div, p{
box-sizing: border-box;
}
body {
background: linear-gradient(90deg, #4e0064, #888);
color: #333;
font-weight: bold;
font-size: 1rem;
}
.container {
width: 940px !important;
}

標題

作為網站名稱文字標題,保留部分區域空間作為其他擴充位置。

小技巧

  • 使用 BS Grid System 功能方便裁切成 4+8 或 4+4+4。
  • 這裡使用 header 標籤當主容器(不用 div),這樣就省寫 class 或其他名稱綁定。
index.html
<!--網站標題-->
<header class="row-fluid">
<div class="span4">第 50 屆全國競賽電競遊 官方網站</div>
<div class="span8"></div>
</header>

需要自訂 CSS 調整,主區塊 CSS 部分重點:

  • 將背景刷白,並固定整體為 100px 高。
  • 利用文字高度跟區塊同高,使標題文字有垂直至中的單行效果。
style.css
/* 網站標題 */
header {
background: #eee;
line-height: 100px;
height: 100px;
}

選單

提供網站選單作用,依題目要求規劃選單項目。

小技巧

  • 使用 BS Navbar 快速搞定選單,並使用 .navbar-inverse 做為我們的黑色風格。
  • 使用 BS Layouts 觀念設計窄版 <div class="container"> ,窄版定義對 ul.nav有效(做為該新外層)。
  • 範例的部分 HTML 屬性以及 LOGO 連結可以移除,且 .active 部分也不使用,剩餘之整體 HTML 結構請加強默背。
  • 超連結之目標位置可以提早或晚寫,這裡注意有 錨點連結model 呼叫(事後說明)兩種寫法。
index.html
<!-- 選單區 -->
<nav class="navbar navbar-inverse">
<div class="navbar-inner">
<div class="container">
<!-- <a class="brand" href="#">Title</a> -->
<ul class="nav">
<!-- <li class="active"><a href="#">Home</a></li> -->
<li><a href="#">回首頁</a></li>
<li><a href="#msg">玩家留言板</a></li>
<li><a href="#pk">最新消息與賽制公告區塊</a></li>
<li><a href="#pkadd" data-toggle="modal">玩家參賽</a></li>
<li><a href="#login" data-toggle="modal">網站管理</a></li>
</ul>
</div>
</div>
</nav>

選單 CSS 重點:

  • 選單列整體浮點靠右
  • 設計整體選單區能根據滾動 沾黏至頂端 ,並消除原有的 margin-bottom,另外拉至 99 層,避免被其他層級吃掉。
style.css
/* 選單區 */
nav ul {
float: right !important;
}
nav {
position: sticky;
top: 0;
margin-bottom: 0 !important;
z-index: 99;
}

廣告

為了讓網站有視覺效果,這裡用簡單的圖片背景跟文字描述(可從試題 PDF 進行文字複製)。這裡使用 section 為標籤,因為差異需求就必須有 class 名稱為 .ad

index.html
<!-- 廣告主題 -->
<section class="ad">
<p class="container">根據市調公司 Newzoo 發布的全球電競市場報告指出,今年全球電競產值將達 9.1 億美元(約新台幣 300 億元),年增 38%,隨著電競產業逐漸成熟,預估 2020 年產值將達到 14
億美元。由於電子競技成長猛烈,國際奧林匹克委員會( IOC )公布第六屆奧林匹克會議細節,其中包括先前奧委會委員 Tony Estanguet
承諾帶進奧委會的電子競技相關討論。簡單來說,奧委會確切認識到,電競作為一種產業與運動,而電競又在年輕世代中特別受歡迎,且許多奧運相關權益關係者,或多或少都有參與電競產業。所以電競很有可能納入奧林匹克的競賽項目之中。</p>
</section>

廣告 CSS 重點:

  • 背景圖由當天素材提供,自行挑選適合圖片放置在指定位置處,並注意 background 特性做中心不重複。
  • 背景圖高度應該跟你的廣告區塊高度一致,譬如兩者高度都是 400px(用 Adobe 裁切圖片)。
  • 為了讓 p 元素能在整個 .ad 中心,使用 flex 進行主軸與交軸皆為中心點。
  • p 元素寬度應該跟 .container 窄版同寬,這樣窄版效果會一致。另外做內距跟半透明黑底之白字效果。
style.css
/* 廣告主題 */
.ad {
background: url("../img/bg.jpg") center no-repeat;
height: 400px;
display: flex;
justify-content: center;
align-items: center;
}
.ad p {
width: 940px;
padding: 20px;
background: #ccc8;
color: #eee;
}

留言

這裡會使用 section 標籤為 HTML 區域。比起前面來說相對複雜,有以下設計要點。

  1. 關於主題區域部分:

    • 使用窄版 section.container,並內含一個 h2 為標題。
    • 我們需要一個 a 超連結來呼叫 modal,model 的原理稍晚進行說明,只需注意 a[data-toggle="modal"] 為必寫。
    • a[data-toggle="modal"] 為了好看,在行內樣式做 float:right 效果,但為了消除後面的 float 溢位需補充 <div style="clear: both;"></div> 強制消除後續的浮動機制。
    • 我們需要提供給選單做錨點連結至該主題,為了錨點效果良好,改為在 section 之前增加 div# 做為替代品。
  2. 關於留言部分:

    • 會有一個以上(資料庫比數)的 div.row-fluid 為每則留言區塊;分為一般跟已刪除兩種留言版型可能,各做一組方便之後 PHP 套用。
    • 每一個留言區塊會使用 BS grid system 做設計 span2+sapn10;span2 會提供人像、人名、編號,span10 會提供內容、電話、信箱、時間與修改和刪除鍵(提供管理員)。
    • 修改與刪除鍵只有後台需要,你可以先設計在前台 index.html,之後快速複製成 admin.html 後,再移除原本 index.html 的這個多餘部分。
    • 修改導向到 modal 且為#msgmdy;刪除的導向目標為api.php?do=msgdel&id=0,這裡的 id 是指資料表內的索引編號也要一併告知 PHP 做處理。
    • 人像題目雖然沒有要求,為了版面好看建議自行到 AI 設計 user.jpg(100*100),並注意使用 HTML 綁定 100*100 寬高。
    • 流水號編號請額外用 span.sn 包覆,方便之後 PHP 操作。
    • 這裡會使用不少 BS 的 CSS 外觀優化。

小技巧

index.html
<!-- 留言板 -->
<div id="msg"></div>
<section class="container">
<h2>玩家留言板</h2>
<a href="#msgadd" class="btn btn-info" data-toggle="modal" style="float:right">我要留言</a>
<div style="clear: both;"></div>

<!-- each ok -->
<div class="thumbnail row-fluid">
<div class="span2">
<img src="img/user.jpg" class="img-circle" width="100" height="100">
<h4>User Name</h4>
<h5>#<span class="sn">1</span></h5>
</div>
<div class="span10">
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Possimus eligendi autem accusantium nam illo
commodi voluptates vero consectetur eum beatae?</p>
<div class="bottom">
<span class="badge badge-info">
&phone; <span class="tel">0988-100200</span>
<span>
<span class="badge badge-info">
<i class="icon-envelope icon-white"></i> <span class="mail">sss@gmail.com</span>
</span>
<span class="badge badge-info">
<i class="icon-time icon-white"></i> 2020/02/02 12:00:00
</span>
<!-- for admin use -->
<!--
<span class="control">
<a href="#msgmdy" class="btn btn-warning" data-toggle="modal">編輯</a>
<a href="api.php?do=msgdel&id=0" class="btn btn-danger">刪除</a>
</span>
-->
</div>
</div>
</div>

<!-- each del -->
<div class="thumbnail row-fluid del">
<div class="span2">#2</div>
<div class="span10">此樓留言已被刪除</div>
</div>
</section>

留言 CSS 重點:

  1. 關於主題區域部分:

    • section 到時候一共有 2+1 大格,唯獨 section.ad 外觀不同需要排除指定。
    • section 上邊緣處使用 margin-top 撐開與前者的距離,並本身黑底背景色。
    • 讓版面至少有一個畫面高,section 最小高度大約為 800px,或用 cals 去計算 100vh -200px 更精準(螢幕高 100%減去上下 margin 的 80*2px 與 padding 20*2px)。
    • 標題 h2 白色、每個留言部分 div.row-fluid 為白底並修正 padding:0 使版面切齊且用 margin-top 讓留言板塊之間有間格。
  2. 留言部分:

    • 留言內容 p 最少需要 100px 高(跟圖片高度 100px 切齊),且文字靠左。
    • 留言下方的 div.bottom 區域為文字靠右、內距上下為 10px、且上緣有個虛線效果。
    • 若是刪除留言的版本 div.del,我們做半透效果並帶明顯紅字,添加 padding 上下內距 10px 可以更好看些。
style.css
/* 留言板 */
section:not(.ad) {
background: #333;
padding: 20px;
margin-top: 80px;
min-height: calc(100vh - 160px);
}
h2 {
color: #eee;
}
.row-fluid {
background: #eee;
padding: 10px;
margin-top: 10px;
}
p {
min-height: 100px;
text-align: left;
}
.bottom {
text-align: right;
border-top: 1px dotted #333;
padding: 10px 0;
}
.del {
padding: 10px 0;
color: red;
opacity: 0.5;
}
.del div {
min-height: unset !important;
}

參賽匹配

使用跟前者相同的 section 標籤結構與 CSS 外觀效果,可以省下一些工作,有以下設計要點。

  1. 關於主題區域部分:

    • 跟前一項主題(留言板)相同,只是主內容變 table,結構為 section.container>h2+table
    • table 有完整的結構 table>thead+tbody,利於之後的 PHP 資料載入。
    • 注意題目描述,這裡的主題名稱為 最新消息與賽制公告區塊
    • 也有 a 超連結來呼叫 modal 做我要參賽,但這部分已做在 Navbar 內。
    • 同樣需要提供給選單做錨點連結至該主題增加 div# 做為替代品。
  2. 關於表格部分:

    • 將會依賴 Bootstrap 來完成表格設計。須注意在 thead 這裡先分配好 100%空間(對 th指定寬度),能讓表格整齊好看。
    • 這裡的列顯示可能有兩種版面(單或雙),先設計成單數結束,後續 PHP 設計階段會再說明如何處理。

小技巧

index.html
<!-- 賽事版 -->
<div id="pk"></div>
<section class="container">
<h2>最新消息與賽制公告區塊</h2>
<table class="table table-striped">
<thead>
<tr>
<th width="10%">賽事場次</th>
<th width="10%"></th>
<th width="30%">玩家</th>
<th width="10%">匹配</th>
<th width="30%">玩家</th>
<th width="10%"></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<!--img's size=50*50-->
<td><img src="img/user.jpg"></td>
<td>User Name</td>
<td>VS</td>
<td>User Name</td>
<!--img's size=50*50-->
<td><img src="img/user.jpg"></td>
</tr>
<tr>
<td>2</td>
<!--img's size=50*50-->
<td><img src="img/user.jpg"></td>
<td>User Name</td>
<td>VS</td>
<td>等待匹配中。..</td>
<td></td>
</tr>
</tbody>
</table>
</section>

賽事 CSS 重點:

  1. 關於主題區域部分:

    • 與前者相同,這裡不用處理(只要你 css 的選擇器是正常發揮)。
  2. 表格部分:

    • 表格所有子元素(對應文字)為白色與水平置中、所有文字欄位做垂直中心
    • 為了單雙列好看,這裡使用偽類操作,做紅藍半透背景操作。
    • 對表格內的圖片做圓角化處理,且限定寬高 50px。
style.css
/* 賽事版 */
table * {
color: #eee;
text-align: center !important;
}
td {
vertical-align: middle !important;
}
tbody tr:nth-child(odd) > td {
background: #f006 !important;
}
tbody tr:nth-child(even) > td {
background: #00f6 !important;
}
tbody img {
border-radius: 10%;
height: 50px;
width: 50px;
}

Modal 是 Bootstrap Javascript 功能,能對應超連結(屬性 data-toggle=”modal” 且 href=#id)進行呼叫燈箱。因此我們集中放到 footer 且共有新增留言、編輯留言、我要參賽、帳號登入(四種 modal)需設計,可善用複製貼上修改局部代碼幫助減少工作量。

index.html
<footer>
<!--put your form.modal*4 in here -->
<form class="modal hide fade">...</form>
</footer>

CSS 只需要設計與前者相同的外距離(上方)即可。

style.css
footer {
margin-top: 80px;
}

小技巧

新增留言 (msgadd)

  • 使用 form[method=”post”],且 action 到api.php?do=msgadd之後由 PHP 處理作業。
  • 善用 modal 本身 .modal-header+.modal-body+.modal-footer 的三個區域使用。
  • 所以表單輸入欄位都應該持有獨有的 name 名稱以及必填驗證功能 (required 與 pattern)。
index.html
<!-- Modal for msg add-->
<form id="msgadd" class="modal hide fade form-horizontal" action="api.php?do=msgadd" method="post">
<div class="modal-header">
<h3>新增留言</h3>
</div>
<div class="modal-body">
<div class="control-group">
<label class="control-label">玩家姓名</label>
<input type="text" name="user" required>
</div>
<div class="control-group">
<label class="control-label">留言內容</label>
<textarea name="info" required></textarea>
</div>
<div class="control-group">
<label class="control-label">Email</label>
<input type="email" name="mail" required>
</div>
<div class="control-group">
<label class="control-label">連絡電話</label>
<input type="tel" name="tel" pattern="[0-9\-]{9,12}" required>
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">取消</button>
<input class="btn btn-primary" type="submit" value="送出">
</div>
</form>

修改留言 (msgmdy)

  • 使用 form[method=”post”],且 action 到api.php?do=msgmdy之後由 PHP 處理作業。
  • 與前者 form#msgadd 相同類似,差別於修改部分多了 value 提供為預設舊值。
  • 修改需要另提供 PHP 欲修改資料之指定編號,這裡需要多做一個 input:hidden 偷偷帶資料編號。
  • 修改功能只有後台會提供,這裡先一併處理掉。之後設計 admin.html 時進行保留,反之 index.html 可移除(不移除也不會怎樣)。
index.html
<!-- for admin to msgmdy-->
<form id="msgmdy" class="modal hide fade form-horizontal" action="api.php?do=msgmdy" method="post">
<div class="modal-header">
<h3>編輯留言</h3>
<input type="hidden" name="id">
</div>
<div class="modal-body">
<div class="control-group">
<label class="control-label">玩家姓名</label>
<input type="text" name="user" required value="Loki">
</div>
<div class="control-group">
<label class="control-label">留言內容</label>
<textarea name="info" required>Old Message....</textarea>
</div>
<div class="control-group">
<label class="control-label">Email</label>
<input type="email" name="mail" required value="blog@lokiwebs.com">
</div>
<div class="control-group">
<label class="control-label">連絡電話</label>
<input type="tel" name="tel" pattern="[0-9\-]{9,12}" value="0204888988">
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">取消</button>
<input class="btn btn-primary" type="submit" value="送出">
</div>
</form>

我要參賽 (pkadd)

  • 與前者 form#msgadd 相同類似,使用 form[method=”post”],且 action 到api.php?do=pkadd之後由 PHP 處理作業。
  • 由於需要檔案上傳,這裡需增加屬性 form['enctype="multipart/form-data"]方能成功傳遞檔案給 PHP。
index.html
<!-- for pkadd -->
<form id="pkadd" class="modal hide fade form-horizontal" action="api.php?do=pkadd" method="post"
enctype="multipart/form-data">
<div class="modal-header">
<h3>玩家參賽</h3>
</div>
<div class="modal-body">
<div class="control-group">
<label class="control-label">玩家姓名</label>
<input type="text" name="user" required>
</div>
<div class="control-group">
<label class="control-label">玩家頭像</label>
<input type="file" name="img" required>
</div>
<div class="control-group">
<label class="control-label">Email</label>
<input type="email" name="mail" required>
</div>
<div class="control-group">
<label class="control-label">連絡電話</label>
<input type="tel" name="tel" pattern="[0-9\-]{9,12}" required>
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">取消</button>
<input class="btn btn-primary" type="submit" value="送出">
</div>
</form>

後台登入 (login)

  • 與前者 form#msgadd 相同類似,使用 form[method=”post”],且 action 到api.php?do=login之後由 PHP 處理作業。
  • 雖題目未表達明確,我們將管理員登入與玩家登入做合併處理,因此 PHP 端需要額外判斷兩種身分可能。
  • 驗證碼部分請使用 svg(約 250*50)進行設計,注意保留1234作為驗證值變化使用。
index.html
<!-- for login -->
<form id="login" class="modal hide fade form-horizontal" action="api.php?do=login" method="post">
<div class="modal-header">
<h3>後台登入</h3>
</div>
<div class="modal-body">
<div class="control-group">
<label class="control-label">帳號<br>(玩家名稱)</label>
<input type="text" name="user" required>
</div>
<div class="control-group">
<label class="control-label">密碼<br>(玩家請輸入 Mail)</label>
<input type="password" name="pwd" required>
</div>
<div class="control-group">
<label class="control-label">驗證碼<br>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px"
y="0px" style="enable-background:new 0 0 100 30;" xml:space="preserve">
<g style="fill: #CCCCCC;stroke: #000000;">
<path
d="M90,30c-28.7,0-57.3,0-86,0c0.6-4.7,1.3-9.4,1.9-13.9C4,16.7,2,17.3,0,18C0,12,0,6,0,0c3,0,6,0,9,0 C6.5,3.5,4.7,7.2,5.9,11.7c4.4,0.7,6-1.7,6.2-5.3C12.3,4.3,12.1,2.1,12,0c4,0,8,0,12,0c-0.9,1.7-2.9,4.4-2.4,5c1.9,2.3,4.4,5,7,5.4 c5.3,0.8,10.6-0.4,13.7-5.8c1.9,0.7,3.5,1.4,5.2,2c0.4-0.3,0.9-0.6,1.3-0.9C47.8,3.8,46.9,1.9,46,0c11.3,0,22.7,0,34,0 c0,3,0.1,6,0.1,9c0.5,0,1-0.1,1.5-0.1C83.1,5.9,84.5,2.9,86,0c4.6,0,9.2,0,14,0c0,8,0,16,0,24c-4-3.6-3.7,0.8-4,1.5 c-4.3-1.4-8.5-2.1-12-4.1c-3.7-2.1-6.7-5.5-10.2-8.5c0,2.9,0,5.4,0,8c-1.2-2.3-2.4-4.6-3.5-6.9c-0.6-0.1-1.1-0.2-1.7-0.2 c-1.4,3.2-2.8,6.4-4.3,9.6c0.3,0.4,0.6,0.8,0.9,1.1c1.5-0.6,3-1.2,4.4-1.9c1.4-0.6,2.9-1.3,4.3-1.9c-0.6,1.2-1.2,2.4-2.1,4.2 c2.7,0.3,4.6,1,6.1,0.6C86,23.4,86,23.3,90,30z M41.6,17.6c-5.7-2.6-18.1,2-20.4,7.3C28.4,27.6,37.3,24.5,41.6,17.6z M13.8,21.8 c0.8-0.1,1.7-0.2,2.5-0.4c3.5-3.7-0.5-5.4-2.6-7.5c-0.6,0.1-1.1,0.3-1.7,0.4C12.6,16.9,13.2,19.3,13.8,21.8z M44.1,6 c-4.8,4.5-3.5,6.3,1.9,7.7C45.4,11,44.9,9,44.1,6z" />
<path
d="M46,0c0.9,1.9,1.8,3.8,2.7,5.7c-0.4,0.3-0.9,0.6-1.3,0.9c-1.6-0.6-3.3-1.3-5.2-2c-3,5.5-8.3,6.6-13.7,5.8 C26,10,23.5,7.3,21.6,5c-0.5-0.6,1.5-3.3,2.4-5C31.3,0,38.7,0,46,0z" />
<path
d="M90,30c-4-6.7-4-6.6-12-4.5c-1.6,0.4-3.5-0.3-6.1-0.6c0.9-1.8,1.5-3,2.1-4.2c0,0-0.2,0.2-0.2,0.2 c0-2.5,0-5.1,0-8c3.6,3.1,6.6,6.4,10.2,8.5c3.5,2,7.7,2.7,12,4.1c0.4-0.7,0.1-5.2,4-1.5c0,2,0,4,0,6C96.7,30,93.3,30,90,30z" />
<path d="M0,18c2-0.7,4-1.3,5.9-1.9C5.3,20.6,4.6,25.3,4,30c-1.3,0-2.7,0-4,0C0,26,0,22,0,18z" />
<path d="M12,0c0.1,2.1,0.3,4.3,0.1,6.4c-0.3,3.6-1.8,6-6.2,5.3C4.7,7.2,6.5,3.5,9,0C10,0,11,0,12,0z" />
<path d="M86,0c-1.5,2.9-2.9,5.9-4.4,8.8c-0.5,0-1,0.1-1.5,0.1c0-3-0.1-6-0.1-9C82,0,84,0,86,0z" />
<path d="M41.6,17.6c-4.3,6.9-13.1,10-20.4,7.3C23.5,19.6,35.9,15,41.6,17.6z" />
<path
d="M73.8,20.9c0,0,0.2-0.2,0.2-0.2c-1.4,0.6-2.9,1.3-4.3,1.9c-1.5,0.6-3,1.2-4.4,1.9c-0.3-0.4-0.6-0.8-0.9-1.1 c1.4-3.2,2.8-6.4,4.3-9.6c0.6,0.1,1.1,0.2,1.7,0.2C71.4,16.3,72.6,18.6,73.8,20.9z" />
<path
d="M13.8,21.8c-0.6-2.5-1.2-5-1.8-7.4c0.6-0.1,1.1-0.3,1.7-0.4c2.1,2.2,6,3.8,2.6,7.5 C15.5,21.6,14.6,21.7,13.8,21.8z" />
<path d="M44.1,6c0.8,3,1.3,5,1.9,7.7C40.6,12.3,39.3,10.6,44.1,6z" />
</g>
<rect x="1" y="3" style="fill: none" width="98" height="24" />
<text transform="matrix(1 0 0 1 12.505 23.5904)" style="font-size:24px;letter-spacing:7">1234</text>
</svg>
</label>
<input type="text" name="ans" required>
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">取消</button>
<input class="btn btn-primary" type="submit" value="送出">
</div>
</form>

後台設計

  • 大致結構相同一致,可從 index.html 複製為 admin.html。再將不必要的元素進行移除。
  • 留言區:標題為玩家留言版後台管理請留意。
  • 選單區:調整內容為回前台與登出(導向到 api.php?do=logout)。
  • 留言區:移除新增留言功能,這是前台的功能,後台不需要。
  • Modal 區:後台需移除廣告區、賽事列表,另外 footer 部分僅保留 修改用 modal 即可;前台移除 footer 之修改用 modal(非必要不影響)、以及移除修改刪除按鈕。
admin.html
<body>
<!--網站標題-->
<header class="row-fluid">
<div class="span4">第 50 屆全國競賽電競遊 官方網站</div>
<div class="span8"></div>
</header>

<!-- 選單區 -->
<nav class="navbar navbar-inverse">
<div class="navbar-inner">
<div class="container">
<ul class="nav">
<li><a href="index.php">回前台首頁</a></li>
<li><a href="api.php?do=logout">管理登出</a></li>
</ul>
</div>
</div>
</nav>

<!-- 留言板 -->
<div id="msg"></div>
<section class="container">
<h2>玩家留言板</h2>
<!--
<a href="#msgadd" class="btn btn-info" data-toggle="modal" style="float:right">我要留言</a>
<div style="clear: both;"></div>
-->

<!-- each ok -->
<div class="thumbnail row-fluid">
<div class="span2">
<img src="img/user.jpg" class="img-circle" width="100" height="100">
<h4>User Name</h4>
<h5>#<span class="sn">1</span></h5>
</div>
<div class="span10">
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Possimus eligendi autem accusantium nam illo
commodi voluptates vero consectetur eum beatae?</p>
<div class="bottom">
<span class="badge badge-info">
&phone; <span class="tel">0988-100200</span>
</span>
<span class="badge badge-info">
<i class="icon-envelope icon-white"></i> <span class="mail">sss@gmail.com</span>
</span>
<span class="badge badge-info">
<i class="icon-time icon-white"></i> 2020/02/02 12:00:00
</span>
<!-- for admin use -->
<span class="control">
<a href="#msgmdy" class="btn btn-warning" data-toggle="modal">編輯</a>
<a href="api.php?do=msgdel&id=0" class="btn btn-danger">刪除</a>
</span>
</div>
</div>
</div>

<!-- each del -->
<div class="thumbnail row-fluid del">
<div class="span2">#2</div>
<div class="span10">此樓留言已被刪除</div>
</div>
</section>
<footer>
<!-- for admin to msgmdy-->
<form id="msgmdy" class="modal hide fade form-horizontal" action="api.php?do=msgmdy" method="post">
<div class="modal-header">
<h3>編輯留言</h3>
<input type="hidden" name="id">
</div>
<div class="modal-body">
<div class="control-group">
<label class="control-label">玩家姓名</label>
<input type="text" name="user" required value="Loki">
</div>
<div class="control-group">
<label class="control-label">留言內容</label>
<textarea name="info" required>Old Message....</textarea>
</div>
<div class="control-group">
<label class="control-label">Email</label>
<input type="email" name="mail" required value="blog@lokiwebs.com">
</div>
<div class="control-group">
<label class="control-label">連絡電話</label>
<input type="tel" name="tel" pattern="[0-9\-]{9,12}" value="0204888988">
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">取消</button>
<input class="btn btn-primary" type="submit" value="送出">
</div>
</form>
</footer>
</body>

資料庫規劃 (MySQL)

使用 XAMPP 的 MySQL 服務之一,你需要自行建立資料庫 db00(00 為你的應考座號)並提供 2 個資料表 (pk,msg),並適時的塞入一些數據方便測試與驗收作業。各項資料表如下:

msg

  • 資料表建立 msg,資料 5 筆
# 名稱 型態 編碼與排序 屬性 空值 預設值 備註 額外資訊
1 id int(10) UNSIGNED AUTO_INCREMENT
3 user text utf8mb4_unicode_ci
3 info text utf8mb4_unicode_ci
3 mail text utf8mb4_unicode_ci
3 tel text utf8mb4_unicode_ci
3 date datetime
2 del int(11)
id user info mail tel date del
1 User A 留言內容A aa@gmail.com 02-02345678 2020-07-20 10:23:04 0
2 User B 留言內容B bb@gmail.com 02-12345678 2020-07-20 11:28:02 1
3 User C 留言內容C cc@gmail.com 02-22345678 2020-07-20 12:51:02 0
4 User D 留言內容D dd@gmail.com 02-32345678 2020-07-20 13:55:54 0
5 User E 留言內容E ee@gmail.com 02-42345678 2020-07-20 14:51:25 0

pk

  • 資料表建立 pk(與前者相同你可以用複製節省時間),資料 5 筆
# 名稱 型態 編碼與排序 屬性 空值 預設值 備註 額外資訊
1 id int(10) UNSIGNED AUTO_INCREMENT
3 user text utf8mb4_unicode_ci
3 info text utf8mb4_unicode_ci
3 mail text utf8mb4_unicode_ci
3 tel text utf8mb4_unicode_ci
3 date datetime
2 del int(11)
id user info mail tel date del
1 User A user.jpg aa@gmail.com 02-02345678 2020-07-20 10:23:04 0
2 User B user.jpg bb@gmail.com 02-12345678 2020-07-20 11:28:02 1
3 User C user.jpg cc@gmail.com 02-22345678 2020-07-20 12:51:02 0
4 User D user.jpg dd@gmail.com 02-32345678 2020-07-20 13:55:54 0
5 User E user.jpg ee@gmail.com 02-42345678 2020-07-20 14:51:25 0

小技巧:你應該具備且熟悉 SQL 指令 (INSERT、SELECT、UPDATE),若不熟悉請多利用 phpmyadmin 來取得範例指令。之後的 PDO 連線會連續使用到 SQL 指令。

後端處理 (PHP)

大致可分為五個檔案進行設計:

  • index.php:將前面的 index.html 更改為 index.php,才能塞入 PHP 代碼提供前台之畫面處理。
  • admin.php:將前面的 admin.html 更改為 admin.php,才能塞入 PHP 代碼提供後台之畫面處理。
  • api.php:核心的 PHP 資料處理兩大功能,負責基礎設定以及所有的資料提交處理 (api.php?do=…)
  • msg.php:前後台都有的留言板(差異於能否修改刪除),這裡獨立出來再匯入能兩份工作一次完成。
  • pk.php:前獨有的賽事板,為了方便也一起獨立出來另匯入做開發。

新增 api.php 並進行已下基礎設定。

api.php
<?php
session_start(); //open session
$db = new PDO("mysql:host=127.0.0.1;dbname=db00;charset=utf8", "root", "", null);
?>

接下來我們會根據功能設計,不斷的添加代碼到這四筆檔案做進行編修。

站台登入

修改好副檔名後,我們開始對 index.php 提供登入功能,這裡還需要驗證碼。

  1. 前台的登入引導會有兩種可能,分別為未登入者引導到 api.php ,以及成功登入者直接到admin.php。這機制需要 session 來操作判斷。

  2. 假定有個變數為 $_SESSION['user'] 能代表登入狀態,如果未登入者為 guest,登入者可能是 admin 或用戶信箱號碼。

  3. 先處理登入按鈕的顯示,在那之前需要先引用 session_start()進來,最快是 include api.php 這個檔案。
    增添到 index.php 到頁首處:

    index.php
    <?php
    include("api.php");
    ?>

    接著於加入 $_SESSION[‘user’] 判斷處理:

    api.php
    if (empty($_SESSION['user'])) $_SESSION['user'] = "guest";
  4. 接著判斷$_SESSION 是否為 guest 來決定登入的導向連結
    修改:

    index.php
    <li><a href="#pkadd" data-toggle="modal">玩家參賽</a></li>
    <li><a href="#login" data-toggle="modal">網站管理</a></li>

    index.php
    <li><a href="#pkadd" data-toggle="modal">玩家參賽</a></li>
    <?php
    if ($_SESSION['user'] != "guest")
    echo '<li><a href="admin.php">網站管理</a></li>';
    else
    echo '<li><a href="#login" data-toggle="modal">網站管理</a></li>';
    ?>
  5. 需要驗證碼所以我們頁首處需要產生隨機數 1000~9999,注意此代碼必須於 include 之後

    index.php
    include("api.php");
    $_SESSION['rand']=rand(1000, 9999);
  6. 接著調整 svg 的文字部分,找到 footer svg text之數字進行替換,像是以下代碼

    index.php
    <text transform="matrix(1 0 0 1 12.505 23.5904)" style="font-size:24px;letter-spacing:7"><?= $_SESSION['rand'] ?></text>
  7. 可測試一下登入功能,並查看是否轉到 api.php?do=login 之畫面為何,並停留到此畫面勿離開。
    增添代碼如下:

    api.php
    if (!empty($_GET['do'])) switch ($_GET['do']) {
    case 'login':
    print_r($_SESSION);
    print_r($_GET);
    print_r($_POST);
    break;
    }

    你可以這樣 print_r() 這些超全域變數做判斷。

  8. 接著我們要做驗證碼處理與後續處理,這裡會有三種狀況分別為:驗證碼錯誤、管理者登入、用戶登入(採 user Name 與 Mail),同時利用 JS 指令提供資訊並轉向到適合的頁面去。
    調整 case 'login' 內容為:

    api.php
    case 'login':
    if ($_SESSION['rand'] != $_POST['ans'])
    echo '<script>alert("驗證碼錯誤,請重新登入");location.href="index.php";</script>';
    else if ($_POST['user'] == "admin" && $_POST['pwd'] == "1234") {
    $_SESSION['user'] = "admin";
    echo '<script>alert("歡迎管理員");location.href="admin.php";</script>';
    } else {
    $sql = "SELECT * FROM msg WHERE user='{$_POST['user']}' AND mail='{$_POST['pwd']}'";
    $msglist = $db->query($sql)->fetchAll();
    if ($msglist) {
    $_SESSION['user'] = $_POST['user'];
    $_SESSION['mail'] = $_POST['pwd'];
    echo '<script>alert("歡迎,' . $_SESSION['user'] . '");location.href="admin.php";</script>';
    } else echo '<script>alert("帳密錯誤,請重新登入");location.href="index.php";</script>';
    }
    break;
  9. 此外我們需要對後台做個驗證機制,凡是沒有經過 api.php?do=login 索取得到$_SESSION[‘user’] 新值的人都給他踢回前台去。

    admin.php
    <?php
    include("api.php");
    if ($_SESSION['user'] == 'guest' || empty($_SESSION['user']))
    echo "<script>alert('你沒有訪問權限,請回前台登入');location.href='index.php';</script>";
    ?>

站台登出

這裡做個站台登出功能,方便我們前後台測試多種帳戶。

api.php
case 'logout':
unset($_SESSION['user']);
echo '<script>alert("已登出");location.href="index.php";</script>';
break;

留言列表

這部分是共用在前後台的,唯一差別在於如果管理者或訪客都是全部顯示,如果是用戶管理則是限定個人顯示。如果是後者,巧的是驗證登入時我們已經 SELECT 過 DB 了,所以可以省下 SELECT 工作

  1. 將留言板挖空另存為 msg.php
    修改

    index.php & admin.php
    <!-- each ok -->
    <div class="thumbnail row-fluid">
    <div class="span2">
    <img src="img/user.jpg" class="img-circle" width="100" height="100">
    <h4>User Name</h4>
    <h5>#<span class="sn">1</span></h5>
    </div>
    <div class="span10">
    <p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Possimus eligendi autem accusantium nam illo
    commodi voluptates vero consectetur eum beatae?</p>
    <div class="bottom">
    <span class="badge badge-info">
    &phone; <span class="tel">0988-100200</span>
    </span>
    <span class="badge badge-info">
    <i class="icon-envelope icon-white"></i> <span class="mail">sss@gmail.com</span>
    </span>
    <span class="badge badge-info">
    <i class="icon-time icon-white"></i> 2020/02/02 12:00:00
    </span>
    <!-- for admin use -->
    <!--
    <span class="control">
    <a href="#msgmdy" class="btn btn-warning" data-toggle="modal">編輯</a>
    <a href="api.php?do=msgdel&id=0" class="btn btn-danger">刪除</a>
    </span>
    -->
    </div>
    </div>
    </div>

    <!-- each del -->
    <div class="thumbnail row-fluid del">
    <div class="span2">#2</div>
    <div class="span10">此樓留言已被刪除</div>
    </div>

    index.php & admin.php
    <?php include("msg.php") ?>
  2. msg.php 需要一些判斷何種顯示,包含了:

    • 是否為管理員(後台)或前台則要完整 SELECT,如果不是就拿之前登入時的驗證資料。
    • 如果資料中有發現 del 成立,畫面上我們要調整為已刪除,否則正常顯示
    • 如果頁面是前台,我們不能顯示 control(即使是管理員),這需要另一個判斷目前是 index.php 還是 admin.php。
    • 之後還要使用 JS 處理加載數據,因此修改按鈕需加入 onclick 並觸發 JS 名為 setval 之函式操作。因此電話信箱也添加了 span.class 名稱
index.php
<?php
include("api.php");
$_SESSION['rand'] = rand(1000, 9999);
$frontsite=TRUE;
?>
admin.php
<?php
include("api.php");
if ($_SESSION['user'] == 'guest' || empty($_SESSION['user']))
echo "<script>alert('你沒有訪問權限,請回前台登入');location.href='index.php';</script>";
$frontsite=FALSE;
?>
msg.php
<?php
if ($frontsite || $_SESSION['user'] == 'admin')
$sql = "SELECT * FROM msg";
else
$sql = "SELECT * FROM msg WHERE user='{$_SESSION['user']}' AND mail='{$_SESSION['mail']}'";

$rows = $db->query($sql)->fetchAll();
foreach ($rows as $row) {
if ($row['del']) { //del
?>
<!-- each del -->
<div class="thumbnail row-fluid del">
<div class="span2">#<?= $row['id'] ?></div>
<div class="span10">此樓留言已被刪除</div>
</div>
<?php
} else {
?>
<!-- each ok -->
<div class="thumbnail row-fluid">
<div class="span2">
<img src="img/user.jpg" class="img-circle" width="100" height="100">
<h4><?= $row['user'] ?></h4>
<h5>#<span class="sn"><?= $row['id'] ?></span></h5>
</div>
<div class="span10">
<p class="info"><?= $row['info'] ?></p>
<div class="bottom">
<span class="badge badge-info">
&phone; <span class="tel"><?= $row['tel'] ?></span>
</span>
<span class="badge badge-info">
<i class="icon-envelope icon-white"></i> <span class="mail"><?= $row['mail'] ?></span>
</span>
<span class="badge badge-info">
<i class="icon-time icon-white"></i> <?= $row['date'] ?>
</span>

<!-- for admin use -->
<?php
if (!$frontsite) echo '
<span class="control">
<a href="#msgmdy" class="btn btn-warning" data-toggle="modal" onclick="setval(this)">編輯</a>
<a href="api.php?do=msgdel&id=' . $row['id'] . '" class="btn btn-danger">刪除</a>
</span>
';
?>

</div>
</div>
</div>
<?php
}
}
?>

目前為止已經能正常顯示來自資料庫的留言資訊,接著是進行增加修改刪除作業。

新增留言

前面 modal 已完成表單設計,這裡只要檢查表單過來的資料為何,組合成 INSERT 指令輸出並轉回首頁即可。

api.php
case 'msgadd':
$sql = "INSERT INTO `msg`(`id`, `user`, `info`, `mail`, `tel`, `date`, `del`) VALUES (null,'" . $_POST['user'] . "','" . $_POST['info'] . "','" . $_POST['mail'] . "','" . $_POST['tel'] . "',NOW(),0)";
$db->query($sql);
echo '<script>alert("留言完成");location.href="index.php";</script>';
break;

修改留言

這裡的修改留言是後台功能,按下修改時我們必須幫使用者設定 value(舊值)。透過 JavaScript 來完成 DOM 的讀取與寫入。同時還要處理提交出去的表單修改。於該頁最後添加:

msg.php
<script>
function setval(e) {
let bigroot = $(e).parents(".row-fluid");
let name = bigroot.find("h4").text();
let info = bigroot.find("p").text();
let mail = bigroot.find(".mail").text();
let tel = bigroot.find(".tel").text();
let id = bigroot.find(".sn").text();

$("#msgmdy").find("input[name=user]").val(name);
$("#msgmdy").find("textarea[name=info]").text(info);
$("#msgmdy").find("input[name=mail]").val(mail);
$("#msgmdy").find("input[name=tel]").val(tel);
$("#msgmdy").find("input[name=id]").val(id);
}
</script>
api.php
case 'msgmdy':
$sql = "UPDATE `msg` SET `user`='" . $_POST['user'] . "',`info`='" . $_POST['info'] . "',`mail`='" . $_POST['mail'] . "',`tel`='" . $_POST['tel'] . "',`date`=NOW() WHERE id=" . $_POST['id'];
$db->query($sql);
echo '<script>alert("更新完成!\n注意:如果有變動人名或信箱將無法於本次載入,請重新登入身分刷新列表!");location.href="admin.php";</script>';
break;

刪除留言

相對來說簡單些,只要提供 id 編號就能轉為 UPDATE 指令要求 del=1,我們不希望真的刪除掉資料。

api.php
case 'msgdel':
$sql = "UPDATE `msg` SET `del`=1 WHERE id=" . $_GET['id'];
$db->query($sql);
echo '<script>alert("刪除完成");location.href="admin.php";</script>';
break;

比賽列表

  1. 雖然沒有共用前後台頁面,但避免程式碼過混亂我們也另抽取為 pk.php。
    修改:

    index.php
    <tbody>
    <tr>
    <td>1</td>
    <!--img's size=50*50-->
    <td><img src="img/user.jpg"></td>
    <td>User Name</td>
    <td>VS</td>
    <td>User Name</td>
    <!--img's size=50*50-->
    <td><img src="img/user.jpg"></td>
    </tr>
    <tr>
    <td>2</td>
    <!--img's size=50*50-->
    <td><img src="img/user.jpg"></td>
    <td>User Name</td>
    <td>VS</td>
    <td>等待匹配中。..</td>
    <td></td>
    </tr>
    </tbody>

    index.php
    <tbody>
    <?php include("pk.php") ?>
    </tbody>
  2. 我們需要判斷如果資料數是基數的要添加匹配中的資訊,反之不用。只是為了版面好看我們要用陣列來處理排序問題。

    pk.php
    <?php
    $rows = $db->query("SELECT * FROM pk")->fetchAll();

    $data = array();
    foreach ($rows as $row) $data[] = [$row['user'], $row['info']];
    $num = ceil(count($data) / 2);

    for ($i = 1; $i <= $num; $i++) {
    ?>
    <tr>
    <?php
    echo '<td>'.$i.'</td>';

    $item = array_shift($data);
    echo '
    <td><img src="img/'.$item[1].'"></td>
    <td>'.$item[0].'</td>
    <td>VS</td>
    ';

    $item = array_shift($data);
    if($item) echo '
    <td>'.$item[0].'</td>
    <td><img src="img/'.$item[1].'"></td>
    ';
    else echo '
    <td>等待匹配中。..</td>
    <td></td>
    ';
    ?>
    </tr>
    <?php
    }
    ?>

比賽報名

也都是表單提交轉 INSERT 指令。比較特別的是這裡需要做圖片上傳,我們需要透過 PHP 將表單的$_FILE 進行複製到 img 資料夾內,接著抽取路徑之新檔案名稱一併 INSERT 上去。

api.php
case 'pkadd':
// print_r($_FILES);
$newname = time() . "_" . $_FILES['img']['name'];
copy($_FILES['img']['tmp_name'], "img/" . $newname);

$sql = "INSERT INTO `pk`(`id`, `user`, `info`, `mail`, `tel`, `date`, `del`) VALUES (null,'" . $_POST['user'] . "','" . $newname . "','" . $_POST['mail'] . "','" . $_POST['tel'] . "',NOW(),0)";
$db->query($sql);
echo '<script>alert("參賽完成");location.href="index.php";</script>';
break;