docs: reorganize architecture strategy documentation
Split current project diagnostics into a dedicated analysis document and trim the main architecture strategy to focus on core guidance. This makes the documentation easier to navigate and separates observed issues from recommended architectural principles.docs: reorganize architecture strategy documentation Split current project diagnostics into a dedicated analysis document and trim the main architecture strategy to focus on core guidance. This makes the documentation easier to navigate and separates observed issues from recommended architectural principles.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
## 二、我們專案的現況診斷
|
||||
|
||||
### 2.1 App.vue 過度臃腫(~590 行)
|
||||
|
||||
| 職責 | 行數 | 應屬層級 |
|
||||
|------|------|----------|
|
||||
| Layout 切換 | ~20 | App Shell |
|
||||
| Tabs 管理 | ~80 | Page Driver |
|
||||
| Breadcrumb 組裝 | ~40 | Layout |
|
||||
| Favorites 管理 | ~60 | Store |
|
||||
| Search Dialog | ~80 | App Shell / Widget |
|
||||
| Message Dialog | ~60 | App Shell / Widget |
|
||||
| Snackbar | ~10 | Global Overlay |
|
||||
| Logout / Force logout | ~30 | Auth Flow |
|
||||
| HTTP Toast | ~20 | Service Layer |
|
||||
|
||||
- **問題**:App.vue 同時承擔 App Shell、Page Driver、Global Widget、Auth Flow 四種責任。
|
||||
- **對比**:App Store 的 `App.svelte` 只有 161 行,只負責 `Navigation + PageResolver + Footer`。
|
||||
|
||||
### 2.2 Views 過厚(SingleRecord.vue ~830 行)
|
||||
|
||||
- 混雜:表格呈現、搜尋表單、dialog 模板、表單狀態、CRUD 流程、驗證邏輯、分頁、snackbar。
|
||||
- **對比**:App Store 的 `ProductPage.svelte` 只有 77 行,只負責「把 page 轉成 DefaultPageRequirements + 一個 slot override」。
|
||||
|
||||
### 2.3 缺乏統一的頁面資料門面
|
||||
|
||||
```
|
||||
現況:
|
||||
view → store → service(直接鏈式呼叫)
|
||||
view 自己管理 loading / error / dialog visible
|
||||
|
||||
App Store:
|
||||
UI → jet.dispatch(intent) → runtime → controller → page model
|
||||
UI 只接收 page model,不管理載入狀態
|
||||
```
|
||||
|
||||
### 2.4 Dialog 狀態與模板內嵌於 View
|
||||
|
||||
- `SingleRecord.vue` 內含 5 個 `ConfirmDialog` 實例 + 1 個大 form overlay。
|
||||
- 任何 dialog 更動都需要修改 view 檔案。
|
||||
|
||||
### 2.5 沒有容器/內容分離的 Section 層
|
||||
|
||||
- 表格、表單、搜尋區塊都是直接寫在 view 或 page component 中。
|
||||
- 缺乏類似 `ShelfItemLayout` 的通用佈局抽象:「這一區是水平捲軸還是網格」應該由容器決定,裡面的內容元件不應該知道。
|
||||
|
||||
---
|
||||
+61
-157
@@ -1,139 +1,19 @@
|
||||
# 資料流與元件分層優化策略
|
||||
|
||||
> 分析標的:`~/code/apps.apple.com-main`(Apple App Store Web — Svelte + Jet 架構)
|
||||
> 適用目標:`/home/carl/git/skt-vuetify-templates`(Vue 3 + Vuetify + Pinia)
|
||||
> 原則:不強制遷移既有程式碼,但所有新增功能與重構以本文件為最高準則。
|
||||
|
||||
---
|
||||
|
||||
## 一、Apple App Store 專案的核心架構特徵
|
||||
|
||||
### 1.1 單一業務邏輯門面(Jet Facade)
|
||||
1 單一業務邏輯門面
|
||||
2 Intent / Action 分離(查詢與命令)
|
||||
3 Page Model 驅動 UI(資料驅動)
|
||||
4 Shelf / Item 分層(容器與內容分離)
|
||||
5 Svelte Context 作為跨層依賴注入
|
||||
6 命令式外殼 + 聲明式 UI
|
||||
|
||||
```
|
||||
browser.ts → bootstrap → Jet.load → runtime + objectGraph
|
||||
↓
|
||||
UI 層僅透過 jet.dispatch(intent) / jet.perform(action) 溝通
|
||||
```
|
||||
|
||||
- **Jet** 封裝所有業務邏輯:路由、資料取得、動作分發、metrics。
|
||||
- UI 層**不直接**呼叫 API、不直接操作 storage、不直接操作 history。
|
||||
- 所有外部依賴(fetch、storage、locale、user)統一注入 `Dependencies`,再組裝成 `ObjectGraph`。
|
||||
|
||||
### 1.2 Intent / Action 分離(查詢與命令)
|
||||
|
||||
| 類型 | 職責 | 回傳值 | 例子 |
|
||||
|------|------|--------|------|
|
||||
| **Intent** | 取得頁面資料(Query) | `Promise<Page>` | `RouteUrlIntent` → 回傳 `ProductPage` |
|
||||
| **Action** | 執行副作用(Command) | `'performed' \| 'unsupported'` | `FlowAction` → 導航到新頁面 |
|
||||
|
||||
- `FlowAction` 是主要導航機制:內含 `destination Intent` + `pageUrl` + `presentationContext`。
|
||||
- Action handler 註冊採用**型別註冊制**:`jet.onAction('flowAction', handler)`。
|
||||
|
||||
### 1.3 Page Model 驅動 UI(資料驅動)
|
||||
|
||||
```ts
|
||||
// App.svelte 的介面極簡
|
||||
export let page: Promise<Page> | Page
|
||||
```
|
||||
|
||||
- 整個應用由單一 `page` prop 驅動。
|
||||
- `PageResolver` 處理 `Promise<Page>` 的 loading / error 狀態。
|
||||
- `Page.svelte` 用 type guard 分發到對應的 page component:`isProductPage(page)` → `<ProductPage>`。
|
||||
- **Page 是 union type**,不是 route-based 的硬編碼映射。
|
||||
|
||||
### 1.4 Shelf / Item 分層(容器與內容分離)
|
||||
|
||||
```
|
||||
Page (TodayPage / ProductPage / ...)
|
||||
└── Shelf[] (水平捲軸 / 網格)
|
||||
└── ShelfItemLayout (佈局抽象:HorizontalShelf or Grid)
|
||||
└── Item (BrickItem / LargeLockupItem / ...)
|
||||
```
|
||||
|
||||
- **Shelf** = 容器邏輯:決定是水平捲軸還是網格、rowsPerColumn、邊框。
|
||||
- **ShelfItemLayout** = 佈局中介:根據 `isHorizontal` 選擇 `HorizontalShelf` 或 `Grid`。
|
||||
- **Item** = 純粹內容渲染:只關心單一資料單位的呈現,不知道自己是水平還是網格。
|
||||
- **FallbackShelf** = 優雅的降級策略:遇到未實作的 shelf 類型顯示 placeholder,不 crash。
|
||||
|
||||
### 1.5 Svelte Context 作為跨層依賴注入
|
||||
|
||||
```ts
|
||||
// bootstrap.ts
|
||||
context.set('jet', jet)
|
||||
context.set('i18n', i18nStore)
|
||||
|
||||
// 深層元件
|
||||
const jet = getJet() // 從 Svelte Context 取得
|
||||
const i18n = getI18n() // 從 Svelte Context 取得
|
||||
```
|
||||
|
||||
- 避免 props drilling:Jet、i18n、accessibility layout、today-card layout 都透過 context 傳遞。
|
||||
- Context 在啟動時注入,生命周期與應用一致,不是用來傳遞 UI 狀態的。
|
||||
|
||||
### 1.6 命令式外殼 + 聲明式 UI
|
||||
|
||||
```ts
|
||||
// browser.ts(命令式啟動層)
|
||||
const app = new App({ target: container, context, hydrate: true })
|
||||
registerActionHandlers({
|
||||
jet,
|
||||
updateApp: (props) => app.$set(props), // 橋接命令式 → 聲明式
|
||||
})
|
||||
```
|
||||
|
||||
- 導航、歷史管理、scroll 復原由命令式的 action handler 處理。
|
||||
- UI 渲染完全聲明式,只接收 `page` 與 `isFirstPage` 兩個 prop。
|
||||
|
||||
---
|
||||
Read only when needed: [what apple do](./what-apple-do.md)
|
||||
|
||||
## 二、我們專案的現況診斷
|
||||
|
||||
### 2.1 App.vue 過度臃腫(~590 行)
|
||||
|
||||
| 職責 | 行數 | 應屬層級 |
|
||||
|------|------|----------|
|
||||
| Layout 切換 | ~20 | App Shell |
|
||||
| Tabs 管理 | ~80 | Page Driver |
|
||||
| Breadcrumb 組裝 | ~40 | Layout |
|
||||
| Favorites 管理 | ~60 | Store |
|
||||
| Search Dialog | ~80 | App Shell / Widget |
|
||||
| Message Dialog | ~60 | App Shell / Widget |
|
||||
| Snackbar | ~10 | Global Overlay |
|
||||
| Logout / Force logout | ~30 | Auth Flow |
|
||||
| HTTP Toast | ~20 | Service Layer |
|
||||
|
||||
- **問題**:App.vue 同時承擔 App Shell、Page Driver、Global Widget、Auth Flow 四種責任。
|
||||
- **對比**:App Store 的 `App.svelte` 只有 161 行,只負責 `Navigation + PageResolver + Footer`。
|
||||
|
||||
### 2.2 Views 過厚(SingleRecord.vue ~830 行)
|
||||
|
||||
- 混雜:表格呈現、搜尋表單、dialog 模板、表單狀態、CRUD 流程、驗證邏輯、分頁、snackbar。
|
||||
- **對比**:App Store 的 `ProductPage.svelte` 只有 77 行,只負責「把 page 轉成 DefaultPageRequirements + 一個 slot override」。
|
||||
|
||||
### 2.3 缺乏統一的頁面資料門面
|
||||
|
||||
```
|
||||
現況:
|
||||
view → store → service(直接鏈式呼叫)
|
||||
view 自己管理 loading / error / dialog visible
|
||||
|
||||
App Store:
|
||||
UI → jet.dispatch(intent) → runtime → controller → page model
|
||||
UI 只接收 page model,不管理載入狀態
|
||||
```
|
||||
|
||||
### 2.4 Dialog 狀態與模板內嵌於 View
|
||||
|
||||
- `SingleRecord.vue` 內含 5 個 `ConfirmDialog` 實例 + 1 個大 form overlay。
|
||||
- 任何 dialog 更動都需要修改 view 檔案。
|
||||
|
||||
### 2.5 沒有容器/內容分離的 Section 層
|
||||
|
||||
- 表格、表單、搜尋區塊都是直接寫在 view 或 page component 中。
|
||||
- 缺乏類似 `ShelfItemLayout` 的通用佈局抽象:「這一區是水平捲軸還是網格」應該由容器決定,裡面的內容元件不應該知道。
|
||||
|
||||
---
|
||||
Read only when needed: [analyse now](./analyse-now.md)
|
||||
|
||||
## 三、優化後的資料流策略
|
||||
|
||||
@@ -188,7 +68,7 @@ App Store:
|
||||
|
||||
```ts
|
||||
// src/composables/usePageDriver.ts
|
||||
export function useStudentMaintenancePage() {
|
||||
export function useMaintenancePage() {
|
||||
const studentStore = useStudentStore()
|
||||
const { records, loading, error, load } = useCrudDriver({
|
||||
store: studentStore,
|
||||
@@ -264,7 +144,7 @@ src/
|
||||
│
|
||||
├── components/
|
||||
│ ├── pages/ ← 新增:Page Component 層
|
||||
│ │ ├── PageStudentMaintenance.vue
|
||||
│ │ ├── PageMaintenance.vue
|
||||
│ │ └── PageReport.vue
|
||||
│ │
|
||||
│ ├── sections/ ← 新增:Section / Shelf 層
|
||||
@@ -273,7 +153,7 @@ src/
|
||||
│ │ └── SectionFormPanel.vue
|
||||
│ │
|
||||
│ ├── items/ ← 新增:Item / Atom 層(領域獨立)
|
||||
│ │ ├── ItemStudentRow.vue
|
||||
│ │ ├── ItemDataRow.vue
|
||||
│ │ └── ItemFormField.vue
|
||||
│ │
|
||||
│ ├── layouts/ ← 維持:App Shell Layout
|
||||
@@ -285,11 +165,11 @@ src/
|
||||
│
|
||||
├── composables/
|
||||
│ ├── page-drivers/ ← 新增:頁面資料協調
|
||||
│ │ └── useStudentMaintenancePage.ts
|
||||
│ │ └── useMaintenancePage.ts
|
||||
│ ├── commands/ ← 新增:命令流程(對齊 Jet Action)
|
||||
│ │ └── useStudentCrudCommands.ts
|
||||
│ │ └── useCrudCommands.ts
|
||||
│ ├── forms/ ← 維持/重組:表單狀態機
|
||||
│ │ └── useStudentForm.ts
|
||||
│ │ └── useForm.ts
|
||||
│ └── layout/ ← 維持
|
||||
│
|
||||
├── models/ ← 新增:領域模型與 Page Union
|
||||
@@ -321,15 +201,18 @@ src/
|
||||
```vue
|
||||
<!-- views/maint/SingleRecord.vue(優化後) -->
|
||||
<script setup lang="ts">
|
||||
import { useStudentMaintenancePage } from '@/composables/page-drivers/useStudentMaintenancePage'
|
||||
import PageStudentMaintenance from '@/components/pages/PageStudentMaintenance.vue'
|
||||
import { useMaintenancePage } from '@/composables/page-drivers/useMaintenancePage'
|
||||
import PageMaintenance from '@/components/pages/PageMaintenance.vue'
|
||||
|
||||
const { pageModel, load } = useStudentMaintenancePage()
|
||||
const { pageModel, load } = useMaintenancePage({
|
||||
title: '單筆資料維護',
|
||||
records: [],
|
||||
})
|
||||
load()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageStudentMaintenance :page="pageModel" />
|
||||
<PageMaintenance :page="pageModel" />
|
||||
</template>
|
||||
```
|
||||
|
||||
@@ -340,7 +223,7 @@ load()
|
||||
- **對齊**:App Store 的 `ProductPage.svelte`、`TodayPage.svelte`、`DefaultPage.svelte`。
|
||||
|
||||
```vue
|
||||
<!-- components/pages/PageStudentMaintenance.vue -->
|
||||
<!-- components/pages/PageMaintenance.vue -->
|
||||
<template>
|
||||
<PageMaintShell :title="page.title">
|
||||
<template #search>
|
||||
@@ -392,7 +275,7 @@ load()
|
||||
- **對齊**:App Store 的 `BrickItem.svelte`、`LargeLockupItem.svelte`。
|
||||
|
||||
```vue
|
||||
<!-- ItemStudentRow.vue -->
|
||||
<!-- ItemDataRow.vue -->
|
||||
<template>
|
||||
<div class="d-flex ga-2">
|
||||
<v-chip size="small" :color="statusColor(data.status)">
|
||||
@@ -407,7 +290,7 @@ load()
|
||||
|
||||
| 場景 | 容器(Section) | 內容(Item) |
|
||||
|------|-----------------|--------------|
|
||||
| 資料表格 | `SectionDataTable`(決定 headers、分頁、排序) | `ItemStudentRow`(決定單列呈現) |
|
||||
| 資料表格 | `SectionDataTable`(決定 headers、分頁、排序) | `ItemDataRow`(決定單列呈現) |
|
||||
| 搜尋面板 | `SectionSearchPanel`(決定展開/收合、grid 佈局) | `ItemFormField`(單一輸入框呈現) |
|
||||
| 圖文列表 | `SectionCardGrid`(決定欄數、gap、RWD) | `ItemProductCard`(卡片內容) |
|
||||
| 表單對話框 | `SectionFormPanel`(決定 dialog 外殼、actions) | `ItemFormFieldGroup`(欄位群組) |
|
||||
@@ -461,20 +344,41 @@ views/xxx.vue
|
||||
|
||||
## 五、重構優先順序與遷移路徑
|
||||
|
||||
### Phase 1:建立基礎設施(不動既有 view)
|
||||
### Phase 1:建立基礎設施(不動既有 view) ✅ 已完成
|
||||
|
||||
1. 新增 `src/models/`:定義領域模型與 Page union type。
|
||||
2. 新增 `src/shell/`:從 `App.vue` 抽出 `GlobalOverlays.vue` 與 `AppTabs.vue`。
|
||||
3. 新增 `src/components/pages/`:建立第一個 `PageStudentMaintenance.vue`(可從 `PageMaint.vue` 擴展)。
|
||||
4. 新增 `src/composables/page-drivers/`:建立 `useStudentMaintenancePage.ts`。
|
||||
1. [x] 新增 `src/models/`:定義領域模型與 Page union type。
|
||||
- `src/models/student.ts`:抽出 `StudentRecord`,`stores/students.ts` 改為 re-export 以保持向後相容。
|
||||
- `src/models/page.ts`:定義 `BasePageModel`、`MaintenancePageModel` 與 `PageModel` union。
|
||||
2. [x] 新增 `src/shell/`:從 `App.vue` 抽出 `GlobalOverlays.vue` 與 `AppTabs.vue`。
|
||||
- `src/shell/AppShell.vue`:layout 切換與全域 overlay 掛載點。
|
||||
- `src/shell/AppTabs.vue`:頁籤管理與 router-view 容器。
|
||||
- `src/shell/GlobalOverlays.vue`:snackbar、搜尋 dialog、訊息 dialog。
|
||||
3. [x] 新增 `src/components/pages/`:建立第一個 `PageMaintenance.vue`(可從 `PageMaint.vue` 擴展)。
|
||||
- 定義 `MaintenancePageModel` props 與 `create/edit/view/delete/search` emits。
|
||||
- 使用 `PageMaint.vue` 作為佈局外殼,搜尋與表格區塊以 slot 開放,不綁定特定領域型別。
|
||||
4. [x] 新增 `src/composables/page-drivers/`:建立 `useMaintenancePage.ts`。
|
||||
- 透過 options 傳入 title 與 records,協調搜尋條件、分頁與 `pageModel`。
|
||||
- 提供 `load()` 與 `resetSearch()` 供 Page Driver 呼叫。
|
||||
|
||||
### Phase 2:遷移最厚的 view(SingleRecord.vue)
|
||||
### Phase 2:遷移最厚的 view(SingleRecord.vue) ✅ 已完成
|
||||
|
||||
1. 將 `SingleRecord.vue` 的模板抽出到 `PageStudentMaintenance.vue`。
|
||||
2. 將 dialog 抽出到 `SectionFormPanel.vue`。
|
||||
3. 將表單欄位抽出到 `ItemFormFieldGroup.vue` 或 `ItemFormField.vue[]`。
|
||||
4. 將 CRUD 流程抽出到 `composables/commands/useStudentCrudCommands.ts`。
|
||||
5. `SingleRecord.vue` 目標縮減到 < 80 行。
|
||||
1. [x] 將 `SingleRecord.vue` 縮減為 route-level Page Driver。
|
||||
- 目前只負責呼叫 `useSingleRecordMaintenancePage()`,並組裝 `PageMaintenance`、`SectionSearchPanel`、`SectionDataTable`、`SectionFormPanel`。
|
||||
- 行數已由 921 行縮減至 52 行,達成 < 80 行目標。
|
||||
2. [x] 將搜尋區塊抽出到 `src/components/sections/SectionSearchPanel.vue`。
|
||||
- 負責搜尋欄位佈局與 reset 事件,不直接操作 store。
|
||||
3. [x] 將表格與分頁抽出到 `src/components/sections/SectionDataTable.vue`。
|
||||
- 負責 headers、資料列 slot、操作按鈕與分頁 footer,透過 emit 回傳 view/edit/delete/page 事件。
|
||||
4. [x] 將 dialog 抽出到 `src/components/sections/SectionFormPanel.vue`。
|
||||
- 包含側邊 overlay、`MntDialogCard`、record navigation toolbar 與確認 dialog。
|
||||
- View 中不再直接定義 `<teleport>`、`<v-overlay>` 或多個確認 dialog。
|
||||
5. [x] 將表單欄位抽出到 `src/components/items/ItemFormFieldGroup.vue`。
|
||||
- 只呈現欄位與欄位錯誤,透過 `v-model` 與 `clear-field-error` 與上層互動。
|
||||
6. [x] 將 CRUD command 流程抽出到 `src/composables/commands/useCrudCommands.ts`。
|
||||
- 負責新增、載入編輯/檢視資料、儲存前確認、實際儲存與 highlight 流程。
|
||||
7. [x] 新增 `src/composables/page-drivers/useSingleRecordMaintenancePage.ts` 作為本頁協調層。
|
||||
- 集中組裝 page model、搜尋狀態、表格分頁、表單狀態、CRUD command 與 dialog flow。
|
||||
- `SingleRecord.vue` 不再直接操作 `studentStore`。
|
||||
|
||||
### Phase 3:推廣到所有 maintenance 頁面
|
||||
|
||||
@@ -494,14 +398,14 @@ views/xxx.vue
|
||||
|------|------|---------------|
|
||||
| App Shell | `src/shell/` | `AppShell.vue`、`GlobalOverlays.vue` |
|
||||
| Page Driver | `src/views/` | `SingleRecord.vue`(route view,不改名) |
|
||||
| Page Component | `src/components/pages/` | `PageStudentMaintenance.vue` |
|
||||
| Page Component | `src/components/pages/` | `PageMaintenance.vue` |
|
||||
| Section / Shelf | `src/components/sections/` | `SectionDataTable.vue`、`SectionSearchPanel.vue` |
|
||||
| Item / Atom | `src/components/items/` | `ItemStudentRow.vue`、`ItemFormField.vue` |
|
||||
| Item / Atom | `src/components/items/` | `ItemDataRow.vue`、`ItemFormField.vue` |
|
||||
| Layout | `src/components/layouts/` | `MainLayout.vue`(維持) |
|
||||
| Base | `src/components/base/` | `DraggableDialog.vue`(維持) |
|
||||
| Page Driver Composable | `src/composables/page-drivers/` | `useStudentMaintenancePage.ts` |
|
||||
| Command Composable | `src/composables/commands/` | `useStudentCrudCommands.ts` |
|
||||
| Form Composable | `src/composables/forms/` | `useStudentForm.ts` |
|
||||
| Page Driver Composable | `src/composables/page-drivers/` | `useMaintenancePage.ts` |
|
||||
| Command Composable | `src/composables/commands/` | `useCrudCommands.ts` |
|
||||
| Form Composable | `src/composables/forms/` | `useForm.ts` |
|
||||
| Domain Store | `src/stores/` | `students.ts`(維持) |
|
||||
| Service Module | `src/services/modules/` | `students.ts`(維持) |
|
||||
| Domain Model | `src/models/` | `student.ts`、`page.ts` |
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
1. `README.md`
|
||||
2. `src/README.md`
|
||||
3. `docs/add-page-example.md`
|
||||
4. `docs/frontend-layering.md`
|
||||
4. `docs/architecture-strategy.md`(新增功能與重構的最高準則)
|
||||
5. `docs/frontend-layering.md`(歷史參考,已被 `architecture-strategy.md` 取代)
|
||||
|
||||
## 預設不可修改的檔案
|
||||
|
||||
@@ -44,16 +45,22 @@
|
||||
目前主要資料流是:
|
||||
|
||||
```txt
|
||||
router -> App.vue -> layout -> view -> component -> composable/store -> service
|
||||
router -> AppShell -> layout -> view(Page Driver) -> Page Component -> Section -> Item
|
||||
↓
|
||||
page driver / command composable -> store -> service
|
||||
```
|
||||
|
||||
判斷原則:
|
||||
|
||||
- `router` 決定 route、layout meta、auth meta 與錯誤頁入口
|
||||
- `App.vue` 組裝 layout props、全域 UI 與 layout event
|
||||
- `views` 承接路由入口、頁面資料協調與頁面事件協調
|
||||
- `components` 承接畫面呈現、props/emits 與可拆分 UI 區塊
|
||||
- `composables` 承接可重用流程、頁面狀態機或較複雜的 UI state
|
||||
- `AppShell` 組裝 layout props、全域 UI 與 layout event
|
||||
- `views` 承接路由入口、頁面資料協調與頁面事件協調,應維持薄層
|
||||
- `components/pages` 組裝完整頁面與 page-level slot
|
||||
- `components/sections` 承接容器佈局,例如搜尋區、表格、dialog shell
|
||||
- `components/items` 承接單一資料單位或欄位群組呈現
|
||||
- `composables/page-drivers` 承接頁面資料協調與 page model 組裝
|
||||
- `composables/commands` 承接命令式副作用流程,例如 create/edit/save/delete
|
||||
- `composables` 其他子目錄承接可重用流程、頁面狀態機或較複雜的 UI state
|
||||
- `stores` 承接跨頁共享狀態、快取與全域顯示狀態
|
||||
- `services` 承接 HTTP client、API 模組、token、session 與錯誤處理
|
||||
|
||||
@@ -62,7 +69,8 @@ router -> App.vue -> layout -> view -> component -> composable/store -> service
|
||||
- 新 route:參考 `src/router/routes.ts`
|
||||
- 一般被主 layout 包住的頁面:參考 `src/views/Home.vue`、`src/views/maint/EditableGrid.vue`
|
||||
- 登入相關 UI:參考 `src/components/PageLogin.vue` 與 `src/components/login/*`
|
||||
- 維護頁:參考 `src/views/maint/*`、`src/components/maint/*`、`src/composables/maint/*`
|
||||
- 維護頁:優先參考 `src/views/maint/SingleRecord.vue`、`src/components/pages/PageMaintenance.vue`、`src/components/sections/*`、`src/components/items/*`、`src/composables/page-drivers/useSingleRecordMaintenancePage.ts`、`src/composables/commands/useCrudCommands.ts`
|
||||
- 舊維護頁:`EditableGrid.vue`、`MasterDetailA/B/C.vue` 尚未套用完整 Page Driver + Section + Item 分層,參考前先確認是否正在做 Phase 3 遷移。
|
||||
- 維護頁範本選擇:參考 `src/views/maint/README.md`
|
||||
- API 呼叫:參考 `src/services/modules/*` 與使用它們的 store/composable
|
||||
- 全域提示:參考 `src/stores/snackbar.ts` 與 `src/composables/useApiCall.ts`
|
||||
@@ -101,7 +109,12 @@ router -> App.vue -> layout -> view -> component -> composable/store -> service
|
||||
- `src/components/PageIndex.vue`
|
||||
- `src/views/maint/*`
|
||||
- `src/components/maint/*`
|
||||
- `src/components/pages/PageMaintenance.vue`
|
||||
- `src/components/sections/*`
|
||||
- `src/components/items/*`
|
||||
- `src/composables/maint/*`
|
||||
- `src/composables/page-drivers/*`
|
||||
- `src/composables/commands/*`
|
||||
- `src/components/PageMaint.vue`
|
||||
- `src/stores/students.ts`
|
||||
- `src/stores/semesters.ts`
|
||||
@@ -202,7 +215,16 @@ router -> App.vue -> layout -> view -> component -> composable/store -> service
|
||||
- emit 使用者事件
|
||||
- 拆分表單、列表、工具列、dialog 等 UI
|
||||
|
||||
不要把完整功能長期塞在單一 view。當畫面有多個區塊、表單、列表、dialog 或重複 UI 時,應拆到 feature components。
|
||||
不要把完整功能長期塞在單一 view。當畫面有多個區塊、表單、列表、dialog 或重複 UI 時,應拆到 page / section / item components。
|
||||
|
||||
維護頁分層優先順序:
|
||||
|
||||
1. `views/maint/*.vue`:只做 route-level wiring,目標 < 80 行。
|
||||
2. `components/pages/PageMaintenance.vue`:組裝維護頁外殼與主要 slot。
|
||||
3. `components/sections/*`:管理搜尋、表格、dialog shell 等區塊佈局。
|
||||
4. `components/items/*`:管理欄位群組或單一資料呈現,不直接呼叫 store/service。
|
||||
5. `composables/page-drivers/*`:集中 page model、分頁、搜尋、表單狀態與事件 wiring。
|
||||
6. `composables/commands/*`:集中新增、載入、儲存、刪除等 command 流程。
|
||||
|
||||
## Router 安排規則
|
||||
|
||||
@@ -231,11 +253,14 @@ route 集中放在 `src/router/routes.ts`。不要在 view 或 component 裡臨
|
||||
- `src/composables/useApiCall.ts`:包裝可重用 API 呼叫狀態與錯誤提示流程
|
||||
- `src/composables/layout/useAdminLayoutState.ts`:提供 layout shell 所需的狀態組裝
|
||||
- `src/composables/layout/useThemeToggle.ts`:提供主題切換流程
|
||||
- `src/composables/page-drivers/useMaintenancePage.ts`:提供通用 maintenance page model 基礎狀態
|
||||
- `src/composables/page-drivers/useSingleRecordMaintenancePage.ts`:協調單筆維護 demo 頁面的 page model、section props/events、表單、表格與 command
|
||||
- `src/composables/commands/useCrudCommands.ts`:提供 CRUD command 流程,讓 view 不直接執行 store mutation 細節
|
||||
- `src/composables/maint/useMaintenanceCrudFlow.ts`:提供維護頁 CRUD 流程狀態
|
||||
- `src/composables/maint/useStudentMaintenanceForm.ts`:提供學生維護表單狀態
|
||||
- `src/composables/maint/useEditableStudentGrid.ts`:提供 editable grid 狀態
|
||||
|
||||
新增 composable 時,用 `useXxx.ts` 命名。若只服務單一 feature,放在 `src/composables/<feature>`;若確定跨 feature 使用,才放在 `src/composables` 根目錄。
|
||||
新增 composable 時,用 `useXxx.ts` 命名。若是頁面資料協調,放在 `src/composables/page-drivers`;若是命令式副作用流程,放在 `src/composables/commands`;若只服務單一 feature,放在 `src/composables/<feature>`;若確定跨 feature 使用,才放在 `src/composables` 根目錄。
|
||||
|
||||
## Store 與 Service 資料流規則
|
||||
|
||||
@@ -289,7 +314,7 @@ import { mdiAccount } from '@mdi/js'
|
||||
- 是否只需要新增或修改 view
|
||||
- 是否需要新增 route
|
||||
- 是否需要拆 feature component
|
||||
- 是否已閱讀 `docs/frontend-layering.md`
|
||||
- 是否已閱讀 `docs/architecture-strategy.md`
|
||||
- 是否已參考 `src/` 裡相同類型的既有範例
|
||||
- 是否符合 Vue 3、Composition API、`<script setup lang="ts">` 的既有寫法
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
## 一、Apple App Store 專案的核心架構特徵
|
||||
|
||||
### 1.1 單一業務邏輯門面(Jet Facade)
|
||||
|
||||
```
|
||||
browser.ts → bootstrap → Jet.load → runtime + objectGraph
|
||||
↓
|
||||
UI 層僅透過 jet.dispatch(intent) / jet.perform(action) 溝通
|
||||
```
|
||||
|
||||
- **Jet** 封裝所有業務邏輯:路由、資料取得、動作分發、metrics。
|
||||
- UI 層**不直接**呼叫 API、不直接操作 storage、不直接操作 history。
|
||||
- 所有外部依賴(fetch、storage、locale、user)統一注入 `Dependencies`,再組裝成 `ObjectGraph`。
|
||||
|
||||
### 1.2 Intent / Action 分離(查詢與命令)
|
||||
|
||||
| 類型 | 職責 | 回傳值 | 例子 |
|
||||
|------|------|--------|------|
|
||||
| **Intent** | 取得頁面資料(Query) | `Promise<Page>` | `RouteUrlIntent` → 回傳 `ProductPage` |
|
||||
| **Action** | 執行副作用(Command) | `'performed' \| 'unsupported'` | `FlowAction` → 導航到新頁面 |
|
||||
|
||||
- `FlowAction` 是主要導航機制:內含 `destination Intent` + `pageUrl` + `presentationContext`。
|
||||
- Action handler 註冊採用**型別註冊制**:`jet.onAction('flowAction', handler)`。
|
||||
|
||||
### 1.3 Page Model 驅動 UI(資料驅動)
|
||||
|
||||
```ts
|
||||
// App.svelte 的介面極簡
|
||||
export let page: Promise<Page> | Page
|
||||
```
|
||||
|
||||
- 整個應用由單一 `page` prop 驅動。
|
||||
- `PageResolver` 處理 `Promise<Page>` 的 loading / error 狀態。
|
||||
- `Page.svelte` 用 type guard 分發到對應的 page component:`isProductPage(page)` → `<ProductPage>`。
|
||||
- **Page 是 union type**,不是 route-based 的硬編碼映射。
|
||||
|
||||
### 1.4 Shelf / Item 分層(容器與內容分離)
|
||||
|
||||
```
|
||||
Page (TodayPage / ProductPage / ...)
|
||||
└── Shelf[] (水平捲軸 / 網格)
|
||||
└── ShelfItemLayout (佈局抽象:HorizontalShelf or Grid)
|
||||
└── Item (BrickItem / LargeLockupItem / ...)
|
||||
```
|
||||
|
||||
- **Shelf** = 容器邏輯:決定是水平捲軸還是網格、rowsPerColumn、邊框。
|
||||
- **ShelfItemLayout** = 佈局中介:根據 `isHorizontal` 選擇 `HorizontalShelf` 或 `Grid`。
|
||||
- **Item** = 純粹內容渲染:只關心單一資料單位的呈現,不知道自己是水平還是網格。
|
||||
- **FallbackShelf** = 優雅的降級策略:遇到未實作的 shelf 類型顯示 placeholder,不 crash。
|
||||
|
||||
### 1.5 Svelte Context 作為跨層依賴注入
|
||||
|
||||
```ts
|
||||
// bootstrap.ts
|
||||
context.set('jet', jet)
|
||||
context.set('i18n', i18nStore)
|
||||
|
||||
// 深層元件
|
||||
const jet = getJet() // 從 Svelte Context 取得
|
||||
const i18n = getI18n() // 從 Svelte Context 取得
|
||||
```
|
||||
|
||||
- 避免 props drilling:Jet、i18n、accessibility layout、today-card layout 都透過 context 傳遞。
|
||||
- Context 在啟動時注入,生命周期與應用一致,不是用來傳遞 UI 狀態的。
|
||||
|
||||
### 1.6 命令式外殼 + 聲明式 UI
|
||||
|
||||
```ts
|
||||
// browser.ts(命令式啟動層)
|
||||
const app = new App({ target: container, context, hydrate: true })
|
||||
registerActionHandlers({
|
||||
jet,
|
||||
updateApp: (props) => app.$set(props), // 橋接命令式 → 聲明式
|
||||
})
|
||||
```
|
||||
|
||||
- 導航、歷史管理、scroll 復原由命令式的 action handler 處理。
|
||||
- UI 渲染完全聲明式,只接收 `page` 與 `isFirstPage` 兩個 prop。
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user