# 第 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 連結