2021年2月8日 星期一

jQuery Mobile 學習筆記 (十三) : 疫苗登記表專案 (上)

這三天放下網路爬蟲, 幫水某的疫苗臨床試驗寫一個行動網頁, 今天終於完成了, 寫完才說因為計畫主持人怕個資外洩, 所以還是用紙本就好. 雖然似乎白忙一場, 但我倒覺得頗有收穫, 因為上回學 jQuery Mobile 後整理筆記接近完成, 就缺與後端的互動這塊, 這個專案剛好就需要用到後端資料庫, 而且過程中還發現了幾個活用 jQuery Mobile 的小技巧, 這些剛好當作將來開發類似專案的暖身與練兵範本, 雖然還只是雛型, 但值得再抽點時間紀錄一下開發步驟. 


1. 資料表與首頁設計 :   

此行動網頁專案要求很簡單, 需要設計一張表單讓使用者透過手機輸入如下四個欄位, 用來記錄疫苗試驗受試者資料 :
  • 名字 (name)
  • 電話 (phone)
  • 年齡 (age)
  • 性別 (gender)
然後實作資料表的 CRUD (新增/讀取/更新/刪除) 操作, 所以首先設計一個資料表 vaccine 如下 :




此處除了上面要求的四個欄位之外, 還添加了作為主鍵的 id 欄位與紀錄登記日期的 rdate, 建立此資料表之 SQL 指令如下 : 

--
-- 資料表結構 `vaccine`
--

CREATE TABLE `vaccine` (
  `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `age` int(11) NOT NULL,
  `phone` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `gender` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL,
  `rdate` datetime NOT NULL,
  `id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

其次, 為了可以線上登入系統後台管理資料表, 還需要一個 users 資料表用來儲存管理員帳密, 其結構如下 : 




建立此資料表之 SQL 語法如下 : 

--
-- 資料表結構 `users`
--

CREATE TABLE `users` (
  `account` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  `password` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

接下來是進行首頁 (index.htm) 設計, 其實就是一個讓登記者輸入個人資料的表單, 搭配表頭進入登入與管理頁面的按鈕, 網頁內容如下 :

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
    <script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
    <link href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" rel="stylesheet">
  </head>
  <body>
    <!-- 第一頁頁面 -->
    <section data-role="page" id="page1">
      <header data-role="header" data-position="fixed">
        <a href="#page2" data-role="button" data-icon="user">登入</a>
        <h1 style="white-space: nowrap">受試者登記表</h1>
        <a href="vaccine_admin.php" class="ui-btn-right" data-role="button" data-icon="lock" data-ajax="false">管理</a>
      </header>
      <article data-role="content">
        <form id="apply_form" method="post" action="vaccine_register.php" data-ajax="false">  
          <div data-role="fieldcontain">
            <label for="name">姓名 (name) :</label>
            <input type="text" id="name" name="name">
          </div>
          <div data-role="fieldcontain">
            <label for="phone">電話 (phone) :</label>
            <input type="tel" id="phone" name="phone">
          </div>
          <div data-role="fieldcontain">
            <label for="age">年齡 (age) :</label>
            <input type="number" id="age" name="age" min="0" max="100">
          </div>
          <fieldset data-role="controlgroup" data-type="horizontal">
            <legend>性別 (gender) :</legend>
            <div data-role="fieldcontain">
              <label for="gender-1">男 (male)</label>
              <input type="radio" name="gender" id="gender-1" value="男" data-inline="true">
              <label for="gender-2">女 (female)</label>
              <input type="radio" name="gender" id="gender-2" value="女" data-inline="true">
            </div>
          </fieldset>
          <div data-role="fieldcontain">
            <button id="register_btn" data-icon="check">登記 (register)</button>
            <button id="info_btn" data-icon="info">說明 (information)</button>
          </div>
        </form>
      </article>
      <footer data-role="footer" data-position="fixed">
        <h3>小狐狸事務所 07-1234567</h3>
      </footer>
    </section>
    <!-- 第二頁頁面 -->
    <section data-role="page" id="page2">
      <header data-role="header" data-position="fixed" data-add-back-btn="true">
        <h1>管理員登入</h1>
      </header>
      <article data-role="content">
        <form method="post" action="login.php" data-ajax="false">
          <div data-role="fieldcontain">
            <label for="account">帳號</label>
            <input type="text" id="account" name="account" placeholder="請輸入帳號">
          </div>
          <div data-role="fieldcontain">
            <label for="account">密碼</label>
            <input type="password" id="password" name="password" placeholder="請輸入密碼">
          </div>
          <input type="button" id="login_btn" value="登入">
        </form>
      </article>
      <footer data-role="footer" data-position="fixed">
        <h3>小狐狸事務所 07-1234567</h3>
      </footer>
    </section>
    <script>
      function trim(s){ 
        return (s || '').replace(/^\s+|\s+$/g, ''); 
        }
      $(document).ready(function() {
        $("#register_btn").on("click", function(e) { 
          e.preventDefault();
          var name=trim($("#name").val());
          var phone=trim($("#phone").val());
          var age=trim($("#age").val());
          var gender=$("[name=gender]:radio:checked").val();
          if (name=="" ||  phone=="" || age=="" || gender==undefined) {
            alert("每一個欄位都必須填寫");
            return;
            }
          var msg="姓名 : " + name + "\n" + 
                  "電話 : " + phone + "\n" +  
                  "年齡 : " + age + "\n" + 
                  "性別 : " + gender + "\n" +
                  "以上資料正確嗎? ";
          if (!confirm(msg)) {return;}
          else {this.form.submit();}
          });
        $("#login_btn").on("click", function(e) { 
          e.preventDefault();
          var account=trim($("#account").val());
          var password=trim($("#password").val());
          if (account=="" || password=="") {
            alert("請輸入帳號與密碼");
            return;
            }
          this.form.submit();
          });
        $("#info_btn").on("click", function(e) { 
          e.preventDefault();
          alert("系統壓力測試中!");
          });
        });
    </script>
  </body>
</html> 

此首頁 index.htm 由兩個頁面 (page) 構成, 第一頁 page1 為登記頁面, 填好資料按登記鈕會跳轉 vaccine_register.php 將資料存入資料表 vaccine 中; 按右上角的管理按鈕則可在已登入狀態下直接進入管理頁 (未登入則仍停在首頁). 

這個網頁有幾個要注意之處 :

首先是 header 與 footer 都要添加 data-position="fixed" 屬性以便將頁首與頁尾都固定在頭尾位置. 其次, 因為 jQuery Mobile 網頁跳轉預設使用 Ajax, 但這樣有時不符操作需求, 因此網頁中會跳轉的超連結與表單元素都加上 data-ajax="false" 屬性關閉預設的 Ajax 模式. 另外, 性別選項中的 radio 元素添加了 data-inline="true" 屬性, 其作用是讓選項按鈕佔據螢幕寬度, 因為預設太窄了. 

第一頁畫面如下 :




輸入個資後按登記鈕程式會先檢查四個欄位是否都有填寫 (validation), 是才提交表單, 否則顯示訊息 : 




第二頁為讓管理者登入之頁面, 按第一頁左上角的登入鈕會進入此登入頁, 輸入帳號密碼按登入鈕即可進入管理頁面 vaccine_admin.php. 

第二頁頁面如下 : 




測試網址如下 (用手機掃 QR code) :





2.  新增資料 : 

在上面首頁 index.htm 中輸入個資按登記鈕會將表單提交給 vaccine_register.php 這個後端網頁程式, 其內容如下 : 

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
    <script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
    <link href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" rel="stylesheet">
  </head>
  <body>
    <!-- 第一頁頁面 -->
    <section data-role="page" id="page1">
      <header data-role="header" data-position="fixed">
        <h1 style="white-space: nowrap">疫苗臨床試驗登記</h1>
      </header>
      <article data-role="content">
<?php
//設定台北時間
date_default_timezone_set("Asia/Taipei");  
//建立 PDO 連線物件
$dsn="mysql:host=localhost;port=3306;dbname=test"; 
$username="root"; 
$password="mysql";
try {$conn=new PDO($dsn, $username, $password);}
catch (PDOException $e) {
  echo "資料庫連線錯誤!";
  die();
  }
//設定資料編碼
$conn->exec("SET CHARACTER SET utf8"); 
//取得傳遞之參數
$name=$_REQUEST["name"];
$phone=$_REQUEST["phone"];
$age=$_REQUEST["age"];
$gender=$_REQUEST["gender"];
//檢查是否已登記過
$SQL="SELECT * FROM `vaccine` WHERE name='".$name."' AND phone='".
      $phone."' AND age='".$age."'"; 
$RS=$conn->query($SQL);
if ($RS->rowCount() > 0) { //已登記過
?>
        <h3>您已登記過了, 勿重複登記!</h3>
<?php
  }
else { //未曾登記 -> 新增紀錄 
  $SQL="INSERT INTO `vaccine`(name,phone,age,gender,rdate) VALUES('".
       $name."','".$phone."','".$age."','".$gender."','".date("Y-m-d H:i:s")."')"; 
  $RS=$conn->query($SQL);
  if ($RS->rowCount() > 0) { //新增成功
?>
        <h3>已完成登記, 將有專人與您聯絡!</h3>
<?php
    }
  else { //新增失敗
?>
        <h3>系統異常, 請稍後再試試!</h3>
<?
    } 
  }
$conn=NULL;
?>
        <a href="index.htm" data-role="button" data-ajax="false">回登記頁面</a>
      </article>
      <footer data-role="footer" data-position="fixed">
        <h3>小狐狸事務所 07-1234567</h3>
      </footer>
    </section>
  </body>
</html>

此程式需注意之處是必須用 date_default_timezone_set("Asia/Taipei") 設定為台北時區, 這樣存入資料表 vaccine 時 rdate 欄位才會記錄台灣 GMT+8 時間. 此處使用 XMPP 本機伺服器, 如果是虛擬主機, 例如我用的英國 Hostinger 的連線資訊要改為例如 :

$dsn="mysql:host=mysql.hostinger.co.uk;port=3306;dbname=u137801000_test"; 
$username="u137801000_test"; 
$password="a123456";

程式首先依據用 $_REQUEST[] 取得的 name, phone, age 參數查詢資料表中是否已登記過, 若未曾登記才新增, 此處使用 PDO 存取資料表, 相關 SQL 語法參考 : 


新增多筆資料後的 vaccine 資料表如下 (從 phpMyAdmin 取得) :




3. 登入後台管理頁面 :  

管理者可在首頁按左上角的登入鈕, 輸入帳密按登入鈕會向 login.php 提交表單, 此程式會查詢 users 資料表比對帳密是否存在, 存在就建立一個 Session 連線資訊在伺服器中以記錄連線者已登入狀態, 在其存續期間都可進入後台管理頁面, 程式內容如下 :

<?php
//啟動 session 功能
session_start();   
//建立 PDO 連線物件
$dsn="mysql:host=localhost;port=3306;dbname=test"; 
$username="root"; 
$password="mysql";
try {$conn=new PDO($dsn, $username, $password);}
catch (PDOException $e) {
  echo "資料庫連線錯誤!";
  die();
  }
//設定資料編碼
$conn->exec("SET CHARACTER SET utf8"); 
//讀取資料表
$account=$_REQUEST["account"];
$password=$_REQUEST["password"];
//echo $account.$password;
$SQL="SELECT * FROM `users` WHERE account='".$account."' ".
     "AND password='".$password."'"; 
$RS=$conn->query($SQL);
if ($RS->rowCount() > 0) { //帳密符合
  $_SESSION["vaccine_admin_account"]=$account;   
  header("Location: vaccine_admin.php");   
  }
else { //帳密不符
  header("Location: index.htm");
  die();
  } 
$conn=NULL;
?>

此處重點是需先開啟 Session 功能, 然後在驗證帳密後設定連線變數再跳轉到 vaccine_admin.php 進入後台管理頁面 :




此後台管理管理頁面有三個按鈕, 其中受試者列表是主角, 另外兩個功能因計畫喊卡未實作, 匯出受試者可將 vaccine 資料表匯出為 EXCEL 檔以便下載 , 系統設定用來更改標題, 切換主題, 重設資料表等, 但也未實作, vaccine_admin.php 網頁程式內容如下 : 

<?php
//啟動 session 功能
session_start();
//設定台北時間
date_default_timezone_set("Asia/Taipei");
if (!isset($_SESSION["vaccine_admin_account"])) { //未登入回首頁
  header("Location: index.htm");
  }
?>
<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
    <script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
    <link href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" rel="stylesheet">
  </head>
  <body>
    <!-- 第一頁頁面 -->
    <section data-role="page" id="page1">
      <header data-role="header">
        <a href="index.htm" data-icon="home" data-ajax="false">回首頁</a>
        <h1>系統管理</h1>
        <a href="logout.php" class="ui-btn-right" data-icon="power" data-ajax="false">登出</a>
      </header>
      <article data-role="content">
        <a href="vaccine_subject_list.php" data-role="button" data-icon="user" data-ajax="false">受試者列表</a>
        <a href="#" data-role="button" data-icon="action" data-ajax="false">匯出受試者</a>
        <a href="#" data-role="button" data-icon="gear" data-ajax="false">系統設定</a>
      </article>
      <footer data-role="footer" data-position="fixed">
        <h3>小狐狸事務所 07-1234567</h3>
      </footer>
    </section>
    <script>
      $(document).ready(function() {
        $("#ok_btn").on("click", function(e) { 
          e.preventDefault();
          var name=trim($("#name").val());
          var phone=trim($("#phone").val());
          var age=trim($("#age").val());
          var gender=$("[name=gender]:radio:checked").val();
          if (name=="" ||  phone=="" || age=="" || gender==undefined) {
            alert("每一個欄位都必須填寫");
            return;
            }
          var msg="姓名 : " + name + "\n" + 
                  "電話 : " + phone + "\n" +  
                  "年齡 : " + age + "\n" + 
                  "性別 : " + gender + "\n" +
                  "以上資料正確嗎? ";
          if (!confirm(msg)) {return;}
          else {this.form.submit();}
          });
        $("#login_btn").on("click", function(e) { 
          e.preventDefault();
          var account=trim($("#account").val());
          var password=trim($("#password").val());
          if (account=="" || password=="") {
            alert("請輸入帳號與密碼");
            return;
            }
          this.form.submit();
          });
        });
    </script>
  </body>
</html>

此網頁重點是前面開啟 Session 功能後, 檢查 Session 變數是否存在, 若不存在就拒絕進入直接跳轉到首頁. 

沒有留言 :