From 7591ecd0622053670b6b25ef21ba1e06276da841 Mon Sep 17 00:00:00 2001 From: skytek_xinliang Date: Fri, 27 Mar 2026 16:29:27 +0800 Subject: [PATCH] chore: update .gitignore to include .env files and add prettier for code formatting --- .gitignore | 3 + docs/frontend-layering.md | 366 ++++++++++++++++++++++++++++++++++++++ package.json | 6 +- pnpm-lock.yaml | 10 ++ 4 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 docs/frontend-layering.md diff --git a/.gitignore b/.gitignore index 96db8a1..91afc94 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ dist .eslintcache .stylelintcache + +.env +.env.* diff --git a/docs/frontend-layering.md b/docs/frontend-layering.md new file mode 100644 index 0000000..f00357f --- /dev/null +++ b/docs/frontend-layering.md @@ -0,0 +1,366 @@ +# 前端分層規則與目前結構 + +## 目的 + +這份文件用來同步目前專案前端分層的實際狀況,讓新增功能、搬移檔案、或拆分元件時有一致判斷基準。 + +這份文件有兩個用途: + +- 描述目前已落地的結構與責任邊界 +- 明確標示哪些地方仍是過渡狀態,避免把「現況」誤認成「目標架構」 + +目前專案的主要分層不是只有 `views / components / composables / layouts`,而是已經形成以下責任鏈: + +- `router` 決定 route 與 layout meta +- `App.vue` 根據 route meta 組裝 app shell 與全域 UI +- `views` 承接路由入口與頁面資料組裝 +- `components` 承接畫面組裝、頁面型元件、功能區塊與基礎元件 +- `composables` 承接可重用流程與 UI state +- `stores` 承接跨頁狀態、快取與 UI 顯示狀態 +- `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 切換的正式入口,這一點會影響 `App.vue` 與 layout 的責任切分。 + +### `src/App.vue` + +[App.vue](/home/carl/git/skt-vuetify-templates/src/App.vue) 目前不是單純的掛載殼層,而是實際的應用組裝層。 + +目前承擔的責任包含: + +- 依 `route.meta.layout` 切換 `SKAdminLayout` 與 `SKEmptyLayout` +- 組裝 breadcrumb / favorites / menu 等 layout props +- 放置全域搜尋結果 dialog +- 放置全域訊息中心 dialog +- 放置全域 snackbar +- 串接 layout event 與路由跳轉 + +判斷原則: + +- 與「整個 app shell」共享、且不屬於單一頁面的 UI,可留在 `App.vue` +- 只屬於單一路由頁面的對話框或互動,不應再往 `App.vue` 堆 + +### `src/views` + +`views` 目前整體方向是「路由入口 + 頁面組裝」,但尚未完全一致。 + +目前已接近薄 view 的頁面: + +- [Dashboard.vue](/home/carl/git/skt-vuetify-templates/src/views/Dashboard.vue) +- [Analysis.vue](/home/carl/git/skt-vuetify-templates/src/views/Analysis.vue) +- [RoleManagement.vue](/home/carl/git/skt-vuetify-templates/src/views/RoleManagement.vue) +- [MenuManagement.vue](/home/carl/git/skt-vuetify-templates/src/views/MenuManagement.vue) +- [DeptManagement.vue](/home/carl/git/skt-vuetify-templates/src/views/DeptManagement.vue) +- [EditableGridMnt.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/EditableGridMnt.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) + +目前仍偏厚的頁面: + +- [Login.vue](/home/carl/git/skt-vuetify-templates/src/views/Login.vue) +- [Home.vue](/home/carl/git/skt-vuetify-templates/src/views/Home.vue) +- [SingleRecordMnt.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/SingleRecordMnt.vue) +- [MasterDetailMnt.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailMnt.vue) +- [MasterDetailMntB.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailMntB.vue) +- [MasterDetailMntC.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailMntC.vue) + +目前 `views` 應遵守的原則: + +- 可以持有 route、store、頁面資料組裝、頁面事件協調 +- 不應長期承擔大量可拆出的模板片段 +- 不應把可重用流程直接留在頁面內反覆複製 + +### `src/components` + +目前 `components` 已經分成四種角色,不能再用單一規則描述。 + +#### 1. 頁面型元件 + +目前以下元件實際上扮演「page component」: + +- [SKLogin.vue](/home/carl/git/skt-vuetify-templates/src/components/SKLogin.vue) +- [SKDashboard.vue](/home/carl/git/skt-vuetify-templates/src/components/SKDashboard.vue) +- [SKAnalysis.vue](/home/carl/git/skt-vuetify-templates/src/components/SKAnalysis.vue) +- [SKRoleManagement.vue](/home/carl/git/skt-vuetify-templates/src/components/SKRoleManagement.vue) +- [SKMenuManagement.vue](/home/carl/git/skt-vuetify-templates/src/components/SKMenuManagement.vue) +- [SKDeptManagement.vue](/home/carl/git/skt-vuetify-templates/src/components/SKDeptManagement.vue) + +這些檔案目前的實際責任是: + +- 接收 view 組好的資料 +- 組裝頁面主畫面 +- 再往下使用較小的子元件 + +這代表它們不是 `base`,也不是 layout,而是現行專案的頁面型組裝層。 + +#### 2. `components/base` + +文件原先寫法是「`base` 只放真正通用元件」,但目前專案並不完全符合。 + +現在 `components/base` 內同時存在兩類東西: + +- 跨頁可重用元件: + - [SKFormEditDialog.vue](/home/carl/git/skt-vuetify-templates/src/components/base/SKFormEditDialog.vue) + - [SKSearchFilter.vue](/home/carl/git/skt-vuetify-templates/src/components/base/SKSearchFilter.vue) + - [SKTableActionBar.vue](/home/carl/git/skt-vuetify-templates/src/components/base/SKTableActionBar.vue) + - [SKTreeTable.vue](/home/carl/git/skt-vuetify-templates/src/components/base/SKTreeTable.vue) +- 頁面家族的內部子元件: + - `base/login/*` + - `base/dashboard/*` + - `base/analysis/*` + - `base/input_field/*` + +因此目前對 `base` 的正確認知應該是: + +- 它已經不是純粹的「全域 base library」 +- 它同時包含「通用元件」與「頁面型元件拆出的子元件」 +- 新增檔案時不要因為方便就繼續把所有頁面子元件丟進 `base` + +目前建議: + +- 真正跨頁重用的元件,才放在 `components/base` 根層 +- 若元件只服務單一頁面家族,優先放到對應資料夾或 feature/domain 資料夾 + +#### 3. `components/layouts` + +目前 layout 相關實作集中於: + +- [SKAdminLayout.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/SKAdminLayout.vue) +- [SKEmptyLayout.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/SKEmptyLayout.vue) +- [SKMainLayout.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/SKMainLayout.vue) +- [SKSimpleLayout.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/SKSimpleLayout.vue) +- `src/components/layouts/sk-admin-layout/*` + +目前主實作仍以 `SKAdminLayout` 為核心,子元件拆分集中在 `sk-admin-layout`。 + +layout 應只承擔: + +- app shell +- drawer / app bar / favorites / breadcrumb 等框架 UI +- 與 layout 視覺結構直接相關的互動 + +layout 不應承擔: + +- 頁面專屬業務流程 +- 特定 domain 的資料規則 + +#### 4. `components/maintenance` + +這個目錄目前已經是最接近 feature folder 的區域: + +- [EditableStudentGrid.vue](/home/carl/git/skt-vuetify-templates/src/components/maintenance/EditableStudentGrid.vue) +- [MaintenanceCrudDialogs.vue](/home/carl/git/skt-vuetify-templates/src/components/maintenance/MaintenanceCrudDialogs.vue) +- [MaintenanceStudentFormFields.vue](/home/carl/git/skt-vuetify-templates/src/components/maintenance/MaintenanceStudentFormFields.vue) +- [MntDialogCard.vue](/home/carl/git/skt-vuetify-templates/src/components/maintenance/MntDialogCard.vue) +- [MntPageCards.vue](/home/carl/git/skt-vuetify-templates/src/components/maintenance/MntPageCards.vue) +- [MntRecordNavToolbar.vue](/home/carl/git/skt-vuetify-templates/src/components/maintenance/MntRecordNavToolbar.vue) +- `master-detail/*` +- `master-detail-b/*` +- `master-detail-c/*` + +結論: + +- `components/maintenance` 已實質扮演 domain component 層 +- 目前沒有必要為了形式硬搬到 `components/features/maintenance` + +### `src/composables` + +目前已明確分成兩組: + +- `composables/layout/*` +- `composables/maintenance/*` + +代表性檔案: + +- [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/maintenance/useMaintenanceCrudFlow.ts) +- [useStudentMaintenanceForm.ts](/home/carl/git/skt-vuetify-templates/src/composables/maintenance/useStudentMaintenanceForm.ts) +- [useEditableStudentGrid.ts](/home/carl/git/skt-vuetify-templates/src/composables/maintenance/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 -> base 子元件` + +已落地頁面: + +- `Login` +- `Dashboard` +- `Analysis` +- `RoleManagement` +- `MenuManagement` +- `DeptManagement` + +代表目前專案已經存在一種穩定模式: + +- `view` 負責資料準備與事件協調 +- page component 負責頁面畫面組裝 +- 較細的視覺區塊再拆到內部子元件 + +這是目前除 maintenance 外最明確的頁面分層。 + +### 模式 2:`view -> maintenance components + maintenance composables` + +已落地區域: + +- `views/maint/*` +- `components/maintenance/*` +- `composables/maintenance/*` + +其中 [EditableGridMnt.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/EditableGridMnt.vue) 是目前最接近目標狀態的 maintenance 頁面。 + +### 模式 3:`router meta -> App.vue -> layout` + +這一層目前已正式成立: + +- route 決定 layout 類型 +- `App.vue` 決定套用哪個 shell +- layout 專注在骨架與共用框架 UI + +這代表 layout 的責任邊界不應再回頭混入頁面內部流程。 + +## 現況偏差與文件修正 + +以下是文件與實際程式碼之間原本不一致的地方,這次同步後已改成以現況描述: + +- `components/base` 並非只放真正通用元件 +- 專案目前不只 maintenance 有分層,`login / dashboard / analysis / management` 也已形成 `view -> page component` 模式 +- `stores` 與 `services` 已經是正式架構層,不能繼續省略不寫 +- `App.vue` 目前實際承擔全域 UI 組裝責任,不能只把它視為掛載入口 +- 錯誤頁已明確收斂到 `views/errors/*` + +## 目前仍待整理的區域 + +### 高優先度 + +- 繼續瘦身: + - [SingleRecordMnt.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/SingleRecordMnt.vue) + - [MasterDetailMnt.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailMnt.vue) + - [MasterDetailMntB.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailMntB.vue) + - [MasterDetailMntC.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailMntC.vue) +- 重新整理: + - [Login.vue](/home/carl/git/skt-vuetify-templates/src/views/Login.vue) + - [Home.vue](/home/carl/git/skt-vuetify-templates/src/views/Home.vue) + +原因: + +- 這些頁面仍保留大量模板、資料轉換或頁面內對話框協調邏輯 + +### 中優先度 + +- 重新命名或重新安置頁面型元件: + - [SKLogin.vue](/home/carl/git/skt-vuetify-templates/src/components/SKLogin.vue) + - [SKDashboard.vue](/home/carl/git/skt-vuetify-templates/src/components/SKDashboard.vue) + - [SKAnalysis.vue](/home/carl/git/skt-vuetify-templates/src/components/SKAnalysis.vue) + - [SKRoleManagement.vue](/home/carl/git/skt-vuetify-templates/src/components/SKRoleManagement.vue) + - [SKMenuManagement.vue](/home/carl/git/skt-vuetify-templates/src/components/SKMenuManagement.vue) + - [SKDeptManagement.vue](/home/carl/git/skt-vuetify-templates/src/components/SKDeptManagement.vue) + +原因: + +- 它們語意上已是頁面型元件,但仍放在 `components` 根目錄 +- 目前仍可接受,但新功能不應沿用這種命名與放置方式擴散 + +### 中低優先度 + +- 清理 `src/stores/stores/*` 重複結構 +- 檢查 `components/base/*` 是否要把頁面家族子元件搬到更明確的資料夾 +- 檢查空資料夾 `src/components/base/management` 是否仍需要保留 +- 重新評估 [SKSearchFilter.vue](/home/carl/git/skt-vuetify-templates/src/components/base/SKSearchFilter.vue)、[SKFormEditDialog.vue](/home/carl/git/skt-vuetify-templates/src/components/base/SKFormEditDialog.vue)、[SKTreeTable.vue](/home/carl/git/skt-vuetify-templates/src/components/base/SKTreeTable.vue) 是否仍保持跨頁通用 + +## 新增或修改檔案時的判斷準則 + +1. 這個檔案是否直接被 route 載入? + - 是:優先放 `views` +2. 這個檔案是否負責某個完整頁面的主畫面組裝? + - 是:放頁面型元件層,不要塞進 `base` +3. 這段重複的是模板還是流程? + - 模板:抽元件 + - 流程:抽 composable 或 store +4. 這個狀態是否跨頁共享,或需要快取 / 全域顯示控制? + - 是:優先考慮 store +5. 這個邏輯是否在處理 API、token、session、錯誤正規化? + - 是:放 `services` +6. 這個元件是否只屬於單一 domain? + - 是:優先放到該 domain 目錄,例如 `components/maintenance` +7. 這個抽象是否真的降低重複與理解成本? + - 否:不要抽 diff --git a/package.json b/package.json index a7fb3e4..4de1da5 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,9 @@ "preview": "vite preview", "build-only": "vite build", "type-check": "vue-tsc --build --force", - "lint": "eslint", - "lint:fix": "eslint --fix", + "lint": "eslint --cache", + "lint:fix": "eslint --fix --cache", + "format": "prettier . --write", "mcp": "ruler apply", "mcp:revert": "ruler revert" }, @@ -33,6 +34,7 @@ "eslint-config-prettier": "^10.1.8", "eslint-config-vuetify": "^4.4.0", "npm-run-all2": "^8.0.4", + "prettier": "^3.8.1", "sass-embedded": "^1.98.0", "typescript": "~5.9.3", "vite": "^8.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d6fc8b..ec72f32 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,6 +57,9 @@ importers: npm-run-all2: specifier: ^8.0.4 version: 8.0.4 + prettier: + specifier: ^3.8.1 + version: 3.8.1 sass-embedded: specifier: ^1.98.0 version: 1.98.0 @@ -1487,6 +1490,11 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -3346,6 +3354,8 @@ snapshots: prelude-ls@1.2.1: {} + prettier@3.8.1: {} + proxy-from-env@1.1.0: {} punycode@2.3.1: {}