实现pagetype根据不同优先级执行模板覆盖

This commit is contained in:
jingrow 2025-10-28 17:43:34 +08:00
parent 58bdd6ff57
commit 0d258d3dfe
2 changed files with 100 additions and 33 deletions

View File

@ -1,65 +1,108 @@
// 基于约定路径自动解析 pagetype 详情覆盖组件
// 约定:/src/views/pagetype/<pagetype>/<pagetype>.vue
// 基于约定路径自动解析 pagetype 覆盖组件
// 详情:/src/views/pagetype/<pagetype>/<pagetype>.vue
// 工具栏:/src/views/pagetype/<pagetype>/<pagetype>_toolbar.vue
// 由 Vite define 注入,类型声明便于 TS
declare const __APPS_ORDER__: string[]
type AsyncComponentLoader = () => Promise<any>
// 扫描所有可能的覆盖组件(仅限 .vue
// 使用 eager: false 延迟加载,生产环境下可按需分包
const allPagetypeViews: Record<string, AsyncComponentLoader> = import.meta.glob(
'/src/views/pagetype/**/**.vue'
)
// - 扫描所有 apps 下的前端视图:@apps/*/frontend/src/views/pagetype
// - 同时扫描当前应用 src 下的视图:/src/views/pagetype
// 说明:@apps 别名与 devServer.fs.allow 已在 vite.config.ts 放行
const globAllApps = import.meta.glob('@apps/*/frontend/src/views/pagetype/**/**.vue')
const globLocal = import.meta.glob('/src/views/pagetype/**/**.vue')
type SourceEntry = {
loader: AsyncComponentLoader
appName: string
fullPath: string
}
const allPagetypeViews: Record<string, SourceEntry> = {}
function getPathSegments(path: string): string[] {
// 统一分隔符
return path.split('/').filter(Boolean)
}
function extractAppName(absPath: string): string {
// 形如 @apps/<app>/frontend/src/...
const parts = absPath.split('/')
const idx = parts.indexOf('apps')
if (idx >= 0 && idx + 1 < parts.length) {
return parts[idx + 1]
}
// 本地 src 视为 jingrow核心
return 'jingrow'
}
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
}
for (const [p, loader] of Object.entries(globAllApps)) {
const appName = extractAppName(p)
allPagetypeViews[p] = { loader: loader as AsyncComponentLoader, appName, fullPath: p }
}
for (const [p, loader] of Object.entries(globLocal)) {
const appName = extractAppName(p)
allPagetypeViews[p] = { loader: loader as AsyncComponentLoader, appName, fullPath: p }
}
function sortByPriority(a: string, b: string): number {
const ra = rankByAppsOrder(allPagetypeViews[a].appName)
const rb = rankByAppsOrder(allPagetypeViews[b].appName)
// apps.txt 靠后优先 => 更大的索引优先
if (ra !== rb) return rb - ra
// 同一应用下:路径更短优先,其次字母序
if (a.length !== b.length) return a.length - b.length
return a.localeCompare(b)
}
/**
* pagetype
* @param pagetypeSlug pagetype线
*/
export async function resolvePagetypeDetailOverride(pagetypeSlug: string): Promise<any | null> {
if (!pagetypeSlug) return null
// URL 使用短横线,文件夹使用下划线,与后端保持一致
const targetHyphen = pagetypeSlug.toLowerCase()
const targetUnderscore = targetHyphen.replace(/-/g, '_')
// 匹配形如 views/pagetype/<name>/<name>.vue 的文件
const candidates = Object.keys(allPagetypeViews).filter((file) => {
const segs = getPathSegments(file)
const len = segs.length
if (len < 5) return false
// 路径应为 views/pagetype/<name>/<name>.vue
const fileName = segs[len - 1]
const folderName = segs[len - 2]
const baseName = fileName.replace(/\.vue$/i, '')
// 需要同时满足:位于 views/pagetype 下,且末两级目录名与目标一致(支持下划线形式)
return (
segs[1] === 'views' &&
segs[2] === 'pagetype' &&
folderName === targetUnderscore &&
segs.includes('views') &&
segs.includes('pagetype') &&
folderName === targetUnderscore &&
baseName === targetUnderscore
)
})
if (candidates.length === 0) return null
// 若有多个命中,按路径长度最短优先(更接近根的优先),其次按字母序
candidates.sort((a, b) => {
if (a.length !== b.length) return a.length - b.length
return a.localeCompare(b)
})
candidates.sort(sortByPriority)
const pick = candidates[0]
try {
const mod = await allPagetypeViews[pick]()
const mod = await allPagetypeViews[pick].loader()
return mod?.default ?? mod
} catch (_e) {
return null
}
}
/**
* pagetype
* /src/views/pagetype/<name>/<name>_toolbar.vue
@ -73,32 +116,27 @@ export async function resolvePagetypeToolbarOverride(pagetypeSlug: string): Prom
const segs = getPathSegments(file)
const len = segs.length
if (len < 5) return false
// 路径应为 views/pagetype/<name>/<name>_toolbar.vue
const fileName = segs[len - 1]
const folderName = segs[len - 2]
const baseName = fileName.replace(/\.vue$/i, '')
return (
segs[1] === 'views' &&
segs[2] === 'pagetype' &&
folderName === targetUnderscore &&
segs.includes('views') &&
segs.includes('pagetype') &&
folderName === targetUnderscore &&
baseName === `${targetUnderscore}_toolbar`
)
})
if (candidates.length === 0) return null
candidates.sort((a, b) => {
if (a.length !== b.length) return a.length - b.length
return a.localeCompare(b)
})
candidates.sort(sortByPriority)
const pick = candidates[0]
try {
const mod = await allPagetypeViews[pick]()
const mod = await allPagetypeViews[pick].loader()
return mod?.default ?? mod
} catch (_e) {
return null
}
}

View File

@ -5,6 +5,27 @@ import { fileURLToPath, URL } from 'node:url'
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
import Components from 'unplugin-vue-components/vite'
import fs from 'node:fs'
import path from 'node:path'
// 读取 apps.txt 确定应用优先级(靠后优先)
function loadAppsOrder(appsDir: string) {
const appsTxt = path.join(appsDir, 'apps.txt')
try {
const content = fs.readFileSync(appsTxt, 'utf-8')
return content
.split(/\r?\n/)
.map((s) => s.trim())
.filter(Boolean)
} catch {
return ['jingrow']
}
}
// 计算本工程中的 apps 目录(当前文件位于 apps/jingrow/frontend/vite.config.ts
const currentDir = fileURLToPath(new URL('.', import.meta.url))
const appsDir = path.resolve(currentDir, '..', '..')
const APPS_ORDER = loadAppsOrder(appsDir)
export default defineConfig({
plugins: [
@ -24,7 +45,9 @@ export default defineConfig({
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
'@': fileURLToPath(new URL('./src', import.meta.url)),
// 跨 app 访问源码(相对计算得到的 apps 目录)
'@apps': appsDir
}
},
server: {
@ -33,6 +56,10 @@ export default defineConfig({
strictPort: true,
open: false,
cors: true,
fs: {
// 放行 monorepo apps 目录,便于 import.meta.glob 跨应用扫描
allow: [appsDir]
},
allowedHosts: ['code.jingrow.com'],
proxy: {
'/api/action': {
@ -58,6 +85,8 @@ export default defineConfig({
},
define: {
// 确保环境变量在构建时可用
__APP_VERSION__: JSON.stringify(process.env.npm_package_version)
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
// 注入 apps.txt 的应用顺序到前端
__APPS_ORDER__: JSON.stringify(APPS_ORDER)
}
})