实现pagetype根据不同优先级执行模板覆盖
This commit is contained in:
parent
58bdd6ff57
commit
0d258d3dfe
@ -1,42 +1,90 @@
|
|||||||
// 基于约定路径自动解析 pagetype 详情覆盖组件
|
// 基于约定路径自动解析 pagetype 覆盖组件
|
||||||
// 约定:/src/views/pagetype/<pagetype>/<pagetype>.vue
|
// 详情:/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>
|
type AsyncComponentLoader = () => Promise<any>
|
||||||
|
|
||||||
// 扫描所有可能的覆盖组件(仅限 .vue)
|
// 扫描所有可能的覆盖组件(仅限 .vue)
|
||||||
// 使用 eager: false 延迟加载,生产环境下可按需分包
|
// - 扫描所有 apps 下的前端视图:@apps/*/frontend/src/views/pagetype
|
||||||
const allPagetypeViews: Record<string, AsyncComponentLoader> = import.meta.glob(
|
// - 同时扫描当前应用 src 下的视图:/src/views/pagetype
|
||||||
'/src/views/pagetype/**/**.vue'
|
// 说明:@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[] {
|
function getPathSegments(path: string): string[] {
|
||||||
// 统一分隔符
|
|
||||||
return path.split('/').filter(Boolean)
|
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 的详情覆盖组件
|
* 解析并返回指定 pagetype 的详情覆盖组件
|
||||||
* @param pagetypeSlug 路由中的原始 pagetype(保持小写与下划线)
|
* @param pagetypeSlug 路由中的原始 pagetype(保持小写与下划线)
|
||||||
*/
|
*/
|
||||||
export async function resolvePagetypeDetailOverride(pagetypeSlug: string): Promise<any | null> {
|
export async function resolvePagetypeDetailOverride(pagetypeSlug: string): Promise<any | null> {
|
||||||
if (!pagetypeSlug) return null
|
if (!pagetypeSlug) return null
|
||||||
// URL 使用短横线,文件夹使用下划线,与后端保持一致
|
|
||||||
const targetHyphen = pagetypeSlug.toLowerCase()
|
const targetHyphen = pagetypeSlug.toLowerCase()
|
||||||
const targetUnderscore = targetHyphen.replace(/-/g, '_')
|
const targetUnderscore = targetHyphen.replace(/-/g, '_')
|
||||||
|
|
||||||
// 匹配形如 views/pagetype/<name>/<name>.vue 的文件
|
|
||||||
const candidates = Object.keys(allPagetypeViews).filter((file) => {
|
const candidates = Object.keys(allPagetypeViews).filter((file) => {
|
||||||
const segs = getPathSegments(file)
|
const segs = getPathSegments(file)
|
||||||
const len = segs.length
|
const len = segs.length
|
||||||
if (len < 5) return false
|
if (len < 5) return false
|
||||||
// 路径应为 views/pagetype/<name>/<name>.vue
|
|
||||||
const fileName = segs[len - 1]
|
const fileName = segs[len - 1]
|
||||||
const folderName = segs[len - 2]
|
const folderName = segs[len - 2]
|
||||||
const baseName = fileName.replace(/\.vue$/i, '')
|
const baseName = fileName.replace(/\.vue$/i, '')
|
||||||
// 需要同时满足:位于 views/pagetype 下,且末两级目录名与目标一致(支持下划线形式)
|
|
||||||
return (
|
return (
|
||||||
segs[1] === 'views' &&
|
segs.includes('views') &&
|
||||||
segs[2] === 'pagetype' &&
|
segs.includes('pagetype') &&
|
||||||
folderName === targetUnderscore &&
|
folderName === targetUnderscore &&
|
||||||
baseName === targetUnderscore
|
baseName === targetUnderscore
|
||||||
)
|
)
|
||||||
@ -44,22 +92,17 @@ export async function resolvePagetypeDetailOverride(pagetypeSlug: string): Promi
|
|||||||
|
|
||||||
if (candidates.length === 0) return null
|
if (candidates.length === 0) return null
|
||||||
|
|
||||||
// 若有多个命中,按路径长度最短优先(更接近根的优先),其次按字母序
|
candidates.sort(sortByPriority)
|
||||||
candidates.sort((a, b) => {
|
|
||||||
if (a.length !== b.length) return a.length - b.length
|
|
||||||
return a.localeCompare(b)
|
|
||||||
})
|
|
||||||
|
|
||||||
const pick = candidates[0]
|
const pick = candidates[0]
|
||||||
try {
|
try {
|
||||||
const mod = await allPagetypeViews[pick]()
|
const mod = await allPagetypeViews[pick].loader()
|
||||||
return mod?.default ?? mod
|
return mod?.default ?? mod
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析并返回指定 pagetype 的工具栏覆盖组件
|
* 解析并返回指定 pagetype 的工具栏覆盖组件
|
||||||
* 约定文件名:/src/views/pagetype/<name>/<name>_toolbar.vue
|
* 约定文件名:/src/views/pagetype/<name>/<name>_toolbar.vue
|
||||||
@ -73,13 +116,12 @@ export async function resolvePagetypeToolbarOverride(pagetypeSlug: string): Prom
|
|||||||
const segs = getPathSegments(file)
|
const segs = getPathSegments(file)
|
||||||
const len = segs.length
|
const len = segs.length
|
||||||
if (len < 5) return false
|
if (len < 5) return false
|
||||||
// 路径应为 views/pagetype/<name>/<name>_toolbar.vue
|
|
||||||
const fileName = segs[len - 1]
|
const fileName = segs[len - 1]
|
||||||
const folderName = segs[len - 2]
|
const folderName = segs[len - 2]
|
||||||
const baseName = fileName.replace(/\.vue$/i, '')
|
const baseName = fileName.replace(/\.vue$/i, '')
|
||||||
return (
|
return (
|
||||||
segs[1] === 'views' &&
|
segs.includes('views') &&
|
||||||
segs[2] === 'pagetype' &&
|
segs.includes('pagetype') &&
|
||||||
folderName === targetUnderscore &&
|
folderName === targetUnderscore &&
|
||||||
baseName === `${targetUnderscore}_toolbar`
|
baseName === `${targetUnderscore}_toolbar`
|
||||||
)
|
)
|
||||||
@ -87,18 +129,14 @@ export async function resolvePagetypeToolbarOverride(pagetypeSlug: string): Prom
|
|||||||
|
|
||||||
if (candidates.length === 0) return null
|
if (candidates.length === 0) return null
|
||||||
|
|
||||||
candidates.sort((a, b) => {
|
candidates.sort(sortByPriority)
|
||||||
if (a.length !== b.length) return a.length - b.length
|
|
||||||
return a.localeCompare(b)
|
|
||||||
})
|
|
||||||
|
|
||||||
const pick = candidates[0]
|
const pick = candidates[0]
|
||||||
try {
|
try {
|
||||||
const mod = await allPagetypeViews[pick]()
|
const mod = await allPagetypeViews[pick].loader()
|
||||||
return mod?.default ?? mod
|
return mod?.default ?? mod
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,27 @@ import { fileURLToPath, URL } from 'node:url'
|
|||||||
import Icons from 'unplugin-icons/vite'
|
import Icons from 'unplugin-icons/vite'
|
||||||
import IconsResolver from 'unplugin-icons/resolver'
|
import IconsResolver from 'unplugin-icons/resolver'
|
||||||
import Components from 'unplugin-vue-components/vite'
|
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({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
@ -24,7 +45,9 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
// 跨 app 访问源码(相对计算得到的 apps 目录)
|
||||||
|
'@apps': appsDir
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
@ -33,6 +56,10 @@ export default defineConfig({
|
|||||||
strictPort: true,
|
strictPort: true,
|
||||||
open: false,
|
open: false,
|
||||||
cors: true,
|
cors: true,
|
||||||
|
fs: {
|
||||||
|
// 放行 monorepo apps 目录,便于 import.meta.glob 跨应用扫描
|
||||||
|
allow: [appsDir]
|
||||||
|
},
|
||||||
allowedHosts: ['code.jingrow.com'],
|
allowedHosts: ['code.jingrow.com'],
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api/action': {
|
'/api/action': {
|
||||||
@ -58,6 +85,8 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
define: {
|
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