5.1.1.快速使用
框架的外层布局位于 src/layout/index.vue 中,使用的是 EleProLayout 高级布局组件,支持多种布局风格组合, 如常见的双侧栏菜单、顶栏侧栏混合菜单、页签栏置于顶栏等风格, 并在 src/store/modules/theme.js 中对各种风格配置进行管理,以实现在线切换各种布局风格, 高级布局组件 EleProLayout 提供了丰富的属性和插槽,既高度封装使用简单,也可灵活自定义为更多其它布局风格,使用示例:
运行代码新窗口打开
实际项目使用通常是在默认插槽中添加路由出口,如果开启多页签还需要监听页签点击事件关闭事件等更新页签数据:
/** 菜单数据和页签数据通常放在状态管理中 */
const { menus } = storeToRefs(userStore);
const { tabs, collapse } = storeToRefs(themeStore);
/** 页签添加事件关闭事件触发时更新状态管理中的页签数据 */
const { addPageTab, removePageTab } = usePageTab();
const { push } = useRouter();
/** 页签点击时使用路由 push 跳转页面 */
const handleTabClick = (option) => {
// 判断当前路由不是点击的页签的路由才进行 push 跳转
// 因为 vue-router 早期的版本如果已经是当前路由再 push 会报警告
const { key, active, item } = option;
const path = item?.fullPath || key;
if (key !== active && path) {
push(path);
}
};
// 菜单和页签的数据也可以不用 pinia 状态管理, 可以直接用 ref 数组,
// tabAdd 事件数组 push 页签数据, tabRemove 事件数组 splice 删除页签数据,
// 实际项目菜单数据是在路由守卫中请求接口获取的, 页签数据在 KeepAlive 中也用到了,
// usePageTab 中也需要操作页签数据, 所以下载的代码是放在 pinia 中方便多个位置使用
</script>
运行代码 新窗口打开
5.1.2.属性列表
开启 fixedHome 属性后主页页签会在始终显示在页签栏最左侧且不可关闭,也不会参与页签拖动排序,关闭后则所有页签都可关闭和拖动排序。
属性 navTrigger 和 boxTrigger 取值为 hover 可在混合导航和双侧栏时鼠标移入分割菜单,鼠标移出恢复菜单分割,v1.1.9新增。
属性 homePath 用于面包屑导航的首页图标,以及开启了固定主页页签时的首页图标点击跳转的路由地址。
属性 redirectPath 是用于当是刷新路由时避免去做菜单的选中切换以及混合导航菜单数据的分割计算等。
5.1.3.事件列表
update:collapse 事件是在移动端点击侧栏菜单时自动收起侧栏触发。
update:maximized 事件是在开启了按返回键退出内容区最大化时触发。
tabAdd 事件是在路由切换后触发,会根据路由封装好页签需要的数据格式。
tabRemove 事件是点击页签的关闭图标时触发,key 是点击的页签的 key ,item 是点击的页签数据,active 是当前选中的页签的 key。
tabClick 事件是点击页签时触发,可以监听此事件做路由跳转,参数含义同上。
tabContextMenu 事件是点击页签的右键菜单的项时触发,command 参数就是点击的下拉菜单项的 command 值,其它参数含义同上。
bodySizeChange 事件会在侧栏折叠展开改变后、浏览器窗口尺寸改变后触发,width 是主体区的宽度,mobile 是当前是否为移动端(小屏幕)。
5.1.4.插槽列表
参数 sidebar 为当前是否有侧栏,levels 为当前面包屑数据,isHome 为当前路由是否是主页,homePath 为主页地址,插槽的使用示例:
<template>
<ele-pro-layout
:menus="menus"
:tabs="tabs"
v-model:collapse="collapse"
@tabAdd="addPageTab"
@tabClick="handleTabClick"
@tabRemove="removePageTab">
<!-- Logo, 侧栏折叠时会隐藏 img 后的 h1 -->
<!-- collapse 为侧栏是否折叠状态, sidebar 为当前是否有侧栏 -->
<!-- v1.2.0之前的版本只有一个 logo 插槽, 里面写 logo 的图片和文字 -->
<!--
<template #logo="{ collapse, sidebar }">
<img src="/src/assets/logo.svg" style="height: 30px;" />
<h1 style="font-size: 16px;">EleAdminPlus</h1>
</template>
-->
<!-- v1.2.0开始 logo 的图片和文字分两个插槽, 以实现双侧栏时也显示文字 -->
<template #logo="{ collapse, sidebar }">
<img src="/src/assets/logo.svg" style="height: 30px;" />
</template>
<template #logoTitle="{ collapse, sidebar }">
<h1 style="font-size: 16px;">EleAdminPlus</h1>
</template>
<!-- 顶栏左侧区域 -->
<template #left="{ sidebar }">
<header-tool @click="onClick">
<el-icon>
<plus style="transform: scale(1.15)" />
</el-icon>
</header-tool>
<header-tool @click="onClick2">
<el-icon>
<search />
</el-icon>
</header-tool>
<!-- v1.2.0 开始建议使用 LayoutTool 代替 HeaderTool 和 SidebarTool -->
<layout-tool @click="onClick2">
<el-icon>
<search />
</el-icon>
</layout-tool>
<layout-tool @click="collapse = !collapse">
<el-icon style="transform: scale(1.14)">
<MenuUnfoldOutlined v-if="collapse" />
<MenuFoldOutlined v-else />
</el-icon>
</layout-tool>
</template>
<!-- 顶栏右侧区域 -->
<template #right>
<header-tool @click="onClick">
<el-icon>
<plus style="transform: scale(1.15)" />
</el-icon>
</header-tool>
<!-- v1.2.0 开始建议使用 LayoutTool 代替 HeaderTool 和 SidebarTool -->
<layout-tool @click="onClick">
<el-icon>
<plus style="transform: scale(1.15)" />
</el-icon>
</layout-tool>
</template>
<!-- 侧栏顶部 -->
<template #top>
<sidebar-tool @click="onClick">
<el-icon>
<plus style="transform: scale(1.15)" />
</el-icon>
</sidebar-tool>
<!-- v1.2.0 开始建议使用 LayoutTool 代替 HeaderTool 和 SidebarTool -->
<layout-tool @click="onClick">
<el-icon>
<plus style="transform: scale(1.15)" />
</el-icon>
</layout-tool>
</template>
<!-- 默认插槽一般放路由出口、修改密码弹窗等, 可以随意加内容 -->
<div style="position: fixed;right: 0;top: 50%;transform: translateY(-50%);z-index: 9999;">
<div style="color: #fff;background: #1677ff;padding: 10px;cursor: pointer;">设置</div>
<div style="color: #fff;background: #f5686f;padding: 10px;cursor: pointer;">主题</div>
<div style="color: #fff;background: #33cc99;padding: 10px;cursor: pointer;">返回</div>
</div>
<router-view />
</ele-pro-layout>
</template>
<script setup>
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/store/modules/user';
import { useThemeStore } from '@/store/modules/theme';
import { usePageTab } from '@/utils/use-page-tab';
import { useRouter } from 'vue-router';
import { Plus, Search } from '@element-plus/icons-vue';
// 顶栏和侧栏中的图标按钮组件需要手动引入后使用
import { HeaderTool, SidebarTool, LayoutTool } from 'ele-admin-plus';
// v1.2.0 开始 HeaderTool 和 SidebarTool 合并为 LayoutTool , 建议使用 LayoutTool
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@/components/icons';
const userStore = useUserStore();
const themeStore = useThemeStore();
const { menus } = storeToRefs(userStore);
const { tabs, collapse } = storeToRefs(themeStore);
const { addPageTab, removePageTab } = usePageTab();
const { push } = useRouter();
const handleTabClick = (option) => {
const { key, active, item } = option;
const path = item?.fullPath || key;
if (key !== active && path) {
push(path);
}
};
const onClick = () => {
console.log('Hello');
};
const onClick2 = () => {
console.log('Hello2');
};
</script>
运行代码 新窗口打开
HeaderTool 和 SidebarTool (v1.2.0 合并为 LayoutTool)是封装的适配顶栏和侧栏样式的按钮组件,当然不使用该组件写任意内容也可以。
5.1.5.菜单数据格式
属性 menus 菜单的数据格式(一般也会用菜单来注册动态路由,所以 EleProLayout 将需要的菜单数据格式设计为近似路由数据的格式):
export interface MenuItem {
/** 路由名称, 非必须 /
name?: string;
/* 菜单地址, 必须且要唯一, 默认也是路由关键字(可在 meta 中再单独设置路由关键字) /
path: string;
/* 路由组件, 父级非必须 /
component?: string;
/* 路由重定向, 非必须, 父级可设计为重新向到第一个子级 /
redirect?: string;
/* 路由元数据, 必须, EleProLayout 许多功能是在 meta 中配置 /
meta: MenuMeta;
/* 子菜单 /
children?: Array<MenuItem>;
}
元数据 meta 的格式:
export interface MenuMeta extends RouteMeta {
/* 菜单标题 /
title?: string;
/* 菜单图标 /
icon?: string | import('vue').Component;
/* 菜单是否隐藏, 为 true 只注册路由不显示在菜单中, 比如非弹窗形式的添加页面 /
hide?: boolean;
/* 选中其它菜单, 比如 hide 为 true 的菜单可以配置为选中其它的菜单 /
active?: string;
/* 是否隐藏页脚 /
hideFooter?: boolean;
/* 是否隐藏侧栏 /
hideSidebar?: boolean;
/* 是否隐藏双侧栏一级 /
hideSidebox?: boolean;
/* 是否隐藏顶栏, v1.2.0新增 /
hideHeader?: boolean;
/* 是否隐藏页签栏, v1.2.0新增 /
hideTabs?: boolean;
/* 页签是否可关闭, 不为 false 就是 true /
closable?: boolean;
/* 页签不同参数是否只存在一个, 不为 false 就是 true /
tabUnique?: boolean;
/* 页签是否缓存, 不为 false 就是 true /
keepAlive?: boolean;
/* 是否在面包屑中显示, 不为 false 就是 true /
breadcrumb?: boolean;
/* 菜单组件其它属性(见 5.9.3 EleMenus 的 items 属性) /
props?: Partial<MenuItemProps>;
/* 是否需要外层布局, 不为 false 就是 true /
layout?: boolean;
/* 路由关键字, 比如注册路由的关键字不想与菜单的 path 一样可用它单独配置路由关键字 /
routePath?: string;
}
// meta 中有些功能配置并不是 EleProLayout 需要和实现的, 如:
// keepAlive: false 让页签不缓存是在 src/store/modules/theme.js 的 keepAliveInclude 中实现的
// layout: false 不显示外层布局是在 src/router/routes.js 的 getMenuRoutes 中实现的
参数 props 支持的配置见文档5.9.3,参数 routePath 使用场景如用户详情的 path 是 /system/user/details/:id 是不显示在菜单中的, 如果需要显示在菜单中可以配置 path 为 /system/user/details/1 然后在 meta 中配置 routePath 为 /system/user/details/:id , 菜单中也可以添加多个,如另一个 path 为 /system/user/details/2 。
参数 tabUnique 不建议使用,因为如果需要同路由不同参数显示不同页签使用 /user/details/:id 这种形式的路由即可, 如果需要同路由不同参数只显示一个页签使用 /user/details?id=1 这种形式的路由即可。
5.1.6.页签数据格式
属性 tabs 页签的数据格式:
export interface TabItem {
/* 页签标题 /
title?: string;
/* 页签唯一标识, tabUnique 为 false 时取 fullPath 否则取 path /
key?: string;
/* 路由地址 /
path?: string;
/* 路由完整地址(带 ? 后的参数) /
fullPath?: string;
/* 是否可关闭 /
closable?: boolean;
/* 是否是主页路由 /
home?: boolean;
/* 组件名称, 用于 KeepAlive /
components?: string[];
/* 是否为刷新状态, 用于开启 KeepAlive 时刷新路由 /
refresh?: boolean;
/* 路由元数据 */
meta?: MenuMeta;
}
页签的数据会随着路由的打开自动进行添加(组件只会触发 tabAdd 事件,需要监听事件添加到状态管理的页签数据中)。
5.1.7.菜单元数据配置示例
例如通过 meta.props 配置菜单组件的更多功能:
<template>
<ele-pro-layout
:menus="menus"
:tab-bar="false"
v-model:collapse="collapse">
<router-view />
<template #left>
<layout-tool @click="collapse = !collapse">
<el-icon style="transform: scale(1.14)">
<MenuUnfoldOutlined v-if="collapse" />
<MenuFoldOutlined v-else />
</el-icon>
</layout-tool>
</template>
</ele-pro-layout>
</template>
<script setup>
import { shallowRef, ref } from 'vue';
import { Setting, User } from '@element-plus/icons-vue';
import { LayoutTool } from 'ele-admin-plus';
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@/components/icons';
const collapse = ref(false);
const menus = shallowRef([
{
path: '/dashboard/workplace',
meta: { title: '首页', icon: User }
},
{
path: '/system',
meta: { title: '系统管理', icon: Setting },
children: [
{
path: '/system/user',
meta: { title: '用户管理', icon: User }
},
{
path: '/system/role',
meta: {
title: '角色管理',
icon: User,
props: {
// 例如设置菜单徽章数据等
badge: { value: 1 }
}
}
},
{
path: '/system/menu',
meta: {
title: '菜单管理',
icon: User,
// 例如设置不需要外层布局
layout: false,
// 例如路由地址默认在当前页面打开, 设置在新窗口打开
props: { pathTarget: '_blank' }
}
}
]
},
{
path: '/iframe',
meta: { title: '内嵌页面', icon: User },
children: [
{
path: '/iframe/eleadmin',
meta: { title: '官网', icon: User }
},
{
path: '/iframe/eleadmin-doc',
meta: {
title: '文档',
icon: User,
props: {
// 例如内嵌页面默认在当前页面打开, 设置在新窗口打开
pathTarget: '_blank'
}
}
}
]
},
{
path: '/user/profile',
meta: {
title: '个人中心',
icon: User,
props: {
// 例如路由地址默认在当前页面打开, 设置在新窗口打开
pathTarget: '_blank'
}
}
},
{
path: 'https://eleadmin.com/goods/11',
meta: {
title: '获取授权',
icon: User,
props: {
// 例如外链地址默认在新窗口打开, 设置在当前页面打开
pathTarget: '_self'
}
}
}
]);
</script>
运行代码 新窗口打开
5.1.8.菜单点击事件拦截
当设置菜单使用 'click' 事件触发时还可以使用 beforeClick 属性钩子进行拦截(v1.1.4新增),如:
<template>
<ele-pro-layout
:menus="menus"
:tab-bar="false"
v-model:collapse="collapse"
item-trigger="click"
:before-click="onBeforeClick">
<router-view />
<template #left>
<layout-tool @click="collapse = !collapse">
<el-icon style="transform: scale(1.14)">
<MenuUnfoldOutlined v-if="collapse" />
<MenuFoldOutlined v-else />
</el-icon>
</layout-tool>
</template>
</ele-pro-layout>
</template>
<script setup>
import { shallowRef, ref } from 'vue';
import { useRouter } from 'vue-router';
import { Setting, User } from '@element-plus/icons-vue';
import { LayoutTool } from 'ele-admin-plus';
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@/components/icons';
//import { getMyToken } from '@/api/my-token';
const collapse = ref(false);
const menus = shallowRef([
{
path: '/dashboard/workplace',
meta: { title: '首页', icon: User }
},
{
path: '/system',
meta: { title: '系统管理', icon: Setting },
children: [
{
path: '/system/user',
meta: { title: '用户管理', icon: User }
}
]
},
{
path: '/iframe/eleadmin',
meta: { title: '这是内嵌', icon: User }
},
{
path: 'https://eleadmin.com/goods/11',
meta: { title: '这是外链', icon: User }
}
]);
const { push } = useRouter();
const onBeforeClick = (item, e) => {
// 点击的菜单项数据, 这里的数据格式是 EleMenus (5.9.2) 的 items 属性的数据格式
console.log('item:', item);
// 返回 false 会阻止默认的点击后的操作
//return false;
// 例如实现外链点击时先请求 token 再跳转
if(item.index === 'https://eleadmin.com/goods/11') {
getMyToken().then((token) => {
window.open(item.index + '?token=' + token);
}).catch((e) => {
console.error(e)
});
// 还可以使用 e.stopPropagation() 阻止默认选中
e.stopPropagation();
return false;
}
// 内嵌也可以阻止默认操作, 然后自己用路由 push 跳转
if(item.index === '/iframe/eleadmin') {
getMyToken().then((token) => {
push('/iframe/eleadmin?token=' + token);
}).catch((e) => {
console.error(e)
});
return false;
}
};
async function getMyToken() {
return 'abcdefg';
}
</script>
运行代码 新窗口打开
5.1.9.再次点击选中菜单刷新
还可以利用 beforeClick 实现点击菜单如果已经打开了页签就刷新页面,示例:
<!-- 例如实现点击 Tab 切换仍 KeepAlive 点击菜单则每次都是新页面, -->
<!-- 或者实现仅再次点击选中的菜单才刷新的。 -->
<!-- 添加 itemTrigger 属性值为 click 以及 beforeClick 监听 -->
<template>
<ele-pro-layout
:menus="menus"
:tabs="tabs"
v-model:collapse="collapse"
@tabAdd="addPageTab"
@tabClick="handleTabClick"
@tabRemove="removePageTab"
item-trigger="click"
:before-click="onBeforeClick">
<router-layout />
<template #left>
<layout-tool @click="collapse = !collapse">
<el-icon style="transform: scale(1.14)">
<MenuUnfoldOutlined v-if="collapse" />
<MenuFoldOutlined v-else />
</el-icon>
</layout-tool>
</template>
</ele-pro-layout>
</template>
<script setup>
import { unref } from 'vue';
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/store/modules/user';
import { useThemeStore } from '@/store/modules/theme';
import { usePageTab } from '@/utils/use-page-tab';
import { useRouter } from 'vue-router';
import RouterLayout from '@/components/RouterLayout/index.vue';
import { LayoutTool } from 'ele-admin-plus';
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@/components/icons';
const userStore = useUserStore();
const themeStore = useThemeStore();
const { menus } = storeToRefs(userStore);
const { tabs, collapse } = storeToRefs(themeStore);
const { addPageTab, removePageTab, reloadPageTab } = usePageTab();
const { push, currentRoute } = useRouter();
const handleTabClick = (option) => {
const { key, active, item } = option;
const path = item?.fullPath || key;
if (key !== active && path) {
push(path);
}
// 如果需要点击页签时已是选中就刷新页签可以加如下代码
/*
if (key === active && path) {
reloadPageTab({ fullPath: path });
}
*/
};
/** 监听菜单点击事件 /
const onBeforeClick = (item) => {
// 点击的菜单项数据, 这里的数据格式是 EleMenus (5.9.2) 的 items 属性的数据格式
console.log('item:', item);
// 判断菜单已经打开过则调用 reloadPageTab 刷新并 return false
if (tabs.value.some((t) => t.fullPath === item.index)) {
reloadPageTab({ fullPath: item.index });
return false;
}
// return false 会阻止默认点击后的操作, 默认是通过路由的 push 跳转
// 如果需要仅仅是再次点击选中的菜单才刷新上面代码改为这样写
/
if (
tabs.value.some((t) => t.fullPath === item.index) &&
unref(currentRoute).fullPath === item.index
) {
reloadPageTab({ fullPath: item.index });
return false;
}
*/
};
</script>
运行代码 新窗口打开
仅再次点击选中的菜单才刷新的效果展示:
运行代码 新窗口打开
5.1.10.再次点击选中页签刷新
如果需要点击当前选中的页签时刷新路由可以这样写:
<template>
<ele-pro-layout
:menus="menus"
:tabs="tabs"
v-model:collapse="collapse"
@tabAdd="addPageTab"
@tabClick="handleTabClick"
@tabRemove="removePageTab">
<router-layout />
<template #left>
<layout-tool @click="collapse = !collapse">
<el-icon style="transform: scale(1.14)">
<MenuUnfoldOutlined v-if="collapse" />
<MenuFoldOutlined v-else />
</el-icon>
</layout-tool>
</template>
</ele-pro-layout>
</template>
<script setup>
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/store/modules/user';
import { useThemeStore } from '@/store/modules/theme';
import { usePageTab } from '@/utils/use-page-tab';
import { useRouter } from 'vue-router';
import RouterLayout from '@/components/RouterLayout/index.vue';
import { LayoutTool } from 'ele-admin-plus';
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@/components/icons';
const userStore = useUserStore();
const themeStore = useThemeStore();
const { menus } = storeToRefs(userStore);
const { tabs, collapse } = storeToRefs(themeStore);
const { addPageTab, removePageTab, reloadPageTab } = usePageTab();
const { push } = useRouter();
const handleTabClick = (option) => {
const { key, active, item } = option;
const path = item?.fullPath || key;
if (key !== active && path) {
push(path);
return;
}
// 添加此段代码, 表示点击的是当前选中的页签就调用刷新方法
if (path) {
reloadPageTab({ fullPath: path });
}
};
</script>
运行代码 新窗口打开
5.1.11.菜单图标插槽
需要注意 EleProLayout 的菜单使用的是 EleMenus 组件,所以菜单图标和菜单标题的插槽参数与 EleMenus 一致, 因此菜单数据 item 的格式也会被处理为 EleMenus 需要的数据,与 menus 属性传的数据并不完全一致,有略微的差别。
关于菜单的图标在 EleMenus 文档中给的示例是直接传的组件,但通过接口获取的菜单数据图标是一个字符串, 所以只有图标组件全部被全局安装了才会显示出字符串的图标,当然也可以使用图标的插槽来自定义渲染:
<!-- v1.3.0版本开始已经默认全局安装图标, 无需使用菜单图标插槽, -->
<!-- 如果追求打包体积优化, 可移除全局安装的形式, 继续使用插槽的形式, 只引入用到的图标 -->
<template>
<ele-pro-layout :menus="menus" :tab-bar="false">
<router-view />
<!-- 这里插槽参数与 EleMenus 组件插槽参数一致 -->
<template #icon="{ icon, item }">
<el-icon v-if="icon" v-bind="item.meta?.props?.iconProps || {}">
<component :is="icon" :style="item.meta?.props?.iconStyle" />
</el-icon>
</template>
</ele-pro-layout>
</template>
<script setup>
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/store/modules/user';
// 这里引入所有菜单用到的图标组件并安装
import * as MenuIcons from '@/layout/menu-icons';
// v1.3.0开始没有此文件, 如果继续使用插槽的方式需自己创建 menu-icons.js 文件
defineOptions({
components: MenuIcons
});
const userStore = useUserStore();
const { menus } = storeToRefs(userStore);
</script>
在 src/layout/menu-icons.js 中导出了菜单需要用的图标,如果觉得图标少或一个一个导入麻烦,可以这样改为全部导入:
/** 菜单用到的图标 */
/export {
OfficeBuilding,
Edit,
Tickets,
//......省略其它代码
} from '@element-plus/icons-vue';/
// 可以改为 export * 引入全部图标
export * from '@element-plus/icons-vue';
// 或者直接在 main.js 中全局安装所有图标
// 可参考 Element 文档 https://element-plus.org/zh-CN/component/icon.html
// 全局安装后可以直接删除 EleProLayout 的 icon 插槽, 可以不用引入 menu-icons.js
菜单标题插槽与图标插槽使用方式类似,插槽名称默认是 icon 和 title 可加属性自定义,可用来实现侧栏菜单和顶栏菜单图标不同的渲染方式。
5.1.12.菜单图标使用图片
菜单图标使用插槽还可以很灵活的自定义,比如菜单图标使用图片的形式而不是组件的形式:
然后需要把图标的图片都放在 public 目录下(不放 src/assets 下是因为这个下面的图片名称打包后会加 hash 值):
|- public
| |- IconProHomeOutlined.png
| |- IconProDesktopOutlined.png
| |- IconProAnalysisOutlined.png
| |- IconProDashboardOutlined.png
| |- IconProSettingOutlined.png
| |- IconProUserOutlined.png
| |- IconProIdcardOutlined.png
| |- ......
图片的文件名称要与接口返回的菜单 icon 字段值一致
运行代码 新窗口打开
5.1.13.菜单图标使用iconfont
菜单图标如果要使用传统的字体图标也是可以使用插槽自定义实现的,使用示例:
然后全局引入字体图标的样式即可,一般在 index.html 中引入图标的 css (css、ttf、woff 等文件可放在 public 中)。
5.1.14.菜单标题国际化
菜单标题国际化的使用示例:
运行代码 新窗口打开
5.1.15.页签标题插槽
需要注意 EleProLayout 的页签栏使用的是 EleTabs 组件,所以页签相关的插槽参数与 EleTabs 一致, 因此页签数据 item 的格式也会被处理为 EleTabs 需要的数据格式,与 tabs 属性传的数据并不完全一致,有略微的差别。
使用 tabTitle 插槽在页签标题左侧显示图标的示例:
<template #tabHome>
主页
运行代码 新窗口打开
5.1.15.页签右键菜单
使用 tabContextMenus 属性配置右键菜单示例(v1.1.0新增),详细使用可查看 EleTabs 中介绍:
运行代码 新窗口打开
(v1.2.1移除)使用 tabContext 插槽自定义页签右键下拉菜单示例(v1.1.0开始通过 tabContextMenus 属性配置):
5.1.17.自定义页签最右侧图标
使用 tabExtra 插槽在页签栏最右侧增加一个下拉菜单的示例:
const { menus } = storeToRefs(userStore);
const { tabs } = storeToRefs(themeStore);
const { addPageTab, removePageTab } = usePageTab();
const { push } = useRouter();
const handleTabClick = (option) => {
const { key, active, item } = option;
const path = item?.fullPath || key;
if (key !== active && path) {
push(path);
}
};
/** 页签栏右侧下拉菜单点击事件 */
const onTabDropdownMenu = (command: string, active: string) => {
if (command === 'reload') {
console.log('点击了刷新');
} else if (command === 'close') {
console.log('点击了关闭当前');
} else if (command === 'all') {
console.log('点击了关闭全部');
}
console.log('当前选中页签:', active);
};
/** 获取页签栏右侧下拉菜单数据 */
const getDropdownItems = (active: string) => {
console.log('active:', active);
const items: DropdownItem[] = [
{ title: '关闭当前', command: 'close' },
{ title: '关闭全部', command: 'all' }
]
// 例如根据 active 返回不同的数据
if(active !== '/system/user') {
items.push({ title: '刷新', command: 'reload' });
}
return items;
};
</script>
运行代码 新窗口打开
组件 TabDropdown 还有属性 dropdownProps 配置 EleDropdown 更多属性,插槽 icon 自定义箭头图标。
5.1.18.菜单默认展开
可以通过 sidebarOpeneds 属性设置侧栏需要默认展开的菜单,例如实现默认展开所有侧栏菜单:
<template>
<ele-pro-layout
:menus="menus"
:tab-bar="false"
:sidebar-openeds="sidebarOpeneds"
:unique-opened="false">
<router-view />
</ele-pro-layout>
</template>
<script setup>
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/store/modules/user';
//import { eachTree } from 'ele-admin-plus';
const userStore = useUserStore();
const { menus } = storeToRefs(userStore);
const sidebarOpeneds = computed(() => {
return menus.value.filter(d => !!d.children?.length).map(d => d.path);
// 上面代码只能展开第一层级菜单, 需要展开所有层级要使用 eachTree 遍历
/*
const openeds = [];
eachTree(menus.value, (d) => {
if(d.children?.length) {
openeds.push(d.path);
}
});
return openeds;
*/
});
</script>
运行代码 新窗口打开
5.1.19.关闭移动端响应式
EleProLayout 在小尺寸屏幕下会自适应,可加 :responsive="false" 属性关闭,且会对 EleAdminPlus 的所有做了响应式的组件都生效:
<template>
<ele-pro-layout :menus="menus" :tab-bar="false" :responsive="false">
<router-view />
</ele-pro-layout>
</template>
<script setup>
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/store/modules/user';
const userStore = useUserStore();
const { menus } = storeToRefs(userStore);
</script>
不想做移动端自适应一般都会给 body 设置一个最小的宽度:
body {
height: 100vh;
min-width: 1200px;
overflow-x: hidden;
overflow-y: auto;
}
这个属性只是 EleAdminPlus 的组件支持的一个便捷的配置, 在使用 Element 的一些组件以及自己在开发的时候也需要注意:
<template>
<ele-pro-layout :menus="menus" :tab-bar="false" :responsive="false">
<!-- 例如使用 el-col 做多列布局的时候别使用 md/sm/xs 等属性 -->
<el-form label-width="78px">
<el-row :gutter="16">
<el-col :sm="12" :xs="24">
<el-form-item label="用户账号">
<el-input v-model="form.username" />
</el-form-item>
<el-form-item label="用户名">
<el-input v-model="form.nickname" />
</el-form-item>
</el-col>
<el-col :sm="12" :xs="24">
<el-form-item label="手机号">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="出生日期">
<el-date-picker v-model="form.birthday" value-format="YYYY-MM-DD" class="ele-fluid" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 应该使用 span 属性 -->
<el-form label-width="78px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="用户账号">
<el-input v-model="form.username" />
</el-form-item>
<el-form-item label="用户名">
<el-input v-model="form.nickname" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="手机号">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="出生日期">
<el-date-picker v-model="form.birthday" value-format="YYYY-MM-DD" class="ele-fluid" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</ele-pro-layout>
</template>
<script setup>
import { reactive } from 'vue';
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/store/modules/user';
const userStore = useUserStore();
const { menus } = storeToRefs(userStore);
const form = reactive({
username: '',
nickname: '',
phone: '',
birthday: ''
});
</script>
在 文档 7.4 中给了一个简单的关闭移动端响应式的实现,可前往查看。
5.1.20.增加全局页脚
EleProLayout 的内容区使用了垂直方向的 flex 布局,因此可以很方便灵活的添加页脚,示例:
<template>
<ele-pro-layout :menus="[{ path: '/test', meta: { title: '示例' } }]" :tabs="[]">
<div style="padding: 16px">
<div>内容</div>
<div>内容</div>
<div>内容</div>
</div>
<!-- 实际项目一般这里为路由出口, 上面代码为内容页面的 -->
<!-- <router-view /> -->
<template #footer>
<div style="padding: 16px 0; text-align: center">
<span>我是页脚 </span>
<span>我是页脚</span>
</div>
</template>
</ele-pro-layout>
</template>
运行代码 新窗口打开
如果想要页脚至少位于底部(内容较少时别贴着内容)可以在内容外面使用 ElePage 页面容器组件:
<template>
<ele-pro-layout :menus="[{ path: '/test', meta: { title: '示例' }}]" :tabs="[]">
<ele-page>
<div>内容</div>
<div>内容</div>
<div>内容</div>
</ele-page>
<!-- 实现原理就是设置 flex: auto 即可 -->
<!-- <div style="flex: auto;">
<div>内容</div>
<div>内容</div>
<div>内容</div>
</div> -->
<!-- 实际项目一般这里为路由出口, 上面代码为内容页面的 -->
<!-- <router-view /> -->
<template #footer>
<div style="padding: 16px 0;text-align: center;">
<span>我是页脚 </span>
<span>我是页脚</span>
</div>
</template>
</ele-pro-layout>
</template>
运行代码 新窗口打开
5.1.21.全局页脚固定在底部
如果想要页脚一直固定在下面,即使页面内容非常多的时候也不要滚动到最底部才能看到页脚,可以使用 sticky 实现:
<template>
<ele-pro-layout :menus="[{ path: '/docs/eGZvZCF8', meta: { title: '示例' } }]" :tabs="[]">
<ele-page>
<div v-for="index in 38" :key="index">内容</div>
</ele-page>
<!-- 实际项目一般这里为路由出口, 上面代码为内容页面的 -->
<!-- <router-view /> -->
<template #footer>
<divstyle="padding: 16px 0;text-align: center;position: sticky;bottom: 0;background: #fff;z-index: 9999;">
<span>我是页脚 </span>
<span>我是页脚</span>
</div>
</template>
</ele-pro-layout>
</template>
运行代码 新窗口打开
也可以为路由出口包一个 div 并加样式 flex: 1 和 overflow: auto 来实现,示例:
<template>
<ele-pro-layout
:menus="[{ path: '/docs/efDZYIAL', meta: { title: '示例' } }]"
:tabs="[]"
back-top-target=".my-layout-body">
<div style="flex: 1; overflow-y: auto" class="my-layout-body">
<ele-page>
<div v-for="index in 38" :key="index">内容</div>
</ele-page>
<!-- 实际项目一般这里为路由出口, 上面代码为内容页面的 -->
<!-- <router-view /> -->
</div>
<template #footer>
<div style="padding: 16px 0; text-align: center">
<span>我是页脚 </span>
<span>我是页脚</span>
</div>
</template>
</ele-pro-layout>
</template>
<!-- 如果开启了返回顶部组件还需要通过 backTopTarget 重新设置内容的选择器 -->
运行代码 新窗口打开
5.1.22.布局组件
从 v1.2.0 开始将 EleProLayout 核心独立出 EleAdminLayout 组件,方便在其它场景使用,全部属性:
全部事件:
全部插槽:
使用示例:
<template>
<ele-admin-layout
:sidebar-menus="sidebarMenus"
:sidebar-active="sidebarActive"
:fixed-body="true"
:tab-bar="false"
:breadcrumb="false"
sidebar-style="light"
v-model:collapse="collapse"
style="height: 100vh; height: 100dvh">
<!-- logo -->
<template #logo>
<img src="@/assets/logo.svg" />
</template>
<template #logoTitle>
<h1>EleAdminPlus</h1>
</template>
<!-- 顶栏左侧操作按钮 -->
<template #left>
<layout-tool @click="collapse = !collapse">
<el-icon style="transform: scale(1.14)">
<MenuUnfoldOutlined v-if="collapse" />
<MenuFoldOutlined v-else />
</el-icon>
</layout-tool>
</template>
<!-- 顶栏右侧操作按钮 -->
<template #right>
<layout-tool>
<el-icon style="transform: scale(1.17)">
<BellOutlined />
</el-icon>
</layout-tool>
<layout-tool style="padding-left: 4px; padding-right: 4px">
<img:style="{ width: '28px', height: '28px', borderRadius: '50%', marginRight: '6px' }"src="https://cdn.eleadmin.com/20200610/avatar.jpg"/>
<div>用户名</div>
<el-icon :size="13" style="margin: 0 0 0 2px;">
<ArrowDown />
</el-icon>
</layout-tool>
</template>
<!-- 内容 -->
<div :style="{ flex: 'auto', padding: '12px' }">
<ele-card>内容内容内容</ele-card>
</div>
<div style="padding: 4px 0 16px 0; text-align: center;">
页脚页脚 页脚页脚 页脚页脚
</div>
</ele-admin-layout>
</template>
<script setup>
import { ref, markRaw } from 'vue';
import { LayoutTool } from 'ele-admin-plus';
import { HomeFilled, VideoCameraFilled, WalletFilled, Promotion } from '@element-plus/icons-vue';
import { BellOutlined, ArrowDown, MenuFoldOutlined, MenuUnfoldOutlined } from '@/components/icons';
// 注意 EleAdminLayout 的各个菜单数据格式都是与 EleMenus 组件一致, 与 EleProLayout 不一致
/** 侧栏数据 */
const sidebarMenus = ref([
{
index: '/home',
title: '首页',
icon: markRaw(HomeFilled)
},
{
index: '/creater',
title: '创作中心',
icon: markRaw(Promotion),
children: [
{
index: '/product',
title: '我的作品',
icon: markRaw(VideoCameraFilled)
},
{
index: '/income',
title: '作品收益',
icon: markRaw(WalletFilled)
}
]
}
]);
/** 侧栏选中 */
const sidebarActive = ref('/home');
/** 侧栏是否折叠 */
const collapse = ref(false);
</script>
运行代码 新窗口打开
5.1.23.返回顶部
返回顶部组件 EleBacktop 用法和 ElBacktop 一致,对属性进行了扩展(v1.2.0新增),全部属性:
全部事件:
全部插槽:
使用示例:
<template>
<div style="position: relative;">
<div ref="wrapRef" style="max-height: 320px;overflow: auto;background: rgb(203, 243, 254);">
<div style="margin-bottom: 120px;">内容内容内容</div>
<div style="margin-bottom: 120px;">内容内容内容</div>
<div style="margin-bottom: 120px;">内容内容内容</div>
<div style="margin-bottom: 120px;">内容内容内容</div>
<div style="margin-bottom: 120px;">内容内容内容</div>
<div style="margin-bottom: 120px;">内容内容内容</div>
</div>
<!-- 设置位置 -->
<ele-backtop :target="wrapRef" :bottom="120" style="position: absolute;" />
<!-- 自定义图标 -->
<ele-backtop :target="wrapRef" style="position: absolute;">
<el-icon>
<CaretTop />
</el-icon>
</ele-backtop>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { CaretTop } from '@element-plus/icons-vue';
const wrapRef = ref(null);
</script>