Files
skt-vuetify-templates/docs/frontend-layering.md
T
skytek_xinliang 9e8cf28d77 fix: docing
2026-05-22 11:17:32 +08:00

333 lines
12 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 已經落地的前端分層與命名規則,讓後續新增檔案、搬移檔案或重構時有一致判斷基準。
本文件是現況快照;新增功能與重構的細節規範以 `docs/architecture-strategy.md``docs/llm-development-guide.md` 與各層 `src/**/GUIDE.md` 為準。
目前專案的主要責任鏈如下:
```txt
router -> App.vue -> AppShell -> layout -> view -> page component -> section -> item
page driver / command composable -> store -> service
```
## 目前目錄的責任邊界
### `src/router`
目前路由集中在:
- [routes.ts](../src/router/routes.ts)
- [index.ts](../src/router/index.ts)
- [guards.ts](../src/router/guards.ts)
責任:
- 定義 route 與 route meta。
- 指定頁面使用哪種 layout。
- 串接導航守衛。
目前 `meta.layout` 是 app shell 切換的正式入口:
- `default` 走 [MainLayout.vue](../src/components/layouts/MainLayout.vue)。
- `none` 走 [PlainLayout.vue](../src/components/layouts/PlainLayout.vue)。
### `src/App.vue` 與 `src/shell`
[App.vue](../src/App.vue) 目前只掛載 [AppShell.vue](../src/shell/AppShell.vue),不再直接承擔全域 UI 組裝。
`src/shell` 是 App Shell 層:
- [AppShell.vue](../src/shell/AppShell.vue)layout 切換、layout props/events、breadcrumb actions、tabs router-view 與 `GlobalOverlays` 掛載。
- [AppTabs.vue](../src/shell/AppTabs.vue)default layout 下的 tabs 與 keep-alive router-view 容器。
- [GlobalOverlays.vue](../src/shell/GlobalOverlays.vue):全域 snackbar、搜尋 dialog、訊息 dialog。
判斷原則:
- 與整個 app shell 共享、且不屬於單一路由頁面的 UI,可放在 `src/shell`
- 只屬於單一路由頁面的對話框或互動,不應放進 `src/shell`
- shell 狀態協調優先放在 `src/composables/layout/useAppShell.ts`
### `src/views`
`views` 是 route entry,方向是薄層:呼叫 page driver、掛載 page component、協調 route-level 事件。
目前較典型的薄 view
- [Home.vue](../src/views/Home.vue)
- [Settings.vue](../src/views/Settings.vue)
- [FncPage.vue](../src/views/FncPage.vue)
- [SingleRecord.vue](../src/views/maint/SingleRecord.vue)
- [EditableGrid.vue](../src/views/maint/EditableGrid.vue)
- [MasterDetailA.vue](../src/views/maint/MasterDetailA.vue)
- [MasterDetailB.vue](../src/views/maint/MasterDetailB.vue)
- [MasterDetailC.vue](../src/views/maint/MasterDetailC.vue)
錯誤頁集中在 `src/views/errors`,通常使用 `meta.layout = 'none'`,並由 [ErrorShell.vue](../src/views/errors/ErrorShell.vue) 共用錯誤頁骨架。
[Login.vue](../src/views/Login.vue) 是 template core 例外:它仍負責登入頁組合、功能開關、小型提示 dialog 與登入流程協調。登入頁 UI 拆在 `components/login/*`captcha 與 announcement 流程拆在頂層 login composable。
`views` 應遵守的原則:
- 可以持有 route、page driver 掛載、頁面資料組裝與頁面事件協調。
- 可以管理只屬於該頁的小型 dialog 顯示狀態。
- 不應長期承擔大型表格、表單、dialog 模板或可重用流程。
- 不應直接處理底層 HTTP 細節。
### `src/components`
`components` 依角色分層,不再用單一規則描述。
#### 1. Root page/template components
目前仍放在 `src/components` 根目錄的頁面外殼:
- [PageLogin.vue](../src/components/PageLogin.vue)
- [PageIndex.vue](../src/components/PageIndex.vue)
- [PageMaint.vue](../src/components/PageMaint.vue)
這些是既有 template 頁面外殼或登入頁組裝元件。新增一般功能頁時,優先使用 `src/components/pages`
#### 2. `components/pages`
`components/pages` 是完整頁面主畫面組裝層:
- [PageHome.vue](../src/components/pages/PageHome.vue)
- [PageSettings.vue](../src/components/pages/PageSettings.vue)
- [PageFunction.vue](../src/components/pages/PageFunction.vue)
- [PageMaintenance.vue](../src/components/pages/PageMaintenance.vue)
- [PageEditableGridMaintenance.vue](../src/components/pages/PageEditableGridMaintenance.vue)
- [PageMasterDetailAMaintenance.vue](../src/components/pages/PageMasterDetailAMaintenance.vue)
- [PageMasterDetailBMaintenance.vue](../src/components/pages/PageMasterDetailBMaintenance.vue)
- [PageMasterDetailCMaintenance.vue](../src/components/pages/PageMasterDetailCMaintenance.vue)
責任:
- 接收 view/page driver 組好的資料與事件。
- 組裝完整頁面的主要 section 順序。
- 再往下使用 sections、items、feature/domain components。
#### 3. `components/sections`
`components/sections` 是頁面區塊容器:
- [SectionSearchPanel.vue](../src/components/sections/SectionSearchPanel.vue)
- [SectionDataTable.vue](../src/components/sections/SectionDataTable.vue)
- [SectionFormPanel.vue](../src/components/sections/SectionFormPanel.vue)
- [SectionFormPage.vue](../src/components/sections/SectionFormPage.vue)
- [SectionQueryPage.vue](../src/components/sections/SectionQueryPage.vue)
責任:
- 決定區塊布局與區塊互動。
- 以 props 接收資料,以 emit 回報事件。
- 不知道 route,不直接呼叫 API。
#### 4. `components/items`
`components/items` 是欄位群組或單筆資料呈現層:
- [ItemFormFieldGroup.vue](../src/components/items/ItemFormFieldGroup.vue)
item 不應知道自己被放在表格、grid、dialog 或頁面哪個位置。
#### 5. `components/login`
登入頁的較細 UI 區塊集中在:
- [CreateAccountLink.vue](../src/components/login/CreateAccountLink.vue)
- [LoginAnnouncementBoard.vue](../src/components/login/LoginAnnouncementBoard.vue)
- [LoginBrand.vue](../src/components/login/LoginBrand.vue)
- [LoginForm.vue](../src/components/login/LoginForm.vue)
- [LoginHeader.vue](../src/components/login/LoginHeader.vue)
- [LoginIllustration.vue](../src/components/login/LoginIllustration.vue)
- [LoginToolBar.vue](../src/components/login/LoginToolBar.vue)
- [LoginVerify.vue](../src/components/login/LoginVerify.vue)
這一層服務 `PageLogin`,不是全域 base library。
#### 6. `components/maint`
`components/maint` 是 maintenance demo / domain component 區域:
- [CommonConfirmDialog.vue](../src/components/maint/CommonConfirmDialog.vue)
- [EditableGrid.vue](../src/components/maint/EditableGrid.vue)
- [MasterFileFormFields.vue](../src/components/maint/MasterFileFormFields.vue)
- [MntDialogCard.vue](../src/components/maint/MntDialogCard.vue)
- [MntRecordNavToolbar.vue](../src/components/maint/MntRecordNavToolbar.vue)
- `master-detail/*`
若只是維護頁專用子元件,不要搬到 `base`
#### 7. `components/layouts`
layout 實作集中於:
- [MainLayout.vue](../src/components/layouts/MainLayout.vue)
- [PlainLayout.vue](../src/components/layouts/PlainLayout.vue)
- `src/components/layouts/main-layout/*`
layout 只承擔 app shell、drawer、app bar、favorites、breadcrumb 等框架 UI,不承擔頁面專屬業務流程。
#### 8. `components/base`
`components/base` 放真正跨頁共用且不屬於特定 domain 的基礎元件:
- [DraggableDialog.vue](../src/components/base/DraggableDialog.vue)
- [BaseFormTextField.vue](../src/components/base/BaseFormTextField.vue)
- [BaseFormSelect.vue](../src/components/base/BaseFormSelect.vue)
只服務單一頁面家族或單一 domain 的元件不要放進 `base`
### `src/composables`
目前 composables 分成:
- `page-drivers/*`:頁面資料協調與 page model 組裝。
- `commands/*`:命令式副作用流程,例如 create/edit/save/delete。
- `layout/*`AppShell / layout 狀態與事件協調。
- `maint/*`maintenance demo 的表單、CRUD、editable grid 狀態。
- 頂層 login / utility composable`useLoginCaptcha.ts``useLoginAnnouncements.ts``useApiCall.ts`
責任:
- 放可重用流程。
- 放可測試的 UI state。
- 放與模板結構耦合較低的狀態機。
- 不 import component 或 view。
### `src/stores`
目前 store 是跨頁共享狀態、快取與全域顯示狀態的正式分層。
代表性檔案:
- [auth.ts](../src/stores/auth.ts)
- [app.ts](../src/stores/app.ts)
- [menu.ts](../src/stores/menu.ts)
- [breadcrumbs.ts](../src/stores/breadcrumbs.ts)
- [favorites.ts](../src/stores/favorites.ts)
- [messages.ts](../src/stores/messages.ts)
- [snackbar.ts](../src/stores/snackbar.ts)
- [students.ts](../src/stores/students.ts)
- [semesters.ts](../src/stores/semesters.ts)
責任:
- 承接跨頁共享狀態。
- 承接畫面快取與全域顯示狀態。
- 作為 view/page driver/composable 與 services 之間的狀態收斂點。
`app.ts` 目前是空的 Pinia scaffold,尚未承擔實際 app state。
### `src/services`
`services` 是 HTTP 與外部 API 邊界。
代表性檔案:
- [client.ts](../src/services/client.ts)
- [interceptors.ts](../src/services/interceptors.ts)
- [error.ts](../src/services/error.ts)
- [http-error.ts](../src/services/http-error.ts)
- [http-toast.ts](../src/services/http-toast.ts)
- [session.ts](../src/services/session.ts)
- [token.ts](../src/services/token.ts)
- `services/modules/*`
責任:
- 提供 `httpClient`
- 封裝 API 模組。
- 統一 token、session 與錯誤處理。
規則:
- 元件不直接處理底層 HTTP 細節。
- service module 不持有 UI 狀態。
- 可共享的請求流程優先收斂到 store、page driver 或 composable,再由它們呼叫 service。
## 目前已落地的分層模式
### 模式 1`view -> page driver -> page component`
已落地頁面:
- `Home`
- `Settings`
- `FncPage`
- `views/maint/*`
穩定模式:
- view 負責掛載 page driver 與 page component。
- page driver 負責 page model、事件與頁面狀態協調。
- page component 負責頁面主畫面組裝。
### 模式 2`Login.vue -> PageLogin -> login components/composables`
登入頁是 template core,功能開關集中在 `Login.vue`
- `withCaptcha`
- `withAnnouncement`
- `withForgotPassword`
- `withRememberAccount`
資料流與 side effect 分別由 `useLoginCaptcha()``useLoginAnnouncements()``PageLogin``LoginForm` 承接。
### 模式 3`router meta -> AppShell -> layout`
這一層已正式成立:
- route 決定 layout 類型。
- `AppShell` 決定套用哪個 shell layout。
- layout 專注在骨架與共用框架 UI。
## 命名規則
### 頁面與 page component
- 直接被 route 載入的檔案放 `views`
- 負責完整頁面畫面組裝的元件,檔名用 `Page` 前綴。
- page component 優先放 `components/pages`;既有 template 外殼可保留在 `components` 根目錄。
- page component 不放進 `base`
### 資料夾命名
- 多字資料夾一律使用 `kebab-case`
- 不新增 `snake_case``PascalCase` 資料夾。
目前例子:
- `main-layout`
- `master-detail`
- `page-drivers`
### component 命名
- Page component`PageXxx.vue`
- Section component`SectionXxx.vue`
- Item component`ItemXxx.vue`
- Base component:不使用 `Page` / `Section` / `Item` 前綴,直接以功能命名。
## 新增或修改檔案時的判斷準則
1. 這個檔案是否直接被 route 載入?
- 是:優先放 `views`
2. 這個檔案是否負責某個完整頁面的主畫面組裝?
- 是:用 `Page` 前綴,優先放 `components/pages`,不要塞進 `base`
3. 這段重複的是模板還是流程?
- 模板:抽元件。
- 流程:抽 composable、page driver、command 或 store。
4. 這個狀態是否跨頁共享,或需要快取 / 全域顯示控制?
- 是:優先考慮 store。
5. 這個邏輯是否在處理 API、token、session、錯誤正規化?
- 是:放 `services`
6. 這個元件是否只屬於單一 domain 或單一頁面家族?
- 是:優先放到該 domain / feature 目錄,例如 `components/maint``components/login`
7. 這個抽象是否真的降低重複與理解成本?
- 否:不要抽。