From f87dfd44004f919ec9ad10212e90a3e313bb05bc Mon Sep 17 00:00:00 2001 From: jingrow Date: Tue, 28 Oct 2025 18:51:01 +0800 Subject: [PATCH] =?UTF-8?q?pagetype=E5=AD=97=E6=AE=B5=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E5=AE=9E=E7=8E=B0=E6=8C=89app=E4=BC=98?= =?UTF-8?q?=E5=85=88=E7=BA=A7=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/core/registry/controlOverride.ts | 147 ++++++++++++++---- 1 file changed, 116 insertions(+), 31 deletions(-) diff --git a/apps/jingrow/frontend/src/core/registry/controlOverride.ts b/apps/jingrow/frontend/src/core/registry/controlOverride.ts index ebda61a..69235ef 100644 --- a/apps/jingrow/frontend/src/core/registry/controlOverride.ts +++ b/apps/jingrow/frontend/src/core/registry/controlOverride.ts @@ -1,49 +1,134 @@ // 字段控件覆盖机制 // 约定:/src/views/pagetype//form/controls/.vue +// 由 Vite define 注入,用于 apps 优先级(靠后优先) +declare const __APPS_ORDER__: string[] + type AsyncComponentLoader = () => Promise -// 扫描所有可能的控件覆盖组件 -const allControlOverrides: Record = 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() +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)//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) 相对路径 ../..//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//form/controls/.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) { + 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 { 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 } }