一、布局组件的结构设计
1. 布局组件的核心作用
布局组件(如 AppProvider.vue
)是应用的顶层容器,负责:
1.1 统一头部、侧边栏、页脚等固定结构;
1.2 通过 渲染动态内容;
1.3 保持界面风格一致性。
1.4 共享国际化文件、统一处理elment组件样式等
1.5 和路由配置关联,可以定制多种布局方式
引用
css
export default defineComponent({
name: 'AppProvider',
inheritAttrs: false,
props: {
prefixCls: { type: String, default: prefixCls },
},
setup(_props) {
// 动态改变页面title
useTitle();
// 监听屏幕变化
createBreakpointListen();
// 添加水印 - 嵌套模式下 不显示水印
if (!isFrameMode()) useUserWatermark();
// ElementPlus配置
const { getElLocale } = useLocale();
const elConf = computed(() => ({
locale: getElLocale.value,
message: { max: 2 },
zIndex: 3000,
}));
return () => (
<ElConfigProvider {...unref(elConf)}>
<RouterView />
</ElConfigProvider>
);
},
});
路由文件示例
css
import { LAYOUT } from '@/router/constant';
export default [
{
path: '/',
component: LAYOUT,
meta: { orderNo: 0, child1Hoist: true },
redirect: PageEnum.BASE_HOME,
children: [
{
path: PageEnum.BASE_HOME,
name: 'Dashboard',
meta: { title: '首页', icon: 'bi:house-door-fill', affix: true },
component: () => import('@/views/dashboard/index.vue'),
},
],
},
{
path: '/',
component: LAYOUT,
meta: { hidden: true },
redirect: '/user-center',
children: [
{
name: 'UserCenter',
path: '/user-center',
meta: { title: '个人信息', icon: 'el-icon-user', hidden: true },
component: () => import('@/views/sys/user_center/index.vue'),
},
],
},
] as AppRouteModule[];
二、多层 RouterView 的嵌套布局
通过嵌套路由,布局组件可支持多层级内容渲染:
css
<!-- Dashboard.vue (子路由组件) -->
<template>
<div class="dashboard">
<Sidebar />
<div class="content">
<!-- 渲染嵌套路由内容 -->
<RouterView />
</div>
</div>
</template>
该文件的routerview是要动态渲染的内容区域,和子路由配置关联
三、静态组件 vs 动态组件
1. 静态组件(预定义路由)
定义:在路由配置文件中显式声明路径和组件的组件。
场景:固定功能页面(如登录页、404页)。
css
// router/routes.ts
const routes = [
{
path: "/about",
component: () => import("@/views/About.vue"), // 静态组件
},
];
2. 动态组件(运行时生成)
定义:通过接口数据或业务逻辑动态生成路径和组件的组件。
场景:权限控制、菜单配置、微前端集成等。
引入组件
css
// 动态引入组件
routes = asyncImportRoute(routeList);
路由处理
css
export function asyncImportRoute(routes: AppRouteRecordRaw[], level = 1) {
if (routes) {
dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}');
routes.forEach((item) => {
if (!item.component && item.meta?.frameSrc) item.component = 'IFRAME';
const { component, children, name } = item;
if (component) {
const layoutFound = LayoutMap.get((component as string).toUpperCase());
item.component = layoutFound || dynamicImport(dynamicViewsModules, component as string);
} else {
item.component = level === 1 ? LAYOUT : getParentLayout(name);
}
if (children && children.length) asyncImportRoute(children, level + 1);
});
}
return routes;
}
dynamicImport
css
/**
* 根据组件名导入对应的视图
*
* @param dynamicViewsModules 一个记录,键是模块路径,值是返回Promise的函数,该Promise解析为一个记录。
* @param component 需要动态导入的组件路径,可以是相对路径,且预期以'.vue'或'.tsx'结尾。
* @returns 如果找到唯一的匹配项,则返回对应模块的加载函数。如果找到多个匹配项或无匹配项,将分别发出警告并返回undefined或EXCEPTION。
*/
function dynamicImport(
dynamicViewsModules: Record<string, () => Promise<Recordable>>,
component: string,
) {
const keys = Object.keys(dynamicViewsModules);
const matchKeys = keys.filter((key) => {
const k = key.replace('../../views', '');
const startFlag = component.startsWith('/');
const endFlag = component.endsWith('.vue') || component.endsWith('.tsx');
const startIndex = startFlag ? 0 : 1;
const lastIndex = endFlag ? k.length : k.lastIndexOf('.');
return k.substring(startIndex, lastIndex) === component;
});
if (matchKeys?.length === 1) {
const matchKey = matchKeys[0];
return dynamicViewsModules[matchKey];
} else if (matchKeys?.length > 1) {
$log.warn(
'请不要在`src/views/`同级目录创建后缀为`.vue`或`.tsx`的同名文件, 这将会导致动态引入失败',
);
return;
} else {
$log.warn('在`src/views/`下找不到`' + component + '`, 请先在前端工程中创建该文件!');
return EXCEPTION;
}
}
核心要点:
- 对接口返回的动态路由最外层设置component属性值,设置布局组件
- 规范组件路由书写文件夹,比如统一在@/views下书写,配置路由的时候根据该目录去配置,动态加载子路由的component
四、 总结
- 布局组件:通过 渲染动态内容,保持界面一致性。
- 静态组件:路径固定,预定义在路由配置中。
- 动态组件:通过接口或逻辑动态生成,无需预先写死路径。
- 动态路由优势:支持权限控制、菜单扩展,提升代码灵活性与可维护性。