US-102: 商品列表與搜尋
User Story
作為 花店員工 我想要 能夠瀏覽商品列表並透過搜尋和過濾快速找到商品 以便 在創建訂單時快速選擇商品,或管理商品目錄
驗收標準 (Acceptance Criteria)
Scenario 1: 查看商品列表(預設狀態)
- Given 我是已登入的員工
- And 我在「商品管理」頁面
- When 頁面載入完成
- Then 我應看到商品列表,預設顯示:
- 所有「已上架」商品
- 按創建日期倒序排列(最新的在前)
- 每頁顯示 20 筆
- And 每個商品項目應顯示:
- 商品縮圖
- 商品名稱
- SKU 編號
- 商品類型標籤
- 基礎價格
- 庫存狀態(正常/低庫存/缺貨)
- 上架狀態(已上架/已下架)
Scenario 2: 搜尋商品(依名稱)
- Given 我是已登入的員工
- And 我在商品列表頁面
- And 系統中有商品「玫瑰花束(12 朵)」和「百合花束」
- When 我在搜尋框輸入「玫瑰」
- Then 列表應即時過濾(防抖 300ms)
- And 只顯示名稱包含「玫瑰」的商品
- And 搜尋結果數量應顯示在列表上方
Scenario 3: 搜尋商品(依 SKU)
- Given 我是已登入的員工
- And 系統中有 SKU 為「ABC-BQT-000001」的商品
- When 我在搜尋框輸入「BQT-000001」
- Then 列表應顯示 SKU 包含「BQT-000001」的商品
Scenario 4: 過濾商品(依類型)
- Given 我是已登入的員工
- And 我在商品列表頁面
- When 我選擇類型過濾為「花束」
- Then 列表應只顯示類型為「花束」的商品
- And 過濾條件應顯示為「篩選中」標籤
Scenario 5: 過濾商品(依上架狀態)
- Given 我是已登入的管理者
- And 我在商品列表頁面
- When 我選擇狀態過濾為「已下架」
- Then 列表應只顯示狀態為「已下架」的商品
Scenario 6: 過濾商品(依庫存狀態)
- Given 我是已登入的員工
- And 我在商品列表頁面
- When 我選擇庫存狀態過濾為「低庫存」
- Then 列表應只顯示庫存 <= 低庫存警戒值的商品
Scenario 7: 過濾商品(依價格區間)
- Given 我是已登入的員工
- And 我在商品列表頁面
- When 我設定價格區間為「500 ~ 1500」
- Then 列表應只顯示基礎價格在 500 到 1500 之間的商品
Scenario 8: 組合搜尋與過濾
- Given 我是已登入的員工
- And 我在商品列表頁面
- When 我在搜尋框輸入「玫瑰」
- And 我選擇類型過濾為「花束」
- And 我選擇價格區間為「1000 ~ 2000」
- Then 列表應只顯示同時符合以下條件的商品:
- 名稱或 SKU 包含「玫瑰」
- 類型為「花束」
- 價格在 1000 ~ 2000 之間
Scenario 9: 排序商品
- Given 我是已登入的員工
- And 我在商品列表頁面
- When 我選擇排序為「價格(高→低)」
- Then 列表應按基礎價格由高到低排序
Scenario 10: 分頁導航
- Given 我是已登入的員工
- And 系統中有 50 筆商品
- And 每頁顯示 20 筆
- When 我點擊「下一頁」
- Then 我應看到第 21-40 筆商品
- And 分頁指示器應顯示「第 2 頁,共 3 頁」
Scenario 11: 清除所有篩選條件
- Given 我是已登入的員工
- And 我已套用多個篩選條件
- When 我點擊「清除篩選」按鈕
- Then 所有篩選條件應被重置
- And 列表應恢復為預設狀態
Scenario 12: 空搜尋結果
- Given 我是已登入的員工
- And 我在商品列表頁面
- When 我搜尋「不存在的商品」
- Then 列表應顯示空狀態訊息「找不到符合條件的商品」
- And 應顯示「清除篩選」按鈕
Scenario 13: 點擊商品進入詳情頁
- Given 我是已登入的員工
- And 我在商品列表頁面
- When 我點擊商品「玫瑰花束(12 朵)」
- Then 我應被導航至該商品的詳情頁
業務規則 (Business Rules)
-
預設顯示規則
- 預設只顯示「已上架」商品
- 管理者可選擇查看「已下架」商品
- 按創建日期倒序排列
-
搜尋規則
- 搜尋範圍:商品名稱、SKU 編號、商品描述
- 模糊搜尋(包含即匹配)
- 不區分大小寫
- 搜尋關鍵字至少 1 個字元
- 即時搜尋(防抖 300ms)
-
過濾條件
- 商品類型: 花束/盆花/高架/花籃/盒花/禮品/客製化(可多選)
- 上架狀態: 已上架/已下架(單選)
- 庫存狀態: 正常/低庫存/缺貨(可多選)
- 價格區間: 最低價 ~ 最高價
- 特殊標記: 熱門商品/新品(可多選)
-
排序選項
- 創建日期(最新/最舊)- 預設
- 更新日期(最新/最舊)
- 價格(高→低/低→高)
- 名稱(A-Z/Z-A)
- 庫存數量(高→低/低→高)
-
分頁規則
- 每頁 20 筆(可選 10/20/50)
- 顯示總筆數與頁數
- URL 參數保留搜尋/過濾/排序/分頁狀態
-
庫存狀態定義
- 正常: 庫存 > 低庫存警戒值
- 低庫存: 0 < 庫存 <= 低庫存警戒值
- 缺貨: 庫存 = 0
-
權限規則
ROLE_STAFF以上可查看商品列表ROLE_STAFF只能看到「已上架」商品ROLE_MANAGER以上可切換查看「已下架」商品- 成本價格欄位僅
ROLE_MANAGER以上可見
-
多租戶隔離
- 列表僅顯示當前租戶的商品
- API 自動過濾
tenantId
UI/UX 需求 (UI/UX Requirements)
頁面佈局
頂部區域
- 標題: 商品管理
- 操作按鈕: 「新增商品」(管理者以上)
搜尋與過濾區域
-
搜尋框:
- Placeholder: 「搜尋商品名稱或 SKU...」
- 搜尋圖示
- 清除按鈕(有內容時顯示)
-
過濾器列表:
- 商品類型(多選下拉)
- 上架狀態(單選下拉)
- 庫存狀態(多選下拉)
- 價格區間(雙滑桿或兩個輸入框)
- 特殊標記(多選下拉)
-
排序: 下拉選單
-
篩選標籤: 已套用的篩選條件顯示為標籤,可點擊 X 移除
-
清除篩選: 按鈕(有任何篩選時顯示)
列表區域
-
列表視圖:
- 表格形式(桌面)
- 卡片形式(手機)
-
表格欄位:
欄位 寬度 說明 縮圖 60px 商品主圖縮圖 商品名稱 auto 含 SKU 編號(副標題) 類型 80px 商品類型標籤 價格 100px 基礎價格 庫存 80px 庫存數量 + 狀態標籤 狀態 80px 上架/下架標籤 操作 100px 編輯、更多選單
分頁區域
- 左側: 「顯示第 X-Y 筆,共 Z 筆」
- 中間: 分頁按鈕(首頁、上一頁、頁碼、下一頁、末頁)
- 右側: 每頁筆數選擇(10/20/50)
狀態標籤樣式
-
上架狀態:
- 已上架: 綠色背景
- 已下架: 灰色背景
-
庫存狀態:
- 正常: 無特殊標記
- 低庫存: 橘色背景「低庫存」
- 缺貨: 紅色背景「缺貨」
-
特殊標記:
- 熱門商品: 星星圖示 + 黃色標籤
- 新品: 「NEW」標籤
互動行為
-
搜尋:
- 輸入後 300ms 自動搜尋
- 搜尋中顯示 Loading 指示器
- 搜尋結果數量更新
-
過濾:
- 選擇後即時套用
- 多個過濾條件為 AND 關係
- 過濾條件顯示為標籤
-
排序:
- 選擇後即時排序
- 當前排序選項顯示為已選中
-
分頁:
- 點擊頁碼/箭頭切換頁面
- 切換頁面時平滑捲動到頂部
- URL 更新(支援書籤)
-
空狀態:
- 顯示插圖 + 文字說明
- 提供「清除篩選」或「新增商品」按鈕
響應式設計
-
桌面 (>= 1024px):
- 表格視圖
- 過濾器水平排列
-
平板 (768px ~ 1023px):
- 表格視圖(隱藏部分欄位)
- 過濾器可摺疊
-
手機 (< 768px):
- 卡片視圖
- 過濾器在 Drawer 中
- 「篩選」按鈕顯示已套用數量
技術規格 (Technical Specifications)
API 端點
獲取商品列表
- 端點:
GET /api/v1/products - 權限要求:
ROLE_STAFF或更高 - 多租戶隔離: 自動過濾
tenantId
Query Parameters:
| 參數 | 類型 | 必填 | 說明 | 預設值 |
|---|---|---|---|---|
| search | string | No | 搜尋關鍵字 | - |
| category | string[] | No | 商品類型(可多選) | - |
| status | string | No | 上架狀態 (active/inactive) | active |
| stockStatus | string[] | No | 庫存狀態 (normal/low/out) | - |
| minPrice | number | No | 最低價格 | - |
| maxPrice | number | No | 最高價格 | - |
| isFeatured | boolean | No | 熱門商品 | - |
| isNew | boolean | No | 新品 | - |
| sortBy | string | No | 排序欄位 | createdAt |
| sortOrder | string | No | 排序方向 (asc/desc) | desc |
| page | number | No | 頁碼(從 1 開始) | 1 |
| limit | number | No | 每頁筆數 | 20 |
請求範例:
GET /api/v1/products?search=玫瑰&category=bouquet&status=active&minPrice=1000&maxPrice=2000&sortBy=basePrice&sortOrder=desc&page=1&limit=20
響應 Payload:
{
"data": [
{
"id": "prod-abc123",
"sku": "ABC-BQT-000001",
"name": "玫瑰花束(12 朵)",
"description": "經典浪漫,情人節首選",
"category": "bouquet",
"categoryLabel": "花束",
"basePrice": 1200,
"costPrice": 800, // 僅 ROLE_MANAGER 以上可見
"stock": 50,
"lowStockThreshold": 5,
"stockStatus": "normal", // "normal" | "low" | "out"
"status": "active",
"isFeatured": true,
"isNew": false,
"mainImageUrl": "https://...",
"thumbnailUrl": "https://...",
"createdAt": "2025-12-03T10:00:00Z",
"updatedAt": "2025-12-03T10:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 45,
"totalPages": 3
}
}
響應 Headers:
X-Total-Count: 45
X-Page: 1
X-Per-Page: 20
X-Total-Pages: 3
前端狀態管理
使用 URL Search Params 保存搜尋/過濾/排序/分頁狀態:
// URL 範例
/products?search=玫瑰&category=bouquet&status=active&sortBy=basePrice&sortOrder=desc&page=1
// React Hook 範例
const useProductListParams = () => {
const [searchParams, setSearchParams] = useSearchParams();
return {
search: searchParams.get('search') || '',
category: searchParams.getAll('category'),
status: searchParams.get('status') || 'active',
stockStatus: searchParams.getAll('stockStatus'),
minPrice: searchParams.get('minPrice') ? Number(searchParams.get('minPrice')) : undefined,
maxPrice: searchParams.get('maxPrice') ? Number(searchParams.get('maxPrice')) : undefined,
sortBy: searchParams.get('sortBy') || 'createdAt',
sortOrder: searchParams.get('sortOrder') || 'desc',
page: Number(searchParams.get('page')) || 1,
limit: Number(searchParams.get('limit')) || 20,
};
};
元件架構
src/applets/product-applet/
├── ProductListPage.tsx # 商品列表頁面
├── components/
│ ├── ProductSearchBar.tsx # 搜尋框組件
│ ├── ProductFilterPanel.tsx # 過濾面板組件
│ ├── ProductFilterDrawer.tsx # 過濾 Drawer(手機)
│ ├── ProductTable.tsx # 商品表格(桌面)
│ ├── ProductCardList.tsx # 商品卡片列表(手機)
│ ├── ProductListItem.tsx # 單一商品列表項
│ ├── ProductSortSelect.tsx # 排序選擇器
│ └── ProductPagination.tsx # 分頁組件
├── hooks/
│ ├── useProductList.ts # 商品列表 Query Hook
│ └── useProductListParams.ts # URL 參數 Hook
└── types.ts # TypeScript 類型定義
測試需求 (Test Requirements)
單元測試
- 搜尋邏輯(模糊搜尋、防抖)
- 過濾邏輯(類型、狀態、價格區間)
- 排序邏輯(各欄位、升降序)
- 分頁邏輯(頁碼計算、邊界條件)
- URL 參數解析與序列化
整合測試
- 商品列表 API 整合(含搜尋/過濾/排序/分頁)
- 權限控制(STAFF vs MANAGER 可見欄位差異)
- 多租戶隔離
E2E 測試
- Scenario 1: 查看商品列表(預設狀態)
- Scenario 2: 搜尋商品(依名稱)
- Scenario 3: 搜尋商品(依 SKU)
- Scenario 4: 過濾商品(依類型)
- Scenario 8: 組合搜尋與過濾
- Scenario 9: 排序商品
- Scenario 10: 分頁導航
- Scenario 11: 清除所有篩選條件
- Scenario 12: 空搜尋結果
驗收檢查清單 (Acceptance Checklist)
- 商品列表正確顯示
- 搜尋功能正常(名稱、SKU、即時搜尋)
- 過濾功能正常(類型、狀態、庫存、價格)
- 排序功能正常(各欄位、升降序)
- 分頁功能正常
- URL 參數正確保存/還原
- 組合搜尋與過濾正常運作
- 清除篩選功能正常
- 空狀態顯示正確
- 響應式設計正常(桌面/平板/手機)
- 庫存狀態標籤正確顯示
- 權限控制正確(成本價格可見性)
- 點擊商品跳轉詳情頁正常
Story Points
估算: 5 points
理由:
- 列表、搜尋、過濾、排序、分頁都是常見模式
- 可參考 US-302(客戶列表與搜尋)實作
- URL 參數狀態管理增加少許複雜度
- 響應式設計(表格 vs 卡片)需要額外工作
相關文檔
最後更新: 2025-12-03 撰寫者: AI Assistant