Files
skt-vuetify-templates/docs/add-page-example.md
T
skytek_xinliang ad00f5c195 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.
2026-05-27 11:18:19 +08:00

5.4 KiB
Raw Blame History

新增頁面範例

這份文件示範如何用目前 src/ 慣例新增一個被 MainLayout 包住的一般功能頁。

範例功能:reports

目前新增一般頁面的預設資料流:

router -> view -> (page driver) -> page component -> sections/items
                       ↓
                 store/composable -> service

Page driver 不是必備層:若頁面邏輯只有簡單的 computed page model(無搜尋、無 dialog、無複雜事件協調),直接在 view 裡寫即可,不需要建立 page driver。

1. 新增 view(含 page model

簡單頁面的 page model 直接在 view 裡用 computed 組裝,不需要額外建立 page driver。

<!-- src/views/reports/Reports.vue -->
<script setup lang="ts">
import { computed, ref } from 'vue'
import PageReports from '@/components/pages/PageReports.vue'
import { useSnackbarStore } from '@/stores/snackbar'

export interface ReportSummary {
  id: number
  title: string
  owner: string
}

const initialRows: ReportSummary[] = [
  { id: 1, title: '學生統計', owner: '教務處' },
  { id: 2, title: '課程統計', owner: '課務組' },
]

const snackbar = useSnackbarStore()
const rows = ref<ReportSummary[]>(initialRows)
const pageModel = computed(() => ({
  title: '報表清單',
  rows: rows.value,
}))

function openReport(row: ReportSummary) {
  snackbar.show({ message: `開啟:${row.title}`, color: 'info' })
}
</script>

<template>
  <PageReports :page="pageModel" @open="openReport" />
</template>

若頁面需要協調多個 composable(搜尋、表單、CRUD flow、dialog 狀態),才建立 page driver。page driver 的慣例見 src/composables/GUIDE.md

2. 新增 page component

完整頁面主畫面放在 src/components/pages,檔名使用 Page 前綴。component 以 props 接收資料,以 emit 回報使用者事件,不直接處理 route 或底層 HTTP。

<!-- src/components/pages/PageReports.vue -->
<script setup lang="ts">
import type { ReportSummary } from '@/views/reports/Reports.vue'

defineProps<{
  page: { title: string; rows: ReportSummary[] }
}>()

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

route 加在 src/router/routes.tsroutes 陣列中,並放在 /: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.vuesrc/shell/*

4. 需要 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 狀態。

httpClientbaseURL 來自 VITE_API_BASE_URL,沒有設定時預設 /service/api。開發模式下,Vite proxy 會將 /service/* 轉送到 VITE_PROXY_TARGET

5. 需要共享狀態時新增 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,
  }
})

6. 驗證

至少執行:

pnpm -s type-check

需要確認建置產物時再執行:

pnpm -s build

若有 route、layout 或主要互動流程變更,再啟動 dev server 並用瀏覽器確認。