重构pagetype列表页和详情页路由分发逻辑,增加专门的路由分发层
This commit is contained in:
parent
44b55d4df1
commit
35aa3081ce
@ -76,23 +76,23 @@ const router = createRouter({
|
||||
{
|
||||
path: 'app/:entity',
|
||||
name: 'PageTypeList',
|
||||
component: () => import('../../core/pagetype/GenericListPage.vue')
|
||||
component: () => import('../../core/pagetype/ListPage.vue')
|
||||
},
|
||||
{
|
||||
path: 'app/:entity/:id',
|
||||
name: 'PageTypeDetail',
|
||||
component: () => import('../../core/pagetype/GenericDetailPage.vue')
|
||||
component: () => import('../../core/pagetype/DetailPage.vue')
|
||||
},
|
||||
// 保持向后兼容
|
||||
{
|
||||
path: 'page/:entity',
|
||||
name: 'PageTypeListLegacy',
|
||||
component: () => import('../../core/pagetype/GenericListPage.vue')
|
||||
component: () => import('../../core/pagetype/ListPage.vue')
|
||||
},
|
||||
{
|
||||
path: 'page/:entity/:id',
|
||||
name: 'PageTypeDetailLegacy',
|
||||
component: () => import('../../core/pagetype/GenericDetailPage.vue')
|
||||
component: () => import('../../core/pagetype/DetailPage.vue')
|
||||
},
|
||||
{
|
||||
path: 'settings/menu',
|
||||
|
||||
51
apps/jingrow/frontend/src/core/pagetype/DetailPage.vue
Normal file
51
apps/jingrow/frontend/src/core/pagetype/DetailPage.vue
Normal 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>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<component :is="overrideComponent || 'div'" v-if="overrideComponent" />
|
||||
<div v-else class="generic-detail-page">
|
||||
<div class="generic-detail-page">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<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 { downloadImageToLocal } from '@/shared/api/common'
|
||||
import { usePageTypeSlug } from '@/shared/utils/slug'
|
||||
import { resolvePagetypeDetailOverride, resolvePagetypeToolbarOverride } from '@/core/registry/pagetypeOverride'
|
||||
import { resolvePagetypeToolbarOverride } from '@/core/registry/pagetypeOverride'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@ -330,7 +329,7 @@ const dialog = useDialog()
|
||||
|
||||
// 使用组合式函数处理URL slug
|
||||
const { pagetypeSlug, entity } = usePageTypeSlug(route)
|
||||
const overrideComponent = shallowRef<any | null>(null)
|
||||
// 覆盖组件引用(工具栏覆盖,整体覆盖由 DetailPage.vue 处理)
|
||||
const toolbarComponent = shallowRef<any | null>(null)
|
||||
const id = computed(() => route.params.id as string)
|
||||
const isNew = computed(() => {
|
||||
@ -1144,14 +1143,10 @@ onMounted(async () => {
|
||||
// 加载侧边栏用户偏好设置
|
||||
loadSidebarPreferences()
|
||||
|
||||
// 优先解析 apps 中的覆盖组件(使用 URL 原始 slug,解析器内部会做 - 到 _ 的转换)
|
||||
const comp = await resolvePagetypeDetailOverride(pagetypeSlug.value)
|
||||
overrideComponent.value = comp ? markRaw(comp) : null
|
||||
// 解析工具栏覆盖组件
|
||||
const tb = await resolvePagetypeToolbarOverride(pagetypeSlug.value)
|
||||
toolbarComponent.value = tb ? markRaw(tb) : null
|
||||
if (overrideComponent.value) {
|
||||
return
|
||||
}
|
||||
|
||||
await loadMeta()
|
||||
await loadDetail()
|
||||
})
|
||||
@ -1164,23 +1159,17 @@ onUnmounted(() => {
|
||||
// 监听路由参数变化
|
||||
watch(() => route.params.id, async (newId, oldId) => {
|
||||
if (newId && newId !== oldId) {
|
||||
if (!overrideComponent.value) {
|
||||
await loadDetail()
|
||||
}
|
||||
await loadDetail()
|
||||
}
|
||||
})
|
||||
|
||||
// 监听 entity/slug 变化,重新解析覆盖组件
|
||||
watch(() => route.params.entity, async (newEntity, oldEntity) => {
|
||||
if (newEntity !== oldEntity) {
|
||||
const comp = await resolvePagetypeDetailOverride(String(newEntity))
|
||||
overrideComponent.value = comp ? markRaw(comp) : null
|
||||
const tb = await resolvePagetypeToolbarOverride(String(newEntity))
|
||||
toolbarComponent.value = tb ? markRaw(tb) : null
|
||||
if (!overrideComponent.value) {
|
||||
await loadMeta()
|
||||
await loadDetail()
|
||||
}
|
||||
await loadMeta()
|
||||
await loadDetail()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<!-- 如果是单页模式,直接显示单页详情组件 -->
|
||||
<SinglePageDetail v-if="isSinglePage" />
|
||||
<!-- 列表页整体覆盖 -->
|
||||
<component :is="listOverrideComponent || 'div'" v-else-if="listOverrideComponent" />
|
||||
<div v-else class="page">
|
||||
<!-- 头部,与 AI 智能体列表一致的结构 -->
|
||||
<div class="page-header">
|
||||
@ -303,7 +301,6 @@ import GenericListPageToolBar from './GenericListPageToolBar.vue'
|
||||
import GenericListPageFilterBar from './GenericListPageFilterBar.vue'
|
||||
import GenericListPageActions from './GenericListPageActions.vue'
|
||||
import {
|
||||
resolvePagetypeListOverride,
|
||||
resolvePagetypeListToolbarOverride,
|
||||
resolvePagetypeListFilterBarOverride,
|
||||
resolvePagetypeListActionsOverride
|
||||
@ -319,8 +316,7 @@ const dialog = useDialog()
|
||||
const { pagetypeSlug, entity } = usePageTypeSlug(route)
|
||||
const title = computed(() => entity.value)
|
||||
|
||||
// 覆盖组件引用
|
||||
const listOverrideComponent = shallowRef<any | null>(null)
|
||||
// 覆盖组件引用(子组件覆盖,整体覆盖由 ListPage.vue 处理)
|
||||
const toolbarComponent = shallowRef<any | null>(null)
|
||||
const filterBarComponent = shallowRef<any | null>(null)
|
||||
const actionsComponent = shallowRef<any | null>(null)
|
||||
@ -913,10 +909,7 @@ async function loadData() {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// 优先解析覆盖组件
|
||||
const listComp = await resolvePagetypeListOverride(pagetypeSlug.value)
|
||||
listOverrideComponent.value = listComp ? markRaw(listComp) : null
|
||||
|
||||
// 解析子组件覆盖(工具栏、过滤栏、操作栏)
|
||||
const listToolbarComp = await resolvePagetypeListToolbarOverride(pagetypeSlug.value)
|
||||
toolbarComponent.value = listToolbarComp ? markRaw(listToolbarComp) : null
|
||||
|
||||
@ -926,11 +919,6 @@ onMounted(async () => {
|
||||
const listActionsComp = await resolvePagetypeListActionsOverride(pagetypeSlug.value)
|
||||
actionsComponent.value = listActionsComp ? markRaw(listActionsComp) : null
|
||||
|
||||
// 如果列表页被完全覆盖,直接返回
|
||||
if (listOverrideComponent.value) {
|
||||
return
|
||||
}
|
||||
|
||||
await loadMeta()
|
||||
// 只有在非单页模式下才加载列表数据
|
||||
if (!isSinglePage.value) {
|
||||
@ -946,10 +934,7 @@ watch([page], () => {
|
||||
// 监听路由参数变化,当entity变化时重新加载数据
|
||||
watch(() => route.params.entity, async (newEntity, oldEntity) => {
|
||||
if (newEntity !== oldEntity) {
|
||||
// 重新解析覆盖组件
|
||||
const listComp = await resolvePagetypeListOverride(String(newEntity))
|
||||
listOverrideComponent.value = listComp ? markRaw(listComp) : null
|
||||
|
||||
// 重新解析子组件覆盖
|
||||
const listToolbarComp = await resolvePagetypeListToolbarOverride(String(newEntity))
|
||||
toolbarComponent.value = listToolbarComp ? markRaw(listToolbarComp) : null
|
||||
|
||||
@ -959,11 +944,6 @@ watch(() => route.params.entity, async (newEntity, oldEntity) => {
|
||||
const listActionsComp = await resolvePagetypeListActionsOverride(String(newEntity))
|
||||
actionsComponent.value = listActionsComp ? markRaw(listActionsComp) : null
|
||||
|
||||
// 如果列表页被完全覆盖,直接返回
|
||||
if (listOverrideComponent.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 重置分页和搜索
|
||||
page.value = 1
|
||||
searchQuery.value = ''
|
||||
|
||||
48
apps/jingrow/frontend/src/core/pagetype/ListPage.vue
Normal file
48
apps/jingrow/frontend/src/core/pagetype/ListPage.vue
Normal 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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user