pagetype字段类型控件实现按app优先级覆盖
This commit is contained in:
parent
0d258d3dfe
commit
f87dfd4400
@ -1,49 +1,134 @@
|
||||
// 字段控件覆盖机制
|
||||
// 约定:/src/views/pagetype/<pagetype>/form/controls/<fieldtype>.vue
|
||||
|
||||
// 由 Vite define 注入,用于 apps 优先级(靠后优先)
|
||||
declare const __APPS_ORDER__: string[]
|
||||
|
||||
type AsyncComponentLoader = () => Promise<any>
|
||||
|
||||
// 扫描所有可能的控件覆盖组件
|
||||
const allControlOverrides: Record<string, AsyncComponentLoader> = import.meta.glob(
|
||||
'/src/views/pagetype/**/form/controls/**.vue'
|
||||
)
|
||||
// 跨应用 + 本地扫描(与 pagetypeOverride 保持一致)
|
||||
const globAllApps = import.meta.glob('@apps/*/frontend/src/views/pagetype/**/form/controls/**.vue')
|
||||
const globLocal = import.meta.glob('/src/views/pagetype/**/form/controls/**.vue')
|
||||
|
||||
// 预计算路径映射,提高查找效率
|
||||
const pathMap = new Map<string, string>()
|
||||
type Entry = {
|
||||
loader: AsyncComponentLoader
|
||||
appName: string
|
||||
fullPath: string
|
||||
entity: string
|
||||
fieldtype: string
|
||||
}
|
||||
|
||||
// 初始化路径映射
|
||||
Object.keys(allControlOverrides).forEach(path => {
|
||||
const segments = path.split('/').filter(Boolean)
|
||||
if (segments.length >= 7) {
|
||||
const entity = segments[3]
|
||||
const fieldtype = segments[6].replace(/\.vue$/i, '')
|
||||
const key = `${entity}:${fieldtype}`
|
||||
pathMap.set(key, path)
|
||||
const allEntries: Entry[] = []
|
||||
|
||||
function appsOrder(): string[] {
|
||||
const order = Array.isArray(__APPS_ORDER__) && __APPS_ORDER__.length > 0 ? __APPS_ORDER__ : ['jingrow']
|
||||
try {
|
||||
// 日志:构建注入的应用优先级
|
||||
console.debug('[control-override] __APPS_ORDER__ =', order)
|
||||
} catch (_e) {}
|
||||
return order
|
||||
}
|
||||
|
||||
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\//, '/')
|
||||
// 本地 src 明确视为 jingrow
|
||||
if (s.startsWith('/src/')) return 'jingrow'
|
||||
// 1) (apps|@apps)/<app>/frontend
|
||||
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]
|
||||
// 2) 相对路径 ../../<app>/frontend/src/
|
||||
const reRel = /(?:^|\/)\.\.\/([^/]+)\/frontend\/src\//
|
||||
const m3 = s.match(reRel)
|
||||
if (m3 && m3[1] && m3[1] !== 'jingrow') return m3[1]
|
||||
// 3) 基于分段查找(同时识别 'apps' 与 '@apps')
|
||||
const parts = s.split('/').filter(Boolean)
|
||||
let i = parts.indexOf('apps')
|
||||
if (i < 0) i = parts.indexOf('@apps')
|
||||
if (i >= 0 && i + 1 < parts.length) return parts[i + 1]
|
||||
return 'jingrow'
|
||||
}
|
||||
|
||||
function parseEntityAndFieldtype(p: string): { entity: string; fieldtype: string } | null {
|
||||
const segs = p.split('/').filter(Boolean)
|
||||
// 形如 .../views/pagetype/<entity>/form/controls/<fieldtype>.vue
|
||||
const idxPagetype = segs.indexOf('pagetype')
|
||||
const idxControls = segs.lastIndexOf('controls')
|
||||
if (idxPagetype < 0 || idxControls < 0 || idxPagetype + 1 >= segs.length || idxControls + 1 >= segs.length) return null
|
||||
const entity = segs[idxPagetype + 1]
|
||||
const fieldFile = segs[idxControls + 1]
|
||||
const fieldtype = fieldFile.replace(/\.vue$/i, '')
|
||||
return { entity, fieldtype }
|
||||
}
|
||||
|
||||
function pushEntries(map: Record<string, any>) {
|
||||
for (const [p, loader] of Object.entries(map)) {
|
||||
const parsed = parseEntityAndFieldtype(p)
|
||||
if (!parsed) continue
|
||||
allEntries.push({
|
||||
loader: loader as AsyncComponentLoader,
|
||||
appName: extractAppName(p),
|
||||
fullPath: p,
|
||||
entity: parsed.entity,
|
||||
fieldtype: parsed.fieldtype
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pushEntries(globAllApps)
|
||||
pushEntries(globLocal)
|
||||
|
||||
/**
|
||||
* 解析并返回指定 pagetype 和 fieldtype 的控件覆盖组件
|
||||
* @param entity pagetype名称
|
||||
* @param fieldtype 字段类型
|
||||
* 解析并返回指定 pagetype 和 fieldtype 的控件覆盖组件(按 apps.txt 优先级,靠后优先)
|
||||
*/
|
||||
export async function resolveControlOverride(entity: string, fieldtype: string): Promise<any | null> {
|
||||
if (!entity || !fieldtype) return null
|
||||
|
||||
// 标准化输入
|
||||
const entityKey = entity.toLowerCase().replace(/-/g, '_')
|
||||
const fieldtypeKey = fieldtype.replace(/\s+/g, '')
|
||||
|
||||
// 直接查找匹配的路径
|
||||
const key = `${entityKey}:${fieldtypeKey}`
|
||||
const path = pathMap.get(key)
|
||||
|
||||
if (!path) return null
|
||||
|
||||
|
||||
const entityKey = String(entity).toLowerCase().replace(/-/g, '_')
|
||||
const fieldtypeKey = String(fieldtype).replace(/\s+/g, '')
|
||||
|
||||
let candidates = allEntries.filter(e => e.entity === entityKey && e.fieldtype === fieldtypeKey)
|
||||
console.debug('[control-override] resolve', { entityKey, fieldtypeKey, found: candidates.length })
|
||||
// 若未找到实体定制控件,尝试全局控件:约定 entity = _global
|
||||
if (candidates.length === 0) {
|
||||
console.debug('[control-override] fallback to _global for fieldtype', fieldtypeKey)
|
||||
candidates = allEntries.filter(e => e.entity === '_global' && e.fieldtype === fieldtypeKey)
|
||||
}
|
||||
if (candidates.length === 0) return null
|
||||
|
||||
candidates.sort((a, b) => {
|
||||
const ra = rankByAppsOrder(a.appName)
|
||||
const rb = rankByAppsOrder(b.appName)
|
||||
if (ra !== rb) return rb - ra // 靠后优先
|
||||
// 当 app 解析结果相同(可能都被识别为 jingrow)时,优先选择来自 apps 目录的候选
|
||||
const aIsExternal = a.fullPath.includes('/apps/') || a.fullPath.includes('@apps/') || (a.fullPath.includes('/@fs/') && a.fullPath.includes('/apps/'))
|
||||
const bIsExternal = b.fullPath.includes('/apps/') || b.fullPath.includes('@apps/') || (b.fullPath.includes('/@fs/') && 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)
|
||||
})
|
||||
|
||||
const pick = candidates[0]
|
||||
console.debug('[control-override] pick', {
|
||||
app: pick.appName,
|
||||
path: pick.fullPath,
|
||||
candidates: candidates.map(c => ({ app: c.appName, path: c.fullPath }))
|
||||
})
|
||||
try { console.log('[control-override] candidates-json', JSON.stringify(candidates.map(c => ({ app: c.appName, path: c.fullPath })))) } catch (_e) {}
|
||||
try {
|
||||
const module = await allControlOverrides[path]()
|
||||
return module?.default ?? module
|
||||
const mod = await pick.loader()
|
||||
return mod?.default ?? mod
|
||||
} catch {
|
||||
console.warn('[control-override] failed to load', pick.fullPath)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user