# 前端分層規則與目前結構 ## 目的 這份文件只描述目前 repo 已經落地的前端分層與命名規則,讓後續新增檔案、搬移檔案、或重構時有一致判斷基準。 重點不是追求理想化架構,而是避免把舊名稱、過渡寫法、或已刪除的結構繼續當成規則。 目前專案的主要責任鏈如下: - `router` 決定 route 與 layout meta - `App.vue` 根據 route meta 組裝 app shell 與全域 UI - `views` 承接路由入口與頁面資料協調 - `components` 承接 layout、page component、domain component 與較細的 UI 區塊 - `composables` 承接可重用流程與 UI state - `stores` 承接跨頁狀態、快取與全域顯示狀態 - `services` 承接 HTTP client、API 模組、token 與錯誤處理 ## 目前目錄的責任邊界 ### `src/router` 目前路由集中在: - [routes.ts](/home/carl/git/skt-vuetify-templates/src/router/routes.ts) - [index.ts](/home/carl/git/skt-vuetify-templates/src/router/index.ts) - [guards.ts](/home/carl/git/skt-vuetify-templates/src/router/guards.ts) 責任: - 定義 route 與 route meta - 指定頁面使用哪種 layout - 串接導航守衛 目前 `meta.layout` 已是 app shell 切換的正式入口: - `default` 走 [MainLayout.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/MainLayout.vue) - `none` 走 [PlainLayout.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/PlainLayout.vue) ### `src/App.vue` [App.vue](/home/carl/git/skt-vuetify-templates/src/App.vue) 目前不是單純掛載入口,而是實際的應用組裝層。 目前承擔的責任包含: - 根據 `route.meta.layout` 切換 layout - 組裝 breadcrumb / favorites / menu 等 layout props - 放置全域搜尋結果 dialog - 放置全域訊息中心 dialog - 放置全域 snackbar - 串接 layout event 與路由跳轉 判斷原則: - 與整個 app shell 共享、且不屬於單一路由頁面的 UI,可留在 `App.vue` - 只屬於單一路由頁面的對話框或互動,不應堆到 `App.vue` ### `src/views` `views` 目前整體方向是「路由入口 + 頁面資料協調 + 頁面事件協調」。 目前較薄的 view: - [Home.vue](/home/carl/git/skt-vuetify-templates/src/views/Home.vue) - [Login.vue](/home/carl/git/skt-vuetify-templates/src/views/Login.vue) - [EditableGrid.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/EditableGrid.vue) - [Forbidden.vue](/home/carl/git/skt-vuetify-templates/src/views/errors/Forbidden.vue) - [ServerError.vue](/home/carl/git/skt-vuetify-templates/src/views/errors/ServerError.vue) - [ServiceUnavailable.vue](/home/carl/git/skt-vuetify-templates/src/views/errors/ServiceUnavailable.vue) - [NetworkError.vue](/home/carl/git/skt-vuetify-templates/src/views/errors/NetworkError.vue) - [Maintenance.vue](/home/carl/git/skt-vuetify-templates/src/views/errors/Maintenance.vue) - [NotFound.vue](/home/carl/git/skt-vuetify-templates/src/views/errors/NotFound.vue) - [FncPage.vue](/home/carl/git/skt-vuetify-templates/src/views/FncPage.vue) 目前仍偏厚的 view: - [SingleRecord.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/SingleRecord.vue) - [MasterDetailA.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailA.vue) - [MasterDetailB.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailB.vue) - [MasterDetailC.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailC.vue) `views` 應遵守的原則: - 可以持有 route、store、頁面資料組裝、頁面事件協調 - 可以管理只屬於該頁的 dialog 顯示狀態 - 不應長期承擔大量可抽出的模板片段 - 不應把可重用流程直接留在頁面內重複複製 ### `src/components` 目前 `components` 已經分成幾種不同角色,不能再用單一規則描述。 #### 1. 頁面型元件 目前以下元件實際上扮演 page component: - [PageLogin.vue](/home/carl/git/skt-vuetify-templates/src/components/PageLogin.vue) - [PageIndex.vue](/home/carl/git/skt-vuetify-templates/src/components/PageIndex.vue) - [PageMaint.vue](/home/carl/git/skt-vuetify-templates/src/components/PageMaint.vue) 這些檔案的責任是: - 接收 view 組好的資料與事件 - 組裝某個完整頁面的主畫面 - 再往下使用較小的子元件或 domain component 命名規則: - 只要是 page component,檔名以 `Page` 為前綴 - page component 可以放在 `components` 根目錄 - 不要把 page component 丟進 `base` #### 2. `components/login` 登入頁的較細 UI 區塊已集中到: - [CreateAccountLink.vue](/home/carl/git/skt-vuetify-templates/src/components/login/CreateAccountLink.vue) - [LoginAnnouncementBoard.vue](/home/carl/git/skt-vuetify-templates/src/components/login/LoginAnnouncementBoard.vue) - [LoginBrand.vue](/home/carl/git/skt-vuetify-templates/src/components/login/LoginBrand.vue) - [LoginForm.vue](/home/carl/git/skt-vuetify-templates/src/components/login/LoginForm.vue) - [LoginHeader.vue](/home/carl/git/skt-vuetify-templates/src/components/login/LoginHeader.vue) - [LoginIllustration.vue](/home/carl/git/skt-vuetify-templates/src/components/login/LoginIllustration.vue) - [LoginToolBar.vue](/home/carl/git/skt-vuetify-templates/src/components/login/LoginToolBar.vue) - [LoginVerify.vue](/home/carl/git/skt-vuetify-templates/src/components/login/LoginVerify.vue) 這一層的定位是: - 服務 `PageLogin` - 屬於 login 頁面家族 - 不是全域 base library #### 3. `components/base` 目前 `components/base` 只剩下: - [DraggableDialog.vue](/home/carl/git/skt-vuetify-templates/src/components/base/DraggableDialog.vue) 目前判斷原則很直接: - `base` 只放真正可跨頁重用、且不屬於特定 domain 的元件 - 若元件只服務單一頁面家族或單一 domain,優先放回對應資料夾 #### 4. `components/layouts` 目前 layout 實作集中於: - [MainLayout.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/MainLayout.vue) - [PlainLayout.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/PlainLayout.vue) - `src/components/layouts/main-layout/*` 其中 `main-layout/*` 是 `MainLayout` 底下拆出的骨架子元件: - [AppBarBreadcrumbCol.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/main-layout/AppBarBreadcrumbCol.vue) - [AppBarFavoritesCol.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/main-layout/AppBarFavoritesCol.vue) - [AppBarTopCol.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/main-layout/AppBarTopCol.vue) - [DrawerDesktopMenu.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/main-layout/DrawerDesktopMenu.vue) - [DrawerMobileFavoritesPanel.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/main-layout/DrawerMobileFavoritesPanel.vue) - [DrawerMobileMenuPanel.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/main-layout/DrawerMobileMenuPanel.vue) layout 應只承擔: - app shell - drawer / app bar / favorites / breadcrumb 等框架 UI - 與 layout 視覺結構直接相關的互動 layout 不應承擔: - 頁面專屬業務流程 - 特定 domain 的資料規則 #### 5. `components/maint` 這個目錄目前是最接近 feature folder 的區域,放 maintenance 領域的 page component 與 domain component: - [PageMaint.vue](/home/carl/git/skt-vuetify-templates/src/components/PageMaint.vue) - [CommonConfirmDialog.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/CommonConfirmDialog.vue) - [EditableGrid.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/EditableGrid.vue) - [MasterFileFormFields.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/MasterFileFormFields.vue) - [MntDialogCard.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/MntDialogCard.vue) - [MntRecordNavToolbar.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/MntRecordNavToolbar.vue) - `master-detail/*` `master-detail/*` 目前屬於維護頁專用的較細組件群: - [CourseMobilePanel.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/master-detail/CourseMobilePanel.vue) - [DetailCollapseGropus.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/master-detail/DetailCollapseGropus.vue) - [DetailFullHeightPanel.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/master-detail/DetailFullHeightPanel.vue) - [DetailNavigation.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/master-detail/DetailNavigation.vue) - [DetailSidePanel.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/master-detail/DetailSidePanel.vue) - [DetailSimpleList.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/master-detail/DetailSimpleList.vue) 結論: - `components/maint` 主要扮演 maintenance domain component 層 - `CommonConfirmDialog` 可以直接在 maintenance 頁或元件使用,不需要再包一層 CRUD dialog aggregator - 若只是維護頁專用子元件,不要搬到 `base` ### `src/composables` 目前已明確分成兩組: - `composables/layout/*` - `composables/maint/*` 代表性檔案: - [useAdminLayoutState.ts](/home/carl/git/skt-vuetify-templates/src/composables/layout/useAdminLayoutState.ts) - [useThemeToggle.ts](/home/carl/git/skt-vuetify-templates/src/composables/layout/useThemeToggle.ts) - [useMaintenanceCrudFlow.ts](/home/carl/git/skt-vuetify-templates/src/composables/maint/useMaintenanceCrudFlow.ts) - [useStudentMaintenanceForm.ts](/home/carl/git/skt-vuetify-templates/src/composables/maint/useStudentMaintenanceForm.ts) - [useEditableStudentGrid.ts](/home/carl/git/skt-vuetify-templates/src/composables/maint/useEditableStudentGrid.ts) - [useApiCall.ts](/home/carl/git/skt-vuetify-templates/src/composables/useApiCall.ts) `composables` 的責任: - 放可重用流程 - 放可測試的 UI state - 放與模板結構耦合較低的狀態機 ### `src/stores` 目前 store 已經是正式分層的一部分,而不只是暫時狀態容器。 代表性檔案: - [auth.ts](/home/carl/git/skt-vuetify-templates/src/stores/auth.ts) - [menu.ts](/home/carl/git/skt-vuetify-templates/src/stores/menu.ts) - [breadcrumbs.ts](/home/carl/git/skt-vuetify-templates/src/stores/breadcrumbs.ts) - [favorites.ts](/home/carl/git/skt-vuetify-templates/src/stores/favorites.ts) - [messages.ts](/home/carl/git/skt-vuetify-templates/src/stores/messages.ts) - [snackbar.ts](/home/carl/git/skt-vuetify-templates/src/stores/snackbar.ts) - [loginAnnouncements.ts](/home/carl/git/skt-vuetify-templates/src/stores/loginAnnouncements.ts) - [students.ts](/home/carl/git/skt-vuetify-templates/src/stores/students.ts) - [semesters.ts](/home/carl/git/skt-vuetify-templates/src/stores/semesters.ts) 責任: - 承接跨頁共享狀態 - 承接畫面快取與顯示狀態 - 作為 view 與 services 之間的狀態收斂點 注意: - 目前仍存在 `src/stores/stores/*` 的重複目錄 - 這不是分層設計的一部分,而是待整理的結構噪音 ### `src/services` `services` 現在已經是一層明確的資料存取邊界,不應再被視為附屬工具資料夾。 代表性檔案: - [client.ts](/home/carl/git/skt-vuetify-templates/src/services/client.ts) - [interceptors.ts](/home/carl/git/skt-vuetify-templates/src/services/interceptors.ts) - [error.ts](/home/carl/git/skt-vuetify-templates/src/services/error.ts) - [http-error.ts](/home/carl/git/skt-vuetify-templates/src/services/http-error.ts) - [http-toast.ts](/home/carl/git/skt-vuetify-templates/src/services/http-toast.ts) - [session.ts](/home/carl/git/skt-vuetify-templates/src/services/session.ts) - [token.ts](/home/carl/git/skt-vuetify-templates/src/services/token.ts) - `services/modules/*` 責任: - 提供 HTTP client - 封裝 API 模組 - 統一 token、session 與錯誤處理 規則: - 元件不直接處理底層 HTTP 細節 - 可共享的請求流程優先收斂到 store 或 composable,再由它們呼叫 service ## 目前已落地的分層模式 ### 模式 1:`view -> page component -> page family components` 已落地頁面: - `Login` - `Home` 目前的穩定模式是: - `view` 負責資料準備與事件協調 - page component 負責頁面主畫面組裝 - 較細的視覺區塊再拆到對應頁面家族資料夾,例如 `components/login/*` ### 模式 2:`view -> page component / domain components + maint composables` 已落地區域: - `views/maint/*` - `components/maint/*` - `composables/maint/*` 這一層目前是 maintenance 領域最清楚的結構: - `views/maint/*` 承接 route 與頁面流程協調 - [PageMaint.vue](/home/carl/git/skt-vuetify-templates/src/components/PageMaint.vue) 承接維護頁共用頁面骨架 - `components/maint/*` 承接維護頁專用元件 - `composables/maint/*` 承接 CRUD 流程、表單狀態與 editable grid 狀態 [EditableGrid.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/EditableGrid.vue) 是目前最接近薄 view 的 maintenance 頁面。 ### 模式 3:`router meta -> App.vue -> layout` 這一層已正式成立: - route 決定 layout 類型 - `App.vue` 決定套用哪個 shell - layout 專注在骨架與共用框架 UI 這代表 layout 的責任邊界不應再回頭混入頁面內部流程。 ## 命名規則 ### 頁面與 page component - 直接被 route 載入的檔案放 `views` - 負責完整頁面畫面組裝的元件,檔名用 `Page` 前綴 - page component 不放進 `base` 目前例子: - [PageLogin.vue](/home/carl/git/skt-vuetify-templates/src/components/PageLogin.vue) - [PageIndex.vue](/home/carl/git/skt-vuetify-templates/src/components/PageIndex.vue) - [PageMaint.vue](/home/carl/git/skt-vuetify-templates/src/components/PageMaint.vue) ### 資料夾命名 - 多字資料夾一律使用 `kebab-case` - 不新增 `snake_case` 或 `PascalCase` 資料夾 目前例子: - `main-layout` - `master-detail` ### domain component 命名 - 與特定領域強綁定的元件,優先用領域意圖命名 - 不要為了抽象而保留含糊的舊前綴 - 若元件只在 maint 領域使用,就留在 `components/maint` ## 目前仍待整理的區域 ### 高優先度 - 繼續瘦身: - [SingleRecord.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/SingleRecord.vue) - [MasterDetailA.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailA.vue) - [MasterDetailB.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailB.vue) - [MasterDetailC.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailC.vue) 原因: - 這些頁面仍保留較多頁面內資料轉換與事件協調 ### 中優先度 - 清理 `src/stores/stores/*` 重複結構 - 檢查 `components/maint` 內是否仍有可再明確命名的舊名稱 - 視 `PageMaint` 的後續演進,決定是否維持在 `components` 根目錄 ### 中低優先度 - 持續檢查 `views` 是否有可再下放到 page component 的模板片段 - 清理命名調整後留下的空資料夾或死連結 ## 新增或修改檔案時的判斷準則 1. 這個檔案是否直接被 route 載入? - 是:優先放 `views` 2. 這個檔案是否負責某個完整頁面的主畫面組裝? - 是:用 `Page` 前綴,放 page component 層,不要塞進 `base` 3. 這段重複的是模板還是流程? - 模板:抽元件 - 流程:抽 composable 或 store 4. 這個狀態是否跨頁共享,或需要快取 / 全域顯示控制? - 是:優先考慮 store 5. 這個邏輯是否在處理 API、token、session、錯誤正規化? - 是:放 `services` 6. 這個元件是否只屬於單一 domain? - 是:優先放到該 domain 目錄,例如 `components/maint` 7. 這個抽象是否真的降低重複與理解成本? - 否:不要抽