# 前端分層規則與目前結構 ## 目的 這份文件描述目前 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. 這個抽象是否真的降低重複與理解成本? - 否:不要抽。