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