重构pagetype列表页和详情页路由分发逻辑,增加专门的路由分发层

This commit is contained in:
jingrow 2025-11-02 18:14:59 +08:00
parent 44b55d4df1
commit 35aa3081ce
5 changed files with 114 additions and 46 deletions

View File

@ -76,23 +76,23 @@ const router = createRouter({
{ {
path: 'app/:entity', path: 'app/:entity',
name: 'PageTypeList', name: 'PageTypeList',
component: () => import('../../core/pagetype/GenericListPage.vue') component: () => import('../../core/pagetype/ListPage.vue')
}, },
{ {
path: 'app/:entity/:id', path: 'app/:entity/:id',
name: 'PageTypeDetail', name: 'PageTypeDetail',
component: () => import('../../core/pagetype/GenericDetailPage.vue') component: () => import('../../core/pagetype/DetailPage.vue')
}, },
// 保持向后兼容 // 保持向后兼容
{ {
path: 'page/:entity', path: 'page/:entity',
name: 'PageTypeListLegacy', name: 'PageTypeListLegacy',
component: () => import('../../core/pagetype/GenericListPage.vue') component: () => import('../../core/pagetype/ListPage.vue')
}, },
{ {
path: 'page/:entity/:id', path: 'page/:entity/:id',
name: 'PageTypeDetailLegacy', name: 'PageTypeDetailLegacy',
component: () => import('../../core/pagetype/GenericDetailPage.vue') component: () => import('../../core/pagetype/DetailPage.vue')
}, },
{ {
path: 'settings/menu', path: 'settings/menu',

View File

@ -0,0 +1,51 @@
<template>
<!-- 详情页路由分发器决定使用覆盖组件还是默认组件 -->
<component
v-if="overrideComponent"
:is="overrideComponent"
:context="context"
/>
<GenericDetailPage v-else />
</template>
<script setup lang="ts">
import { onMounted, shallowRef, markRaw, computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { usePageTypeSlug } from '@/shared/utils/slug'
import { resolvePagetypeDetailOverride } from '@/core/registry/pagetypeOverride'
import GenericDetailPage from './GenericDetailPage.vue'
const route = useRoute()
const router = useRouter()
const { pagetypeSlug } = usePageTypeSlug(route)
const overrideComponent = shallowRef<any | null>(null)
// context GenericDetailPage
const context = computed(() => ({
route,
router,
pagetypeSlug: pagetypeSlug.value,
entity: route.params.entity as string,
id: route.params.id as string
}))
onMounted(async () => {
//
const detailComp = await resolvePagetypeDetailOverride(pagetypeSlug.value)
overrideComponent.value = detailComp ? markRaw(detailComp) : null
})
// entity id
watch(() => [route.params.entity, route.params.id], async ([newEntity, newId], [oldEntity, oldId]) => {
if (newEntity !== oldEntity || newId !== oldId) {
const detailComp = await resolvePagetypeDetailOverride(String(newEntity || pagetypeSlug.value))
overrideComponent.value = detailComp ? markRaw(detailComp) : null
}
}, { immediate: false })
</script>
<style scoped>
/* DetailPage 仅作为路由分发器,不需要样式 */
</style>

View File

@ -1,6 +1,5 @@
<template> <template>
<component :is="overrideComponent || 'div'" v-if="overrideComponent" /> <div class="generic-detail-page">
<div v-else class="generic-detail-page">
<!-- 页面头部 --> <!-- 页面头部 -->
<div class="page-header"> <div class="page-header">
<n-space justify="space-between" align="center"> <n-space justify="space-between" align="center">
@ -321,7 +320,7 @@ import { get_session_api_headers } from '@/shared/api/auth'
import { updateRecord, getRecord, getRecordAttachments, deleteAttachment, uploadAttachment, deleteRecords } from '@/shared/api/common' import { updateRecord, getRecord, getRecordAttachments, deleteAttachment, uploadAttachment, deleteRecords } from '@/shared/api/common'
import { downloadImageToLocal } from '@/shared/api/common' import { downloadImageToLocal } from '@/shared/api/common'
import { usePageTypeSlug } from '@/shared/utils/slug' import { usePageTypeSlug } from '@/shared/utils/slug'
import { resolvePagetypeDetailOverride, resolvePagetypeToolbarOverride } from '@/core/registry/pagetypeOverride' import { resolvePagetypeToolbarOverride } from '@/core/registry/pagetypeOverride'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -330,7 +329,7 @@ const dialog = useDialog()
// 使URL slug // 使URL slug
const { pagetypeSlug, entity } = usePageTypeSlug(route) const { pagetypeSlug, entity } = usePageTypeSlug(route)
const overrideComponent = shallowRef<any | null>(null) // DetailPage.vue
const toolbarComponent = shallowRef<any | null>(null) const toolbarComponent = shallowRef<any | null>(null)
const id = computed(() => route.params.id as string) const id = computed(() => route.params.id as string)
const isNew = computed(() => { const isNew = computed(() => {
@ -1144,14 +1143,10 @@ onMounted(async () => {
// //
loadSidebarPreferences() loadSidebarPreferences()
// apps 使 URL slug - _ //
const comp = await resolvePagetypeDetailOverride(pagetypeSlug.value)
overrideComponent.value = comp ? markRaw(comp) : null
const tb = await resolvePagetypeToolbarOverride(pagetypeSlug.value) const tb = await resolvePagetypeToolbarOverride(pagetypeSlug.value)
toolbarComponent.value = tb ? markRaw(tb) : null toolbarComponent.value = tb ? markRaw(tb) : null
if (overrideComponent.value) {
return
}
await loadMeta() await loadMeta()
await loadDetail() await loadDetail()
}) })
@ -1164,23 +1159,17 @@ onUnmounted(() => {
// //
watch(() => route.params.id, async (newId, oldId) => { watch(() => route.params.id, async (newId, oldId) => {
if (newId && newId !== oldId) { if (newId && newId !== oldId) {
if (!overrideComponent.value) { await loadDetail()
await loadDetail()
}
} }
}) })
// entity/slug // entity/slug
watch(() => route.params.entity, async (newEntity, oldEntity) => { watch(() => route.params.entity, async (newEntity, oldEntity) => {
if (newEntity !== oldEntity) { if (newEntity !== oldEntity) {
const comp = await resolvePagetypeDetailOverride(String(newEntity))
overrideComponent.value = comp ? markRaw(comp) : null
const tb = await resolvePagetypeToolbarOverride(String(newEntity)) const tb = await resolvePagetypeToolbarOverride(String(newEntity))
toolbarComponent.value = tb ? markRaw(tb) : null toolbarComponent.value = tb ? markRaw(tb) : null
if (!overrideComponent.value) { await loadMeta()
await loadMeta() await loadDetail()
await loadDetail()
}
} }
}) })
</script> </script>

View File

@ -1,8 +1,6 @@
<template> <template>
<!-- 如果是单页模式直接显示单页详情组件 --> <!-- 如果是单页模式直接显示单页详情组件 -->
<SinglePageDetail v-if="isSinglePage" /> <SinglePageDetail v-if="isSinglePage" />
<!-- 列表页整体覆盖 -->
<component :is="listOverrideComponent || 'div'" v-else-if="listOverrideComponent" />
<div v-else class="page"> <div v-else class="page">
<!-- 头部 AI 智能体列表一致的结构 --> <!-- 头部 AI 智能体列表一致的结构 -->
<div class="page-header"> <div class="page-header">
@ -303,7 +301,6 @@ import GenericListPageToolBar from './GenericListPageToolBar.vue'
import GenericListPageFilterBar from './GenericListPageFilterBar.vue' import GenericListPageFilterBar from './GenericListPageFilterBar.vue'
import GenericListPageActions from './GenericListPageActions.vue' import GenericListPageActions from './GenericListPageActions.vue'
import { import {
resolvePagetypeListOverride,
resolvePagetypeListToolbarOverride, resolvePagetypeListToolbarOverride,
resolvePagetypeListFilterBarOverride, resolvePagetypeListFilterBarOverride,
resolvePagetypeListActionsOverride resolvePagetypeListActionsOverride
@ -319,8 +316,7 @@ const dialog = useDialog()
const { pagetypeSlug, entity } = usePageTypeSlug(route) const { pagetypeSlug, entity } = usePageTypeSlug(route)
const title = computed(() => entity.value) const title = computed(() => entity.value)
// // ListPage.vue
const listOverrideComponent = shallowRef<any | null>(null)
const toolbarComponent = shallowRef<any | null>(null) const toolbarComponent = shallowRef<any | null>(null)
const filterBarComponent = shallowRef<any | null>(null) const filterBarComponent = shallowRef<any | null>(null)
const actionsComponent = shallowRef<any | null>(null) const actionsComponent = shallowRef<any | null>(null)
@ -913,10 +909,7 @@ async function loadData() {
} }
onMounted(async () => { onMounted(async () => {
// //
const listComp = await resolvePagetypeListOverride(pagetypeSlug.value)
listOverrideComponent.value = listComp ? markRaw(listComp) : null
const listToolbarComp = await resolvePagetypeListToolbarOverride(pagetypeSlug.value) const listToolbarComp = await resolvePagetypeListToolbarOverride(pagetypeSlug.value)
toolbarComponent.value = listToolbarComp ? markRaw(listToolbarComp) : null toolbarComponent.value = listToolbarComp ? markRaw(listToolbarComp) : null
@ -926,11 +919,6 @@ onMounted(async () => {
const listActionsComp = await resolvePagetypeListActionsOverride(pagetypeSlug.value) const listActionsComp = await resolvePagetypeListActionsOverride(pagetypeSlug.value)
actionsComponent.value = listActionsComp ? markRaw(listActionsComp) : null actionsComponent.value = listActionsComp ? markRaw(listActionsComp) : null
//
if (listOverrideComponent.value) {
return
}
await loadMeta() await loadMeta()
// //
if (!isSinglePage.value) { if (!isSinglePage.value) {
@ -946,10 +934,7 @@ watch([page], () => {
// entity // entity
watch(() => route.params.entity, async (newEntity, oldEntity) => { watch(() => route.params.entity, async (newEntity, oldEntity) => {
if (newEntity !== oldEntity) { if (newEntity !== oldEntity) {
// //
const listComp = await resolvePagetypeListOverride(String(newEntity))
listOverrideComponent.value = listComp ? markRaw(listComp) : null
const listToolbarComp = await resolvePagetypeListToolbarOverride(String(newEntity)) const listToolbarComp = await resolvePagetypeListToolbarOverride(String(newEntity))
toolbarComponent.value = listToolbarComp ? markRaw(listToolbarComp) : null toolbarComponent.value = listToolbarComp ? markRaw(listToolbarComp) : null
@ -959,11 +944,6 @@ watch(() => route.params.entity, async (newEntity, oldEntity) => {
const listActionsComp = await resolvePagetypeListActionsOverride(String(newEntity)) const listActionsComp = await resolvePagetypeListActionsOverride(String(newEntity))
actionsComponent.value = listActionsComp ? markRaw(listActionsComp) : null actionsComponent.value = listActionsComp ? markRaw(listActionsComp) : null
//
if (listOverrideComponent.value) {
return
}
// //
page.value = 1 page.value = 1
searchQuery.value = '' searchQuery.value = ''

View File

@ -0,0 +1,48 @@
<template>
<!-- 列表页路由分发器决定使用覆盖组件还是默认组件 -->
<component
v-if="overrideComponent"
:is="overrideComponent"
:context="context"
/>
<GenericListPage v-else />
</template>
<script setup lang="ts">
import { onMounted, shallowRef, markRaw, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
import { usePageTypeSlug } from '@/shared/utils/slug'
import { resolvePagetypeListOverride } from '@/core/registry/pagetypeOverride'
import GenericListPage from './GenericListPage.vue'
const route = useRoute()
const { pagetypeSlug } = usePageTypeSlug(route)
const overrideComponent = shallowRef<any | null>(null)
// context GenericListPage
const context = computed(() => ({
route,
pagetypeSlug: pagetypeSlug.value,
entity: route.params.entity as string
}))
onMounted(async () => {
//
const listComp = await resolvePagetypeListOverride(pagetypeSlug.value)
overrideComponent.value = listComp ? markRaw(listComp) : null
})
// entity
watch(() => route.params.entity, async (newEntity, oldEntity) => {
if (newEntity !== oldEntity) {
const listComp = await resolvePagetypeListOverride(String(newEntity))
overrideComponent.value = listComp ? markRaw(listComp) : null
}
})
</script>
<style scoped>
/* ListPage 仅作为路由分发器,不需要样式 */
</style>