feat: refactor layouts and login components
This commit is contained in:
+144
-94
@@ -2,22 +2,19 @@
|
|||||||
|
|
||||||
## 目的
|
## 目的
|
||||||
|
|
||||||
這份文件用來同步目前專案前端分層的實際狀況,讓新增功能、搬移檔案、或拆分元件時有一致判斷基準。
|
這份文件只描述目前 repo 已經落地的前端分層與命名規則,讓後續新增檔案、搬移檔案、或重構時有一致判斷基準。
|
||||||
|
|
||||||
這份文件有兩個用途:
|
重點不是追求理想化架構,而是避免把舊名稱、過渡寫法、或已刪除的結構繼續當成規則。
|
||||||
|
|
||||||
- 描述目前已落地的結構與責任邊界
|
目前專案的主要責任鏈如下:
|
||||||
- 明確標示哪些地方仍是過渡狀態,避免把「現況」誤認成「目標架構」
|
|
||||||
|
|
||||||
目前專案的主要分層不是只有 `views / components / composables / layouts`,而是已經形成以下責任鏈:
|
|
||||||
|
|
||||||
- `router` 決定 route 與 layout meta
|
- `router` 決定 route 與 layout meta
|
||||||
- `App.vue` 根據 route meta 組裝 app shell 與全域 UI
|
- `App.vue` 根據 route meta 組裝 app shell 與全域 UI
|
||||||
- `views` 承接路由入口與頁面資料組裝
|
- `views` 承接路由入口與頁面資料協調
|
||||||
- `components` 承接畫面組裝、頁面型元件、功能區塊與基礎元件
|
- `components` 承接 layout、page component、domain component 與較細的 UI 區塊
|
||||||
- `composables` 承接可重用流程與 UI state
|
- `composables` 承接可重用流程與 UI state
|
||||||
- `stores` 承接跨頁狀態、快取與 UI 顯示狀態
|
- `stores` 承接跨頁狀態、快取與全域顯示狀態
|
||||||
- `services` 承接 HTTP client、API 模組、token 與錯誤正規化
|
- `services` 承接 HTTP client、API 模組、token 與錯誤處理
|
||||||
|
|
||||||
## 目前目錄的責任邊界
|
## 目前目錄的責任邊界
|
||||||
|
|
||||||
@@ -35,15 +32,18 @@
|
|||||||
- 指定頁面使用哪種 layout
|
- 指定頁面使用哪種 layout
|
||||||
- 串接導航守衛
|
- 串接導航守衛
|
||||||
|
|
||||||
目前 `meta.layout` 已經成為 app shell 切換的正式入口,這一點會影響 `App.vue` 與 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`
|
### `src/App.vue`
|
||||||
|
|
||||||
[App.vue](/home/carl/git/skt-vuetify-templates/src/App.vue) 目前不是單純的掛載殼層,而是實際的應用組裝層。
|
[App.vue](/home/carl/git/skt-vuetify-templates/src/App.vue) 目前不是單純掛載入口,而是實際的應用組裝層。
|
||||||
|
|
||||||
目前承擔的責任包含:
|
目前承擔的責任包含:
|
||||||
|
|
||||||
- 依 `route.meta.layout` 切換 `SKAdminLayout` 與 `SKEmptyLayout`
|
- 根據 `route.meta.layout` 切換 layout
|
||||||
- 組裝 breadcrumb / favorites / menu 等 layout props
|
- 組裝 breadcrumb / favorites / menu 等 layout props
|
||||||
- 放置全域搜尋結果 dialog
|
- 放置全域搜尋結果 dialog
|
||||||
- 放置全域訊息中心 dialog
|
- 放置全域訊息中心 dialog
|
||||||
@@ -52,15 +52,17 @@
|
|||||||
|
|
||||||
判斷原則:
|
判斷原則:
|
||||||
|
|
||||||
- 與「整個 app shell」共享、且不屬於單一頁面的 UI,可留在 `App.vue`
|
- 與整個 app shell 共享、且不屬於單一路由頁面的 UI,可留在 `App.vue`
|
||||||
- 只屬於單一路由頁面的對話框或互動,不應再往 `App.vue` 堆
|
- 只屬於單一路由頁面的對話框或互動,不應堆到 `App.vue`
|
||||||
|
|
||||||
### `src/views`
|
### `src/views`
|
||||||
|
|
||||||
`views` 目前整體方向是「路由入口 + 頁面組裝」,但尚未完全一致。
|
`views` 目前整體方向是「路由入口 + 頁面資料協調 + 頁面事件協調」。
|
||||||
|
|
||||||
目前已接近薄 view 的頁面:
|
目前較薄的 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)
|
- [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)
|
- [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)
|
- [ServerError.vue](/home/carl/git/skt-vuetify-templates/src/views/errors/ServerError.vue)
|
||||||
@@ -70,68 +72,90 @@
|
|||||||
- [NotFound.vue](/home/carl/git/skt-vuetify-templates/src/views/errors/NotFound.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)
|
- [FncPage.vue](/home/carl/git/skt-vuetify-templates/src/views/FncPage.vue)
|
||||||
|
|
||||||
目前仍偏厚的頁面:
|
目前仍偏厚的 view:
|
||||||
|
|
||||||
- [Login.vue](/home/carl/git/skt-vuetify-templates/src/views/Login.vue)
|
|
||||||
- [Home.vue](/home/carl/git/skt-vuetify-templates/src/views/Home.vue)
|
|
||||||
- [SingleRecord.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/SingleRecord.vue)
|
- [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)
|
- [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)
|
- [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)
|
- [MasterDetailC.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailC.vue)
|
||||||
|
|
||||||
目前 `views` 應遵守的原則:
|
`views` 應遵守的原則:
|
||||||
|
|
||||||
- 可以持有 route、store、頁面資料組裝、頁面事件協調
|
- 可以持有 route、store、頁面資料組裝、頁面事件協調
|
||||||
- 不應長期承擔大量可拆出的模板片段
|
- 可以管理只屬於該頁的 dialog 顯示狀態
|
||||||
- 不應把可重用流程直接留在頁面內反覆複製
|
- 不應長期承擔大量可抽出的模板片段
|
||||||
|
- 不應把可重用流程直接留在頁面內重複複製
|
||||||
|
|
||||||
### `src/components`
|
### `src/components`
|
||||||
|
|
||||||
目前 `components` 已經分成三種主要角色,不能再用單一規則描述。
|
目前 `components` 已經分成幾種不同角色,不能再用單一規則描述。
|
||||||
|
|
||||||
#### 1. 頁面型元件
|
#### 1. 頁面型元件
|
||||||
|
|
||||||
目前以下元件實際上扮演「page component」:
|
目前以下元件實際上扮演 page component:
|
||||||
|
|
||||||
- [PageLogin.vue](/home/carl/git/skt-vuetify-templates/src/components/PageLogin.vue)
|
- [PageLogin.vue](/home/carl/git/skt-vuetify-templates/src/components/PageLogin.vue)
|
||||||
- [PageIndex.vue](/home/carl/git/skt-vuetify-templates/src/components/PageIndex.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 組好的資料
|
- 接收 view 組好的資料與事件
|
||||||
- 組裝頁面主畫面
|
- 組裝某個完整頁面的主畫面
|
||||||
- 再往下使用較小的子元件
|
- 再往下使用較小的子元件或 domain component
|
||||||
|
|
||||||
這代表它們不是 `base`,也不是 layout,而是現行專案的頁面型組裝層。
|
命名規則:
|
||||||
|
|
||||||
#### 2. `components/base`
|
- 只要是 page component,檔名以 `Page` 為前綴
|
||||||
|
- page component 可以放在 `components` 根目錄
|
||||||
|
- 不要把 page component 丟進 `base`
|
||||||
|
|
||||||
文件原先寫法是「`base` 只放真正通用元件」,但目前專案並不完全符合。
|
#### 2. `components/login`
|
||||||
|
|
||||||
現在 `components/base` 內主要保留頁面家族的內部子元件:
|
登入頁的較細 UI 區塊已集中到:
|
||||||
- `base/login/*`
|
|
||||||
|
|
||||||
因此目前對 `base` 的正確認知應該是:
|
- [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)
|
||||||
|
|
||||||
- 它已經不是純粹的「全域 base library」
|
這一層的定位是:
|
||||||
- 它同時包含「通用元件」與「頁面型元件拆出的子元件」
|
|
||||||
- 新增檔案時不要因為方便就繼續把所有頁面子元件丟進 `base`
|
|
||||||
|
|
||||||
目前建議:
|
- 服務 `PageLogin`
|
||||||
|
- 屬於 login 頁面家族
|
||||||
|
- 不是全域 base library
|
||||||
|
|
||||||
- 若元件只服務單一頁面家族,優先放到對應資料夾或 feature/domain 資料夾
|
#### 3. `components/base`
|
||||||
|
|
||||||
#### 3. `components/layouts`
|
目前 `components/base` 只剩下:
|
||||||
|
|
||||||
目前 layout 相關實作集中於:
|
- [DraggableDialog.vue](/home/carl/git/skt-vuetify-templates/src/components/base/DraggableDialog.vue)
|
||||||
|
|
||||||
- [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)
|
- `base` 只放真正可跨頁重用、且不屬於特定 domain 的元件
|
||||||
- [SKSimpleLayout.vue](/home/carl/git/skt-vuetify-templates/src/components/layouts/SKSimpleLayout.vue)
|
- 若元件只服務單一頁面家族或單一 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/*`
|
- `src/components/layouts/sk-admin-layout/*`
|
||||||
|
|
||||||
目前主實作仍以 `SKAdminLayout` 為核心,子元件拆分集中在 `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 應只承擔:
|
layout 應只承擔:
|
||||||
|
|
||||||
@@ -144,41 +168,50 @@ layout 不應承擔:
|
|||||||
- 頁面專屬業務流程
|
- 頁面專屬業務流程
|
||||||
- 特定 domain 的資料規則
|
- 特定 domain 的資料規則
|
||||||
|
|
||||||
#### 3. `components/maint`
|
#### 5. `components/maint`
|
||||||
|
|
||||||
這個目錄目前已經是最接近 feature folder 的區域:
|
這個目錄目前是最接近 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)
|
- [CommonConfirmDialog.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/CommonConfirmDialog.vue)
|
||||||
- [EditableStudentGrid.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/EditableStudentGrid.vue)
|
- [EditableGrid.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/EditableGrid.vue)
|
||||||
- [MasterFormFields.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/MasterFormFields.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)
|
- [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)
|
- [MntRecordNavToolbar.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/MntRecordNavToolbar.vue)
|
||||||
- [PageMaint.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/PageMaint.vue)
|
|
||||||
- `master-detail/*`
|
- `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 層
|
- `components/maint` 主要扮演 maintenance domain component 層
|
||||||
- 像 `ConfirmDialog` 這類通用確認視窗可以直接在頁面使用,不需要再包一層 page-specific dialog aggregator
|
- `CommonConfirmDialog` 可以直接在 maintenance 頁或元件使用,不需要再包一層 CRUD dialog aggregator
|
||||||
- 目前沒有必要為了形式硬搬到 `components/features/maintenance`
|
- 若只是維護頁專用子元件,不要搬到 `base`
|
||||||
|
|
||||||
### `src/composables`
|
### `src/composables`
|
||||||
|
|
||||||
目前已明確分成兩組:
|
目前已明確分成兩組:
|
||||||
|
|
||||||
- `composables/layout/*`
|
- `composables/layout/*`
|
||||||
- `composables/maintenance/*`
|
- `composables/maint/*`
|
||||||
|
|
||||||
代表性檔案:
|
代表性檔案:
|
||||||
|
|
||||||
- [useAdminLayoutState.ts](/home/carl/git/skt-vuetify-templates/src/composables/layout/useAdminLayoutState.ts)
|
- [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)
|
- [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)
|
- [useMaintenanceCrudFlow.ts](/home/carl/git/skt-vuetify-templates/src/composables/maint/useMaintenanceCrudFlow.ts)
|
||||||
- [useStudentMaintenanceForm.ts](/home/carl/git/skt-vuetify-templates/src/composables/maintenance/useStudentMaintenanceForm.ts)
|
- [useStudentMaintenanceForm.ts](/home/carl/git/skt-vuetify-templates/src/composables/maint/useStudentMaintenanceForm.ts)
|
||||||
- [useEditableStudentGrid.ts](/home/carl/git/skt-vuetify-templates/src/composables/maintenance/useEditableStudentGrid.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)
|
- [useApiCall.ts](/home/carl/git/skt-vuetify-templates/src/composables/useApiCall.ts)
|
||||||
|
|
||||||
目前 `composables` 的責任是正確的:
|
`composables` 的責任:
|
||||||
|
|
||||||
- 放可重用流程
|
- 放可重用流程
|
||||||
- 放可測試的 UI state
|
- 放可測試的 UI state
|
||||||
@@ -186,7 +219,7 @@ layout 不應承擔:
|
|||||||
|
|
||||||
### `src/stores`
|
### `src/stores`
|
||||||
|
|
||||||
目前 store 已經成為正式分層的一部分,而不只是暫時狀態容器。
|
目前 store 已經是正式分層的一部分,而不只是暫時狀態容器。
|
||||||
|
|
||||||
代表性檔案:
|
代表性檔案:
|
||||||
|
|
||||||
@@ -208,7 +241,8 @@ layout 不應承擔:
|
|||||||
|
|
||||||
注意:
|
注意:
|
||||||
|
|
||||||
- 目前存在 `src/stores/stores/*` 的重複目錄,這不是分層設計的一部分,應視為待整理結構噪音
|
- 目前仍存在 `src/stores/stores/*` 的重複目錄
|
||||||
|
- 這不是分層設計的一部分,而是待整理的結構噪音
|
||||||
|
|
||||||
### `src/services`
|
### `src/services`
|
||||||
|
|
||||||
@@ -238,34 +272,39 @@ layout 不應承擔:
|
|||||||
|
|
||||||
## 目前已落地的分層模式
|
## 目前已落地的分層模式
|
||||||
|
|
||||||
### 模式 1:`view -> page component -> base 子元件`
|
### 模式 1:`view -> page component -> page family components`
|
||||||
|
|
||||||
已落地頁面:
|
已落地頁面:
|
||||||
|
|
||||||
- `Login`
|
- `Login`
|
||||||
- `Home`
|
- `Home`
|
||||||
|
|
||||||
代表目前專案已經存在一種穩定模式:
|
目前的穩定模式是:
|
||||||
|
|
||||||
- `view` 負責資料準備與事件協調
|
- `view` 負責資料準備與事件協調
|
||||||
- page component 負責頁面畫面組裝
|
- page component 負責頁面主畫面組裝
|
||||||
- 較細的視覺區塊再拆到內部子元件
|
- 較細的視覺區塊再拆到對應頁面家族資料夾,例如 `components/login/*`
|
||||||
|
|
||||||
這是目前在一般展示頁面中最明確的頁面分層。
|
### 模式 2:`view -> page component / domain components + maint composables`
|
||||||
|
|
||||||
### 模式 2:`view -> page-level maintenance component + maintenance components + maintenance composables`
|
|
||||||
|
|
||||||
已落地區域:
|
已落地區域:
|
||||||
|
|
||||||
- `views/maint/*`
|
- `views/maint/*`
|
||||||
- `components/maint/*`
|
- `components/maint/*`
|
||||||
- `composables/maintenance/*`
|
- `composables/maint/*`
|
||||||
|
|
||||||
其中 [EditableGrid.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/EditableGrid.vue) 是目前最接近目標狀態的 maintenance 頁面。
|
這一層目前是 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`
|
### 模式 3:`router meta -> App.vue -> layout`
|
||||||
|
|
||||||
這一層目前已正式成立:
|
這一層已正式成立:
|
||||||
|
|
||||||
- route 決定 layout 類型
|
- route 決定 layout 類型
|
||||||
- `App.vue` 決定套用哪個 shell
|
- `App.vue` 決定套用哪個 shell
|
||||||
@@ -273,15 +312,35 @@ layout 不應承擔:
|
|||||||
|
|
||||||
這代表 layout 的責任邊界不應再回頭混入頁面內部流程。
|
這代表 layout 的責任邊界不應再回頭混入頁面內部流程。
|
||||||
|
|
||||||
## 現況偏差與文件修正
|
## 命名規則
|
||||||
|
|
||||||
以下是文件與實際程式碼之間原本不一致的地方,這次同步後已改成以現況描述:
|
### 頁面與 page component
|
||||||
|
|
||||||
- `components/base` 並非只放真正通用元件
|
- 直接被 route 載入的檔案放 `views`
|
||||||
- 專案目前除了 `login / home` 的 `view -> page component` 模式外,`maintenance` 也已形成頁面級元件 + domain component 的模式
|
- 負責完整頁面畫面組裝的元件,檔名用 `Page` 前綴
|
||||||
- `stores` 與 `services` 已經是正式架構層,不能繼續省略不寫
|
- page component 不放進 `base`
|
||||||
- `App.vue` 目前實際承擔全域 UI 組裝責任,不能只把它視為掛載入口
|
|
||||||
- 錯誤頁已明確收斂到 `views/errors/*`
|
目前例子:
|
||||||
|
|
||||||
|
- [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`
|
||||||
|
|
||||||
## 目前仍待整理的區域
|
## 目前仍待整理的區域
|
||||||
|
|
||||||
@@ -292,37 +351,28 @@ layout 不應承擔:
|
|||||||
- [MasterDetailA.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailA.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)
|
- [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)
|
- [MasterDetailC.vue](/home/carl/git/skt-vuetify-templates/src/views/maint/MasterDetailC.vue)
|
||||||
- 重新整理:
|
|
||||||
- [Login.vue](/home/carl/git/skt-vuetify-templates/src/views/Login.vue)
|
|
||||||
|
|
||||||
原因:
|
原因:
|
||||||
|
|
||||||
- 這些頁面仍保留大量資料轉換或頁面內對話框協調邏輯
|
- 這些頁面仍保留較多頁面內資料轉換與事件協調
|
||||||
|
|
||||||
### 中優先度
|
### 中優先度
|
||||||
|
|
||||||
- 重新命名或重新安置頁面型元件:
|
- 清理 `src/stores/stores/*` 重複結構
|
||||||
- [PageLogin.vue](/home/carl/git/skt-vuetify-templates/src/components/PageLogin.vue)
|
- 檢查 `components/maint` 內是否仍有可再明確命名的舊名稱
|
||||||
- [PageIndex.vue](/home/carl/git/skt-vuetify-templates/src/components/PageIndex.vue)
|
- 視 `PageMaint` 的後續演進,決定是否維持在 `components` 根目錄
|
||||||
- [PageMaint.vue](/home/carl/git/skt-vuetify-templates/src/components/maint/PageMaint.vue)
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 它們仍是頁面型元件,但目前放在 `components` 根目錄
|
|
||||||
- 若後續繼續保留 page component 分層,建議改成更清楚的命名與歸位方式
|
|
||||||
|
|
||||||
### 中低優先度
|
### 中低優先度
|
||||||
|
|
||||||
- 清理 `src/stores/stores/*` 重複結構
|
- 持續檢查 `views` 是否有可再下放到 page component 的模板片段
|
||||||
- 檢查 `components/base/*` 是否要把頁面家族子元件搬到更明確的資料夾
|
- 清理命名調整後留下的空資料夾或死連結
|
||||||
- 檢查空資料夾 `src/components/base/management` 是否仍需要保留
|
|
||||||
|
|
||||||
## 新增或修改檔案時的判斷準則
|
## 新增或修改檔案時的判斷準則
|
||||||
|
|
||||||
1. 這個檔案是否直接被 route 載入?
|
1. 這個檔案是否直接被 route 載入?
|
||||||
- 是:優先放 `views`
|
- 是:優先放 `views`
|
||||||
2. 這個檔案是否負責某個完整頁面的主畫面組裝?
|
2. 這個檔案是否負責某個完整頁面的主畫面組裝?
|
||||||
- 是:放頁面型元件層,且命名以 `Page` 為前綴,不要塞進 `base`
|
- 是:用 `Page` 前綴,放 page component 層,不要塞進 `base`
|
||||||
3. 這段重複的是模板還是流程?
|
3. 這段重複的是模板還是流程?
|
||||||
- 模板:抽元件
|
- 模板:抽元件
|
||||||
- 流程:抽 composable 或 store
|
- 流程:抽 composable 或 store
|
||||||
|
|||||||
+5
-5
@@ -185,8 +185,8 @@ import {
|
|||||||
} from '@mdi/js'
|
} from '@mdi/js'
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import SKAdminLayout from '@/components/layouts/SKAdminLayout.vue'
|
import MainLayout from '@/components/layouts/MainLayout.vue'
|
||||||
import SKEmptyLayout from '@/components/layouts/SKEmptyLayout.vue'
|
import PlainLayout from '@/components/layouts/PlainLayout.vue'
|
||||||
import { HTTP_TOAST_EVENT } from './services/http-toast'
|
import { HTTP_TOAST_EVENT } from './services/http-toast'
|
||||||
import { SESSION_FORCE_LOGOUT_EVENT } from './services/session'
|
import { SESSION_FORCE_LOGOUT_EVENT } from './services/session'
|
||||||
import { useAuthStore } from './stores/auth'
|
import { useAuthStore } from './stores/auth'
|
||||||
@@ -238,13 +238,13 @@ const _menuItemsExample = [
|
|||||||
* 佈局對映表
|
* 佈局對映表
|
||||||
*/
|
*/
|
||||||
const layoutMap = {
|
const layoutMap = {
|
||||||
default: SKAdminLayout,
|
default: MainLayout,
|
||||||
none: SKEmptyLayout,
|
none: PlainLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取得當前應使用的組件
|
// 取得當前應使用的組件
|
||||||
const activeLayout = computed(() => {
|
const activeLayout = computed(() => {
|
||||||
return layoutMap[route.meta.layout] || SKAdminLayout
|
return layoutMap[route.meta.layout] || MainLayout
|
||||||
})
|
})
|
||||||
|
|
||||||
function buildMergedMenuItems(items) {
|
function buildMergedMenuItems(items) {
|
||||||
|
|||||||
@@ -225,12 +225,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { mdiBullhornVariantOutline } from '@mdi/js'
|
import { mdiBullhornVariantOutline } from '@mdi/js'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import LoginAnnouncementBoard from './base/login/LoginAnnouncementBoard.vue'
|
import LoginAnnouncementBoard from './login/LoginAnnouncementBoard.vue'
|
||||||
import LoginBrand from './base/login/LoginBrand.vue'
|
import LoginBrand from './login/LoginBrand.vue'
|
||||||
import LoginForm from './base/login/LoginForm.vue'
|
import LoginForm from './login/LoginForm.vue'
|
||||||
import LoginHeader from './base/login/LoginHeader.vue'
|
import LoginHeader from './login/LoginHeader.vue'
|
||||||
import LoginToolBar from './base/login/LoginToolBar.vue'
|
import LoginToolBar from './login/LoginToolBar.vue'
|
||||||
import LoginVerify from './base/login/LoginVerify.vue'
|
import LoginVerify from './login/LoginVerify.vue'
|
||||||
|
|
||||||
interface BrandingConfig {
|
interface BrandingConfig {
|
||||||
title?: string
|
title?: string
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
<template>
|
|
||||||
<SKAdminLayout
|
|
||||||
:favorite-items="favoriteItems"
|
|
||||||
:favorites-config="favoritesConfig"
|
|
||||||
:menu-items="menuItems"
|
|
||||||
:system-title="systemTitle"
|
|
||||||
:theme-toggle-label="themeToggleLabel"
|
|
||||||
:logout-label="logoutLabel"
|
|
||||||
:features="features"
|
|
||||||
@logout="$emit('logout')"
|
|
||||||
@select="$emit('select', $event)"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</SKAdminLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { AdminLayoutFavoritesConfig, AdminLayoutMenuItem } from './sk-admin-layout/types'
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import SKAdminLayout from './SKAdminLayout.vue'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
systemTitle?: string
|
|
||||||
themeToggleLabel?: string
|
|
||||||
logoutLabel?: string
|
|
||||||
favoriteHeaderLabel?: string
|
|
||||||
favoriteItems?: AdminLayoutMenuItem[]
|
|
||||||
menuItems?: AdminLayoutMenuItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
systemTitle: '管理系統',
|
|
||||||
themeToggleLabel: '切換主題',
|
|
||||||
logoutLabel: '登出',
|
|
||||||
favoriteHeaderLabel: '我的最愛',
|
|
||||||
favoriteItems: () => [],
|
|
||||||
menuItems: () => [
|
|
||||||
{ title: '首頁', path: '/' },
|
|
||||||
{ title: '設定', path: '/settings' },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
defineEmits<{
|
|
||||||
logout: []
|
|
||||||
select: [item: AdminLayoutMenuItem]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const features = {
|
|
||||||
showThemeToggle: true,
|
|
||||||
showFavorites: true,
|
|
||||||
showBreadcrumb: false,
|
|
||||||
showSearch: false,
|
|
||||||
showToolbarActions: false,
|
|
||||||
showUserInfo: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
const favoritesConfig = computed<AdminLayoutFavoritesConfig>(() => ({
|
|
||||||
label: props.favoriteHeaderLabel,
|
|
||||||
addLabel: props.favoriteHeaderLabel,
|
|
||||||
showAdd: false,
|
|
||||||
}))
|
|
||||||
</script>
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<template>
|
|
||||||
<SKAdminLayout
|
|
||||||
:menu-items="menuItems"
|
|
||||||
:system-title="systemTitle"
|
|
||||||
:features="features"
|
|
||||||
@select="$emit('select', $event)"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</SKAdminLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { AdminLayoutMenuItem } from './sk-admin-layout/types'
|
|
||||||
import SKAdminLayout from './SKAdminLayout.vue'
|
|
||||||
|
|
||||||
withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
systemTitle?: string
|
|
||||||
menuItems?: AdminLayoutMenuItem[]
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
systemTitle: '簡潔模式',
|
|
||||||
menuItems: () => [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
defineEmits<{
|
|
||||||
select: [item: AdminLayoutMenuItem]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const features = {
|
|
||||||
showThemeToggle: false,
|
|
||||||
showFavorites: false,
|
|
||||||
showBreadcrumb: false,
|
|
||||||
showSearch: false,
|
|
||||||
showToolbarActions: false,
|
|
||||||
showUserInfo: false,
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -429,7 +429,7 @@ import DetailSidePanel from '@/components/maint/master-detail/DetailSidePanel.vu
|
|||||||
import MasterFileFormFields from '@/components/maint/MasterFileFormFields.vue'
|
import MasterFileFormFields from '@/components/maint/MasterFileFormFields.vue'
|
||||||
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
|
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
|
||||||
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
|
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
|
||||||
import PageMaint from '@/components/maint/PageMaint.vue'
|
import PageMaint from '@/components/PageMaint.vue'
|
||||||
import { useMaintenanceCrudFlow } from '@/composables/maint/useMaintenanceCrudFlow'
|
import { useMaintenanceCrudFlow } from '@/composables/maint/useMaintenanceCrudFlow'
|
||||||
import { useStudentMaintenanceForm } from '@/composables/maint/useStudentMaintenanceForm'
|
import { useStudentMaintenanceForm } from '@/composables/maint/useStudentMaintenanceForm'
|
||||||
import { type SemesterRecord, useSemesterStore } from '@/stores/semesters'
|
import { type SemesterRecord, useSemesterStore } from '@/stores/semesters'
|
||||||
|
|||||||
@@ -508,7 +508,7 @@ import DetailFullHeightPanel from '@/components/maint/master-detail/DetailFullHe
|
|||||||
import MasterFileFormFields from '@/components/maint/MasterFileFormFields.vue'
|
import MasterFileFormFields from '@/components/maint/MasterFileFormFields.vue'
|
||||||
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
|
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
|
||||||
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
|
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
|
||||||
import PageMaint from '@/components/maint/PageMaint.vue'
|
import PageMaint from '@/components/PageMaint.vue'
|
||||||
import { useMaintenanceCrudFlow } from '@/composables/maint/useMaintenanceCrudFlow'
|
import { useMaintenanceCrudFlow } from '@/composables/maint/useMaintenanceCrudFlow'
|
||||||
import { useStudentMaintenanceForm } from '@/composables/maint/useStudentMaintenanceForm'
|
import { useStudentMaintenanceForm } from '@/composables/maint/useStudentMaintenanceForm'
|
||||||
import { type CourseRecord, type SemesterRecord, useSemesterStore } from '@/stores/semesters'
|
import { type CourseRecord, type SemesterRecord, useSemesterStore } from '@/stores/semesters'
|
||||||
|
|||||||
@@ -495,7 +495,7 @@ import DetailSimpleList from '@/components/maint/master-detail/DetailSimpleList.
|
|||||||
import MasterFileFormFields from '@/components/maint/MasterFileFormFields.vue'
|
import MasterFileFormFields from '@/components/maint/MasterFileFormFields.vue'
|
||||||
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
|
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
|
||||||
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
|
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
|
||||||
import PageMaint from '@/components/maint/PageMaint.vue'
|
import PageMaint from '@/components/PageMaint.vue'
|
||||||
import { useMaintenanceCrudFlow } from '@/composables/maint/useMaintenanceCrudFlow'
|
import { useMaintenanceCrudFlow } from '@/composables/maint/useMaintenanceCrudFlow'
|
||||||
import { useStudentMaintenanceForm } from '@/composables/maint/useStudentMaintenanceForm'
|
import { useStudentMaintenanceForm } from '@/composables/maint/useStudentMaintenanceForm'
|
||||||
import { type CourseRecord, type SemesterRecord, useSemesterStore } from '@/stores/semesters'
|
import { type CourseRecord, type SemesterRecord, useSemesterStore } from '@/stores/semesters'
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ import { useDisplay } from 'vuetify'
|
|||||||
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
|
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
|
||||||
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
|
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
|
||||||
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
|
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
|
||||||
import PageMaint from '@/components/maint/PageMaint.vue'
|
import PageMaint from '@/components/PageMaint.vue'
|
||||||
import { useMaintenanceCrudFlow } from '@/composables/maint/useMaintenanceCrudFlow'
|
import { useMaintenanceCrudFlow } from '@/composables/maint/useMaintenanceCrudFlow'
|
||||||
import { useStudentMaintenanceForm } from '@/composables/maint/useStudentMaintenanceForm'
|
import { useStudentMaintenanceForm } from '@/composables/maint/useStudentMaintenanceForm'
|
||||||
import { type StudentRecord, useStudentStore } from '@/stores/students'
|
import { type StudentRecord, useStudentStore } from '@/stores/students'
|
||||||
|
|||||||
Reference in New Issue
Block a user