| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- <template>
- <div class="top-menu-container">
- <el-menu
- :default-active="activeMenu"
- mode="horizontal"
- :ellipsis="false"
- background-color="#1ca851"
- text-color="#ffffff"
- active-text-color="#ffffff"
- @select="handleSelect"
- >
- <el-menu-item v-for="(item, index) in topMenus" :key="index" :index="isExternalLink(item) ? `external_${index}` : item.path">
- <template v-if="isExternalLink(item)">
- <a :href="getExternalUrl(item)" target="_blank" rel="noopener noreferrer" class="external-link" @click.stop>
- <svg-icon v-if="item.meta && item.meta.icon" :icon-class="item.meta.icon" />
- <span>{{ item.meta?.title || item.name }}</span>
- </a>
- </template>
- <template v-else>
- <svg-icon v-if="item.meta && item.meta.icon" :icon-class="item.meta.icon" />
- <span>{{ item.meta?.title || item.name }}</span>
- </template>
- </el-menu-item>
- </el-menu>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, computed, watch, onMounted } from 'vue';
- import { usePermissionStore } from '@/store/modules/permission';
- import { useRouter, useRoute } from 'vue-router';
- import { RouteRecordRaw } from 'vue-router';
- const router = useRouter();
- const route = useRoute();
- const permissionStore = usePermissionStore();
- // 顶部菜单列表
- const topMenus = ref<RouteRecordRaw[]>([]);
- // 当前激活的一级菜单
- const activeMenu = computed(() => {
- const path = route.path;
- let activePath = path;
- // 查找当前路径对应的一级菜单
- if (topMenus.value.length > 0) {
- const currentTopMenu = topMenus.value.find((item) => {
- // 清理双斜杠
- const menuPath = item.path.replace(/\/+/g, '/');
- return path.startsWith(menuPath);
- });
- if (currentTopMenu && currentTopMenu.path) {
- // 清理双斜杠
- activePath = currentTopMenu.path.replace(/\/+/g, '/');
- }
- }
- return activePath;
- });
- // 设置顶部菜单
- const setTopMenus = () => {
- const routers = permissionStore.getTopbarRoutes();
- console.log('TopMenu - 顶部路由:', routers);
- // 过滤出一级菜单(visible为true,且不是隐藏的菜单,且不是按钮类型)
- topMenus.value = routers.filter((item: any) => {
- return !item.hidden && item.meta?.visible !== false && item.menuType !== 'F';
- });
- console.log('TopMenu - 顶部菜单列表:', topMenus.value);
- };
- // 判断是否为外链
- const isExternalLink = (item: any): boolean => {
- // isFrame 为 "0" 表示是外链
- return item.isFrame === '0';
- };
- // 获取外链完整URL
- const getExternalUrl = (item: any): string => {
- // 对于 isFrame='0' 的外链菜单,path 字段就是完整的外链地址
- // 对于内链(isFrame='1' 且 path 是 http 开头),地址存储在 meta.link 中
- const url = item.path || item.meta?.link || '';
- console.log('TopMenu - 外链地址:', url, '(isFrame:', item.isFrame, ')');
- return url;
- };
- // 菜单选择事件
- const handleSelect = (index: string) => {
- console.log('TopMenu - 选择菜单:', index);
- // 如果是外链菜单的 index(格式:external_数字),直接返回
- if (index.startsWith('external_')) {
- console.log('TopMenu - 外链菜单,由 <a> 标签处理打开');
- return;
- }
- // 查找对应的菜单项
- const menuItem = topMenus.value.find((item) => {
- const cleanPath = item.path.replace(/\/+/g, '/');
- const cleanIndex = index.replace(/\/+/g, '/');
- return cleanPath === cleanIndex || item.path === index;
- });
- console.log('TopMenu - 找到的菜单项:', menuItem);
- // 清理双斜杠
- const cleanIndex = index.replace(/\/+/g, '/');
- console.log('TopMenu - 清理后路径:', cleanIndex);
- if (menuItem) {
- // 判断是否有子菜单(过滤掉隐藏的和按钮类型的)
- const hasChildren = menuItem.children && menuItem.children.length > 0;
- const visibleChildren = hasChildren ? menuItem.children.filter((child: any) => !child.hidden && child.menuType !== 'F') : [];
- // 如果有子菜单(说明是目录菜单)
- if (visibleChildren.length > 0) {
- // 判断当前路由是否已经在该菜单下
- const currentPath = route.path;
- const isUnderThisMenu = currentPath.startsWith(cleanIndex);
- console.log('TopMenu - 当前路径:', currentPath, '是否在该菜单下:', isUnderThisMenu);
- // 如果当前路由不在该菜单下,则跳转到第一个子菜单
- if (!isUnderThisMenu) {
- const firstChild = visibleChildren[0];
- console.log('TopMenu - 跳转到第一个子菜单:', firstChild);
- if (firstChild) {
- // 构建完整路径
- let targetPath = firstChild.path;
- // 如果子菜单的path不是以/开头,说明是相对路径,需要拼接父路径
- if (!targetPath.startsWith('/')) {
- targetPath = `${cleanIndex}/${targetPath}`;
- }
- // 清理可能的双斜杠
- targetPath = targetPath.replace(/\/+/g, '/');
- console.log('TopMenu - 最终跳转路径:', targetPath);
- router.push(targetPath);
- }
- }
- // 如果已经在该菜单下,不做跳转,保持当前路由,让左侧菜单树显示
- else {
- console.log('TopMenu - 已在该菜单下,不跳转,保持当前页面');
- }
- }
- // 如果没有子菜单,说明是直接页面,直接跳转
- else {
- console.log('TopMenu - 直接跳转到:', cleanIndex);
- router.push(cleanIndex);
- }
- }
- };
- // 初始化
- onMounted(() => {
- setTopMenus();
- });
- // 监听路由变化,更新菜单
- watch(
- () => permissionStore.topbarRouters,
- () => {
- setTopMenus();
- },
- { deep: true }
- );
- </script>
- <style lang="scss" scoped>
- .top-menu-container {
- height: 100%;
- :deep(.el-menu) {
- border: none;
- height: 100%;
- &.el-menu--horizontal {
- border-bottom: none;
- }
- }
- :deep(.el-menu-item) {
- height: 100%;
- line-height: 50px;
- border-bottom: 3px solid transparent;
- padding: 0 20px;
- font-size: 14px;
- font-weight: 500;
- transition: all 0.3s;
- &:hover {
- background-color: rgba(255, 255, 255, 0.15) !important;
- color: #ffffff !important;
- }
- &.is-active {
- border-bottom-color: #ffffff;
- background-color: rgba(255, 255, 255, 0.2) !important;
- color: #ffffff !important;
- font-weight: 600;
- }
- .svg-icon {
- margin-right: 8px;
- font-size: 16px;
- vertical-align: middle;
- }
- span {
- vertical-align: middle;
- }
- .external-link {
- display: flex;
- align-items: center;
- color: inherit;
- text-decoration: none;
- width: 100%;
- height: 100%;
- &:hover {
- color: #ffffff;
- }
- .svg-icon {
- margin-right: 8px;
- font-size: 16px;
- }
- }
- }
- }
- </style>
|