5.6 KiB
新增頁面範例
這份文件示範如何用目前 src/ 慣例新增一個被 MainLayout 包住的一般功能頁。
範例功能:reports
目前新增一般頁面的預設資料流:
router -> view -> page driver -> page component -> sections/items
↓
store/composable -> service
1. 新增 page driver
頁面資料、事件與暫時 UI state 優先放在 page driver,view 只負責掛載。
// 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,
}
}
若資料來自 API,page driver 可呼叫 store 或 composable;底層 HTTP 細節仍放在 services/modules/*。
2. 新增 page component
完整頁面主畫面放在 src/components/pages,檔名使用 Page 前綴。component 以 props 接收資料,以 emit 回報使用者事件,不直接處理 route 或底層 HTTP。
<!-- 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。
<!-- 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 前面。
// 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
// 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。
// 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. 驗證
至少執行:
pnpm -s type-check
需要確認建置產物時再執行:
pnpm -s build
若有 route、layout 或主要互動流程變更,再啟動 dev server 並用瀏覽器確認。