实现pagetype根据不同优先级执行模板覆盖
This commit is contained in:
parent
58bdd6ff57
commit
0d258d3dfe
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user