# MainLayout 規格表 來源元件:`src/components/layouts/MainLayout.vue` `MainLayout` 是預設後台版型的 app shell,負責組合側邊選單、頂部工具列、常用功能列、breadcrumb、內容區與輔助視窗。元件本身不直接呼叫後端 API;所有資料都透過 props 傳入,互動則透過 emit 交給外層處理。 ## 功能總覽 | 功能區塊 | 功能說明 | 主要輸入 | 主要輸出事件 | 需要後端 API | |---|---|---|---|---| | 系統品牌區 | 顯示系統標題、副標題,或由 `title` slot 覆蓋。 | `systemTitle`, `systemSubtitle`, `title` slot | 無 | 否,目前為 props/default。 | | 使用者資訊 | 顯示使用者頭像文字、姓名、角色。可用 feature toggle 關閉。 | `userProfile`, `features.showUserInfo` | 無 | 否,目前為 props/default。 | | 側邊選單 | 桌面版顯示多層 drawer menu;支援展開群組、收合 rail、選單項目導頁。 | `menuItems`, `isRail`, `drawerConfig` | `select`, `toggle-sidebar`, `update:isRail` | 是,`menuItems` 目前由 `GetMenu` 取得後轉換。 | | 行動版選單 | 行動版 drawer 使用階層式選單面板,點擊有子層項目會進入下一層,點擊葉節點會選取並關閉 drawer。 | `menuItems`, Vuetify display 狀態 | `select`, `toggle-sidebar` | 是,資料同 `menuItems`,目前由 `GetMenu` 取得後轉換。 | | 行動版階層導覽 | 在 drawer 上方顯示「主選單」與目前進入的選單層級,可返回任一層。 | `menuItems` 衍生出的 `mobileMenuLevels` | 無 | 是,層級內容間接來自 `GetMenu`。 | | 常用功能列 | 桌面版顯示常用功能 chip,可選取、移除,也可顯示新增按鈕。 | `favoriteItems`, `favoritesConfig`, `favoritesBarVisible`, `features.showFavorites` | `select`, `add-favorite`, `remove-favorite`, `update:favoritesBarVisible` | 否,目前不是由後端提供;`GetFavorite` 已有 service/store 方法但登入流程中未啟用。 | | 行動版常用功能 | 行動版 drawer 可切換到常用功能面板並選取項目。 | `favoriteItems`, `features.showFavorites` | `select` | 否,目前不是由後端提供。 | | 搜尋列 | 輸入關鍵字後,按 Enter 或按鈕才觸發搜尋;觸發後清空輸入。 | `searchConfig`, `features.showSearch` | `search` | 否,`MainLayout` 只送出關鍵字;目前外層用已載入的選單做前端比對。 | | 工具列通知 | 顯示通知按鈕與 badge 數量。 | `toolbarActions.notificationsLabel`, `toolbarCounts.notifications` | `action('notifications')` | 否,目前只是按鈕與 props 數字。 | | 工具列訊息 | 顯示訊息按鈕與 badge 數量。 | `toolbarActions.messagesLabel`, `toolbarCounts.messages` | `action('messages')` | 否,目前外層開啟示意訊息 dialog,沒有 API。 | | 工具列設定 | 顯示設定 menu,可切換常用功能列與 breadcrumb 顯示狀態。 | `showFavoritesBar`, `breadcrumbBarVisible` | `update:favoritesBarVisible`, `update:breadcrumbBarVisible` | 否,屬本機 UI 狀態。 | | 登出 | 顯示登出按鈕,點擊後交由外層處理。 | `logoutLabel` | `logout` | 否,layout 本身不呼叫 API。 | | 主題切換 | feature 開啟時顯示主題切換按鈕,透過 `useThemeToggle` 切換 Vuetify theme。 | `features.showThemeToggle`, `themeToggleLabel` | `toggle-theme` | 否,本機 theme 狀態。 | | Breadcrumb | 桌面版顯示目前頁面路徑;未傳入項目時顯示預設首頁。可插入 `breadcrumb-actions` slot。 | `breadcrumbItems`, `breadcrumbConfig`, `breadcrumbBarVisible`, `features.showBreadcrumb` | `update:breadcrumbBarVisible`, `update:favoritesBarVisible` | 否,目前由外層依路由與已載入選單推導。 | | 內容區 | 以 `slot` 承載各頁面內容,並依 app bar 高度動態設定 `v-main` padding。 | default slot | 無 | 否。 | | 操作說明浮窗 | 收到 `help` action 時顯示暫時說明內容,可關閉。 | `toolbarActions.helpLabel` | `action('help')` | 否,目前內容為靜態文字,且 help 按鈕在子元件中未顯示。 | ## 後端 API 需求 | API | 目前狀態 | 呼叫位置 | 提供給 `MainLayout` 的資料 | Request | Response 對應 | |---|---|---|---|---|---| | `Menu/GetMenu` | 已啟用 | `src/views/Login.vue` 登入成功後呼叫 `menuStore.getMenu(authStore.user?.id ?? '')` | `menuItems` | `{ userID: string }` | `res.data.data` 存入 `menuStore.menu`,再由 `toLayoutMenuItems()` 轉成 layout 選單。 | | `Menu/GetFavorite` | 未啟用 | service/store 已存在,但登入流程呼叫被註解 | 無 | `{ userID: string }` | 若未來啟用,可轉成 `favoriteItems`;目前不列為後端需求。 | ## 一般建議補齊的 API 配合清單 這份清單是以一般後台系統實作來看,列出 `MainLayout` 常見會需要後端配合的資料。現況仍只有 `Menu/GetMenu` 已接上;其餘項目可依產品需求決定是否實作。 | 類別 | 建議 API | 對應 layout 功能 | 必要性 | 說明 | |---|---|---|---|---| | 選單與權限 | `Menu/GetMenu` | 側邊選單、行動版選單、前端選單搜尋、breadcrumb 推導 | 必要,已啟用 | 依使用者、角色、權限回傳可用功能。 | | 使用者資訊 | `User/GetCurrentUser` 或 `User/GetProfile` | 使用者資訊區 `userProfile` | 建議 | 回傳姓名、角色、單位、頭像文字或頭像 URL。現況使用 default props。 | | 常用功能查詢 | `Menu/GetFavorite` 或 `Favorite/GetFavorites` | 常用功能列、行動版常用功能 | 建議 | 若常用功能要跨裝置保存,就應由後端提供。現況 service/store 已有 `Menu/GetFavorite`,但登入流程未啟用。 | | 常用功能維護 | `Favorite/AddFavorite`, `Favorite/RemoveFavorite`, `Favorite/UpdateFavoriteOrder` | 新增常用、移除常用、常用排序 | 建議 | 現況常用功能主要由前端本機 store 處理;若要保存到帳號需補 API。 | | 未讀數量 | `Notification/GetUnreadCounts` | 通知 badge、訊息 badge `toolbarCounts` | 建議 | 可一次回傳 `{ notifications, messages }`,避免 layout 分別打多支 API。現況預設皆為 `0`。 | | 通知清單 | `Notification/GetNotifications` | 通知按鈕點擊後的通知列表 | 視需求 | 目前 layout 只 emit `action('notifications')`,外層尚未實作通知 UI。 | | 通知已讀 | `Notification/MarkAsRead`, `Notification/MarkAllAsRead` | 通知清單互動、badge 歸零 | 視需求 | 若有通知列表,通常需要搭配已讀狀態更新。 | | 訊息清單 | `Message/GetMessages` | 訊息按鈕點擊後的訊息 dialog | 建議 | 現況 `App.vue` 使用示意資料,不含 API。 | | 訊息已讀 | `Message/MarkAsRead`, `Message/MarkAllAsRead` | 訊息清單互動、badge 歸零 | 視需求 | 若訊息中心要顯示未讀數,通常需要已讀 API。 | | 搜尋 | `Search/SearchMenu` 或 `Search/GlobalSearch` | 搜尋列 | 視需求 | 只搜尋目前已載入選單可維持前端搜尋;若要搜尋公告、頁面、業務資料或權限內功能,應補後端搜尋 API。 | | 登出 | `Auth/Logout` 或 `Auth/RevokeToken` | 登出按鈕 | 視認證架構 | 若後端有 session、refresh token 或 token revoke 機制,需要呼叫後端;若只是清 local token,可維持前端處理。 | | 使用者偏好 | `UserPreference/GetLayoutSettings`, `UserPreference/SaveLayoutSettings` | 側欄收合、常用列顯示、breadcrumb 顯示、主題 | 可選 | 目前可用 localStorage/store 處理;只有需要跨裝置同步時才需要 API。 | | 操作說明 | `Help/GetPageHelp` 或 CMS API | 操作說明浮窗 | 可選 | 現況為靜態暫時文字,且 help 按鈕未顯示;若說明內容需依頁面、角色或版本管理才需要 API。 | ## API 優先順序建議 | 優先順序 | API / 功能 | 建議理由 | |---|---|---| | 1 | `Menu/GetMenu` | layout 最核心資料,決定使用者可見功能與導頁入口。 | | 2 | `User/GetCurrentUser` | 使用者資訊區不應長期使用假資料,且常被其他功能共用。 | | 3 | `Favorite/GetFavorites` 與常用功能維護 API | 常用功能若要符合使用者帳號體驗,需要後端保存。 | | 4 | `Notification/GetUnreadCounts` / `Message/GetMessages` | toolbar badge 與訊息中心目前是 demo 狀態,若要上線需補。 | | 5 | `Search/GlobalSearch` | 只有當搜尋範圍超過目前選單時才需要。 | | 6 | `UserPreference` 類 API | 屬體驗同步,不影響核心操作,可最後處理。 | ## 建議 API 回傳格式 以下格式是給後端製作 API 時的建議契約。若沿用現有 service 包裝,前端實際讀取位置可能是 `res.data.data`;欄位命名可配合既有後端規範調整,但資料語意應保持一致。 ### `Menu/GetMenu` ```json { "success": true, "message": "", "data": [ { "mdl_id": "student", "mdl_name": "學生資訊", "children": [ { "unt_id": "course", "unt_name": "選課作業", "children": [ { "fnc_id": "course-add", "fnc_name": "線上加選" } ] } ] } ] } ``` ### `User/GetCurrentUser` 或 `User/GetProfile` ```json { "success": true, "message": "", "data": { "id": "A123456789", "name": "王小明", "role": "資訊工程系 - 學生", "avatarText": "王", "departmentId": "CS", "departmentName": "資訊工程系" } } ``` ### `Menu/GetFavorite` 或 `Favorite/GetFavorites` ```json { "success": true, "message": "", "data": [ { "id": "fav-1", "title": "線上加選", "path": "/course-add", "icon": "mdiPlus", "sort": 1 } ] } ``` ### `Favorite/AddFavorite` ```json { "success": true, "message": "新增成功", "data": { "id": "fav-1", "title": "線上加選", "path": "/course-add", "icon": "mdiPlus", "sort": 1 } } ``` ### `Favorite/RemoveFavorite` ```json { "success": true, "message": "移除成功", "data": { "id": "fav-1", "path": "/course-add" } } ``` ### `Favorite/UpdateFavoriteOrder` ```json { "success": true, "message": "排序已更新", "data": [ { "id": "fav-1", "path": "/course-add", "sort": 1 }, { "id": "fav-2", "path": "/score-query", "sort": 2 } ] } ``` ### `Notification/GetUnreadCounts` ```json { "success": true, "message": "", "data": { "notifications": 3, "messages": 12 } } ``` ### `Notification/GetNotifications` ```json { "success": true, "message": "", "data": { "items": [ { "id": "notice-1", "title": "系統維護提醒", "content": "系統將於週六凌晨維護。", "source": "資訊中心", "publishedAt": "2026-05-07T09:00:00+08:00", "isRead": false, "link": "/announcements/notice-1" } ], "total": 1, "unreadCount": 1 } } ``` ### `Notification/MarkAsRead` ```json { "success": true, "message": "已標記為已讀", "data": { "id": "notice-1", "isRead": true, "unreadCount": 0 } } ``` ### `Notification/MarkAllAsRead` ```json { "success": true, "message": "已全部標記為已讀", "data": { "unreadCount": 0 } } ``` ### `Message/GetMessages` ```json { "success": true, "message": "", "data": { "items": [ { "id": "msg-1", "title": "教務處公告", "summary": "加退選時間即將開始。", "sender": "教務處", "sentAt": "2026-05-07T10:30:00+08:00", "isRead": false, "link": "/messages/msg-1" } ], "total": 1, "unreadCount": 1 } } ``` ### `Message/MarkAsRead` ```json { "success": true, "message": "已標記為已讀", "data": { "id": "msg-1", "isRead": true, "unreadCount": 0 } } ``` ### `Message/MarkAllAsRead` ```json { "success": true, "message": "已全部標記為已讀", "data": { "unreadCount": 0 } } ``` ### `Search/SearchMenu` 或 `Search/GlobalSearch` ```json { "success": true, "message": "", "data": { "items": [ { "id": "course-add", "type": "menu", "title": "線上加選", "path": "/course-add", "parents": ["學生資訊", "選課作業"], "icon": "mdiPlus" } ], "total": 1 } } ``` ### `Auth/Logout` 或 `Auth/RevokeToken` ```json { "success": true, "message": "登出成功", "data": { "revoked": true } } ``` ### `UserPreference/GetLayoutSettings` ```json { "success": true, "message": "", "data": { "isRail": false, "favoritesBarVisible": true, "breadcrumbBarVisible": true, "themeName": "light" } } ``` ### `UserPreference/SaveLayoutSettings` ```json { "success": true, "message": "設定已儲存", "data": { "isRail": false, "favoritesBarVisible": true, "breadcrumbBarVisible": true, "themeName": "light", "updatedAt": "2026-05-07T10:30:00+08:00" } } ``` ### `Help/GetPageHelp` ```json { "success": true, "message": "", "data": { "pageKey": "home", "title": "操作說明", "content": "這裡顯示目前頁面的操作說明。", "updatedAt": "2026-05-07T10:30:00+08:00" } } ``` ## 可維持前端處理的功能 | 功能 | 原因 | |---|---| | Breadcrumb 顯示與路徑推導 | 可由目前 route、`menuItems`、`favoriteItems` 推導,不一定要後端提供。 | | 側欄收合狀態 | 屬使用者介面偏好,localStorage 即可;除非要求跨裝置同步。 | | 常用列顯示 / breadcrumb 顯示 | 屬畫面偏好,localStorage/store 即可。 | | 主題切換 | 本機 theme 狀態即可;除非要求登入後跨裝置一致。 | | 前端選單搜尋 | 若搜尋範圍只限已載入的 `menuItems`,不需要後端 API。 | ## `GetMenu` 欄位轉換 | 後端節點層級 | 後端欄位 | Layout 欄位 | 說明 | |---|---|---|---| | 模組層 | `mdl_name` | `title` | 第一層選單標題。 | | 模組層 | `children` | `subItems` | 第二層單元清單。 | | 單元層 | `unt_name` | `title` | 第二層選單標題。 | | 單元層 | `children` | `subItems` | 第三層功能清單。 | | 功能層 | `fnc_name` | `title` | 葉節點功能名稱。 | | 功能層 | `fnc_id` | `path` | 有值時轉成 `/${fnc_id}`,作為 router path。 | | 模組層與單元層 | 無 | `navigable: false` | 群組節點預設不可導頁。 | ## Props 與狀態來源 | Prop / 狀態 | 用途 | 預設值或目前來源 | 後端需求 | |---|---|---|---| | `menuItems` | 桌面與行動版主選單。 | `App.vue` 使用 `menuStore.menuItems` 加上固定選單合併後傳入。 | 是,來自 `GetMenu`。 | | `favoriteItems` | 常用功能列與行動版常用面板。 | `App.vue` 合併 `menuStore.favoriteItems` 與 `favoritesStore.layoutItems`;目前 `GetFavorite` 未啟用。 | 否。 | | `breadcrumbItems` | Breadcrumb 顯示。 | `breadcrumbStore` 依 route、`menuItems`、`favoriteItems` 推導。 | 否。 | | `userProfile` | 使用者資訊區。 | `MainLayout` default props。 | 否。 | | `toolbarCounts` | 通知、訊息 badge。 | `MainLayout` default props,預設皆為 `0`。 | 否。 | | `searchConfig` | 搜尋 placeholder 與 label。 | `MainLayout` default props。 | 否。 | | `toolbarActions` | 通知、訊息、說明、設定 label。 | `MainLayout` default props。 | 否。 | | `favoritesConfig` | 常用列 label、新增按鈕 label、是否顯示新增。 | `MainLayout` default props。 | 否。 | | `breadcrumbConfig` | 首頁 breadcrumb label、disabled、icon。 | `MainLayout` default props。 | 否。 | | `features` | 控制主題切換、常用列、breadcrumb、搜尋、工具列、使用者資訊是否顯示。 | `MainLayout` default props。 | 否。 | | `drawerConfig` | drawer 寬度與 rail 寬度。 | `MainLayout` default props。 | 否。 | | `isRail` | 桌面側欄是否收合。 | `App.vue` 以 `v-model:is-rail` 綁定 `menuStore.isRail`,store 會寫入 localStorage。 | 否。 | | `favoritesBarVisible` | 常用功能列是否顯示。 | `App.vue` 以 `v-model:favorites-bar-visible` 綁定 `favoritesStore`。 | 否。 | | `breadcrumbBarVisible` | Breadcrumb 是否顯示。 | `App.vue` 以 `v-model:breadcrumb-bar-visible` 綁定 `favoritesStore`。 | 否。 | ## 事件契約 | 事件 | 觸發時機 | 外層目前處理 | |---|---|---| | `select(item)` | 選取側邊選單、常用功能或搜尋結果延伸選取時。 | `App.vue` 呼叫 `router.push(item.path)`。 | | `search(keyword)` | 搜尋列按 Enter 或搜尋按鈕。 | `App.vue` 以已載入的合併選單做前端搜尋並顯示 dialog。 | | `action(type)` | 點擊通知、訊息、說明等工具列 action。 | `messages` 會開啟訊息 dialog;其他目前無處理。 | | `logout` | 點擊登出按鈕。 | `App.vue` 清除 auth、tabs,導回 login。 | | `toggle-sidebar(payload)` | 點擊 drawer 收合/展開按鈕。 | 目前外層未綁定。 | | `toggle-theme(themeName)` | 切換主題成功。 | 目前外層未綁定。 | | `add-favorite` | 點擊常用功能新增按鈕。 | 目前外層未綁定。 | | `remove-favorite(item)` | 點擊常用 chip close。 | `App.vue` 從本機常用清單切換移除。 | | `update:isRail(value)` | 受控模式下更新側欄 rail 狀態。 | `v-model:is-rail` 寫回 `menuStore.isRail`。 | | `update:favoritesBarVisible(value)` | 更新常用列顯示狀態。 | `v-model:favorites-bar-visible` 寫回 `favoritesStore`。 | | `update:breadcrumbBarVisible(value)` | 更新 breadcrumb 顯示狀態。 | `v-model:breadcrumb-bar-visible` 寫回 `favoritesStore`。 |