| rsf-design/src/components/core/layouts/art-menus/art-sidebar-menu/style.scss | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-design/src/router/core/ComponentLoader.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-design/src/router/core/MenuProcessor.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-design/src/router/core/RouteRegistry.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-design/src/router/guards/beforeEach.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
rsf-design/src/components/core/layouts/art-menus/art-sidebar-menu/style.scss
@@ -42,6 +42,8 @@ flex-direction: column; align-items: center; justify-content: center; box-sizing: border-box; width: calc(100% - 16px); margin: 8px; overflow: hidden; text-align: center; @@ -52,11 +54,17 @@ display: flex !important; align-items: center; justify-content: center; width: 100%; width: 24px; height: 24px; margin: 0 auto !important; margin-right: auto !important; margin-left: auto !important; font-size: 20px; :deep(svg) { display: block; margin: auto; } } span { rsf-design/src/router/core/ComponentLoader.js
@@ -2,6 +2,15 @@ class ComponentLoader { constructor() { this.modules = import.meta.glob('../../views/**/*.vue') this.warmPromises = new Map() } resolveModule(componentPath) { if (!componentPath) { return null } const fullPath = `../../views${componentPath}.vue` const fullPathWithIndex = `../../views${componentPath}/index.vue` return this.modules[fullPath] || this.modules[fullPathWithIndex] || null } /** * 加载组件 @@ -10,10 +19,10 @@ if (!componentPath) { return this.createEmptyComponent() } const fullPath = `../../views${componentPath}.vue` const fullPathWithIndex = `../../views${componentPath}/index.vue` const module = this.modules[fullPath] || this.modules[fullPathWithIndex] const module = this.resolveModule(componentPath) if (!module) { const fullPath = `../../views${componentPath}.vue` const fullPathWithIndex = `../../views${componentPath}/index.vue` console.error( `[ComponentLoader] 未找到组件: ${componentPath},尝试过的路径: ${fullPath} 和 ${fullPathWithIndex}` ) @@ -22,6 +31,30 @@ return module } /** * 预热组件模块,避免首次点击页面时再触发冷加载。 */ warm(componentPath) { const normalizedPath = typeof componentPath === 'string' ? componentPath.trim() : '' if (!normalizedPath) { return Promise.resolve(false) } if (this.warmPromises.has(normalizedPath)) { return this.warmPromises.get(normalizedPath) } const module = this.resolveModule(normalizedPath) if (!module) { return Promise.resolve(false) } const warmTask = module() .then(() => true) .catch((error) => { console.warn(`[ComponentLoader] 组件预热失败: ${normalizedPath}`, error) return false }) this.warmPromises.set(normalizedPath, warmTask) return warmTask } /** * 加载布局组件 */ loadLayout() { rsf-design/src/router/core/MenuProcessor.js
@@ -5,6 +5,34 @@ import { RoutesAlias } from '../routesAlias' import { adaptBackendMenuTree } from '../adapters/backendMenuAdapter' import { formatMenuTitle } from '@/utils' const WORKBENCH_PATH = '/dashboard/console' const WORKBENCH_ROOT_PATH = '/dashboard' const WORKBENCH_MENU = Object.freeze({ name: 'Dashboard', path: WORKBENCH_ROOT_PATH, component: RoutesAlias.Layout, redirect: WORKBENCH_PATH, meta: { title: 'menus.dashboard.title', icon: 'ri:home-smile-2-line' }, children: [ { name: 'Console', path: 'console', component: WORKBENCH_PATH, meta: { title: 'menus.dashboard.console', icon: 'ri:home-smile-2-line', keepAlive: false, fixedTab: true } } ] }) class MenuProcessor { /** * 获取菜单数据 @@ -17,6 +45,7 @@ } else { menuList = await this.processBackendMenu() } menuList = this.prependWorkbenchMenu(menuList) this.validateMenuPaths(menuList) return this.normalizeMenuPaths(menuList) } @@ -206,5 +235,56 @@ } return `/${path}` } prependWorkbenchMenu(menuList) { if (!Array.isArray(menuList)) { return [this.createWorkbenchMenu()] } const hasWorkbench = menuList.some((item) => this.containsWorkbenchMenu(item)) if (hasWorkbench) { return menuList } return [this.createWorkbenchMenu(), ...menuList] } createWorkbenchMenu() { return { ...WORKBENCH_MENU, meta: { ...WORKBENCH_MENU.meta }, children: WORKBENCH_MENU.children.map((child) => ({ ...child, meta: { ...child.meta } })) } } isWorkbenchMenu(item) { if (!item || typeof item !== 'object') { return false } const path = String(item.path || '') const component = String(item.component || '') return ( path === WORKBENCH_ROOT_PATH || path === WORKBENCH_PATH || component === WORKBENCH_PATH ) } containsWorkbenchMenu(item) { if (this.isWorkbenchMenu(item)) { return true } if (!Array.isArray(item?.children) || item.children.length === 0) { return false } return item.children.some((child) => this.containsWorkbenchMenu(child)) } } export { MenuProcessor } rsf-design/src/router/core/RouteRegistry.js
@@ -1,6 +1,8 @@ import { ComponentLoader } from './ComponentLoader.js' import { RouteValidator } from './RouteValidator.js' import { RouteTransformer } from './RouteTransformer.js' const DEFAULT_WARMUP_LIMIT = 12 const HOME_COMPONENT_PATH = '/dashboard/console' class RouteRegistry { constructor(router, options = {}) { this.router = router @@ -75,5 +77,56 @@ markAsRegistered() { this.registered = true } /** * 空闲时预热高频页面,降低登录后首次切页的冷加载成本。 */ warm(menuList, options = {}) { const limit = Number.isFinite(options.limit) ? options.limit : DEFAULT_WARMUP_LIMIT const paths = collectWarmupPaths(menuList, limit) if (paths.length === 0) { return } const schedule = globalThis.requestIdleCallback ? (task) => globalThis.requestIdleCallback(task, { timeout: 1200 }) : (task) => setTimeout(task, 80) schedule(() => { void warmSequentially(paths, this.componentLoader) }) } } function collectWarmupPaths(menuList, limit) { const paths = [] const visited = new Set() const walk = (items) => { if (!Array.isArray(items)) { return } for (const item of items) { if (paths.length >= limit) { return } const componentPath = typeof item?.component === 'string' ? item.component.trim() : '' if ( componentPath && componentPath !== '/' && componentPath !== HOME_COMPONENT_PATH && !visited.has(componentPath) && !item?.meta?.isFirstLevel ) { visited.add(componentPath) paths.push(componentPath) } if (Array.isArray(item?.children) && item.children.length > 0) { walk(item.children) } } } walk(menuList) return paths } async function warmSequentially(paths, componentLoader) { for (const componentPath of paths) { await componentLoader.warm(componentPath) } } export { RouteRegistry } rsf-design/src/router/guards/beforeEach.js
@@ -151,6 +151,7 @@ throw new Error('获取菜单列表失败,请重新登录') } routeRegistry?.register(menuList) routeRegistry?.warm(menuList) const menuStore = useMenuStore() menuStore.setMenuList(menuList) menuStore.addRemoveRouteFns(routeRegistry?.getRemoveRouteFns() || [])