mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-07-02 21:11:51 +08:00
feat(plugin): 侧栏全页 AppPage、多 nav_key 联邦加载与 sidebar_nav 缓存
- 新增路由 plugin-app 与壳页,按 nav_key 尝试 AppPage{Pascal}/AppPage/Page
- DefaultLayout 与 appcenter 合并插件侧栏项;plugin/sidebar_nav 经 Pinia 去重缓存
- 工具 pluginSidebarNav、联邦 loader 与文档/示例更新;登出时清空侧栏缓存
Made-with: Cursor
This commit is contained in:
@@ -29,6 +29,58 @@ async function fetchSingleRemoteModule(id: string): Promise<RemoteModule | null>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 nav_key 转为联邦暴露名的 Pascal 片段(如 settings -> Settings,my-tool -> MyTool)
|
||||
*/
|
||||
function navKeyToPascalSegment(navKey: string): string {
|
||||
return navKey
|
||||
.trim()
|
||||
.split(/[-_\s]+/)
|
||||
.filter(Boolean)
|
||||
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
||||
.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载插件全页组件(支持同一插件多界面)。
|
||||
*
|
||||
* 解析顺序(nav_key 为 main 或空时):
|
||||
* `AppPage` → `Page`
|
||||
*
|
||||
* 其它 nav_key(例如 settings、my_tool):
|
||||
* `AppPage{Pascal}` → `AppPage` → `Page`
|
||||
* 例:nav_key=settings → 尝试 `AppPageSettings`,再回退 `AppPage`、`Page`
|
||||
*
|
||||
* 也可在单个 `AppPage.vue` 内根据 `navKey` prop 分支渲染,无需多文件。
|
||||
*/
|
||||
export async function loadRemoteAppPageComponent(id: string, navKey: string = 'main') {
|
||||
const raw = (navKey || 'main').trim()
|
||||
const isMain = raw === '' || raw.toLowerCase() === 'main'
|
||||
|
||||
const candidateNames: string[] = []
|
||||
if (isMain) {
|
||||
candidateNames.push('AppPage', 'Page')
|
||||
} else {
|
||||
const pascal = navKeyToPascalSegment(raw)
|
||||
if (pascal) {
|
||||
candidateNames.push(`AppPage${pascal}`)
|
||||
}
|
||||
candidateNames.push('AppPage', 'Page')
|
||||
}
|
||||
|
||||
let lastError: unknown
|
||||
for (const name of candidateNames) {
|
||||
try {
|
||||
return await loadRemoteComponent(id, name)
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
console.debug(`[federation] 插件 ${id} 全页尝试 ./${name} 失败,回退下一候选`)
|
||||
}
|
||||
}
|
||||
console.warn(`[federation] 插件 ${id} 全页均加载失败 (navKey=${raw})`, lastError)
|
||||
throw lastError ?? new Error(`无法加载插件 ${id} 的全页组件`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载远程组件
|
||||
* @param id 远程模块ID
|
||||
|
||||
54
src/utils/pluginSidebarNav.ts
Normal file
54
src/utils/pluginSidebarNav.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { Composer } from 'vue-i18n'
|
||||
import type { NavMenu } from '@/@layouts/types'
|
||||
import type { PluginSidebarNavItem } from '@/api/types'
|
||||
import { pluginSidebarSectionToHeaderKey } from '@/router/i18n-menu'
|
||||
import { filterMenusByPermission } from '@/utils/permission'
|
||||
|
||||
export type PluginNavMenuEntry = {
|
||||
navMenu: NavMenu & { permission?: string }
|
||||
section: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 将后端 sidebar_nav 单项转为侧栏 / 应用中心 共用的 NavMenu
|
||||
*/
|
||||
export function navMenuFromPluginSidebarItem(
|
||||
item: PluginSidebarNavItem,
|
||||
t: Composer['t'],
|
||||
): NavMenu & { permission?: string } {
|
||||
const section = item.section || 'system'
|
||||
const header = pluginSidebarSectionToHeaderKey(section, t)
|
||||
return {
|
||||
title: item.title,
|
||||
icon: item.icon,
|
||||
to: {
|
||||
name: 'plugin-app',
|
||||
params: {
|
||||
pluginId: item.plugin_id,
|
||||
navKey: item.nav_key,
|
||||
},
|
||||
},
|
||||
header,
|
||||
permission: item.permission ?? undefined,
|
||||
} as NavMenu & { permission?: string }
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤有权限的插件导航项,并保留 section 供 DefaultLayout 分栏插入
|
||||
*/
|
||||
export function filterPluginSidebarNavEntries(
|
||||
items: PluginSidebarNavItem[],
|
||||
t: Composer['t'],
|
||||
userPermissions: Record<string, unknown>,
|
||||
): PluginNavMenuEntry[] {
|
||||
const out: PluginNavMenuEntry[] = []
|
||||
for (const item of items) {
|
||||
const section = item.section || 'system'
|
||||
const navMenu = navMenuFromPluginSidebarItem(item, t)
|
||||
if (!filterMenusByPermission([navMenu], userPermissions).length) {
|
||||
continue
|
||||
}
|
||||
out.push({ navMenu, section })
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user