自製免費點名系統 - 複製貼上就能成為工程師

文章目錄

本文將教授如何使用程式製作一個簡單的點名網頁。透過這個點名系統,你可以方便地管理你的學生出席狀況,並且隨時查詢歷史出席紀錄。我們將使用 Google Apps Script, GitHub 和 Google Sheets 來建立這個點名系統。我敢保證即使你完全不會程式也可以在5分鐘內完成。本文會詳細說明從建立 Google Sheets 到部屬網頁的步驟,並提供完整的程式碼和演示網頁。

我在兩年前曾經寫過一篇【GAS】自製點名系統,出乎意料地幫助到許多人,所以我決定重新寫一次,比上次更容易製作和操作,也更好看一些(我覺得啦)。

範例網頁

首先,我們先來看一下最終的成果。這是我們要製作的點名系統的演示網頁。你可以點擊這裡查看完整的演示網頁。他有以下幾個功能

  • 點名:點擊學生姓名,即可完成點名
  • 新增學生:輸入學生姓名點擊新增按鈕,即可新增學生
  • 查詢歷史出席紀錄:輸入學生姓名點擊查詢按鈕,即可查詢歷史出席紀錄

好了,我們現在就開始製作這個點名系統吧w

步驟一:建立 Google Sheets 文件

首先,我們需要建立一個 Google Sheets 文件,用於存儲學生的出席情況。在這個文件中,我們可以添加學生名稱、出席時間、剩餘課堂等信息。

請打開我建立的這個範例文件並建立副本

建立副本

這樣Google Sheet就做好了。請複製這個文件的ID,我們稍後會用到。ID就是網址中的一長串字母和數字,比如說這個試算表:

1https://docs.google.com/spreadsheets/d/1m0F6pOejN-ldKFIrFwssmoEPB3EPDmSQJKEPr9T88-E/edit#gid=0

它的ID就是1m0F6pOejN-ldKFIrFwssmoEPB3EPDmSQJKEPr9T88-E

步驟二:建立 Google Apps Script

現在,我們需要建立一個 Google Apps Script,用於向 Google Sheets 文件中添加和讀取數據。請在網址輸入script.new,進入 Google Apps Script 編輯器。接著貼上我的這一串程式。請把第一行的雙引號裡面換成剛才複製的ID。

1const id = "1m0F6pOejN-ldKFIrFwssmoEPB3EPDmSQJKEPr9T88-E"
2
3function doGet(e){let t=e.parameter,a=SpreadsheetApp.openById(id).getSheets();switch(t.type){case"call":if(!t.time)return ContentService.createTextOutput(!1);return a[0].appendRow([t.name,t.time,t.remain]),ContentService.createTextOutput(!0);case"list":var r=a[1].getRange(2,1,a[1].getLastRow()-1,a[1].getLastColumn()).getValues().filter(e=>""!==e[0]).map(e=>({name:e[0],left:e[2]}));return ContentService.createTextOutput(JSON.stringify(r)).setMimeType(ContentService.MimeType.JSON);case"search":var[n,...r]=a[0].getDataRange().getValues();let[u,i,p]=n,s=n.indexOf(u),m=n.indexOf(i),c=n.indexOf(p),l=r.filter(e=>e[s]===t.name).map(e=>({time:e[m],left:e[c]}));return ContentService.createTextOutput(JSON.stringify(l)).setMimeType(ContentService.MimeType.JSON);case"new":let f=a[1].getLastRow()+1;return a[1].appendRow([t.name,`=COUNTIF('紀錄'!A:A,A${f})`,`=D${f}-B${f}`]),ContentService.createTextOutput(!0);default:return ContentService.createTextOutput("別亂撞我~")}}

我們需要把它部屬成網頁,請點擊左上角的部屬,新增部屬作業,選擇部屬為網頁應用程式。執行身分選自己(我),誰可以存取選所有人。接著點擊部屬,複製網頁應用程式網址。比如說:

1https://script.google.com/macros/s/AKfycbzxqGIMBbLkCka2aveltdVHYtdG-k_X98qzSd_V9MHDxWaOYXFwZgE3rRHDzCakzTxs/exec

部屬網頁應用程式

步驟三:建立網頁

請你在任意一個網頁代管服務,比如說Vercel,Github Pages, Gitlab Pages, Netlify等等,建立一個網頁。接著在網頁中貼上以下程式碼。

如果你沒有使用過這些服務,可以參考以下教學:

使用 Github Pages部屬網頁

請先註冊帳號,你可以參考以下影片:

部屬網頁有兩個辦法。選一個就可以了

用戶名.github.io

第一個是影片說明的方法,就是建立一個叫做用戶名.github.io的倉庫,然後建立一個index.html的檔案並貼上以下程式。

  1<!DOCTYPE html>
  2<html lang="zh-TW">
  3
  4<head>
  5    <meta charset="UTF-8">
  6    <meta http-equiv="X-UA-Compatible" content="IE=edge">
  7    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  8    <title>簡易點名系統</title>
  9    <meta name="theme-color" content="3B4252" />
 10    <style>
 11        h1 {
 12            /* 標題顏色 */
 13            color: var(--nord7)
 14        }
 15
 16        body {
 17            /* 背景顏色 */
 18            background-color: var(--nord0)
 19        }
 20
 21        body {
 22            /* 可選顏色 */
 23            --nord0: #2E3440;
 24            --nord1: #3B4252;
 25            --nord2: #434C5E;
 26            --nord3: #4C566A;
 27            --nord4: #D8DEE9;
 28            --nord5: #E5E9F0;
 29            --nord6: #ECEFF4;
 30            --nord7: #8FBCBB;
 31            --nord8: #88C0D0;
 32            --nord9: #81A1C1;
 33            --nord10: #5E81AC;
 34            --nord11: #BF616A;
 35            --nord12: #D08770;
 36            --nord13: #EBCB8B;
 37            --nord14: #A3BE8C;
 38            --nord15: #B48EAD;
 39            --black: #000;
 40            --line: #4C566A
 41        }
 42
 43        main,
 44        nav {
 45            display: flex
 46        }
 47
 48        .call button,
 49        button:hover {
 50            background-color: var(--nord2)
 51        }
 52
 53        button,
 54        section>div {
 55            background-color: var(--nord1);
 56            box-shadow: rgba(0, 0, 0, .2) 0 0 .5rem
 57        }
 58
 59        main,
 60        section>div {
 61            padding: 1rem;
 62            width: 100%
 63        }
 64
 65        footer,
 66        footer a {
 67            color: var(--nord4)
 68        }
 69
 70        .search button,
 71        button,
 72        input,
 73        section>div {
 74            box-shadow: rgba(0, 0, 0, .2) 0 0 .5rem
 75        }
 76
 77        body,
 78        button,
 79        h2,
 80        html {
 81            text-align: center
 82        }
 83
 84        * {
 85            padding: 0;
 86            margin: 0;
 87            box-sizing: border-box;
 88            font-family: Arial, "微軟正黑體", Helvetica, sans-serif;
 89            color: var(--nord6)
 90        }
 91
 92        body,
 93        html {
 94            min-height: 100%
 95        }
 96
 97        main {
 98            flex-direction: column;
 99            height: 100vh;
100            height: 100dvh;
101            max-width: 500px;
102            margin: 0 auto
103        }
104
105        nav {
106            justify-content: space-between
107        }
108
109        button {
110            display: block;
111            height: 50px;
112            width: calc(1/3*100% - 1rem);
113            line-height: 50px;
114            border-radius: 1rem;
115            text-decoration: none;
116            border: none;
117            cursor: pointer;
118            transition: background-color .2s ease-in-out
119        }
120
121        #call,
122        .search,
123        footer {
124            display: flex
125        }
126
127        .call button {
128            width: calc(1/4*100% - 1rem);
129            margin: .5rem
130        }
131
132        button:hover {
133            filter: brightness(1.2)
134        }
135
136        button:active {
137            background-color: var(--nord3);
138            filter: brightness(1.5)
139        }
140
141        section {
142            flex-grow: 1;
143            margin: 1rem 0;
144            position: relative
145        }
146
147        footer {
148            justify-content: flex-end;
149            align-items: flex-end
150        }
151
152        section>div {
153            border-radius: 1rem;
154            overflow-x: hidden;
155            overflow-y: auto;
156            position: absolute;
157            height: 100%;
158            transition: opacity .5s ease-in-out
159        }
160
161        #add,
162        #history {
163            opacity: 0
164        }
165
166        #call {
167            z-index: 2;
168            flex-wrap: wrap;
169            justify-content: space-between
170        }
171
172        .search {
173            justify-content: center;
174            align-items: center
175        }
176
177        .search button,
178        input {
179            height: 2rem;
180            width: 50%;
181            border-radius: .5rem;
182            border: transparent;
183            padding: 0 1rem;
184            background-color: var(--nord3);
185            color: var(--nord4)
186        }
187
188        h2,
189        table {
190            width: 100%
191        }
192
193        input:focus {
194            outline: transparent
195        }
196
197        .search button {
198            margin-left: 1rem;
199            width: auto;
200            line-height: 100%
201        }
202
203        table {
204            border-collapse: collapse;
205            margin-top: 1rem
206        }
207
208        tr {
209            border-bottom: 1px solid var(--line)
210        }
211
212        td {
213            padding: .5rem
214        }
215
216        h2 {
217            margin-top: 2rem;
218            font-weight: 600
219        }
220
221        #status {
222            margin: .5rem 0 1rem;
223            color: var(--nord13);
224            font-size: 1.3rem
225        }
226    </style>
227</head>
228
229<body>
230    <main>
231        <h1>簡易點名系統</h1>
232        <h2 id="status">歡迎使用</h2>
233        <nav>
234            <button onclick="searchA()">查詢紀錄</button><button onclick="callA()">點名</button><button
235                onclick="addA()">新增學生</button>
236        </nav>
237        <section>
238            <div id="history">
239                <div class="search">
240                    <input type="text"><button>搜尋</button>
241                </div>
242                <table>
243                    <thead>
244                        <tr>
245                            <td>時間</td>
246                            <td>剩下課堂</td>
247                        </tr>
248                    </thead>
249                    <tbody>
250                    </tbody>
251                </table>
252            </div>
253            <div id="call">
254                <h2>載入中</h2>
255            </div>
256            <div id="add">
257                <div class="search"><input type="text"><button>新增</button></div>
258            </div>
259        </section>
260        <footer><a href="edit-mr.github.io/">毛哥EM</a>製作 | <a href="https://emtech.cc/post/roll-call">教學</a>
261        </footer>
262    </main>
263    <script>
264        //部屬連結放這裡
265        var url = "https://script.google.com/macros/s/AKfycbzxqGIMBbLkCka2aveltdVHYtdG-k_X98qzSd_V9MHDxWaOYXFwZgE3rRHDzCakzTxs/exec";
266        const [history, call, add] = ["history", "call", "add"].map(t => document.getElementById(t)), searchA = () => { history.style.opacity = 1, history.style.zIndex = 2, call.style.opacity = add.style.opacity = 0, call.style.zIndex = add.style.zIndex = 1 }, callA = () => { history.style.opacity = add.style.opacity = 0, history.style.zIndex = add.style.zIndex = 1, call.style.opacity = 1, call.style.zIndex = 2 }, addA = () => { history.style.opacity = call.style.opacity = 0, history.style.zIndex = call.style.zIndex = 1, add.style.opacity = 1, add.style.zIndex = 2 }; fetch(url + "?type=list").then(t => t.json()).then(t => { let e = document.getElementById("call"); e.innerHTML = "", t.forEach((t, n) => { let a = document.createElement("button"); a.textContent = t.name, a.id = `student-${n + 1}`, a.addEventListener("click", () => { rollCall(t.name, t.left, n + 1) }), e.appendChild(a) }) }).catch(t => console.error(t)); const status = document.getElementById("status"); function rollCall(t, e, n) { status.innerHTML = `${t} 點名中...`; var a = new Date, a = a.toLocaleString("zh-TW", { year: "numeric", month: "2-digit", day: "2-digit", hour: "numeric", minute: "numeric", second: "numeric", hour12: !0 }).replace("-", "/").replace(" ", " "); fetch(url + `?type=call&name=${t}&time=${a}&remain=${e}`).then(a => { a.ok ? (status.innerHTML = `${t} 已點名成功!剩餘課堂:${e - 1}`, document.getElementById("student-" + n).style.backgroundColor = "var(--nord14)") : (status.innerHTML = `${t} 已點名失敗!剩餘課堂:${e}`, document.getElementById("student-" + n).style.backgroundColor = "var(--nord11)") }).catch(t => { status.innerHTML = `發生錯誤:${t}` }) } const searchBtn = document.querySelector("#history button"), searchInput = document.querySelector("#history input"), historyTableBody = document.querySelector("#history tbody"); searchBtn.addEventListener("click", () => { status.innerHTML = "搜尋中..."; let t = searchInput.value, e = `${e}?type=search&name=${encodeURIComponent(t)}`; fetch(e).then(t => t.json()).then(t => { let e = document.querySelector("#history table tbody"); e.innerHTML = "", t.forEach(t => { let n = document.createElement("tr"), a = document.createElement("td"), l = document.createElement("td"); var r = new Date(t.time); a.textContent = r.toLocaleString("zh-TW", { year: "numeric", month: "2-digit", day: "2-digit", hour: "numeric", minute: "numeric", second: "numeric", hour12: !0 }).replace("-", "/").replace(" ", " "), l.textContent = t.left, n.appendChild(a), n.appendChild(l), e.appendChild(n), status.innerHTML = "搜尋完成" }) }).catch(t => status.innerHTML = t) }); const addBtn = document.querySelector("#add button"), addInput = document.querySelector("#add input"); addBtn.addEventListener("click", () => { let t = addInput.value; t && (status.innerHTML = "新增中...", fetch(`${url}?type=new&name=${encodeURIComponent(t)}`).then(t => status.innerHTML = "新增成功").catch(t => { status.innerHTML = t })) });
267    </script>
268
269</body>
270
271</html>

請把第265行的雙引號裡面換成剛才複製的網頁應用程式網址,然後按下儲存。這樣你的網頁就完成了!你可以到網址https://你的Github帳號.github.io/來使用你的網頁。

Use this template

第二個方式也很簡單,請先到這個Github倉庫並點擊右上角的Fork,或是Use this template。倉庫名稱Repository name會成為你的網址(例如:https://你的Github帳號.github.io/倉庫名稱),然後點擊Create repository from template。

請點擊檔案index.html並點擊右上角的鉛筆按鈕編輯, 把第265行的雙引號裡面換成剛才複製的網頁應用程式網址,然後按下儲存。

然後再到你的倉庫裡面,點擊Settings,然後點擊左邊的Pages,把Branch改成main,然後按下Save,就完成了!

Github Pages設定

好啦,現在你的網頁就完成了!你可以到網址https://你的Github帳號.github.io/倉庫名稱來使用你的網頁。

自訂

這樣你的網頁就建立完成且可以使用了。如果你想客製化顏色的話可以修改CSS。比如說如果你想改標題你可以修改第13行

1color: var(--nord7)

你可以改成任何顏色,例如:color: red,或是color: #ff0000,或是color: rgb(255, 0, 0)

你可以Google colorpicker 選取顏色,然後把HEX或是RGB的數字貼上去。

或是你可以使用預設的Nord顏色組。使用方式就是預設那樣,只要修改數字就好了。對應的顏色如下:

--nord0: #2E3440;

--nord1: #3B4252;

--nord2: #434C5E;

--nord3: #4C566A;

--nord4: #D8DEE9;

--nord5: #E5E9F0;

--nord6: #ECEFF4;

--nord7: #8FBCBB;

--nord8: #88C0D0;

--nord9: #81A1C1;

--nord10: #5E81AC;

--nord11: #BF616A;

--nord12: #D08770;

--nord13: #EBCB8B;

--nord14: #A3BE8C;

--nord15: #B48EAD;

希望你喜歡這個網頁!如果你覺得這篇文章有幫助到你歡迎在InstagramGoogle新聞追蹤毛哥EM資訊密技。如果你有任何問題,歡迎直接到毛哥EM資訊密技的Instagram私訊我,我很樂意協助解決你的問題。

Posts in this Series