pagetype的sidebar实现app优先级覆盖机制
This commit is contained in:
parent
6ce935f34a
commit
b06109f675
@ -260,10 +260,24 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { NButton, NSpace, NIcon, useMessage, NLayout, NLayoutSider, NLayoutContent } from 'naive-ui'
|
||||
import FieldRenderer from '@/core/components/form/FieldRenderer.vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import FormPanel from '@/core/components/form/panel/FormPanel.vue'
|
||||
import ImageSection from '@/core/components/form/panel/ImageSection.vue'
|
||||
import AttachmentSection from '@/core/components/form/panel/AttachmentSection.vue'
|
||||
import TagSection from '@/core/components/form/panel/TagSection.vue'
|
||||
import { resolveSidebarPanel } from '@/core/registry/sidebarOverride'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
const FormPanel = defineAsyncComponent(async () => {
|
||||
const mod = await resolveSidebarPanel(entity.value, 'FormPanel.vue')
|
||||
return mod || (await import('@/core/components/form/panel/FormPanel.vue'))
|
||||
})
|
||||
const ImageSection = defineAsyncComponent(async () => {
|
||||
const mod = await resolveSidebarPanel(entity.value, 'ImageSection.vue')
|
||||
return mod || (await import('@/core/components/form/panel/ImageSection.vue'))
|
||||
})
|
||||
const AttachmentSection = defineAsyncComponent(async () => {
|
||||
const mod = await resolveSidebarPanel(entity.value, 'AttachmentSection.vue')
|
||||
return mod || (await import('@/core/components/form/panel/AttachmentSection.vue'))
|
||||
})
|
||||
const TagSection = defineAsyncComponent(async () => {
|
||||
const mod = await resolveSidebarPanel(entity.value, 'TagSection.vue')
|
||||
return mod || (await import('@/core/components/form/panel/TagSection.vue'))
|
||||
})
|
||||
import axios from 'axios'
|
||||
import { t } from '@/shared/i18n'
|
||||
import { get_session_api_headers } from '@/shared/api/auth'
|
||||
|
||||
151
apps/jingrow/frontend/src/core/registry/sidebarOverride.ts
Normal file
151
apps/jingrow/frontend/src/core/registry/sidebarOverride.ts
Normal file
@ -0,0 +1,151 @@
|
||||
// 侧边栏相关组件覆盖机制,优先级:
|
||||
// 1) pagetype 视图下的 panel 组件(entity 专属)
|
||||
// - @apps/*/frontend/src/views/pagetype/<entity>/form/panel/<Comp>.vue
|
||||
// - /src/views/pagetype/<entity>/form/panel/<Comp>.vue
|
||||
// 2) 核心路径的 panel 组件(源路径级覆盖)
|
||||
// - @apps/*/frontend/src/core/components/form/panel/<Comp>.vue
|
||||
// - /src/core/components/form/panel/<Comp>.vue
|
||||
// 组件名 <Comp>:FormPanel.vue / ImageSection.vue / AttachmentSection.vue / TagSection.vue
|
||||
|
||||
declare const __APPS_ORDER__: string[]
|
||||
|
||||
type AsyncComponentLoader = () => Promise<any>
|
||||
|
||||
// 核心路径扫描
|
||||
const globCoreApps = import.meta.glob('@apps/*/frontend/src/core/**/**.vue')
|
||||
const globCoreLocal = import.meta.glob('/src/core/**/**.vue')
|
||||
// pagetype 视图下的 panel 扫描
|
||||
const globViewApps = import.meta.glob('@apps/*/frontend/src/views/pagetype/**/form/panel/**.vue')
|
||||
const globViewLocal = import.meta.glob('/src/views/pagetype/**/form/panel/**.vue')
|
||||
|
||||
type Entry = {
|
||||
loader: AsyncComponentLoader
|
||||
appName: string
|
||||
fullPath: string
|
||||
key?: string // 核心源路径键(相对 src)
|
||||
entity?: string // 视图下的 entity
|
||||
compName: string // 组件名(文件名)
|
||||
}
|
||||
|
||||
function appsOrder(): string[] {
|
||||
return Array.isArray(__APPS_ORDER__) && __APPS_ORDER__.length > 0 ? __APPS_ORDER__ : ['jingrow']
|
||||
}
|
||||
|
||||
function rankByAppsOrder(appName: string): number {
|
||||
const order = appsOrder()
|
||||
const idx = order.indexOf(appName)
|
||||
return idx >= 0 ? idx : -1
|
||||
}
|
||||
|
||||
function extractAppName(p: string): string {
|
||||
const s = p.replace(/\\/g, '/').replace(/^\/@fs\//, '/')
|
||||
if (s.startsWith('/src/')) return 'jingrow'
|
||||
const reApps1 = /(?:^|\/)apps\/([^/]+)\/frontend\//
|
||||
const reApps2 = /(?:^|\/)@apps\/([^/]+)\/frontend\//
|
||||
const m1 = s.match(reApps1)
|
||||
if (m1 && m1[1]) return m1[1]
|
||||
const m2 = s.match(reApps2)
|
||||
if (m2 && m2[1]) return m2[1]
|
||||
return 'jingrow'
|
||||
}
|
||||
|
||||
function toKey(p: string): string | null {
|
||||
const s = p.replace(/\\/g, '/').replace(/^\/@fs\//, '/')
|
||||
const idx = s.indexOf('/src/')
|
||||
if (idx >= 0) return s.substring(idx + '/src/'.length)
|
||||
return null
|
||||
}
|
||||
|
||||
function parseCompName(p: string): string {
|
||||
const s = p.replace(/\\/g, '/')
|
||||
const parts = s.split('/')
|
||||
const last = parts[parts.length - 1] || ''
|
||||
return last
|
||||
}
|
||||
|
||||
function parseEntityFromViewPath(p: string): string | null {
|
||||
const s = p.replace(/\\/g, '/')
|
||||
const parts = s.split('/')
|
||||
const idx = parts.indexOf('pagetype')
|
||||
if (idx >= 0 && idx + 1 < parts.length) return parts[idx + 1]
|
||||
return null
|
||||
}
|
||||
|
||||
const allEntries: Entry[] = []
|
||||
|
||||
// 核心路径 entries
|
||||
for (const [p, loader] of Object.entries(globCoreApps)) {
|
||||
const key = toKey(p)
|
||||
if (!key) continue
|
||||
allEntries.push({ loader: loader as AsyncComponentLoader, appName: extractAppName(p), fullPath: p, key, compName: parseCompName(p) })
|
||||
}
|
||||
for (const [p, loader] of Object.entries(globCoreLocal)) {
|
||||
const key = toKey(p)
|
||||
if (!key) continue
|
||||
allEntries.push({ loader: loader as AsyncComponentLoader, appName: extractAppName(p), fullPath: p, key, compName: parseCompName(p) })
|
||||
}
|
||||
|
||||
// 视图路径 entries(带 entity)
|
||||
for (const [p, loader] of Object.entries(globViewApps)) {
|
||||
const entity = parseEntityFromViewPath(p)
|
||||
if (!entity) continue
|
||||
allEntries.push({ loader: loader as AsyncComponentLoader, appName: extractAppName(p), fullPath: p, entity, compName: parseCompName(p) })
|
||||
}
|
||||
for (const [p, loader] of Object.entries(globViewLocal)) {
|
||||
const entity = parseEntityFromViewPath(p)
|
||||
if (!entity) continue
|
||||
allEntries.push({ loader: loader as AsyncComponentLoader, appName: extractAppName(p), fullPath: p, entity, compName: parseCompName(p) })
|
||||
}
|
||||
|
||||
// 索引:
|
||||
// - viewIndex[(entity, compName)] -> entries(pagetype 覆盖)
|
||||
// - coreIndex[(compName)] -> entries(核心路径覆盖)
|
||||
const viewIndex: Record<string, Entry[]> = {}
|
||||
const coreIndex: Record<string, Entry[]> = {}
|
||||
|
||||
for (const e of allEntries) {
|
||||
if (e.entity) {
|
||||
const key = `${e.entity}:${e.compName}`
|
||||
if (!viewIndex[key]) viewIndex[key] = []
|
||||
viewIndex[key].push(e)
|
||||
} else if (e.key) {
|
||||
const key = e.compName
|
||||
if (!coreIndex[key]) coreIndex[key] = []
|
||||
coreIndex[key].push(e)
|
||||
}
|
||||
}
|
||||
|
||||
function sortEntries(list: Entry[]) {
|
||||
list.sort((a, b) => {
|
||||
const ra = rankByAppsOrder(a.appName)
|
||||
const rb = rankByAppsOrder(b.appName)
|
||||
if (ra !== rb) return rb - ra
|
||||
const aIsExternal = a.fullPath.includes('/apps/') || a.fullPath.includes('@apps/')
|
||||
const bIsExternal = b.fullPath.includes('/apps/') || b.fullPath.includes('@apps/')
|
||||
if (aIsExternal !== bIsExternal) return aIsExternal ? -1 : 1
|
||||
if (a.fullPath.length !== b.fullPath.length) return a.fullPath.length - b.fullPath.length
|
||||
return a.fullPath.localeCompare(b.fullPath)
|
||||
})
|
||||
}
|
||||
|
||||
for (const k of Object.keys(viewIndex)) sortEntries(viewIndex[k])
|
||||
for (const k of Object.keys(coreIndex)) sortEntries(coreIndex[k])
|
||||
|
||||
// 解析侧栏面板组件:优先 entity 下的视图 panel,其次核心路径 panel
|
||||
export async function resolveSidebarPanel(entity: string, compName: string): Promise<any | null> {
|
||||
const entityKey = String(entity).toLowerCase().replace(/-/g, '_')
|
||||
const name = compName.endsWith('.vue') ? compName : `${compName}.vue`
|
||||
let bucket = viewIndex[`${entityKey}:${name}`]
|
||||
if ((!bucket || bucket.length === 0)) {
|
||||
bucket = coreIndex[name]
|
||||
}
|
||||
if (!bucket || bucket.length === 0) return null
|
||||
try {
|
||||
const mod = await bucket[0].loader()
|
||||
return mod?.default ?? mod
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user