重构pagetype列表页和详情页路由分发逻辑,增加专门的路由分发层
This commit is contained in:
parent
44b55d4df1
commit
35aa3081ce
@ -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',
|
||||||
|
|||||||
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>
|
<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>
|
||||||
|
|||||||
@ -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 = ''
|
||||||
|
|||||||
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