mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-16 05:45:35 +00:00
fix(请求拦截器): 处理blob类型错误响应并优化导出功能
refactor(路由): 优化混合布局路由解析和菜单更新逻辑 perf(权限存储): 使用路由映射表提高菜单查找效率 style(顶部菜单): 优化菜单项显示逻辑和过滤隐藏项 fix(导航栏): 确保用户菜单在移动端正常显示 refactor(导出功能): 统一处理导出错误和资源释放
This commit is contained in:
@@ -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>
|
||||
|
||||
<!-- 引导 -->
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user