pagetype字段类型控件实现按app优先级覆盖

This commit is contained in:
jingrow 2025-10-28 18:51:01 +08:00
parent 0d258d3dfe
commit f87dfd4400

View File

@ -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
}
}