US-003: 基礎 Layout 與導航
User Story
作為 已登入的用戶 我想要 看到一個清晰、一致的系統 Layout 與導航結構 以便 快速訪問各個功能模組並了解當前所在位置
驗收標準 (Acceptance Criteria)
Scenario 1: 登入後看到完整的 Layout 結構
- Given 我是已登入的店員
- When 我成功登入並進入首頁
- Then 我應看到完整的 Layout 結構:
- Header: 包含 Logo、Application Launcher、全螢幕切換、主題切換、通知鈴鐺(Phase 2)、用戶選單
- Main Content: 中央內容區域(Applet 容器)
- Footer: 頁尾資訊(版權、版本號)
Scenario 2: Header 中顯示用戶資訊與角色
- Given 我是已登入的店員
- And 我的姓名是「王小明」,角色是「店員」
- When 我查看 Header 右上角
- Then 我應看到我的姓名「王小明」
- And 我應看到角色標籤「店員」(Badge 樣式)
- And 我應看到用戶頭像(預設為姓名首字母圓形 Avatar)
Scenario 3: 點擊用戶選單顯示下拉選項
- Given 我在任意頁面
- When 我點擊 Header 右上角的用戶頭像/姓名
- Then 應顯示下拉選單,包含:
- 個人設定
- 登出
- And 當我點擊「登出」時,系統應登出並重定向至登入頁面
Scenario 4: Application Launcher 根據角色動態顯示 Applet
- Given 我是已登入的店員(
ROLE_STAFF) - When 我點擊 Header 的 Application Launcher 按鈕(Grid3x3 圖示)
- Then 應顯示 Modal 應用啟動器
- And 我應看到以下 Applet:
- Home(首頁)
- Orders(訂單管理)
- Customers(客戶管理)
- Calendar(行事曆)
- Messages(訊息)
- And 我不應看到「Settings」(需要更高權限)
Scenario 5: 從 Application Launcher 導航至 Applet
- Given Application Launcher 已開啟
- When 我點擊「Orders」Applet
- Then 系統應導航至訂單列表頁面(
/orders) - And Application Launcher 應自動關閉
- And URL 應更新為
/orders
Scenario 6: Application Launcher 搜尋功能
- Given Application Launcher 已開啟
- When 我在搜尋框輸入「訂單」
- Then 應僅顯示符合搜尋條件的 Applet(如 Orders)
- And 其他不符合的 Applet 應被過濾
Scenario 7: Application Launcher 分類過濾
- Given Application Launcher 已開啟
- When 我點擊「Sales & Marketing」分類按鈕
- Then 應僅顯示該分類的 Applet(如 Orders、Customers)
- And 其他分類的 Applet 應被過濾
Scenario 8: 麵包屑導航顯示當前位置(Phase 2)
- Given 我在訂單詳情頁面(訂單編號
ABC-20251031-0001) - When 我查看 Main Content 頂部
- Then 我應看到麵包屑導航:
首頁 > 訂單管理 > ABC-20251031-0001 - And 每個層級都應可點擊(除了當前頁面)
Scenario 9: 通知鈴鐺顯示未讀數量
- Given 我有 3 則未讀通知
- When 我查看 Header 右上角
- Then 通知鈴鐺圖示右上角應顯示紅色 Badge「3」
- When 我點擊通知鈴鐺
- Then 應顯示通知列表下拉選單(Phase 2 功能)
Scenario 10: Logo 點擊返回首頁
- Given 我在任意頁面(如訂單管理)
- When 我點擊 Header 左側的 Logo
- Then 系統應導航至首頁(
/)
業務規則 (Business Rules)
-
Layout 結構固定
- 所有已登入頁面都使用相同的 Layout 結構
- 登入頁面、404 頁面、403 頁面不使用此 Layout
-
Application Launcher 顯示規則
- 根據用戶角色動態顯示可用 Applet(參考 US-002)
- 無權限的 Applet 完全隱藏(而非顯示為禁用狀態)
- 支援搜尋和分類過濾
-
Applet 分類
- sales-marketing(銷售行銷)
- operations(營運管理)
- finance(財務會計)
- utilities(工具程式)
- other(其他)
-
響應式設計斷點
- 手機 (< 768px): 精簡 Header
- 平板 (768px - 1023px): 自適應 Header
- 桌面 (≥ 1024px): 完整 Header 工具列
-
Logo 與品牌
- Logo 使用租戶上傳的圖片(如有)
- 預設 Logo: 系統預設的花店圖示
- 顯示租戶名稱(如「花店 ABC」)
UI/UX 需求 (UI/UX Requirements)
Layout 結構
┌──────────────────────────────────────────────────────────┐
│ Header │
│ [Logo] 花店 ABC [⊞] [⛶] [🌙] [🔔] 王小明 [店員] ▼ │
├──────────────────────────────────────────────────────────┤
│ │
│ Main Content Area │
│ (Applet 容器) │
│ │
├──────────────────────────────────────────────────────────┤
│ Footer: © 2025 AppFuse · v1.0.0 │
└──────────────────────────────────────────────────────────┘
Header 工具列說明:
[⊞] Application Launcher - 開啟應用程式啟動器
[⛶] 全螢幕切換 - 進入/退出全螢幕模式
[🌙] 主題切換 - 深色/淺色模式
[🔔] 通知鈴鐺 - Phase 2 功能
Header 規格
實作位置: src/layouts/header.tsx
高度: 56px(h-14)
左側區域:
- Logo(40x40px
w-10 h-10)+ 租戶名稱「花店 ABC」 - 點擊 Logo 返回首頁
/
右側區域(從左到右):
- Application Launcher - Grid3x3 圖示,開啟應用程式啟動器
- 全螢幕切換 - Maximize/Minimize 圖示,切換全螢幕模式
- 主題切換 - ThemeToggle 組件(深色/淺色模式)
- 通知鈴鐺 - Bell 圖示(Phase 2,目前 disabled)
- 用戶選單 - UserMenu 組件(頭像 + 下拉選單)
樣式:
- 背景:
bg-base-100 - 邊框: 底部 1px 邊框
border-base-300 - 固定在頁面頂部(
sticky top-0 z-30) - 按鈕懸停:
hover:bg-base-200
Application Launcher 規格
實作位置: src/components/application-launcher/application-launcher.tsx
觸發方式:
- Header 右側 Grid3x3 圖示按鈕
- 點擊開啟 Modal
Modal 結構:
┌─────────────────────────────────────┐
│ 🔍 搜尋框 [X] 關閉 │
│─────────────────────────────────────│
│ [All] [Sales] [Ops] [Finance] ... │
│─────────────────────────────────────│
│ │
│ Sales & Marketing │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ 🛒 │ │ 👥 │ │ │ │
│ │Order│ │Custo│ │ │ │
│ └─────┘ └─────┘ └─────┘ │
│ │
│ Operations │
│ ┌─────┐ ┌─────┐ │
│ │ 📦 │ │ 🚚 │ │
│ │Produ│ │Deliv│ │
│ └─────┘ └─────┘ │
│ │
└─────────────────────────────────────┘
Applet 卡片樣式:
- 大小: 56x56px(
w-14 h-14) - 圖示背景: DaisyUI 語義色(
bg-primary、bg-success等) - 圖示: 白色 lucide-react 圖標
- 懸停效果:
scale-110變換 - 標籤: 下方顯示名稱(
line-clamp-2)
Modal 樣式:
- 背景遮罩:
bg-base-content/30 backdrop-blur-sm - 內容背景:
bg-base-200/95 backdrop-blur-xl - 邊框:
border-base-300 - 陰影:
shadow-2xl
鍵盤操作:
- ESC 鍵關閉 Modal
- 點擊背景遮罩關閉
Main Content 規格
Padding: 24px(桌面)、16px(手機)
最大寬度: 根據內容自適應,但不超過 1920px
最小高度: calc(100vh - Header高度 - Footer高度)
Footer 規格
高度: 48px
內容:
- 版權資訊:
© 2025 AppFuse - 版本號:
v1.0.0 - 連結: 隱私政策、使用條款(Phase 2)
樣式:
- 背景:
bg-base-200 - 文字:
text-base-content/60(60% 透明度) - 文字大小:
text-sm
用戶選單下拉
寬度: 200px
選單項目:
┌────────────────┐
│ 個人設定 │
│ ───────────── │
│ 登出 │
└────────────────┘
樣式:
- 背景:
bg-base-100 - 邊框:
border border-base-300 - 陰影:
shadow-lg - 圓角:
rounded-lg
通知下拉(Phase 2)
寬度: 360px
高度: 最多顯示 5 則通知,其餘可滾動
通知項目結構:
[圖示] 通知標題
通知內容摘要
時間戳(如「2 分鐘前」)
技術規格 (Technical Specifications)
組件結構
src/layouts/
├── main-layout.tsx # 主 Layout(Header + Main + Footer)
├── header.tsx # Header 組件(含 Application Launcher 觸發)
├── user-menu.tsx # 用戶選單下拉
├── menu-config.ts # 角色層級定義
└── (NotificationBell) # 通知鈴鐺(Phase 2)
src/components/
├── theme-toggle.tsx # 主題切換組件
└── application-launcher/
├── application-launcher.tsx # 應用程式啟動器 Modal
└── index.ts
src/config/
└── applet-registry.ts # Applet 註冊與角色過濾
路由配置
// src/routes/index.tsx
import { MainLayout } from '@/layouts/MainLayout';
const routes = [
{
path: '/',
element: <MainLayout />, // 套用 MainLayout
children: [
{ index: true, element: <HomePage /> },
{ path: 'orders', element: <OrderListPage /> },
{ path: 'orders/:id', element: <OrderDetailPage /> },
{ path: 'customers', element: <CustomerListPage /> },
// ...其他路由
]
},
// 不使用 MainLayout 的路由
{ path: '/login', element: <LoginPage /> },
{ path: '/403', element: <ForbiddenPage /> },
{ path: '/404', element: <NotFoundPage /> },
];
前端組件
-
使用框架組件:
@appfuse/appfuse-web的Dropdown組件(用戶選單)lucide-react的圖示組件(Home, ShoppingCart, Users, Package, Settings, Bell, Grid3x3, Maximize, Minimize, ChevronLeft, ChevronRight, X)
-
自定義組件:
main-layout.tsx- 主 Layout(整合 Header + Outlet + Footer)header.tsx- Header 組件(Logo + Application Launcher + 工具列)user-menu.tsx- 用戶選單下拉theme-toggle.tsx- 主題切換(深色/淺色)application-launcher.tsx- 應用程式啟動器 Modalapplet-registry.ts- Applet 配置與角色過濾
狀態管理
Application Launcher 狀態(在 header.tsx 內部管理):
const [isLauncherOpen, setIsLauncherOpen] = useState(false);
// 開啟 Application Launcher
<button onClick={() => setIsLauncherOpen(true)}>
<Grid3x3 />
</button>
// 渲染 Modal
<ApplicationLauncher
isOpen={isLauncherOpen}
onClose={() => setIsLauncherOpen(false)}
/>
用戶資訊:
- 從 Redux Store 讀取(
src/features/iam/me-slice.ts) useAppSelector(selectCurrentUser)獲取當前用戶
選單配置(src/layouts/menu-config.ts):
export interface MenuItem {
id: string;
label: string;
icon: LucideIcon;
path: string;
requiredRole?: RoleName; // 最低所需角色
children?: MenuItem[]; // 子選單(Phase 2)
}
export const menuItems: MenuItem[] = [
{ id: 'home', label: '首頁', icon: Home, path: '/' },
{ id: 'orders', label: '訂單管理', icon: ShoppingCart, path: '/orders', requiredRole: 'ROLE_SALES' },
{ id: 'customers', label: '客戶管理', icon: Users, path: '/customers', requiredRole: 'ROLE_SALES' },
{ id: 'products', label: '商品管理', icon: Package, path: '/products', requiredRole: 'ROLE_OWNER' },
{ id: 'reports', label: '報表分析', icon: FileText, path: '/reports', requiredRole: 'ROLE_OWNER' },
{ id: 'settings', label: '系統設定', icon: Settings, path: '/settings', requiredRole: 'ROLE_ADMIN' },
];
響應式斷點
使用 TailwindCSS 斷點:
const breakpoints = {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
};
Application Launcher 顯示邏輯:
- 所有斷點:透過 Header 的 Grid3x3 按鈕觸發 Modal
SBE 場景 (Specification by Example)
詳細的測試場景與範例數據:
估算 (Estimation)
- Story Points: 5 點
- 預估工時: 2-3 天
- 複雜度: 中等
工作拆分
-
Layout 組件開發 (1 天)
- 創建
MainLayout、Header、Footer組件 - 實作響應式設計(桌面、平板、手機)
- 創建
-
Application Launcher 開發 (1 天)
- 實作 Application Launcher Modal 組件
- 實作 Applet 註冊配置(
applet-registry.ts) - 整合角色權限過濾
- 實作搜尋和分類過濾功能
-
用戶選單與通知 (0.5 天)
- 實作用戶選單下拉(個人設定、登出)
- 實作登出邏輯(清除 Token + 重定向)
- 實作通知鈴鐺(僅 UI,Phase 2 實作通知列表)
-
測試與優化 (0.5 天)
- 單元測試(Application Launcher 過濾邏輯)
- 響應式測試(桌面、平板、手機)
- 無障礙性測試(鍵盤導航、螢幕閱讀器)
依賴 (Dependencies)
前置條件
- US-001: 用戶登入 - 需要用戶資訊(姓名、角色)
- US-002: 角色權限控制 - 需要動態顯示選單
並行開發
- 無(US-001 和 US-002 必須先完成)
測試策略 (Testing Strategy)
單元測試
- 測試 Application Launcher 過濾邏輯(
filterAppletsByRole函數) - 測試搜尋邏輯(
searchApplets函數) - 測試分類過濾邏輯
整合測試
- 測試登出流程(點擊「登出」→ 清除 Token → 重定向至登入頁面)
E2E 測試
- 測試完整 Layout 結構顯示
- 測試 Application Launcher 開啟與關閉
- 測試 Applet 導航(點擊 Applet → 導航至對應頁面)
- 測試 Logo 點擊返回首頁
響應式測試
- 測試桌面版(≥ 1024px)- 完整 Header 工具列
- 測試平板版(768px - 1023px)- 自適應 Header
- 測試手機版(< 768px)- 精簡 Header
無障礙性測試
- 測試鍵盤導航(Tab 鍵切換焦點)
- 測試螢幕閱讀器(選單項目有正確的
aria-label)
完成定義 (Definition of Done)
- 所有 Layout 組件實作完成(MainLayout、Header、Footer)
- Application Launcher 實作完成(含角色過濾、搜尋、分類)
- 響應式設計實作完成(桌面、平板、手機)
- Applet 註冊配置實作完成(
applet-registry.ts) - 用戶選單下拉實作完成(個人設定、登出)
- 通知鈴鐺 UI 實作完成(Phase 2 功能預留)
- 單元測試通過
- E2E 測試通過(至少涵蓋 Scenario 1、4、5)
- 響應式測試通過(桌面、平板、手機)
- 無障礙性測試通過(WCAG 2.1 AA)
- Code Review 通過
- Storybook Stories 已創建(展示各組件)
- 產品經理驗收通過
備註 (Notes)
設計決策
- 為何使用 Application Launcher 而非固定 Sidebar? Modal 方式更節省螢幕空間,適合多 Applet 的應用程式架構。
- 為何使用搜尋和分類過濾? 當 Applet 數量增加時,用戶能快速找到需要的應用。
- 為何 Applet 無權限時完全隱藏? 避免混淆用戶,簡化 UI。
未來優化
- 支援 Applet 收藏功能(用戶自定義常用應用)
- 支援 Applet 拖放排序(管理者自定義顯示順序)
- 支援麵包屑導航(顯示層級路徑)
- 支援全域搜尋功能(Header 中央搜尋框)
最後更新: 2025-12-20