docs: clarify optional page drivers in page guide

Update documentation to show that simple pages can define page models
directly in views without creating a page driver. Adjust examples,
section numbering, and naming guidance to better distinguish simple view
state from reusable page-driver patterns.docs: clarify optional page drivers in page guide

Update documentation to show that simple pages can define page models
directly in views without creating a page driver. Adjust examples,
section numbering, and naming guidance to better distinguish simple view
state from reusable page-driver patterns.
This commit is contained in:
skytek_xinliang
2026-05-27 11:18:19 +08:00
parent b8664b5c3e
commit ad00f5c195
8 changed files with 59 additions and 232 deletions
+1 -1
View File
@@ -20,7 +20,7 @@
- `src/services/modules/<domain>.ts` — service modules - `src/services/modules/<domain>.ts` — service modules
- Examples of correct vs. incorrect naming: - Examples of correct vs. incorrect naming:
-`PageStudentMaintenance.vue` → ✅ `PageMaintenance.vue` -`PageStudentMaintenance.vue` → ✅ `PageMaintenance.vue`
-`useStudentMaintenancePage.ts` → ✅ `useMaintenancePage.ts` -`useStudentMaintenancePage.ts` → ✅ `useSingleRecordMaintenancePage.ts`
-`ItemStudentRow.vue` → ✅ `ItemDataRow.vue` -`ItemStudentRow.vue` → ✅ `ItemDataRow.vue`
-`useStudentCrudCommands.ts` → ✅ `useCrudCommands.ts` -`useStudentCrudCommands.ts` → ✅ `useCrudCommands.ts`
-`models/student.ts`, `stores/students.ts` — domain layer, specific names are correct -`models/student.ts`, `stores/students.ts` — domain layer, specific names are correct
+30 -52
View File
@@ -7,18 +7,22 @@
目前新增一般頁面的預設資料流: 目前新增一般頁面的預設資料流:
```txt ```txt
router -> view -> page driver -> page component -> sections/items router -> view -> (page driver) -> page component -> sections/items
store/composable -> service store/composable -> service
``` ```
## 1. 新增 page driver Page driver 不是必備層:若頁面邏輯只有簡單的 `computed` page model(無搜尋、無 dialog、無複雜事件協調),直接在 view 裡寫即可,不需要建立 page driver
頁面資料、事件與暫時 UI state 優先放在 page driverview 只負責掛載。 ## 1. 新增 view(含 page model
```ts 簡單頁面的 page model 直接在 view 裡用 `computed` 組裝,不需要額外建立 page driver。
// src/composables/page-drivers/useReportsPage.ts
```vue
<!-- src/views/reports/Reports.vue -->
<script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import PageReports from '@/components/pages/PageReports.vue'
import { useSnackbarStore } from '@/stores/snackbar' import { useSnackbarStore } from '@/stores/snackbar'
export interface ReportSummary { export interface ReportSummary {
@@ -27,37 +31,29 @@ export interface ReportSummary {
owner: string owner: string
} }
export interface ReportsPageModel {
title: string
rows: ReportSummary[]
}
const initialRows: ReportSummary[] = [ const initialRows: ReportSummary[] = [
{ id: 1, title: '學生統計', owner: '教務處' }, { id: 1, title: '學生統計', owner: '教務處' },
{ id: 2, title: '課程統計', owner: '課務組' }, { id: 2, title: '課程統計', owner: '課務組' },
] ]
export function useReportsPage() { const snackbar = useSnackbarStore()
const snackbar = useSnackbarStore() const rows = ref<ReportSummary[]>(initialRows)
const rows = ref<ReportSummary[]>(initialRows) const pageModel = computed(() => ({
title: '報表清單',
rows: rows.value,
}))
const pageModel = computed<ReportsPageModel>(() => ({ function openReport(row: ReportSummary) {
title: '報表清單', snackbar.show({ message: `開啟:${row.title}`, color: 'info' })
rows: rows.value,
}))
function openReport(row: ReportSummary) {
snackbar.show({ message: `開啟:${row.title}`, color: 'info' })
}
return {
pageModel,
openReport,
}
} }
</script>
<template>
<PageReports :page="pageModel" @open="openReport" />
</template>
``` ```
資料來自 APIpage driver 可呼叫 store 或 composable;底層 HTTP 細節仍放在 `services/modules/*` 頁面需要協調多個 composable(搜尋、表單、CRUD flow、dialog 狀態),才建立 page driver。page driver 的慣例見 `src/composables/GUIDE.md`
## 2. 新增 page component ## 2. 新增 page component
@@ -66,10 +62,10 @@ export function useReportsPage() {
```vue ```vue
<!-- src/components/pages/PageReports.vue --> <!-- src/components/pages/PageReports.vue -->
<script setup lang="ts"> <script setup lang="ts">
import type { ReportSummary, ReportsPageModel } from '@/composables/page-drivers/useReportsPage' import type { ReportSummary } from '@/views/reports/Reports.vue'
defineProps<{ defineProps<{
page: ReportsPageModel page: { title: string; rows: ReportSummary[] }
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
@@ -106,25 +102,7 @@ const emit = defineEmits<{
若畫面是固定的「篩選條件 + 查詢按鈕 + 結果表格」,優先使用 `components/sections/SectionQueryPage.vue`。若是「表單欄位 + 送出/存檔按鈕」,優先使用 `components/sections/SectionFormPage.vue` 若畫面是固定的「篩選條件 + 查詢按鈕 + 結果表格」,優先使用 `components/sections/SectionQueryPage.vue`。若是「表單欄位 + 送出/存檔按鈕」,優先使用 `components/sections/SectionFormPage.vue`
## 3. 新增 route view ## 3. 加入 route
view 維持薄層,只呼叫 page driver 並掛載 page component。
```vue
<!-- src/views/reports/Reports.vue -->
<script setup lang="ts">
import PageReports from '@/components/pages/PageReports.vue'
import { useReportsPage } from '@/composables/page-drivers/useReportsPage'
const page = useReportsPage()
</script>
<template>
<PageReports :page="page.pageModel.value" @open="page.openReport" />
</template>
```
## 4. 加入 route
route 加在 `src/router/routes.ts``routes` 陣列中,並放在 `/:pathMatch(.*)*` catch-all route 前面。 route 加在 `src/router/routes.ts``routes` 陣列中,並放在 `/:pathMatch(.*)*` catch-all route 前面。
@@ -147,7 +125,7 @@ route 加在 `src/router/routes.ts` 的 `routes` 陣列中,並放在 `/:pathMa
- 新功能若使用後端選單,優先調整後端選單資料或對應 API mock,不要把頁面專屬選單邏輯塞進 layout。 - 新功能若使用後端選單,優先調整後端選單資料或對應 API mock,不要把頁面專屬選單邏輯塞進 layout。
- 若只是新增 route,通常不需要修改 `MainLayout.vue``src/shell/*` - 若只是新增 route,通常不需要修改 `MainLayout.vue``src/shell/*`
## 5. 需要 API 時新增 service module ## 4. 需要 API 時新增 service module
```ts ```ts
// src/services/modules/reports.ts // src/services/modules/reports.ts
@@ -170,7 +148,7 @@ service 只封裝 HTTP 細節,不持有 UI 狀態。
`httpClient``baseURL` 來自 `VITE_API_BASE_URL`,沒有設定時預設 `/service/api`。開發模式下,Vite proxy 會將 `/service/*` 轉送到 `VITE_PROXY_TARGET` `httpClient``baseURL` 來自 `VITE_API_BASE_URL`,沒有設定時預設 `/service/api`。開發模式下,Vite proxy 會將 `/service/*` 轉送到 `VITE_PROXY_TARGET`
## 6. 需要共享狀態時新增 store ## 5. 需要共享狀態時新增 store
只有跨頁共享、需要快取、或全域狀態才新增 store。單頁暫時狀態留在 view、component 或 composable。 只有跨頁共享、需要快取、或全域狀態才新增 store。單頁暫時狀態留在 view、component 或 composable。
@@ -202,7 +180,7 @@ export const useReportsStore = defineStore('reports', () => {
}) })
``` ```
## 7. 驗證 ## 6. 驗證
至少執行: 至少執行:
-65
View File
@@ -1,65 +0,0 @@
## 二、我們專案的現況診斷
本文件是 `docs/architecture-strategy.md` 第二章的現況快照。分層細節以 `docs/architecture-strategy.md``src/**/GUIDE.md` 為準。
### 2.1 App Shell 已拆分
`App.vue` 目前只掛載 `src/shell/AppShell.vue`,不再承擔 layout props、tabs、搜尋 dialog、訊息 dialog 或 snackbar 的具體組裝。
目前責任分布:
| 職責 | 目前位置 |
|------|----------|
| Layout 切換 | `src/shell/AppShell.vue` |
| Tabs / keep-alive router-view | `src/shell/AppTabs.vue` |
| Breadcrumb / favorites / menu wiring | `src/composables/layout/useAppShell.ts` + `AppShell.vue` |
| Search Dialog / Message Dialog / Snackbar | `src/shell/GlobalOverlays.vue` |
| Logout / force logout | `src/composables/layout/useAppShell.ts` |
| HTTP Toast | `src/services/http-toast.ts` + `GlobalOverlays.vue` |
### 2.2 Views 已大幅變薄
維護頁與一般頁面目前多數已轉為 route-level wiring
- `Home.vue`:呼叫 `useHomePage()`,掛載 `PageHome`
- `Settings.vue`:呼叫 `useSettingsPage()`,掛載 `PageSettings`
- `FncPage.vue`:呼叫 `useFunctionPage()`,掛載 `PageFunction`
- `views/maint/*`:呼叫對應 page driver,掛載 `components/pages/*Maintenance.vue`
`SingleRecord.vue` 已不再直接管理 store mutation、大型 dialog 模板、表格分頁與 CRUD 細節;這些流程已移到 page driver、section component、item component 與 command composable。
`Login.vue` 是 template core 例外,仍負責登入頁組合、功能開關、小型提示 dialog 與登入流程協調。登入頁的 captcha、announcement、忘記密碼與記住帳號流程已透過 composable / props / emits 拆分,後續調整應維持該模式。
### 2.3 Page Driver / Command / Page Component 已落地
目前已存在的主要分層:
```txt
view -> page driver -> page component -> section/item
command/store/service
```
- `src/composables/page-drivers/*`:組裝 page model、route/query 轉換與頁面事件。
- `src/composables/commands/useCrudCommands.ts`:承接維護頁 CRUD 命令流程。
- `src/components/pages/*`:完整頁面的主畫面組裝。
- `src/components/sections/*`:搜尋區、表格區、表單 dialog/panel、表單/查詢頁外殼。
- `src/components/items/*`:欄位群組或單筆資料呈現。
### 2.4 Dialog 與區塊拆分狀態
維護頁的大型 dialog 與表單欄位已從 view 抽出:
- `SectionFormPanel.vue`:維護頁表單 overlay/dialog shell。
- `MntDialogCard.vue``MntRecordNavToolbar.vue`:維護頁 dialog 內部骨架。
- `ItemFormFieldGroup.vue`:表單欄位群組。
新增頁面時,若只是小型提示 dialog 且只屬於單一路由,可先留在 page driver / page component。若 dialog 包含大型表單、確認流程或可重用骨架,優先抽到 section 或 feature component。
### 2.5 仍需注意的邊界
- `src/models/page.ts` 目前主要服務 maintenance page model;部分頁面仍在各自 page driver 內定義局部 page model 型別。
- `components/maint/*` 與 maintenance page components 屬於 demo / maintenance 領域,不應直接升格為全域 base 元件。
- `src/components/base` 目前只放跨頁共用基礎元件,例如 `DraggableDialog``BaseFormTextField``BaseFormSelect`
- `src/stores/app.ts` 仍是 Pinia scaffold,尚未承擔實際 app state。
- 一般功能需求不應修改 `App.vue``src/shell/*`、layout、router guard 或 HTTP core,除非需求明確牽涉這些 template core。
+22 -29
View File
@@ -67,23 +67,15 @@ Read only when needed: [analyse now](./analyse-now.md)
範例: 範例:
```ts ```ts
// src/composables/usePageDriver.ts // views/maint/Example.vue — 簡單頁面直接在 view 組裝 page model
export function useMaintenancePage() { const studentStore = useStudentStore()
const studentStore = useStudentStore() const pageModel = computed<MaintenancePageModel>(() => ({
const { records, loading, error, load } = useCrudDriver({ type: 'maintenance',
store: studentStore, title: '單筆資料維護',
loadAction: () => studentStore.fetchStudents(), records: studentStore.students,
}) loading: false,
error: null,
const pageModel = computed(() => ({ }))
title: '單筆資料維護',
records: records.value,
loading: loading.value,
error: error.value,
}))
return { pageModel, load }
}
``` ```
### 3.3 查詢(Query)與命令(Command)分離 ### 3.3 查詢(Query)與命令(Command)分離
@@ -164,8 +156,8 @@ src/
│ └── DraggableDialog.vue │ └── DraggableDialog.vue
├── composables/ ├── composables/
│ ├── page-drivers/ ← 新增:頁面資料協調 │ ├── page-drivers/ ← 新增:頁面資料協調(僅複雜頁面需要)
│ │ └── useMaintenancePage.ts │ │ └── useSingleRecordMaintenancePage.ts
│ ├── commands/ ← 新增:命令流程(對齊 Jet Action) │ ├── commands/ ← 新增:命令流程(對齊 Jet Action)
│ │ └── useCrudCommands.ts │ │ └── useCrudCommands.ts
│ ├── forms/ ← 維持/重組:表單狀態機 │ ├── forms/ ← 維持/重組:表單狀態機
@@ -201,14 +193,10 @@ src/
```vue ```vue
<!-- views/maint/SingleRecord.vue優化後 --> <!-- views/maint/SingleRecord.vue優化後 -->
<script setup lang="ts"> <script setup lang="ts">
import { useMaintenancePage } from '@/composables/page-drivers/useMaintenancePage'
import PageMaintenance from '@/components/pages/PageMaintenance.vue' import PageMaintenance from '@/components/pages/PageMaintenance.vue'
import { useSingleRecordMaintenancePage } from '@/composables/page-drivers/useSingleRecordMaintenancePage'
const { pageModel, load } = useMaintenancePage({ const { pageModel, commands, formPanelProps, formPanelEvents } = useSingleRecordMaintenancePage()
title: '單筆資料維護',
records: [],
})
load()
</script> </script>
<template> <template>
@@ -356,9 +344,10 @@ views/xxx.vue
3. [x] 新增 `src/components/pages/`:建立第一個 `PageMaintenance.vue`(可從 `PageMaint.vue` 擴展)。 3. [x] 新增 `src/components/pages/`:建立第一個 `PageMaintenance.vue`(可從 `PageMaint.vue` 擴展)。
- 定義 `MaintenancePageModel` props 與 `create/edit/view/delete/search` emits。 - 定義 `MaintenancePageModel` props 與 `create/edit/view/delete/search` emits。
- 使用 `PageMaint.vue` 作為佈局外殼,搜尋與表格區塊以 slot 開放,不綁定特定領域型別。 - 使用 `PageMaint.vue` 作為佈局外殼,搜尋與表格區塊以 slot 開放,不綁定特定領域型別。
4. [x] 新增 `src/composables/page-drivers/`:建立 `useMaintenancePage.ts` 4. [x] 新增 `src/composables/page-drivers/`:建立第一個 page driver 範例
- 透過 options 傳入 title 與 records協調搜尋條件、分頁與 `pageModel` - 協調搜尋條件、分頁與 `pageModel`
- 提供 `load()``resetSearch()` 供 Page Driver 呼叫。 - 提供 `load()``resetSearch()` 供 Page Driver 呼叫。
- 後續已刪除純包裝型 driver(如 `useMaintenancePage`)。僅當頁面需要協調多個 composable 時才建立 page driver。
### Phase 2:遷移最厚的 viewSingleRecord.vue ✅ 已完成 ### Phase 2:遷移最厚的 viewSingleRecord.vue ✅ 已完成
@@ -382,6 +371,8 @@ views/xxx.vue
### Phase 3:推廣到所有 maintenance 頁面 ✅ 已完成 ### Phase 3:推廣到所有 maintenance 頁面 ✅ 已完成
> 後續簡化時,B/C/EditableGrid 的薄 page driver 已 inline 回 view,只保留有真實複雜邏輯的 driver。
1. [x] `EditableGrid.vue` 依 Page Driver + Page Component 模式重構。 1. [x] `EditableGrid.vue` 依 Page Driver + Page Component 模式重構。
- `src/views/maint/EditableGrid.vue` 縮減為 10 行 route-level wiring。 - `src/views/maint/EditableGrid.vue` 縮減為 10 行 route-level wiring。
- 新增 `src/composables/page-drivers/useEditableGridMaintenancePage.ts` - 新增 `src/composables/page-drivers/useEditableGridMaintenancePage.ts`
@@ -399,6 +390,8 @@ views/xxx.vue
### Phase 4:非 maintenance 頁面統一 ✅ 已完成 ### Phase 4:非 maintenance 頁面統一 ✅ 已完成
> 後續簡化時,Settings/FncPage 的薄 page driver 已 inline 回 view,型別移至 page component 自身。
1. [x] `Home.vue``Settings.vue``FncPage.vue` 套用 Page Driver + Page Component 模式。 1. [x] `Home.vue``Settings.vue``FncPage.vue` 套用 Page Driver + Page Component 模式。
- `src/views/Home.vue` 縮減為 17 行,新增 `src/components/pages/PageHome.vue``src/composables/page-drivers/useHomePage.ts` - `src/views/Home.vue` 縮減為 17 行,新增 `src/components/pages/PageHome.vue``src/composables/page-drivers/useHomePage.ts`
- `src/views/Settings.vue` 縮減為 10 行,新增 `src/components/pages/PageSettings.vue``src/composables/page-drivers/useSettingsPage.ts` - `src/views/Settings.vue` 縮減為 10 行,新增 `src/components/pages/PageSettings.vue``src/composables/page-drivers/useSettingsPage.ts`
@@ -421,7 +414,7 @@ views/xxx.vue
| Item / Atom | `src/components/items/` | `ItemDataRow.vue``ItemFormField.vue` | | Item / Atom | `src/components/items/` | `ItemDataRow.vue``ItemFormField.vue` |
| Layout | `src/components/layouts/` | `MainLayout.vue`(維持) | | Layout | `src/components/layouts/` | `MainLayout.vue`(維持) |
| Base | `src/components/base/` | `DraggableDialog.vue`(維持) | | Base | `src/components/base/` | `DraggableDialog.vue`(維持) |
| Page Driver Composable | `src/composables/page-drivers/` | `useMaintenancePage.ts` | | Page Driver Composable | `src/composables/page-drivers/` | `useSingleRecordMaintenancePage.ts` |
| Command Composable | `src/composables/commands/` | `useCrudCommands.ts` | | Command Composable | `src/composables/commands/` | `useCrudCommands.ts` |
| Form Composable | `src/composables/forms/` | `useForm.ts` | | Form Composable | `src/composables/forms/` | `useForm.ts` |
| Domain Store | `src/stores/` | `students.ts`(維持) | | Domain Store | `src/stores/` | `students.ts`(維持) |
-81
View File
@@ -1,81 +0,0 @@
## 一、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 drillingJet、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。
---
+1 -1
View File
@@ -76,7 +76,7 @@ router -> AppShell -> layout -> view(Page Driver) -> Page Component -> Section -
1. 新增或修改 `views/*` route entry。 1. 新增或修改 `views/*` route entry。
2. 若有完整頁面 UI,新增 `components/pages/PageXxx.vue` 2. 若有完整頁面 UI,新增 `components/pages/PageXxx.vue`
3. 若有頁面資料協調或 route param 轉換,新增 `composables/page-drivers/useXxxPage.ts` 3. 若有複雜的資料協調(多 composable、搜尋狀態、CRUD flow、dialog 狀態),新增 `composables/page-drivers/useXxxPage.ts`簡單頁面直接在 view 用 `computed` 組裝 page model。
4. 若畫面有獨立區塊,拆到 `components/sections/*` 4. 若畫面有獨立區塊,拆到 `components/sections/*`
5. 若區塊內有欄位群組或單筆資料呈現,拆到 `components/items/*` 5. 若區塊內有欄位群組或單筆資料呈現,拆到 `components/items/*`
6. 跨頁共享狀態才新增或修改 `stores/*` 6. 跨頁共享狀態才新增或修改 `stores/*`
+1 -1
View File
@@ -23,4 +23,4 @@
- 各頁面的 specific model 擴展 `BasePageModel`(例如 `MaintenancePageModel``type``records`)。 - 各頁面的 specific model 擴展 `BasePageModel`(例如 `MaintenancePageModel``type``records`)。
- `PageModel` union 供 page component props 型別使用。 - `PageModel` union 供 page component props 型別使用。
新增頁面類型時,先擴充 `PageModel` union 再新增對應的 page driver 新增頁面類型時,先擴充 `PageModel` union。若頁面需要協調多個 composable(搜尋、表單、CRUD flow、dialog 狀態),再建立對應的 page driver;簡單頁面直接在 view 用 `computed` 組裝 page model 即可
+4 -2
View File
@@ -18,14 +18,16 @@
import PageReports from '@/components/pages/PageReports.vue' import PageReports from '@/components/pages/PageReports.vue'
import { useReportsPage } from '@/composables/page-drivers/useReportsPage' import { useReportsPage } from '@/composables/page-drivers/useReportsPage'
const page = useReportsPage() const { pageModel } = useReportsPage()
</script> </script>
<template> <template>
<PageReports :page="page.pageModel.value" /> <PageReports :page="pageModel" />
</template> </template>
``` ```
若頁面只是簡單的 `computed` 組裝(無搜尋、無 dialog、無複雜事件),直接在 view 寫 page model,不需要建立 page driver。以 destructure 方式取用 composable 回傳值,模板不寫 `.value`
## Login.vue 開關 ## Login.vue 開關
`Login.vue` 是登入頁的組合層,登入頁功能開關集中在 view 內宣告,再透過 `PageLogin` / composable 往下傳遞,不在子元件各自決定是否啟用。 `Login.vue` 是登入頁的組合層,登入頁功能開關集中在 view 內宣告,再透過 `PageLogin` / composable 往下傳遞,不在子元件各自決定是否啟用。