Node-Red +SQLite <範例2>
直接共用原本的 student.db 設定,不需建立新資料庫檔案。
[{"id":"in_gr_subject_v6","type":"ui_dropdown","z":"d84044bddad55edc","name":"選擇輸入科目","label":"● 選擇欲輸入科目","tooltip":"","place":"請選擇科目","group":"ui_grp_grade_left_form_v6","order":1,"width":0,"height":0,"passthru":true,"multiple":false,"options":[{"label":"國文","value":"chinese","type":"str"},{"label":"英文","value":"english","type":"str"},{"label":"數學","value":"math","type":"str"}],"payload":"","topic":"selected_subject","topicType":"str","className":"","x":140,"y":80,"wires":[["change_save_grade_v6"]]},{"id":"in_gr_id_v6","type":"ui_text_input","z":"d84044bddad55edc","name":"成績學號輸入","label":"1. 學號 (輸入欲登記成績之學號)","tooltip":"","group":"ui_grp_grade_left_form_v6","order":3,"width":0,"height":0,"passthru":false,"mode":"text","delay":0,"topic":"student_id","sendOnBlur":true,"className":"","topicType":"str","x":140,"y":120,"wires":[["change_save_grade_v6"]]},{"id":"in_gr_score_v6","type":"ui_text_input","z":"d84044bddad55edc","name":"分數輸入","label":"2. 分數 (請輸入該科分數 0-100)","tooltip":"","group":"ui_grp_grade_left_form_v6","order":5,"width":0,"height":0,"passthru":false,"mode":"text","delay":0,"topic":"score","sendOnBlur":true,"className":"","topicType":"str","x":120,"y":160,"wires":[["change_save_grade_v6"]]},{"id":"change_save_grade_v6","type":"change","z":"d84044bddad55edc","name":"即時暫存成績欄位資料","rules":[{"t":"set","p":"grade_form[msg.topic]","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":120,"wires":[["cd1ed0c6ac95cc06"]]},{"id":"btn_gr_1_v6","type":"ui_button","z":"d84044bddad55edc","name":"鍵1_建立成績表","group":"ui_grp_grade_right_btn_v6","order":1,"width":0,"height":0,"passthru":false,"label":"建立成績資料表","tooltip":"","color":"#ffffff","bgcolor":"#17a2b8","className":"","icon":"","payload":"","payloadType":"str","topic":"CREATE_GRADE_TABLE","topicType":"str","x":140,"y":220,"wires":[["func_grade_core_v6"]]},{"id":"btn_gr_2_v6","type":"ui_button","z":"d84044bddad55edc","name":"鍵2_同步學生名單","group":"ui_grp_grade_right_btn_v6","order":2,"width":0,"height":0,"passthru":false,"label":"從學生系統同步(匯入)名單","tooltip":"","color":"#ffffff","bgcolor":"#ffc107","className":"","icon":"","payload":"","payloadType":"str","topic":"SYNC_STUDENTS","topicType":"str","x":160,"y":260,"wires":[["func_grade_core_v6"]]},{"id":"btn_gr_3_v6","type":"ui_button","z":"d84044bddad55edc","name":"鍵3_輸入更正科目成績","group":"ui_grp_grade_right_btn_v6","order":3,"width":0,"height":0,"passthru":false,"label":"登記 / 更正該科成績","tooltip":"","color":"#ffffff","bgcolor":"#28a745","className":"","icon":"","payload":"","payloadType":"str","topic":"UPDATE_GRADE","topicType":"str","x":170,"y":300,"wires":[["func_grade_core_v6"]]},{"id":"btn_gr_4_v6","type":"ui_button","z":"d84044bddad55edc","name":"鍵4_結算顯示所有成績","group":"ui_grp_grade_right_btn_v6","order":4,"width":0,"height":0,"passthru":false,"label":"結算並顯示所有成績","tooltip":"","color":"#ffffff","bgcolor":"#007bff","className":"","icon":"","payload":"","payloadType":"str","topic":"DISPLAY_ALL_GRADES","topicType":"str","x":170,"y":340,"wires":[["func_grade_core_v6"]]},{"id":"btn_gr_5_v6","type":"ui_button","z":"d84044bddad55edc","name":"鍵5_查詢單生健康成績","group":"ui_grp_grade_right_btn_v6","order":5,"width":0,"height":0,"passthru":false,"label":"查詢單生資料(依學號)","tooltip":"","color":"#ffffff","bgcolor":"#6c757d","className":"","icon":"","payload":"","payloadType":"str","topic":"QUERY_GRADE","topicType":"str","x":170,"y":380,"wires":[["func_grade_core_v6"]]},{"id":"func_grade_core_v6","type":"function","z":"d84044bddad55edc","name":"成績系統核心字串拼接邏輯","func":"var action = msg.topic;\nvar form = flow.get(\"grade_form\") || {};\n\nvar subject = form.selected_subject ? form.selected_subject.trim() : \"\";\nvar student_id = form.student_id ? form.student_id.trim() : \"\";\nvar score = (form.score !== undefined && form.score !== \"\") ? form.score.trim() : \"0\";\n\nswitch(action) {\n case \"CREATE_GRADE_TABLE\":\n msg.topic = `CREATE TABLE IF NOT EXISTS grades (\n student_id TEXT PRIMARY KEY,\n chinese REAL DEFAULT 0,\n english REAL DEFAULT 0,\n math REAL DEFAULT 0,\n total REAL GENERATED ALWAYS AS (chinese + english + math),\n average REAL GENERATED ALWAYS AS ((chinese + english + math) / 3.0),\n FOREIGN KEY(student_id) REFERENCES students(student_id) ON DELETE CASCADE\n );`;\n break;\n \n case \"SYNC_STUDENTS\":\n msg.topic = \"INSERT OR IGNORE INTO grades (student_id) SELECT student_id FROM students;\";\n break;\n \n case \"UPDATE_GRADE\":\n if (student_id === \"\") {\n node.error(\"【系統警告】登記失敗:請輸入學號!\");\n return null;\n }\n if (subject === \"\") {\n node.error(\"【系統警告】登記失敗:請先在下拉選單選擇輸入科目!\");\n return null;\n }\n // 純字串拼接,因為分數是數字型態,不需要包單引號\n msg.topic = \"UPDATE grades SET \" + subject + \" = \" + score + \" WHERE student_id = '\" + student_id + \"';\";\n break;\n \n case \"QUERY_GRADE\":\n if (student_id === \"\") {\n node.error(\"【系統警告】查詢失敗:請輸入學號!\");\n return null;\n }\n // 使用 LEFT JOIN 實時跨表從 students 關聯抓取姓名 (s.name AS name)\n msg.topic = `SELECT g.student_id, s.name AS name, g.chinese, g.english, g.math, g.total, ROUND(g.average, 2) AS average \n FROM grades g \n LEFT JOIN students s ON g.student_id = s.student_id \n WHERE g.student_id = '${student_id}';`;\n break;\n \n case \"DISPLAY_ALL_GRADES\":\n // 顯示所有資料共 7 欄位,姓名完全實時關聯取得\n msg.topic = `SELECT g.student_id, s.name AS name, g.chinese, g.english, g.math, g.total, ROUND(g.average, 2) AS average \n FROM grades g \n LEFT JOIN students s ON g.student_id = s.student_id \n ORDER BY g.student_id ASC;`;\n break;\n \n default:\n return null;\n}\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":440,"y":300,"wires":[["sqlite_node_grade_v6"]]},{"id":"sqlite_node_grade_v6","type":"sqlite","z":"d84044bddad55edc","mydb":"sqlite_config_switch_v2","sqlquery":"msg.topic","sql":"","name":"SQLite 成績庫","x":680,"y":300,"wires":[["ui_table_grade_v6"]]},{"id":"ui_table_grade_v6","type":"ui_table","z":"d84044bddad55edc","group":"ui_grp_grade_bottom_table_v6","name":"學生成績大面板","order":1,"width":12,"height":6,"columns":[{"field":"student_id","title":"學號","width":"14%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"name","title":"姓名","width":"14%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"chinese","title":"國文","width":"12%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"english","title":"英文","width":"12%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"math","title":"數學","width":"12%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"total","title":"總分","width":"18%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"average","title":"平均分數","width":"18%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}}],"outputs":0,"cts":false,"x":860,"y":300,"wires":[]},{"id":"cd1ed0c6ac95cc06","type":"debug","z":"d84044bddad55edc","name":"debug 391","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":610,"y":120,"wires":[]},{"id":"ui_grp_grade_left_form_v6","type":"ui_group","name":"1. 成績輸入與科目選擇","tab":"d0cd6479caf695f3","order":4,"disp":true,"width":"8","collapse":false,"className":""},{"id":"ui_grp_grade_right_btn_v6","type":"ui_group","name":"2. 成績操作快捷鍵","tab":"d0cd6479caf695f3","order":5,"disp":true,"width":"4","collapse":false,"className":""},{"id":"sqlite_config_switch_v2","type":"sqlitedb","db":"student.db","mode":"RWC"},{"id":"ui_grp_grade_bottom_table_v6","type":"ui_group","name":"3. 成績資料顯示面板 (自動關聯姓名、結算總分與平均)","tab":"d0cd6479caf695f3","order":6,"disp":true,"width":"12","collapse":false,"className":""},{"id":"d0cd6479caf695f3","type":"ui_tab","name":"學生成績管理系統","icon":"dashboard","disabled":false,"hidden":false}]
這份 JSON 程式碼構建了系統的 「第二部分:學生成績管理系統」。它與第一部分(學生資料系統)共享同一個 SQLite 資料庫檔案 student.db,並透過經典的 Node-RED Dashboard 元件,完美實現了前端數據收集、動態 SQL 拼接與後端關聯式資料庫的互動。
以下為您詳細拆解這段程式的架構與運作邏輯:
🏗️ 一、 前端網頁佈局 (UI Dashboard)
這段 Flow 同樣利用 Grid(網格)系統將網頁畫面精準地切分為三個區塊,維持系統操作的一致性:
左側輸入區 (
ui_grp_grade_left_form_v6,寬度 8): 包含 3 個核心元件:一個下拉選單(選擇國文、英文或數學)與兩個文字輸入框(學號、分數)。右側按鍵區 (
ui_grp_grade_right_btn_v6,寬度 4): 垂直排列 5 個功能實體按鈕(建立成績表、同步名單、登記/更正、結算顯示、單生查詢)。下方大面板 (
ui_grp_grade_bottom_table_v6,寬度 12): 放置一個 7 欄位的表格元件 (ui_table),用來完整呈現包含姓名與計算結果的成绩單。
🔄 二、 數據流程與暫存機制
與第一部分相同,為了避免使用者每打一個字就去衝擊資料庫,系統採用了「非同步暫存」設計:
當使用者在網頁左側操作「選擇科目」、「輸入學號」或「輸入分數」時,這三個節點會將各自的數值發送到 「即時暫存成績欄位資料」(Change 節點)。
Change 節點會以各自的
msg.topic(selected_subject、student_id、score)作為鑰匙,將數值存入 Node-RED 的記憶體環境變數flow.grade_form中。同時,它會將資料複製一份送往右上方的 「debug 391」 節點,方便您在除錯視窗即時監看目前輸入的暫存內容是否正確。
🧠 三、 核心 Function 邏輯解析 (func_grade_core_v6)
當使用者點擊右側 5 個按鈕中的任意一個時,按鈕會發送專屬的 msg.topic 指令(如 ADD、UPDATE_GRADE 等)進入大腦 Function 節點。
Function 節點會從記憶體中取出暫存的科目、學號與分數,並根據點擊的按鈕執行 switch(action) 進行 SQL 字串拼接。以下是各個按鍵觸發的 SQL 邏輯:
鍵 1:建立成績資料表 (CREATE_GRADE_TABLE)
CREATE TABLE IF NOT EXISTS grades (
student_id TEXT PRIMARY KEY,
chinese REAL DEFAULT 0,
english REAL DEFAULT 0,
math REAL DEFAULT 0,
total REAL GENERATED ALWAYS AS (chinese + english + math),
average REAL GENERATED ALWAYS AS ((chinese + english + math) / 3.0),
FOREIGN KEY(student_id) REFERENCES students(student_id) ON DELETE CASCADE
);
說明: 這裏完美應用了您指定的 SQLite 進階技術。
total與average欄位被設定為GENERATED ALWAYS AS(產生欄位)。這代表總分與平均完全不需要寫程式去計算,SQLite 驅動層會在分數寫入的瞬間自動在底層算好。
鍵 2:從學生系統同步(匯入)名單 (SYNC_STUDENTS)
INSERT OR IGNORE INTO grades (student_id) SELECT student_id FROM students;
說明: 這行指令解決了「名單必須由第一系統取得」的需求。它會直接去學生基本資料表(
students)把所有人的學號撈出來並倒進成績表(grades)中。使用INSERT OR IGNORE可以確保重複點擊時不會因為主鍵衝突而報錯。
鍵 3:登記 / 更正該科成績 (UPDATE_GRADE)
msg.topic = "UPDATE grades SET " + subject + " = " + score + " WHERE student_id = '" + student_id + "';";
說明: 這裏實現了「一科目一科目連續輸入」的設計。程式會動態讀取變數
subject的值(chinese、english或math),並動態拼接成 SQL 欄位。範例: 如果您選了國文,輸入學號
DB112203,分數95,拼接出來就是:UPDATE grades SET chinese = 95 WHERE student_id = 'DB112203';因為score提取出來後是數字,所以拼接時它很聰明地沒有包單引號;而學號是文字,兩側確實包了單引號'${student_id}'。
鍵 4 & 鍵 5:結算顯示與單生查詢 (DISPLAY_ALL_GRADES / QUERY_GRADE)
SELECT g.student_id, s.name AS name, g.chinese, g.english, g.math, g.total, ROUND(g.average, 2) AS average
FROM grades g
LEFT JOIN students s ON g.student_id = s.student_id
說明(核心技術): 因為成績表本身只存儲學號,不存姓名。為了動態得到姓名,這裏使用了
LEFT JOIN students s ON g.student_id = s.student_id(左外部連接)。它會實時(Real-time)根據學號去第一部分的基本資料表撈出對應的
name(姓名),並利用ROUND(g.average, 2)將平均分數四捨五入到小數點後兩位。
🗄️ 四、 後端執行與前端表格顯示
SQLite 成績庫節點 (
sqlite_node_grade_v6): 其 SQL Type 設定為via msg.topic(即sqlquery: "msg.topic"),它會死死接收前面 Function 拼接好的純文字 SQL 命令並直接送入student.db執行。學生成績大面板 (
ui_table_grade_v6): 當執行查詢或顯示所有時,SQLite 回傳的 7 欄位數據(學號、姓名、國文、英文、數學、總分、平均分數)會精準流入表格元件。因為表格內部設定的欄位 Key(field: "name",field: "total"等)與 SQL 查詢出來的別名完全對齊,畫面便能毫無時差地渲染出一張精美的成績大報表。























