📘 網頁程式設計作業統整

洪晨祐 · 111410517 · 國立金門大學資訊工程系
專案 GitHub 倉庫: 111410517/_wp
Homework 01

個人網站 HTML

homework/01/aboutme.html

一個介紹自己基本資料與技術技能的靜態個人自我介紹網頁。

基本資料展示區 — 顯示姓名、學號與學校資訊
技術技能區 — 呈現目前已學習與探索中的技術
自我介紹區 — 個人學習理念與未來目標
頁尾版權資訊 — 註明作者與 GitHub 連結
HTML5 CSS3 霓虹發光動畫
Homework 02

表單問卷 HTML

homework/02/form.html

一個包含會員登入、註冊與課程問卷調查的多功能互動式表單網頁。

會員登入 — 電子郵件與安全密碼輸入欄位
新手註冊 — 生日、年齡與基本註冊資料輸入
課程問卷 — 生理性別、入學管道、基礎自評等元件
頁籤切換與驗證 — 點選 Tab 切換與密碼長度檢查
HTML5 Form CSS3 Card JavaScript Tab 切換
Homework 03

Node.js 初探

homework/03/hello.js

在 Node.js 環境中執行第一個 JavaScript 程式,輸出個人資訊與招呼語。

// hello.js
console.log("hello, world!");
console.log("金門大學資工系一年級 洪晨祐 111410517");
PS D:\develop\Projects\MyProject\NQU\1_wp\homework\03> node hello.js hello, world! 金門大學資工系一年級 洪晨祐 111410517
Homework 04

JavaScript 基礎語法練習

homework/04/

十題 JavaScript 基礎邏輯與物件操作練習,涵蓋 if-else、迴圈、JSON 解析與物件方法中的 this 關鍵字。

01_score_evaluator.js — 學生成績等第判定
function evaluateScore(score) {
  if (score < 0 || score > 100) {
    return "無效的分數,分數必須介於 0 到 100 之間。";
  }
  let grade = "";
  let comment = "";
  if (score >= 90) {
    grade = "A";
    comment = "表現優異,繼續保持!";
  } else if (score >= 80) {
    grade = "B";
    comment = "良好表現,還有進步空間。";
  } else if (score >= 70) {
    grade = "C";
    comment = "普通,需要多加練習。";
  } else if (score >= 60) {
    grade = "D";
    comment = "勉強及格,請多努力。";
  } else {
    grade = "F";
    comment = "不及格,請重新複習課程內容。";
  }
  return "得分: " + score + ", 等第: " + grade + " (" + comment + ")";
}
console.log(evaluateScore(95));
得分: 95, 等第: A (表現優異,繼續保持!)
02_fibonacci_loop.js — 費氏數列生成
function getFibonacciSeries(n) {
  if (n <= 0) return [];
  if (n === 1) return [0];
  const series = [0, 1];
  for (let i = 2; i < n; i++) {
    const nextNum = series[i - 1] + series[i - 2];
    series.push(nextNum);
  }
  return series;
}
console.log("前 5 項費氏數列: ", getFibonacciSeries(5));
前 5 項費氏數列: [ 0, 1, 1, 2, 3 ]
03_sensor_average.js — 感測器平均溫度計算
function calculateSensorAverage(sensorData) {
  const validReadings = [];
  let index = 0;
  while (index < sensorData.length) {
    const currentData = sensorData[index];
    if (currentData === -999) {
      console.log("讀取到結束訊號 -999,停止讀取。");
      break;
    }
    validReadings.push(currentData);
    index++;
  }
  if (validReadings.length === 0) return 0;
  let sum = 0;
  for (let i = 0; i < validReadings.length; i++) {
    sum += validReadings[i];
  }
  const average = sum / validReadings.length;
  console.log("有效讀取次數: " + validReadings.length);
  return average;
}
console.log("平均溫度: " + calculateSensorAverage([25.3, 26.1, 24.8, -999, 28.0]));
讀取到結束訊號 -999,停止讀取。 有效讀取次數: 3 平均溫度: 25.4
04_rpg_hero.js — RPG 遊戲角色系統
const hero = {
  name: "亞瑟",
  level: 1,
  hp: 100,
  maxHp: 100,
  attackPower: 15,
  isAlive: true,
  takeDamage(damage) {
    if (!this.isAlive) {
      console.log(this.name + " 已經陣亡,無法再受到傷害。");
      return;
    }
    this.hp -= damage;
    console.log(this.name + " 受到 " + damage + " 點傷害!目前生命值: " + this.hp + "/" + this.maxHp);
    if (this.hp <= 0) {
      this.hp = 0;
      this.isAlive = false;
      console.log(this.name + " 不幸陣亡了!");
    }
  },
  levelUp() {
    if (!this.isAlive) {
      console.log(this.name + " 已經陣亡,無法升級。");
      return;
    }
    this.level += 1;
    this.maxHp += 20;
    this.hp = this.maxHp;
    this.attackPower += 5;
    console.log(this.name + " 升級了!目前等級: " + this.level + ", 最大生命值: " + this.maxHp);
  }
};
hero.takeDamage(30);
hero.levelUp();
亞瑟 受到 30 點傷害!目前生命值: 70/100 亞瑟 升級了!目前等級: 2, 最大生命值: 120
05_book_catalog.js — 圖書目錄解析 (JSON)
const jsonString = '{"catalogName":"科技圖書館","books":[{"id":"B01","title":"JavaScript 入門","author":"林小明","price":390}]}';
const catalog = JSON.parse(jsonString);
console.log("圖書館名稱: " + catalog.catalogName);
const newBook = { id: "B04", title: "HTML5 網頁美學", author: "王大同", price: 350 };
catalog.books.push(newBook);
console.log("序列化 JSON: ", JSON.stringify(catalog));
圖書館名稱: 科技圖書館 序列化 JSON: {"catalogName":"科技圖書館","books":[{"id":"B01","title":"JavaScript 入門","author":"林小明","price":390},{"id":"B04","title":"HTML5 網頁美學","author":"王大同","price":350}]}
06_task_manager.js — 個人任務管理器過濾
const tasks = [
  { name: "準備期末報告", completed: false },
  { name: "繳交作業四", completed: true }
];
function showTasksByStatus(completedStatus) {
  for (let i = 0; i < tasks.length; i++) {
    if (tasks[i].completed === completedStatus) {
      console.log("- " + tasks[i].name);
    }
  }
}
showTasksByStatus(false);
=== 未完成 任務列表 === - 準備期末報告
07_tax_calculator.js — 個人所得稅計算 (Function)
function calculateIncomeTax(annualIncome) {
  if (annualIncome <= 0) return 0;
  let tax = 0;
  if (annualIncome <= 500000) {
    tax = annualIncome * 0.05;
  } else if (annualIncome <= 1200000) {
    tax = 500000 * 0.05 + (annualIncome - 500000) * 0.12;
  } else {
    tax = 500000 * 0.05 + 700000 * 0.12 + (annualIncome - 1200000) * 0.20;
  }
  return tax;
}
console.log("應繳所得稅: " + calculateIncomeTax(800000));
應繳所得稅: 61000
08_weather_data.js — 天氣統計分析 (Nested JSON)
const weatherJson = '{"location":"金門","days":["週一","週二","週三"],"temperatures":[28.5, 31.2, 27.0]}';
const data = JSON.parse(weatherJson);
let maxTemp = data.temperatures[0];
for (let i = 1; i < data.temperatures.length; i++) {
  if (data.temperatures[i] > maxTemp) maxTemp = data.temperatures[i];
}
console.log("最高溫: " + maxTemp);
最高溫: 31.2
09_grade_grouper.js — 學生成績及格分組
const studentGrades = [
  { name: "小明", score: 85 },
  { name: "小華", score: 58 }
];
function groupStudents(students) {
  const passed = [];
  const failed = [];
  for (let i = 0; i < students.length; i++) {
    if (students[i].score >= 60) passed.push(students[i].name);
    else failed.push(students[i].name);
  }
  console.log("及格: " + passed.join(", "));
  console.log("不及格: " + failed.join(", "));
}
groupStudents(studentGrades);
及格: 小明 不及格: 小華
10_store_inventory.js — 超商庫存管理 (this 綁定)
const inventorySystem = {
  storeName: "全家便利店 (大學店)",
  items: [
    { name: "御飯糰", price: 35, quantity: 12 },
    { name: "黑咖啡", price: 45, quantity: 20 }
  ],
  calculateTotalValue() {
    let totalValue = 0;
    for (let i = 0; i < this.items.length; i++) {
      totalValue += this.items[i].price * this.items[i].quantity;
    }
    return totalValue;
  },
  restockItem(itemName, qty) {
    for (let i = 0; i < this.items.length; i++) {
      if (this.items[i].name === itemName) {
        this.items[i].quantity += qty;
        console.log(`[補貨] ${itemName} 成功補貨 ${qty} 個!`);
      }
    }
  }
};
console.log("初始總價值: " + inventorySystem.calculateTotalValue());
inventorySystem.restockItem("黑咖啡", 10);
console.log("更新後總價值: " + inventorySystem.calculateTotalValue());
初始總價值: 1320 [補貨] 黑咖啡 成功補貨 10 個! 更新後總價值: 1770
Homework 05

待辦筆記系統 (Notes App)

homework/05/

使用 Node.js + Express + EJS 模板引擎,並搭配 express-session 與權限判定開發的分版本筆記看板管理系統 (Notes App)。

會話與帳號驗證 — 結合 express-session 狀態維持,限制登入用戶管理筆記
筆記隱私過濾 — Public/Private 權限控制,私人筆記僅作者本人可看
全文檢索搜尋 — 實作後端關鍵字搜尋,動態篩選符合標題或內容的筆記
資料備份匯出 — 設定 Response Header 將個人筆記打包序列化為 JSON 檔下載
Express.js express-session EJS Template Engine JSON Download API

專案演進分版本架構:

版本資料夾核心技術要素功能說明
notes_basic/原生 Express 路由基礎筆記的新增、刪除與列表展示,無使用者概念。
notes_auth/express-session加入登入、註冊機制,用戶需要登入才能建立與刪除自己的筆記。
notes_privacy/角色與擁有權驗證支援筆記 Public/Private 設定,私人筆記僅作者本人能查看與編輯。
notes_publisher/後端檢索、Header 檔案下載支援關鍵字搜尋過濾,並提供下載個人全部筆記為 JSON 備份功能。

Notes Publisher 核心路由邏輯 (app.js 節錄):

// 全文檢索搜尋與權限過濾路由
app.get("/", (req, res) => {
  const query = (req.query.q || "").toLowerCase();
  const user = req.session.user;
  
  // 篩選出使用者可見的筆記:如果是 Public,或是本人所有的 Private 筆記
  let visibleNotes = notes.filter(n => {
    return n.public || (user && n.owner === user.username);
  });
  
  // 若有搜尋關鍵字,進一步對標題或內容過濾
  if (query) {
    visibleNotes = visibleNotes.filter(n => 
      n.title.toLowerCase().includes(query) || 
      n.content.toLowerCase().includes(query)
    );
  }
  res.render("index", { notes: visibleNotes, user, query });
});

// 打包個人筆記並匯出為 JSON 下載
app.get("/export", (req, res) => {
  if (!req.session.user) return res.redirect("/login");
  const userNotes = notes.filter(n => n.owner === req.session.user.username);
  
  res.setHeader("Content-Disposition", "attachment; filename=my_notes.json");
  res.setHeader("Content-Type", "application/json");
  res.send(JSON.stringify(userNotes, null, 2));
});
Homework 06

JavaScript 進階概念練習

homework/06/

十題 JavaScript 進階函式挑戰,學習 Closure、IIFE、傳址傳參考之根本差異。

task01_callback.js — Callback 實作
function mathTool(num1, num2, action) {
    return action(num1, num2);
}
console.log(mathTool(10, 5, (a, b) => a + b)); // 輸出: 15
console.log(mathTool(10, 5, (a, b) => a - b)); // 輸出: 5
15 5
task02_iife.js — IIFE 隔離作用域
(function() {
    let count = 100;
    console.log("IIFE 內部的 count: " + count);
})();
// 外部嘗試存取 count 會發生 ReferenceError: count is not defined
IIFE 內部的 count: 100
task03_arrow_map.js — Array.map() 轉換價格
const prices = [100, 200, 300, 400];
const discounted = prices.map(p => p * 0.8);
console.log("八折後價格:", discounted);
八折後價格: [ 80, 160, 240, 320 ]
task04_array_mutation.js — 傳址修改與副作用 (Side Effect)
function cleanData(arr) {
    arr.pop();
    arr.unshift("Start");
}
const dataList = [1, 2, 3];
cleanData(dataList);
console.log("修改後的原始陣列:", dataList);
修改後的原始陣列: [ 'Start', 1, 2 ]
task05_higher_order.js — 高階函數與閉包 (Closure)
const multiplier = (factor) => (n) => n * factor;
const double = multiplier(2);
const triple = multiplier(3);
console.log("2 倍計算:", double(10));
console.log("3 倍計算:", triple(10));
2 倍計算: 20 3 倍計算: 30
task06_custom_filter.js — 手寫自訂 Array Filter
function myFilter(arr, callback) {
    const result = [];
    for (let i = 0; i < arr.length; i++) {
        if (callback(arr[i])) {
            result.push(arr[i]);
        }
    }
    return result;
}
console.log(myFilter([1, 2, 3, 4], x => x > 2));
[ 3, 4 ]
task07_object_filter.js — filter 過濾物件陣列
const users = [
    { name: "Alice", age: 25 },
    { name: "Bob", age: 17 },
    { name: "Charlie", age: 20 }
];
const adults = users.filter(user => user.age >= 18);
console.log("成年人列表:", adults);
成年人列表: [ { name: 'Alice', age: 25 }, { name: 'Charlie', age: 20 } ]
task08_reference_trap.js — 傳參重新賦值實驗
function process(a, b) {
    a.push(99); // 傳址修改 (會影響外部)
    b = [100];  // 重新賦值 (指向新記憶體位址,不影響外部)
}
let listA = [1, 2];
let listB = [3, 4];
process(listA, listB);
console.log("listA (傳址修改):", listA);
console.log("listB (重新賦值):", listB);
listA (傳址修改): [ 1, 2, 99 ] listB (重新賦值): [ 3, 4 ]
task09_delay_callback.js — setTimeout 延遲非同步
console.log("任務開始...");
setTimeout(() => {
    console.log("延遲 2 秒的非同步任務完成");
}, 2000);
console.log("任務結束,等待非同步中...");
任務開始... 任務結束,等待非同步中... 延遲 2 秒的非同步任務完成 (2秒後印出)
task10_total_price.js — reduce 累加與折扣計算
function calculateTotal(cart, discountFunc) {
    const total = cart.reduce((sum, price) => sum + price, 0);
    return discountFunc(total);
}
const cartPrices = [100, 250, 400];
console.log("折後總價:", calculateTotal(cartPrices, t => t - 100));
折後總價: 650
Homework 07

JavaScript 後端挑戰練習

homework/07/

十題針對 Express 後端語法設計的實戰練習,探討 error-first callback 錯誤優先與權限驗證。

challenge01_prop_access.js — 屬性存取 (點運算子與中括號)
const config = { port: 8080, dbUrl: "mongodb://localhost" };
console.log("Port (點存取):", config.port);
console.log("DB URL (中括號存取):", config["dbUrl"]);
Port (點存取): 8080 DB URL (中括號存取): mongodb://localhost
challenge02_destructuring.js — Request Body 解構賦值
const req = { body: { username: "yoyo", email: "yoyo@nqu.edu" } };
const { username, email } = req.body;
console.log("解構出的資料:", username, email);
解構出的資料: yoyo yoyo@nqu.edu
challenge03_foreach_template.js — forEach 樣板字串拼接
const posts = [{ id: 1, title: "後端架構" }, { id: 2, title: "邊緣計算" }];
posts.forEach(post => {
    console.log(`<li>ID: ${post.id}, Title: ${post.title}</li>`);
});
<li>ID: 1, Title: 後端架構</li> <li>ID: 2, Title: 邊緣計算</li>
challenge04_params_dict.js — 動態屬性字典 (userId)
const params = {};
const key = "userId";
params[key] = "111410517";
console.log("動態參數字典:", params);
動態參數字典: { userId: '111410517' }
challenge05_error_first.js — Error-First Callback 錯誤優先回呼
function fetchData(id, callback) {
    if (id <= 0) {
        return callback("無效的 ID 參數", null);
    }
    const fakeData = { id: id, status: "OK" };
    callback(null, fakeData); 
}
fetchData(1, (err, data) => {
    if (err) console.error("發生錯誤: " + err);
    else console.log("取得資料成功:", data);
});
取得資料成功: { id: 1, status: 'OK' }
challenge06_json_parse.js — 安全 JSON.parse 解析
function safeParseJSON(jsonStr, callback) {
    try {
        const obj = JSON.parse(jsonStr);
        callback(null, obj);
    } catch (err) {
        callback(err, null);
    }
}
safeParseJSON('{"items":[1,2]}', (err, res) => {
    if (err) console.log("解析失敗");
    else console.log("解析成功,items:", res.items);
});
解析成功,items: [ 1, 2 ]
challenge07_fake_db.js — 模擬資料庫非同步檢索失敗
function fakeGet(sql, params, callback) {
    const fakeRow = { id: 1, title: "後端非同步與錯誤優先" };
    if (params[0] !== 1) {
        return callback(new Error("SQL 錯誤: 找不到該筆資料 (Record Not Found)"), null);
    }
    callback(null, fakeRow);
}
fakeGet("SELECT * FROM posts WHERE id = ?", [2], (err, data) => {
    if (err) console.log("查詢失敗,如預期拋錯:" + err.message);
    else console.log("查詢結果:", data);
});
查詢失敗,如預期拋錯:SQL 錯誤: 找不到該筆資料 (Record Not Found)
challenge08_template_logic.js — 樣板字串中內嵌三元運算子
const userSession = { loggedIn: true, name: "晨祐" };
console.log(`歡迎看板: ${userSession.loggedIn ? `Hello, ${userSession.name}` : "請先登入"}`);
歡迎看板: Hello, 晨祐
challenge09_sort_slice.js — 陣列擷取與省略號處理
const textList = ["這是一篇非常長長長長長的文章", "短文"];
const processed = textList.map(text => text.length > 10 ? text.slice(0, 10) + "..." : text);
console.log("處理後結果:", processed);
處理後結果: [ '這是一篇非常長長長...', '短文' ]
challenge10_admin_check.js — 管理員身分安全檢查
function checkAdmin(role, callback) {
    if (role !== "admin") {
        callback(new Error("Access Denied"), null);
        return;
    }
    callback(null, "Welcome");
}
checkAdmin("user", (err, msg) => {
    if (err) console.log("安全攔截: " + err.message);
    else console.log(msg);
});
安全攔截: Access Denied
Homework 08

Node.js 購物車網站 (期中專案)

homework/08/online_shop/

手寫 Node.js 原生 HTTP 靜態網頁伺服器,設計支援 LocalStorage 與跨分頁同步的幾何包浩斯電商商城。

原生伺服器邏輯 — 排除 Express,手寫路徑解析與自動判定 MIME Content-Type 檔案流分發
安全越界防禦 — 透過 path.relativeBASE_DIR 鋼鐵邊界比對,杜絕路徑遍歷攻擊
跨分頁狀態即時同步 — 監聽 window.storage 事件,當購物車在任一分頁更新時,其他頁面即時同歩更新
密碼學 Ledger 收據 — 結帳時以 async/await 非同步打字機效果列印收據明細,並加蓋 PAID 戳記
Native http, fs, path Stream & Pipe Path Traversal Defense Storage Event
⚡ 點此開啟期中購物車線上網頁 (Render 託管) ➔

期中後端伺服器核心防護代碼 (server.js 節錄):

// 🌟 鋼鐵防線:防止目錄越界路徑遍歷攻擊 (Path Traversal Defense)
const relative = path.relative(BASE_DIR, targetFilePath);
const isOut = relative.startsWith('..') || path.isAbsolute(relative);
if (isOut) {
    console.warn(`🚨 [安全警報] 偵測到越界路徑遍歷攻擊! 請求路徑: ${pathname}`);
    res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
    return res.end("403 Forbidden: 拒絕存取越界路徑。");
}

// 🌟 緩衝管道流分發,避免一次性讀入記憶體,杜絕 OOM 記憶體溢出崩潰
const stream = fs.createReadStream(targetFilePath);
stream.pipe(res);

期中開發問題分析與修正:

  • 問題 1:載入網頁時 CSS 樣式失效(呈現純文字排版)
    * **原因**:在原生 `server.js` 中直接使用 `fs.readFile` 回傳檔案時,沒有正確設定 HTTP 回傳標頭的 `Content-Type: text/css`,導致瀏覽器安全機制拒絕載入並套用該樣式檔。
    * **修正**:建立 MIME Type 對照字典,根據檔案副檔名 `path.extname` 自動加上 `res.writeHead(200, { "Content-Type": "text/css" })`,問題順利解決。
  • 問題 2:網誌圖片資源載入失敗,呈現破圖 (404)
    * **原因**:圖片路徑拼接時,未正確針對 `/images/` 目錄進行精確路由配置與讀取,且使用了錯誤的圖片 MIME 解析。
    * **修正**:優化 `server.js` 路由優先權順序,遵循「**精確匹配優先、通用規則置後**」原則,手動加上對 `req.url.startsWith("/images/")` 的靜態檔案分流讀取,圖片即順利正常顯示。
Homework 09

期末專案:作業整合 Portal 網站 (本站)

./index.html

將本學期作業 1 到作業 8 整合在同一個 Portal 網頁中。網頁採用極簡幾何排版,以 iframe 嵌入前端作業,並以靜態代碼高亮與模擬終端機輸出呈現控制台作業,確保載入效能與安全性。

網頁設計風格:

整個網頁的風格是用比較簡單乾淨的極簡風(幾何格子的感覺)。網頁使用溫和的灰白色當背景,卡片用純白色,框線全部都是 2px 的黑實線,而且不使用圓角,看起來比較俐落。

作業展示方式:

為了把不同類型的作業整合進同一個網頁,我用了兩種方式來呈現:

  • 1. 用 iframe 嵌入網頁(作業 1 與作業 2)
    對於有網頁畫面的作業,像是自我介紹網頁跟互動式表單,我直接用 <iframe> 標籤把 HTML 網頁內嵌進去,這樣就能直接在 Portal 網頁裡操作。
  • 2. 程式碼呈現與模擬終端機輸出(作業 3、4、6、7、8)
    對於純 JavaScript 的控制台程式,我把程式碼放進網頁中,並用 CSS 樣式幫程式碼的關鍵字加上顏色(像是註解灰色、關鍵字紅色、函數紫色等)。同時,我也設計了一個模擬終端機的框框,把程式在電腦上執行後的輸出結果貼在裡面,這樣不用執行也能清楚看到結果。

說明與心得:

本期末 Portal 網站的 HTML 排版、CSS 網格佈局與 iframe 安全機制均由 AI(Gemini)輔助生成。藉由這個期末整合,我掌握了多網頁在單一 Portal 頁面動態嵌入與安全性設計,並練習了手動將 JS 控制台作業轉換為高亮程式碼與模擬 Stdout 輸出以提升載入效能的靜態展示技巧。