Files
skytek_xinliang 7b0cfe4448 refactor(login): compose page from focused login components
Split the login page into smaller reusable components for branding,
toolbar, header, form, announcements, and mobile layout behavior. This
keeps the view responsible for orchestration while moving UI sections into
focused components.

Update page creation docs to reflect the simplified flow where views render
sections/items directly and composables coordinate store/service access when
needed.refactor(login): compose page from focused login components

Split the login page into smaller reusable components for branding,
toolbar, header, form, announcements, and mobile layout behavior. This
keeps the view responsible for orchestration while moving UI sections into
focused components.

Update page creation docs to reflect the simplified flow where views render
sections/items directly and composables coordinate store/service access when
needed.
2026-05-27 13:43:43 +08:00

4.1 KiB
Raw Permalink Blame History

新增頁面範例

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

範例功能:reports

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

router -> view -> sections/items
              ↓
    composable -> store -> 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 { 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

若畫面是固定的「篩選條件 + 查詢按鈕 + 結果表格」,優先使用 components/sections/SectionQueryPage.vue。若是「表單欄位 + 送出/存檔按鈕」,優先使用 components/sections/SectionFormPage.vue

2. 加入 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/*

3. 需要 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

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

5. 驗證

至少執行:

pnpm -s type-check

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

pnpm -s build

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