# 第 5-8 週 JavaScript 作業設計 > 後端導向 JavaScript 基礎課程,銜接 4-6 月 Node.js 課程 > > **技術環境:Node.js + 原生 JavaScript** > > **作業情境:電商購物車系統** --- ## 作業設計規劃 ### 設計原則 1. **後端思維導向**:作業設計著重資料處理、API 串接、邏輯拆分 2. **銜接 Node.js**:使用電商系統常見的資料結構與業務邏輯 3. **循序漸進**:從純邏輯處理 → 非同步操作 → 套件使用 → 專案整合 4. **實務情境**:模擬真實後端會遇到的資料處理場景 5. **測試驅動**:每週作業皆有 Jest 測試,培養測試習慣 ### 為什麼用電商系統? 電商系統涵蓋後端開發最核心的概念: - **產品模組**:資料查詢、篩選、分類 - **購物車模組**:狀態管理、數量計算、CRUD 操作 - **訂單模組**:資料驗證、流程整合、統計報表 - **使用者驗證**:表單驗證、認證機制 這些概念在 Node.js 課程中會直接延續,學員可以從「呼叫 API」變成「建立 API」。 ### 技術選型 | 項目 | 選擇 | 原因 | |------|------|------| | 執行環境 | Node.js 18+ | 內建 fetch,與後端環境一致 | | 測試框架 | Jest | 業界主流,語法直覺 | | HTTP 請求 | fetch | 原生 API,銜接後端課程再學 axios | | 日期處理 | dayjs | 輕量、API 直覺 | | API 來源 | LiveJS 電商 API | 六角學院提供,穩定可靠 | ### 學習路線圖 ``` 第五週 第六週 第七週 第八週 ──────────────────────────────────────────────────────── 純邏輯處理 → 非同步操作 → 第三方套件 → 專案整合 函式拆分 fetch API dayjs 模組化架構 資料處理 Promise fetch 進階 服務層設計 陣列方法 async/await 資料驗證 完整 CRUD ``` --- ## 給助教的說明 ### 測試方式差異 | 週次 | 測試方式 | 執行指令 | 原因 | |------|----------|----------|------| | 第五週 | 原生 JS 測試 | `node test.js` | 純邏輯練習,不需安裝套件 | | 第六~八週 | Jest | `npm test` | 開始接觸 npm、非同步測試 | ### 為什麼第五週不用 Jest? 1. **降低入門門檻**:第五週專注在 JS 邏輯,不需學 npm、套件安裝 2. **快速驗證**:學員只要執行 `node test.js` 就能看到結果 3. **循序漸進**:第六週開始用 npm,再引入 Jest 測試 4. **理解測試原理**:用原生 JS 寫測試框架,學員更能理解測試的運作方式 ### 第五週如何驗收 ```bash # 執行測試檔案 node test.js ``` 測試結果會顯示通過/失敗數量,例如: ```text ========== 任務一:產品查詢模組 ========== ✓ getProductById - 找到產品應回傳物件 ✓ getProductById - 找不到應回傳 null ✓ getProductsByCategory - 應回傳陣列 ... ========================================== 測試結果: 28 通過, 0 失敗, 共 28 項 ========================================== 🎉 太棒了!所有測試都通過了! ``` **驗收重點**: - 測試通過數量 - 是否有修改原陣列(任務三要求不修改原陣列) ### 第六~八週為什麼用 Jest? 1. **客觀評分**:測試通過數量可直接對應分數,減少主觀判斷 2. **即時回饋**:學員可自行執行 `npm test` 確認完成度 3. **培養習慣**:Node.js 課程會大量使用測試,提前熟悉 4. **非同步測試**:Jest 支援 async/await 測試,適合 API 串接 ### 第六~八週如何驗收 ```bash # 1. clone 學員的 repo git clone <學員repo網址> # 2. 進入對應週次資料夾 cd week6/assignment # 3. 安裝套件 npm install # 4. 執行測試 npm test ``` 測試結果會顯示通過/失敗數量,例如: ```text Tests: 18 passed, 3 failed, 21 total ``` **評分建議**:通過率 × 該週配分 = 得分 ### 每週作業設計目的 | 週次 | 設計目的 | 驗收重點 | |------|----------|----------| | 第五週 | 練習函式拆分、陣列方法 | 純邏輯正確、不修改原陣列 | | 第六週 | 學會 fetch、async/await | API 串接成功、錯誤處理 | | 第七週 | 熟悉第三方套件使用 | dayjs 正確使用、fetch 進階 | | 第八週 | 整合能力、模組化思維 | 服務層架構、程式組織 | ### 難度調整建議 如果學員普遍卡關,可考慮以下調整: - **第五週**:任務四(訂單統計)可改為選做 - **第六週**:任務四(管理員 API)可改為加分題 - **第七週**:任務五(OrderService 整合)可改為加分題 - **第八週**:後台訂單管理可設為加分題 ### 常見問題排解 | 問題 | 解決方式 | |------|----------| | `.env` 找不到 | 確認 `.env` 在專案根目錄(2025-js-plan/) | | API 請求失敗 | 確認 `API_PATH` 和 `API_KEY` 正確 | | Jest 超時 | 網路問題,可調高 `jest.setTimeout()` | | 模組找不到 | 執行 `npm install` 安裝相依套件 | --- ## 使用的 API 本課程使用**六角學院 LiveJS 電商 API**: - API 文件:https://hexschool.github.io/hexschoolliveswagger/ - 學員需先申請自己的 `api_path` 和 `api_key` --- ## 課程定位 | JS 週次 | 學習重點 | 銜接 Node.js | |---------|----------|--------------| | 第五週 | 函式拆分、邏輯整合 | Express MVC、Controller | | 第六週 | Promise、fetch、async/await | 非同步資料庫操作 | | 第七週 | 第三方套件(dayjs)、fetch 進階 | npm 生態系 | | 第八週 | RESTful API 整合 | 自建 RESTful API | --- ## 第五週:JavaScript 模組化開發 ### 作業主題 電商資料處理系統(純邏輯,不串 API) ### 任務清單 | 任務 | 配分 | 內容 | |------|------|------| | 任務一 | 20% | 產品查詢模組 | | 任務二 | 25% | 購物車計算模組 | | 任務三 | 30% | 購物車操作模組 | | 任務四 | 25% | 訂單統計模組 | ### 需實作函式 **任務一:產品查詢** - `getProductById(products, productId)` - 根據 ID 查詢產品 - `getProductsByCategory(products, category)` - 根據分類篩選 - `getDiscountRate(product)` - 計算折扣率(如 "8折") - `getAllCategories(products)` - 取得不重複分類 **任務二:購物車計算** - `calculateCartOriginalTotal(carts)` - 原價總金額 - `calculateCartTotal(carts)` - 售價總金額 - `calculateSavings(carts)` - 省下金額 - `calculateCartItemCount(carts)` - 商品總數量 - `isProductInCart(carts, productId)` - 檢查是否在購物車 **任務三:購物車操作** - `addToCart(carts, product, quantity)` - 新增商品 - `updateCartItemQuantity(carts, cartId, newQuantity)` - 更新數量 - `removeFromCart(carts, cartId)` - 移除商品 - `clearCart()` - 清空購物車 **任務四:訂單統計** - `calculateTotalRevenue(orders)` - 已付款訂單營收 - `filterOrdersByStatus(orders, isPaid)` - 篩選訂單狀態 - `generateOrderReport(orders)` - 產生統計報表 - `groupOrdersByPayment(orders)` - 依付款方式分組 --- ## 第六週:非同步程式開發 ### 作業主題 電商 API 資料串接(fetch + async/await) ### 任務清單 | 任務 | 配分 | 內容 | |------|------|------| | 任務一 | 20% | 基礎 fetch 練習 | | 任務二 | 30% | POST/PATCH/DELETE 請求 | | 任務三 | 30% | Promise 處理與整合 | | 任務四 | 15% | 管理員 API(需認證) | | 加分題 | 5% | HTTP 知識測驗 | ### 需實作函式 **任務一:基礎 GET 請求** - `getProducts()` - 取得產品列表 - `getCart()` - 取得購物車 - `getProductsSafe()` - 含錯誤處理 **任務二:購物車操作** - `addToCart(productId, quantity)` - 加入購物車 (POST) - `updateCartItem(cartId, quantity)` - 更新數量 (PATCH) - `removeCartItem(cartId)` - 刪除商品 (DELETE) - `clearCart()` - 清空購物車 (DELETE) **任務三:Promise 整合** - `getProductsAndCart()` - 同時取得產品和購物車 (Promise.all) - `addMultipleToCart(items)` - 批次加入購物車 - `checkout(userInfo)` - 完整結帳流程 **任務四:管理員 API** - `getOrders()` - 取得訂單(需認證) - `updateOrderStatus(orderId, isPaid)` - 更新訂單狀態 - `deleteOrder(orderId)` - 刪除訂單 --- ## 第七週:第三方套件整合 ### 作業主題 使用 dayjs 優化電商系統(延續 fetch 練習) ### 套件選用分析 第七週是學員第一次使用 `npm install` 安裝外部套件,選用原則如下: | 任務 | 建議方式 | 原因 | |------|----------|------| | 日期處理 | ✅ **dayjs** | 原生寫法太繁瑣,dayjs 能明顯簡化 | | 資料驗證 | ✅ **原生 JS** | 規則簡單,用 if/正則即可,不需套件 | | ID 產生 | ✅ **原生 JS** | `Date.now() + Math.random()` 就夠用 | | API 串接 | ✅ **fetch** | 學員需先熟悉原生,axios 留給 Node.js 課程 | ### 為什麼用 dayjs? 任務一的 4 個函式都需要日期處理,原生寫法 vs dayjs 差異明顯: ```javascript // === formatOrderDate === // 原生 JS - 繁瑣 function formatOrderDate(timestamp) { const d = new Date(timestamp * 1000); const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); const hour = String(d.getHours()).padStart(2, '0'); const min = String(d.getMinutes()).padStart(2, '0'); return `${year}/${month}/${day} ${hour}:${min}`; } // dayjs - 一行搞定 function formatOrderDate(timestamp) { return dayjs.unix(timestamp).format('YYYY/MM/DD HH:mm'); } ``` ```javascript // === getThisWeekOrders === // 原生 JS - 要自己算週的開始結束 function getThisWeekOrders(orders) { const now = new Date(); const dayOfWeek = now.getDay(); const startOfWeek = new Date(now); startOfWeek.setDate(now.getDate() - dayOfWeek); startOfWeek.setHours(0, 0, 0, 0); // ... 還要算 endOfWeek,很繁瑣 ... } // dayjs - 直覺 function getThisWeekOrders(orders) { const startOfWeek = dayjs().startOf('week'); const endOfWeek = dayjs().endOf('week'); return orders.filter(order => { const orderDate = dayjs.unix(order.createdAt); return orderDate.isAfter(startOfWeek) && orderDate.isBefore(endOfWeek); }); } ``` ### 為什麼不用 axios? 學員應該先熟悉原生 fetch,再學 axios: ``` 第六週:fetch 基礎(GET/POST/PATCH/DELETE) 第七週:fetch 進階 + dayjs(錯誤處理、日期格式化) 第八週:fetch 整合(模組化架構) Node.js:axios(後端課程再學) ``` ### 為什麼資料驗證和 ID 產生用原生? 1. **資料驗證**:規則簡單(正則表達式 + if 判斷),不需要 validator 套件 2. **ID 產生**:`Date.now().toString(36) + Math.random().toString(36)` 就夠用,不需要 uuid 套件 這樣學員能學到: - 原生 JS 能力(正則、隨機數) - 判斷「什麼時候該用套件」的思維 ### 任務清單 | 任務 | 配分 | 內容 | |------|------|------| | 任務一 | 25% | dayjs 日期處理 | | 任務二 | 20% | 資料驗證(原生 JS) | | 任務三 | 10% | ID 產生(原生 JS) | | 任務四 | 20% | fetch API 進階 | | 任務五 | 25% | OrderService 整合 | ### 需實作函式 **任務一:dayjs 日期處理** - `formatOrderDate(timestamp)` - 轉為 "YYYY/MM/DD HH:mm" - `getDaysAgo(timestamp)` - 計算距今幾天 - `isOrderOverdue(timestamp)` - 判斷是否超過 7 天 - `getThisWeekOrders(orders)` - 篩選本週訂單 **任務二:資料驗證(原生 JS)** - `validateOrderUser(data)` - 驗證訂單使用者資料 - `validateCartQuantity(quantity)` - 驗證數量(1-99) **任務三:ID 產生(原生 JS)** - `generateOrderId()` - 格式 "ORD-xxxxxxxx" - `generateCartItemId()` - 格式 "CART-xxxxxxxx" **任務四:fetch API 進階** - `getProducts()` - 取得產品(含錯誤處理) - `addToCart(productId, quantity)` - 加入購物車 - `getOrders()` - 取得訂單(需認證) **任務五:OrderService 整合** ```javascript const OrderService = { fetchOrders(), // fetch 取得訂單 formatOrders(), // dayjs 格式化日期 filterUnpaidOrders(), // 篩選未付款 validateUserInfo(), // 驗證使用者 getUnpaidOrdersFormatted() // 整合方法 } ``` --- ## 第八週:期末專案 ### 作業主題 電商系統整合(模組化架構) ### 任務清單 | 任務 | 配分 | 內容 | |------|------|------| | API 模組 | 25% | api.js | | 工具函式 | 15% | utils.js | | 產品服務 | 20% | productService.js | | 購物車服務 | 25% | cartService.js | | 訂單服務 | 15% | orderService.js | ### 專案架構 ``` week8/assignment/ ├── config.js # API 設定 ├── api.js # API 請求函式 ├── utils.js # 工具函式 ├── services/ │ ├── productService.js │ ├── cartService.js │ └── orderService.js ├── app.js # 主程式入口 └── test.js # Jest 測試 ``` ### 各檔案需實作函式 **api.js(10 個函式)** - 客戶端:fetchProducts, fetchCart, addToCart, updateCartItem, deleteCartItem, clearCart, createOrder - 管理員:fetchOrders, updateOrderStatus, deleteOrder **utils.js(6 個函式)** - getDiscountRate, getAllCategories, formatDate, getDaysAgo, validateOrderUser, validateCartQuantity **productService.js(5 個函式)** - getProducts, getProductsByCategory, getProductById, getCategories, displayProducts **cartService.js(7 個函式)** - getCart, addProductToCart, updateProduct, removeProduct, emptyCart, getCartTotal, displayCart **orderService.js(8 個函式)** - placeOrder, getOrders, getUnpaidOrders, getPaidOrders, updatePaymentStatus, removeOrder, formatOrder, displayOrders --- ## API 端點參考 ``` Base URL: https://livejs-api.hexschool.io === 客戶端 API === GET /api/livejs/v1/customer/{api_path}/products 取得產品 GET /api/livejs/v1/customer/{api_path}/carts 取得購物車 POST /api/livejs/v1/customer/{api_path}/carts 加入購物車 PATCH /api/livejs/v1/customer/{api_path}/carts 更新數量 DELETE /api/livejs/v1/customer/{api_path}/carts 清空購物車 DELETE /api/livejs/v1/customer/{api_path}/carts/{id} 刪除商品 POST /api/livejs/v1/customer/{api_path}/orders 建立訂單 === 管理員 API(需 Token)=== GET /api/livejs/v1/admin/{api_path}/orders 取得訂單 PUT /api/livejs/v1/admin/{api_path}/orders 更新訂單 DELETE /api/livejs/v1/admin/{api_path}/orders/{id} 刪除訂單 ``` --- ## 驗證規則參考 | 欄位 | 規則 | |------|------| | name | 不可為空 | | tel | 09 開頭的 10 位數字 | | email | 包含 @ 符號 | | address | 不可為空 | | payment | ATM / Credit Card / Apple Pay | | quantity | 正整數,1-99 | --- ## 測試指令 每週作業都使用 Jest 測試: ```bash # 進入該週資料夾 cd week6/assignment # 安裝套件 npm install # 執行測試 npm test ``` --- ## 繳交方式 1. 完成作業檔案中的所有函式 2. 執行 `npm test` 確保測試通過 3. 上傳至 GitHub 4. 提交 GitHub 連結