refactor: simplify page models and view driver usage
Move simple page models into page components and build trivial computed models directly in views to avoid unnecessary page drivers. Update views to destructure page driver returns and rely on template ref unwrapping, and document the guidance for when page drivers should be introduced.refactor: simplify page models and view driver usage Move simple page models into page components and build trivial computed models directly in views to avoid unnecessary page drivers. Update views to destructure page driver returns and rely on template ref unwrapping, and document the guidance for when page drivers should be introduced.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { FunctionPageModel } from '@/composables/page-drivers/useFunctionPage'
|
||||
export interface FunctionPageModel {
|
||||
fncId: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
page: FunctionPageModel
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { SettingsPageModel } from '@/composables/page-drivers/useSettingsPage'
|
||||
export interface SettingsPageModel {
|
||||
title: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
page: SettingsPageModel
|
||||
|
||||
@@ -22,14 +22,23 @@
|
||||
|
||||
## Page Driver
|
||||
|
||||
Page driver 負責:
|
||||
Page driver 只應在「需要協調多個 composable / store / route」時才成立。若頁面邏輯只有:
|
||||
|
||||
- 組裝一個 `computed` page model(3-5 個欄位)
|
||||
- 沒有搜尋、沒有 dialog、沒有複雜事件
|
||||
|
||||
則**不要建立 page driver**,直接在 view 裡寫 `computed` 即可。
|
||||
|
||||
當需要 page driver 時,它負責:
|
||||
- route param/query 轉成頁面資料
|
||||
- 組裝 page model
|
||||
- 組裝 page component 需要的 props/events
|
||||
- 協調 store、command composable、表單 composable
|
||||
- 組裝 page component 需要的 props/events
|
||||
|
||||
View 只呼叫 page driver 並掛載 page component。
|
||||
View 以 destructure 方式取用 page driver 回傳值:
|
||||
```ts
|
||||
const { pageModel, search, handleSubmit } = useXxxPage()
|
||||
```
|
||||
模板中直接使用,不寫 `.value`:`:page="pageModel"`、`v-model="search"`。
|
||||
|
||||
## Commands
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { computed } from 'vue'
|
||||
import type { MaintenancePageModel } from '@/models/page'
|
||||
import { useStudentStore } from '@/stores/students'
|
||||
|
||||
export function useEditableGridMaintenancePage() {
|
||||
const studentStore = useStudentStore()
|
||||
|
||||
const pageModel = computed<MaintenancePageModel>(() => ({
|
||||
type: 'maintenance',
|
||||
title: '可編輯表格維護示範',
|
||||
records: studentStore.students,
|
||||
loading: false,
|
||||
error: null,
|
||||
}))
|
||||
|
||||
return {
|
||||
pageModel,
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
export interface FunctionPageModel {
|
||||
fncId: string
|
||||
}
|
||||
|
||||
export function useFunctionPage() {
|
||||
const route = useRoute()
|
||||
|
||||
const pageModel = computed<FunctionPageModel>(() => ({
|
||||
fncId: String(route.params.fncId ?? ''),
|
||||
}))
|
||||
|
||||
return {
|
||||
pageModel,
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import type { MaintenancePageModel } from '@/models/page'
|
||||
|
||||
export interface UseMaintenancePageOptions {
|
||||
title: string
|
||||
records: unknown[]
|
||||
itemsPerPage?: number
|
||||
}
|
||||
|
||||
export function useMaintenancePage(options: UseMaintenancePageOptions) {
|
||||
const search = ref<Record<string, unknown>>({})
|
||||
const searchPanelOpen = ref(false)
|
||||
const currentPage = ref(1)
|
||||
const itemsPerPage = options.itemsPerPage ?? 10
|
||||
|
||||
const pageCount = computed(() =>
|
||||
Math.max(1, Math.ceil(options.records.length / itemsPerPage))
|
||||
)
|
||||
|
||||
const pageModel = computed<MaintenancePageModel>(() => ({
|
||||
type: 'maintenance',
|
||||
title: options.title,
|
||||
records: options.records,
|
||||
loading: false,
|
||||
error: null,
|
||||
}))
|
||||
|
||||
function load() {
|
||||
// 由呼叫方在 load 中觸發資料載入;未來可擴充為非同步
|
||||
}
|
||||
|
||||
function resetSearch() {
|
||||
search.value = {}
|
||||
}
|
||||
|
||||
return {
|
||||
pageModel,
|
||||
search,
|
||||
searchPanelOpen,
|
||||
currentPage,
|
||||
itemsPerPage,
|
||||
pageCount,
|
||||
load,
|
||||
resetSearch,
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { MaintenancePageModel } from '@/models/page'
|
||||
import { useStudentStore } from '@/stores/students'
|
||||
|
||||
export function useMasterDetailBMaintenancePage() {
|
||||
const studentStore = useStudentStore()
|
||||
|
||||
const pageModel = computed<MaintenancePageModel>(() => ({
|
||||
type: 'maintenance',
|
||||
title: '主從資料維護示範B',
|
||||
records: studentStore.students,
|
||||
loading: false,
|
||||
error: null,
|
||||
}))
|
||||
|
||||
return {
|
||||
pageModel,
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { MaintenancePageModel } from '@/models/page'
|
||||
import { useStudentStore } from '@/stores/students'
|
||||
|
||||
export function useMasterDetailCMaintenancePage() {
|
||||
const studentStore = useStudentStore()
|
||||
|
||||
const pageModel = computed<MaintenancePageModel>(() => ({
|
||||
type: 'maintenance',
|
||||
title: '主從資料維護示範C',
|
||||
records: studentStore.students,
|
||||
loading: false,
|
||||
error: null,
|
||||
}))
|
||||
|
||||
return {
|
||||
pageModel,
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
export interface SettingsPageModel {
|
||||
title: string
|
||||
}
|
||||
|
||||
export function useSettingsPage() {
|
||||
const pageModel = computed<SettingsPageModel>(() => ({
|
||||
title: '設定頁面',
|
||||
}))
|
||||
|
||||
return { pageModel }
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import PageFunction from '@/components/pages/PageFunction.vue'
|
||||
import { useFunctionPage } from '@/composables/page-drivers/useFunctionPage'
|
||||
import type { FunctionPageModel } from '@/components/pages/PageFunction.vue'
|
||||
|
||||
const { pageModel } = useFunctionPage()
|
||||
const route = useRoute()
|
||||
const pageModel = computed<FunctionPageModel>(() => ({
|
||||
fncId: String(route.params.fncId ?? ''),
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
+7
-7
@@ -2,16 +2,16 @@
|
||||
import PageHome from '@/components/pages/PageHome.vue'
|
||||
import { useHomePage } from '@/composables/page-drivers/useHomePage'
|
||||
|
||||
const page = useHomePage()
|
||||
const { handleMessageCenter, handleNews, handleQuick, isNewsDialogOpen, pageModel, selectedNews } = useHomePage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageHome
|
||||
v-model:news-dialog-open="page.isNewsDialogOpen.value"
|
||||
:page="page.pageModel.value"
|
||||
:selected-news="page.selectedNews.value"
|
||||
@message-center="page.handleMessageCenter"
|
||||
@news="page.handleNews"
|
||||
@quick="page.handleQuick"
|
||||
v-model:news-dialog-open="isNewsDialogOpen"
|
||||
:page="pageModel"
|
||||
:selected-news="selectedNews"
|
||||
@message-center="handleMessageCenter"
|
||||
@news="handleNews"
|
||||
@quick="handleQuick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import PageSettings from '@/components/pages/PageSettings.vue'
|
||||
import { useSettingsPage } from '@/composables/page-drivers/useSettingsPage'
|
||||
import type { SettingsPageModel } from '@/components/pages/PageSettings.vue'
|
||||
|
||||
const { pageModel } = useSettingsPage()
|
||||
const pageModel = computed<SettingsPageModel>(() => ({
|
||||
title: '設定頁面',
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
import PageSectionFormPageDemo from '@/components/pages/PageSectionFormPageDemo.vue'
|
||||
import { useSectionsDemoPage } from '@/composables/page-drivers/useSectionsDemoPage'
|
||||
|
||||
// Demo view 維持薄層,只掛 page driver,並把 page model / actions 傳給 page component。
|
||||
const page = useSectionsDemoPage()
|
||||
const { demoForm, handleFormBack, handleFormSubmit, pageModel, resetDemoForm } = useSectionsDemoPage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageSectionFormPageDemo
|
||||
v-model:demo-form="page.demoForm.value"
|
||||
:page="page.pageModel.value"
|
||||
@back="page.handleFormBack"
|
||||
@reset="page.resetDemoForm"
|
||||
@submit="page.handleFormSubmit"
|
||||
v-model:demo-form="demoForm"
|
||||
:page="pageModel"
|
||||
@back="handleFormBack"
|
||||
@reset="resetDemoForm"
|
||||
@submit="handleFormSubmit"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
import PageSectionQueryPageDemo from '@/components/pages/PageSectionQueryPageDemo.vue'
|
||||
import { useSectionsDemoPage } from '@/composables/page-drivers/useSectionsDemoPage'
|
||||
|
||||
// Demo view 維持薄層,只掛 page driver,並把 page model / actions 傳給 page component。
|
||||
const page = useSectionsDemoPage()
|
||||
const { handleQueryBack, handleQuerySearch, pageModel, queryFilters } = useSectionsDemoPage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageSectionQueryPageDemo
|
||||
v-model:query-filters="page.queryFilters.value"
|
||||
:page="page.pageModel.value"
|
||||
@back="page.handleQueryBack"
|
||||
@search="page.handleQuerySearch"
|
||||
v-model:query-filters="queryFilters"
|
||||
:page="pageModel"
|
||||
@back="handleQueryBack"
|
||||
@search="handleQuerySearch"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import PageEditableGridMaintenance from '@/components/pages/PageEditableGridMaintenance.vue'
|
||||
import { useEditableGridMaintenancePage } from '@/composables/page-drivers/useEditableGridMaintenancePage'
|
||||
import type { MaintenancePageModel } from '@/models/page'
|
||||
import { useStudentStore } from '@/stores/students'
|
||||
|
||||
const page = useEditableGridMaintenancePage()
|
||||
const studentStore = useStudentStore()
|
||||
const pageModel = computed<MaintenancePageModel>(() => ({
|
||||
type: 'maintenance',
|
||||
title: '可編輯表格維護示範',
|
||||
records: studentStore.students,
|
||||
loading: false,
|
||||
error: null,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageEditableGridMaintenance :page="page.pageModel.value" />
|
||||
<PageEditableGridMaintenance :page="pageModel" />
|
||||
</template>
|
||||
|
||||
@@ -2,33 +2,37 @@
|
||||
import PageMasterDetailAMaintenance from '@/components/pages/PageMasterDetailAMaintenance.vue'
|
||||
import { useMasterDetailAMaintenancePage } from '@/composables/page-drivers/useMasterDetailAMaintenancePage'
|
||||
|
||||
const page = useMasterDetailAMaintenancePage()
|
||||
const {
|
||||
currentPage, formState, itemsPerPage, masterDetailEvents, masterDetailProps,
|
||||
openAddDialog, openEditDialog, openViewDialog, pageCount, pageModel, pageSummary,
|
||||
resetSearch, search, searchPanelOpen, snackbarVisible, students, tableHeaders,
|
||||
} = useMasterDetailAMaintenancePage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageMasterDetailAMaintenance
|
||||
v-model:search="page.search.value"
|
||||
v-model:search-panel-open="page.searchPanelOpen.value"
|
||||
v-bind="page.masterDetailProps.value"
|
||||
:current-page="page.currentPage.value"
|
||||
:grade-label="page.formState.gradeLabel"
|
||||
:headers="page.tableHeaders.value"
|
||||
:items="page.students.value"
|
||||
:items-per-page="page.itemsPerPage"
|
||||
:page="page.pageModel.value"
|
||||
:page-count="page.pageCount.value"
|
||||
:page-summary="page.pageSummary.value"
|
||||
:row-props="page.formState.rowProps"
|
||||
:status-color="page.formState.statusColor"
|
||||
@create="page.openAddDialog"
|
||||
@edit="page.openEditDialog"
|
||||
@reset-search="page.resetSearch"
|
||||
@update:current-page="page.currentPage.value = $event"
|
||||
@view="page.openViewDialog"
|
||||
v-on="page.masterDetailEvents"
|
||||
v-model:search="search"
|
||||
v-model:search-panel-open="searchPanelOpen"
|
||||
v-bind="masterDetailProps"
|
||||
:current-page="currentPage"
|
||||
:grade-label="formState.gradeLabel"
|
||||
:headers="tableHeaders"
|
||||
:items="students"
|
||||
:items-per-page="itemsPerPage"
|
||||
:page="pageModel"
|
||||
:page-count="pageCount"
|
||||
:page-summary="pageSummary"
|
||||
:row-props="formState.rowProps"
|
||||
:status-color="formState.statusColor"
|
||||
@create="openAddDialog"
|
||||
@edit="openEditDialog"
|
||||
@reset-search="resetSearch"
|
||||
@update:current-page="currentPage = $event"
|
||||
@view="openViewDialog"
|
||||
v-on="masterDetailEvents"
|
||||
/>
|
||||
|
||||
<v-snackbar v-model="page.snackbarVisible.value" color="success" location="bottom right" :timeout="2200">
|
||||
<v-snackbar v-model="snackbarVisible" color="success" location="bottom right" :timeout="2200">
|
||||
儲存成功
|
||||
</v-snackbar>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import PageMasterDetailBMaintenance from '@/components/pages/PageMasterDetailBMaintenance.vue'
|
||||
import { useMasterDetailBMaintenancePage } from '@/composables/page-drivers/useMasterDetailBMaintenancePage'
|
||||
import type { MaintenancePageModel } from '@/models/page'
|
||||
import { useStudentStore } from '@/stores/students'
|
||||
|
||||
const page = useMasterDetailBMaintenancePage()
|
||||
const studentStore = useStudentStore()
|
||||
const pageModel = computed<MaintenancePageModel>(() => ({
|
||||
type: 'maintenance',
|
||||
title: '主從資料維護示範B',
|
||||
records: studentStore.students,
|
||||
loading: false,
|
||||
error: null,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageMasterDetailBMaintenance :page="page.pageModel.value" />
|
||||
<PageMasterDetailBMaintenance :page="pageModel" />
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import PageMasterDetailCMaintenance from '@/components/pages/PageMasterDetailCMaintenance.vue'
|
||||
import { useMasterDetailCMaintenancePage } from '@/composables/page-drivers/useMasterDetailCMaintenancePage'
|
||||
import type { MaintenancePageModel } from '@/models/page'
|
||||
import { useStudentStore } from '@/stores/students'
|
||||
|
||||
const page = useMasterDetailCMaintenancePage()
|
||||
const studentStore = useStudentStore()
|
||||
const pageModel = computed<MaintenancePageModel>(() => ({
|
||||
type: 'maintenance',
|
||||
title: '主從資料維護示範C',
|
||||
records: studentStore.students,
|
||||
loading: false,
|
||||
error: null,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageMasterDetailCMaintenance :page="page.pageModel.value" />
|
||||
<PageMasterDetailCMaintenance :page="pageModel" />
|
||||
</template>
|
||||
|
||||
@@ -5,48 +5,53 @@ import SectionFormPanel from '@/components/sections/SectionFormPanel.vue'
|
||||
import SectionSearchPanel from '@/components/sections/SectionSearchPanel.vue'
|
||||
import { useSingleRecordMaintenancePage } from '@/composables/page-drivers/useSingleRecordMaintenancePage'
|
||||
|
||||
const page = useSingleRecordMaintenancePage()
|
||||
const {
|
||||
commands, currentPage, departments, flow, formPanelEvents, formPanelProps,
|
||||
formState, gradeOptions, itemsPerPage, pageCount, pageModel, pageSummary,
|
||||
resetSearch, search, searchPanelOpen, snackbarVisible,
|
||||
statuses, students, tableHeaders,
|
||||
} = useSingleRecordMaintenancePage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageMaintenance
|
||||
v-model:search-panel-open="page.searchPanelOpen.value"
|
||||
:page="page.pageModel.value"
|
||||
@create="page.commands.openAddDialog"
|
||||
v-model:search-panel-open="searchPanelOpen"
|
||||
:page="pageModel"
|
||||
@create="commands.openAddDialog"
|
||||
>
|
||||
<template #search-fields>
|
||||
<SectionSearchPanel
|
||||
v-model="page.search.value"
|
||||
:departments="page.departments"
|
||||
:grade-options="page.gradeOptions"
|
||||
:statuses="page.statuses"
|
||||
@reset="page.resetSearch"
|
||||
v-model="search"
|
||||
:departments="departments"
|
||||
:grade-options="gradeOptions"
|
||||
:statuses="statuses"
|
||||
@reset="resetSearch"
|
||||
/>
|
||||
</template>
|
||||
<template #table>
|
||||
<SectionDataTable
|
||||
v-model:current-page="page.currentPage.value"
|
||||
:grade-label="page.formState.gradeLabel"
|
||||
:headers="page.tableHeaders.value"
|
||||
:items="page.students.value"
|
||||
:items-per-page="page.itemsPerPage"
|
||||
:page-count="page.pageCount.value"
|
||||
:page-summary="page.pageSummary.value"
|
||||
:row-props="page.formState.rowProps"
|
||||
:status-color="page.formState.statusColor"
|
||||
@delete="page.flow.requestDeleteConfirmation"
|
||||
@edit="page.commands.openEditDialog"
|
||||
@view="page.commands.openViewDialog"
|
||||
v-model:current-page="currentPage"
|
||||
:grade-label="formState.gradeLabel"
|
||||
:headers="tableHeaders"
|
||||
:items="students"
|
||||
:items-per-page="itemsPerPage"
|
||||
:page-count="pageCount"
|
||||
:page-summary="pageSummary"
|
||||
:row-props="formState.rowProps"
|
||||
:status-color="formState.statusColor"
|
||||
@delete="flow.requestDeleteConfirmation"
|
||||
@edit="commands.openEditDialog"
|
||||
@view="commands.openViewDialog"
|
||||
/>
|
||||
</template>
|
||||
</PageMaintenance>
|
||||
|
||||
<SectionFormPanel
|
||||
v-bind="page.formPanelProps.value"
|
||||
v-on="page.formPanelEvents"
|
||||
v-bind="formPanelProps"
|
||||
v-on="formPanelEvents"
|
||||
/>
|
||||
|
||||
<v-snackbar v-model="page.snackbarVisible.value" color="success" location="bottom right" :timeout="2200">
|
||||
<v-snackbar v-model="snackbarVisible" color="success" location="bottom right" :timeout="2200">
|
||||
儲存成功
|
||||
</v-snackbar>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user