Files
skt-vuetify-templates/docs/frontend-layering.md
T
2026-03-30 15:04:27 +08:00

387 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端分層規則與目前結構
## 目的
這份文件只描述目前 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/sk-admin-layout/*`
其中 `sk-admin-layout/*``MainLayout` 底下拆出的骨架子元件:
- [SkAdminAppBarBreadcrumbCol.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/sk-admin-layout/SkAdminAppBarBreadcrumbCol.vue)
- [SkAdminAppBarFavoritesCol.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/sk-admin-layout/SkAdminAppBarFavoritesCol.vue)
- [SkAdminAppBarTopCol.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/sk-admin-layout/SkAdminAppBarTopCol.vue)
- [SkAdminDrawerDesktopMenu.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/sk-admin-layout/SkAdminDrawerDesktopMenu.vue)
- [SkAdminDrawerMobileFavoritesPanel.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/sk-admin-layout/SkAdminDrawerMobileFavoritesPanel.vue)
- [SkAdminDrawerMobileMenuPanel.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/sk-admin-layout/SkAdminDrawerMobileMenuPanel.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` 資料夾
目前例子:
- `sk-admin-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. 這個抽象是否真的降低重複與理解成本?
- 否:不要抽