Files
skt-vuetify-templates/docs/add-page-example.md
T
skytek_xinliang 9e8cf28d77 fix: docing
2026-05-22 11:17:32 +08:00

220 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 新增頁面範例
這份文件示範如何用目前 `src/` 慣例新增一個被 `MainLayout` 包住的一般功能頁。
範例功能:`reports`
目前新增一般頁面的預設資料流:
```txt
router -> view -> page driver -> page component -> sections/items
store/composable -> service
```
## 1. 新增 page driver
頁面資料、事件與暫時 UI state 優先放在 page driverview 只負責掛載。
```ts
// src/composables/page-drivers/useReportsPage.ts
import { computed, ref } from 'vue'
import { useSnackbarStore } from '@/stores/snackbar'
export interface ReportSummary {
id: number
title: string
owner: string
}
export interface ReportsPageModel {
title: string
rows: ReportSummary[]
}
const initialRows: ReportSummary[] = [
{ id: 1, title: '學生統計', owner: '教務處' },
{ id: 2, title: '課程統計', owner: '課務組' },
]
export function useReportsPage() {
const snackbar = useSnackbarStore()
const rows = ref<ReportSummary[]>(initialRows)
const pageModel = computed<ReportsPageModel>(() => ({
title: '報表清單',
rows: rows.value,
}))
function openReport(row: ReportSummary) {
snackbar.show({ message: `開啟:${row.title}`, color: 'info' })
}
return {
pageModel,
openReport,
}
}
```
若資料來自 APIpage driver 可呼叫 store 或 composable;底層 HTTP 細節仍放在 `services/modules/*`
## 2. 新增 page component
完整頁面主畫面放在 `src/components/pages`,檔名使用 `Page` 前綴。component 以 props 接收資料,以 emit 回報使用者事件,不直接處理 route 或底層 HTTP。
```vue
<!-- src/components/pages/PageReports.vue -->
<script setup lang="ts">
import type { ReportSummary, ReportsPageModel } from '@/composables/page-drivers/useReportsPage'
defineProps<{
page: ReportsPageModel
}>()
const emit = defineEmits<{
open: [row: ReportSummary]
}>()
</script>
<template>
<v-card flat>
<v-card-title class="text-h6">{{ page.title }}</v-card-title>
<v-table>
<thead>
<tr>
<th>名稱</th>
<th>負責單位</th>
<th class="text-right">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="row in page.rows" :key="row.id">
<td>{{ row.title }}</td>
<td>{{ row.owner }}</td>
<td class="text-right">
<v-btn color="primary" size="small" variant="text" @click="emit('open', row)">
開啟
</v-btn>
</td>
</tr>
</tbody>
</v-table>
</v-card>
</template>
```
若畫面是固定的「篩選條件 + 查詢按鈕 + 結果表格」,優先使用 `components/sections/SectionQueryPage.vue`。若是「表單欄位 + 送出/存檔按鈕」,優先使用 `components/sections/SectionFormPage.vue`
## 3. 新增 route view
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 前面。
```ts
// src/router/routes.ts
{
path: '/reports',
name: 'reports',
component: () => import('@/views/reports/Reports.vue'),
meta: { layout: 'default', requiresAuth: true },
}
```
`layout: 'default'` 會讓頁面被 `MainLayout` 包住。登入頁、錯誤頁、維護中頁才使用 `layout: 'none'`
若頁面需要出現在 drawer menu、favorites 或 breadcrumb
- menu 來源目前由 `src/stores/menu.ts` 轉換後端選單資料。
- breadcrumb 會依 route path、menu/favorite items 與 fallback title 產生。
- 新功能若使用後端選單,優先調整後端選單資料或對應 API mock,不要把頁面專屬選單邏輯塞進 layout。
- 若只是新增 route,通常不需要修改 `MainLayout.vue``src/shell/*`
## 5. 需要 API 時新增 service module
```ts
// src/services/modules/reports.ts
import { httpClient } from '../client'
export interface ReportSummary {
id: number
title: string
owner: string
}
export const reportsApi = {
list: async () => ({
data: await httpClient.get('Reports').json<ReportSummary[]>(),
}),
}
```
service 只封裝 HTTP 細節,不持有 UI 狀態。
`httpClient``baseURL` 來自 `VITE_API_BASE_URL`,沒有設定時預設 `/service/api`。開發模式下,Vite proxy 會將 `/service/*` 轉送到 `VITE_PROXY_TARGET`
## 6. 需要共享狀態時新增 store
只有跨頁共享、需要快取、或全域狀態才新增 store。單頁暫時狀態留在 view、component 或 composable。
```ts
// src/stores/reports.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { reportsApi, type ReportSummary } from '@/services/modules/reports'
export const useReportsStore = defineStore('reports', () => {
const items = ref<ReportSummary[]>([])
const loading = ref(false)
const load = async () => {
loading.value = true
try {
const { data } = await reportsApi.list()
items.value = data
} finally {
loading.value = false
}
}
return {
items,
loading,
load,
}
})
```
## 7. 驗證
至少執行:
```bash
pnpm -s type-check
```
需要確認建置產物時再執行:
```bash
pnpm -s build
```
若有 route、layout 或主要互動流程變更,再啟動 dev server 並用瀏覽器確認。