fix(请求拦截器): 处理blob类型错误响应并优化导出功能

refactor(路由): 优化混合布局路由解析和菜单更新逻辑
perf(权限存储): 使用路由映射表提高菜单查找效率
style(顶部菜单): 优化菜单项显示逻辑和过滤隐藏项
fix(导航栏): 确保用户菜单在移动端正常显示
refactor(导出功能): 统一处理导出错误和资源释放
This commit is contained in:
zhangtao
2025-08-01 00:46:28 +08:00
parent 0de4ff2905
commit 0374292b67
14 changed files with 240 additions and 184 deletions
@@ -58,31 +58,51 @@ const topMenus = ref<RouteRecordRaw[]>([]);
// 处理后的顶部菜单列表 - 智能显示唯一子菜单的标题
const processedTopMenus = computed(() => {
return topMenus.value.map((route) => {
// 如果路由设置了 alwaysShow=true,或者没有子菜单,直接返回原路由
if (route.meta?.alwaysShow || !route.children || route.children.length === 0) {
return topMenus.value
.map((route) => {
// 如果路由本身设置了hidden=true,直接返回null
if (route.meta?.hidden === true) {
return null;
}
// 如果路由设置了 alwaysShow=true,或者没有子菜单,直接返回原路由
if (route.meta?.alwaysShow || !route.children || route.children.length === 0) {
return route;
}
// 过滤出非隐藏的子菜单
const visibleChildren = route.children.filter((child) => !child.meta?.hidden);
// 如果没有可见子菜单,返回null
if (visibleChildren.length === 0) {
return null;
}
// 如果只有一个非隐藏的子菜单,显示子菜单的信息
if (visibleChildren.length === 1) {
const onlyChild = visibleChildren[0];
// 检查子菜单是否应该显示在顶部菜单
if (onlyChild.meta?.hidden !== true) {
return {
...route,
meta: {
...route.meta,
title: onlyChild.meta?.title || route.meta?.title,
icon: onlyChild.meta?.icon || route.meta?.icon,
},
// 使用子菜单的path确保路由匹配正确
path: onlyChild.path
};
}
// 如果子菜单应该隐藏,则不显示该顶级菜单
return null;
}
// 其他情况返回原路由
return route;
}
// 过滤出非隐藏的子菜单
const visibleChildren = route.children.filter((child) => !child.meta?.hidden);
// 如果只有一个非隐藏的子菜单,显示子菜单的信息
if (visibleChildren.length === 1) {
const onlyChild = visibleChildren[0];
return {
...route,
meta: {
...route.meta,
title: onlyChild.meta?.title || route.meta?.title,
icon: onlyChild.meta?.icon || route.meta?.icon,
},
};
}
// 其他情况返回原路由
return route;
});
})
// 过滤掉null值
.filter((route) => route !== null);
});
/**
@@ -99,15 +119,18 @@ const handleMenuSelect = (routePath: string) => {
* @param skipNavigation 是否跳过导航(路由变化时为true,点击菜单时为false)
*/
const updateMenuState = (topMenuPath: string, skipNavigation = false) => {
// 不相同才更新,避免重复操作
if (topMenuPath !== appStore.activeTopMenuPath) {
// 确保路径有效且不相同才更新,避免重复操作
if (topMenuPath && topMenuPath !== appStore.activeTopMenuPath) {
appStore.activeTopMenu(topMenuPath); // 设置激活的顶部菜单
permissionStore.updateSideMenu(topMenuPath); // 更新左侧菜单
// 只有当路由映射表中存在该路径时才更新侧边菜单
if (permissionStore.routePathMap && permissionStore.routePathMap[topMenuPath]) {
permissionStore.updateSideMenu(topMenuPath); // 更新左侧菜单
}
}
// 如果是点击菜单且状态已变更,才进行导航
if (!skipNavigation) {
navigateToFirstLeftMenu(permissionStore.sideMenuRoutes); // 跳转到左侧第一个菜单
if (!skipNavigation && topMenuPath === appStore.activeTopMenuPath) {
navigateToFirstLeftMenu(permissionStore.sideMenuRoutes || []); // 跳转到左侧第一个菜单
}
};
@@ -22,58 +22,59 @@
<LangSelect />
</div>
<!-- 通知 -->
<div class="navbar-actions__item">
<Notification />
</div>
<!-- 用户菜单 -->
<div class="navbar-actions__item">
<el-dropdown trigger="click">
<div class="user-profile">
<template v-if="userStore.basicInfo.avatar">
<el-avatar size="small" :src="userStore.basicInfo.avatar" />
</template>
<template v-else>
<el-avatar icon="UserFilled" />
</template>
<span class="user-profile__name">{{ userStore.basicInfo.username }}</span>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handleProfileClick">
<el-icon><User /></el-icon>
{{ t("navbar.profile") }}
</el-dropdown-item>
<el-dropdown-item @click="handleConfigClick">
<el-icon><Setting /></el-icon>
{{ t("navbar.config") }}
</el-dropdown-item>
<el-dropdown-item @click="handleDocumentClick">
<el-icon><Document /></el-icon>
{{ t("navbar.document") }}
</el-dropdown-item>
<el-dropdown-item @click="handleGiteeClick">
<el-icon><Reading /></el-icon>
{{ t("navbar.gitee") }}
</el-dropdown-item>
<el-dropdown-item @click="handleTourClick">
<el-icon><Position /></el-icon>
{{ t("navbar.tour") }}
</el-dropdown-item>
<el-dropdown-item divided @click="handlelockScreen">
<el-icon><Lock /></el-icon>
{{ t("navbar.lock") }}
</el-dropdown-item>
<el-dropdown-item @click="logout">
<el-icon><SwitchButton /></el-icon>
{{ t("navbar.logout") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<!-- 通知 -->
<div class="navbar-actions__item">
<Notification />
</div>
<!-- 用户菜单,不管桌面还是移动端都显示 -->
<div class="navbar-actions__item">
<el-dropdown trigger="click">
<div class="user-profile">
<template v-if="userStore.basicInfo.avatar">
<el-avatar size="small" :src="userStore.basicInfo.avatar" />
</template>
<template v-else>
<el-avatar icon="UserFilled" />
</template>
<span class="user-profile__name">{{ userStore.basicInfo.username }}</span>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handleProfileClick">
<el-icon><User /></el-icon>
{{ t("navbar.profile") }}
</el-dropdown-item>
<el-dropdown-item @click="handleConfigClick">
<el-icon><Setting /></el-icon>
{{ t("navbar.config") }}
</el-dropdown-item>
<el-dropdown-item @click="handleDocumentClick">
<el-icon><Document /></el-icon>
{{ t("navbar.document") }}
</el-dropdown-item>
<el-dropdown-item @click="handleGiteeClick">
<el-icon><Reading /></el-icon>
{{ t("navbar.gitee") }}
</el-dropdown-item>
<el-dropdown-item @click="handleTourClick">
<el-icon><Position /></el-icon>
{{ t("navbar.tour") }}
</el-dropdown-item>
<el-dropdown-item divided @click="handlelockScreen">
<el-icon><Lock /></el-icon>
{{ t("navbar.lock") }}
</el-dropdown-item>
<el-dropdown-item @click="logout">
<el-icon><SwitchButton /></el-icon>
{{ t("navbar.logout") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<!-- 引导 -->
+9 -19
View File
@@ -109,33 +109,24 @@ function resolvePath(routePath: string) {
}
if (routePath.startsWith("/")) {
return activeTopMenuPath.value + routePath;
return routePath;
}
return `${activeTopMenuPath.value}/${routePath}`;
return `${routePath}`;
}
// 监听路由变化,确保左侧菜单能随TagsView切换而正确激活
// 优化后的路由监听逻辑,仅在顶级路径实际变化时更新菜单
let prevTopMenuPath = '';
watch(
() => route.path,
(newPath) => {
console.log("📍 Route changed in MixLayout:", newPath);
// 获取顶级路径
const topMenuPath =
const topMenuPath =
newPath.split("/").filter(Boolean).length > 1 ? newPath.match(/^\/[^/]+/)?.[0] || "/" : "/";
// 如果当前路径属于当前激活的顶部菜单
if (newPath.startsWith(activeTopMenuPath.value)) {
console.log("📍 Route is under active top menu, ensuring menu item is activated");
}
// 如果路径改变了顶级菜单,确保顶部菜单和左侧菜单都更新
else if (topMenuPath !== activeTopMenuPath.value) {
console.log(
"📍 Top menu changed, updating active menu from:",
activeTopMenuPath.value,
"to:",
topMenuPath
);
// 仅在顶级路径实际变化时才执行更新
if (topMenuPath !== prevTopMenuPath) {
prevTopMenuPath = topMenuPath;
// 主动更新顶部菜单和左侧菜单
const appStore = useAppStore();
@@ -212,7 +203,6 @@ watch(
flex-shrink: 0;
align-items: center;
height: 100%;
padding: 0 16px;
}
}