Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/components/WangEditor/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const FileAPI = {
defineProps({
height: {
type: String,
default: "200px",
default: "300px",
},
});
// 双向绑定
Expand Down
72 changes: 25 additions & 47 deletions frontend/src/layouts/components/Menu/MixTopMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,53 +58,31 @@ const topMenus = ref<RouteRecordRaw[]>([]);

// 处理后的顶部菜单列表 - 智能显示唯一子菜单的标题
const processedTopMenus = computed(() => {
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;
})
// 过滤掉null值
.filter((route) => route !== null)
);
return topMenus.value.map((route) => {
// 如果路由设置了 alwaysShow=true,或者没有子菜单,直接返回原路由
if (route.meta?.alwaysShow || !route.children || route.children.length === 0) {
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;
});
});

/**
Expand Down
105 changes: 59 additions & 46 deletions frontend/src/layouts/components/TagsView/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
>
<!-- 为所有标签添加右键菜单 -->
<el-dropdown
v-if="tagsViewStore.isActive(tag)"
trigger="contextmenu"
@visible-change="(visible) => onContextMenuVisibleChange(visible, tag)"
@click.stop
Expand All @@ -44,29 +43,23 @@
{{ t("navbar.close") }}
</el-dropdown-item>

<el-dropdown-item
:disabled="isFirstView || !tagsViewStore.isActive(tag)"
@click="closeLeftTags"
>
<el-dropdown-item :disabled="isFirstView(tag)" @click="closeLeftTags(tag)">
<el-icon>
<Back />
</el-icon>
{{ t("navbar.closeLeft") }}
</el-dropdown-item>

<el-dropdown-item
:disabled="isLastView || !tagsViewStore.isActive(tag)"
@click="closeRightTags"
>
<el-dropdown-item :disabled="isLastView(tag)" @click="closeRightTags(tag)">
<el-icon>
<Right />
</el-icon>
{{ t("navbar.closeRight") }}
</el-dropdown-item>

<el-dropdown-item
:disabled="visitedViews.length <= 1 || !tagsViewStore.isActive(tag)"
@click="closeOtherTags"
:disabled="visitedViews.length <= 1"
@click="closeOtherTags(tag)"
>
<el-icon>
<Remove />
Expand All @@ -92,7 +85,6 @@
</el-dropdown-menu>
</template>
</el-dropdown>
<span v-else class="tag-text">{{ translateRouteTitle(tag.title) }}</span>

<span
v-if="!tag.affix"
Expand Down Expand Up @@ -141,14 +133,20 @@
{{ t("navbar.close") }}
</el-dropdown-item>

<el-dropdown-item :disabled="isFirstView" @click="handleAction('closeLeft')">
<el-dropdown-item
:disabled="isFirstView(routePathMap.get(route.path))"
@click="handleAction('closeLeft')"
>
<el-icon>
<Back />
</el-icon>
{{ t("navbar.closeLeft") }}
</el-dropdown-item>

<el-dropdown-item :disabled="isLastView" @click="handleAction('closeRight')">
<el-dropdown-item
:disabled="isLastView(routePathMap.get(route.path))"
@click="handleAction('closeRight')"
>
<el-icon>
<Right />
</el-icon>
Expand Down Expand Up @@ -205,40 +203,48 @@ const displayedViews = computed(() => {
// 当前选中的标签
const selectedTag = ref<TagView | null>(null);

// 基于当前激活路由的回退标签(当未选中右键标签时)
const activeTag = computed(() => {
return selectedTag.value || routePathMap.value.get(route.path) || null;
});

// 滚动条引用
const scrollbarRef = ref();

// 标签切换来源跟踪
const tagSwitchSource = ref<"menu" | "tab" | null>(null);

// 路由映射缓存,提升查找性能
const routePathMap = computed(() => {
const map = new Map<string, TagView>();
const routePathMap = new Map<string, TagView>();

// 更新路由映射缓存
const updateRoutePathMap = () => {
routePathMap.clear();
visitedViews.value.forEach((tag) => {
map.set(tag.path, tag);
routePathMap.set(tag.path, tag);
});
return map;
});
};

// 判断是否为第一个标签
const isFirstView = computed(() => {
if (!activeTag.value) return false;
return (
activeTag.value.path === "/dashboard" ||
activeTag.value.fullPath === visitedViews.value[1]?.fullPath
);
});
const isFirstView = (tag?: TagView) => {
if (tag) {
// 传入特定标签时使用传入的标签
return tag.path === "/" || tag.fullPath === visitedViews.value[1]?.fullPath;
} else {
// 未传入标签时使用当前激活标签(向后兼容)
const currentTag = routePathMap.get(route.path);
if (!currentTag) return false;
return currentTag.path === "/" || currentTag.fullPath === visitedViews.value[1]?.fullPath;
}
};

// 判断是否为最后一个标签
const isLastView = computed(() => {
if (!activeTag.value) return false;
return activeTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1]?.fullPath;
});
const isLastView = (tag?: TagView) => {
if (tag) {
// 传入特定标签时使用传入的标签
return tag.fullPath === visitedViews.value[visitedViews.value.length - 1]?.fullPath;
} else {
// 未传入标签时使用当前激活标签(向后兼容)
const currentTag = routePathMap.get(route.path);
if (!currentTag) return false;
return currentTag.fullPath === visitedViews.value[visitedViews.value.length - 1]?.fullPath;
}
};

/**
* 递归提取固定标签
Expand Down Expand Up @@ -340,7 +346,7 @@ const addCurrentTag = () => {
*/
const updateCurrentTag = () => {
nextTick(() => {
const currentTag = routePathMap.value.get(route.path);
const currentTag = routePathMap.get(route.path);

if (currentTag && currentTag.fullPath !== route.fullPath) {
tagsViewStore.updateVisitedView({
Expand Down Expand Up @@ -422,7 +428,7 @@ const refreshSelectedTag = (tag: TagView | null) => {
*/
const closeSelectedTag = (tag: TagView | null) => {
// 如果传入了具体的标签,使用传入的标签;否则使用当前路由对应的标签
const targetTag = tag || routePathMap.value.get(route.path);
const targetTag = tag || routePathMap.get(route.path);
if (!targetTag) return;

tagsViewStore.delView(targetTag).then((result: any) => {
Expand All @@ -439,8 +445,8 @@ const closeSelectedTag = (tag: TagView | null) => {
/**
* 关闭左侧标签
*/
const closeLeftTags = () => {
const targetTag = selectedTag.value || routePathMap.value.get(route.path);
const closeLeftTags = (tag?: TagView) => {
const targetTag = tag || selectedTag.value || routePathMap.get(route.path);
if (!targetTag) return;

tagsViewStore.delLeftViews(targetTag).then((result: any) => {
Expand All @@ -459,8 +465,8 @@ const closeLeftTags = () => {
/**
* 关闭右侧标签
*/
const closeRightTags = () => {
const targetTag = selectedTag.value || routePathMap.value.get(route.path);
const closeRightTags = (tag?: TagView) => {
const targetTag = tag || selectedTag.value || routePathMap.get(route.path);
if (!targetTag) return;

tagsViewStore.delRightViews(targetTag).then((result: any) => {
Expand All @@ -479,8 +485,8 @@ const closeRightTags = () => {
/**
* 关闭其他标签
*/
const closeOtherTags = () => {
const targetTag = selectedTag.value || routePathMap.value.get(route.path);
const closeOtherTags = (tag?: TagView) => {
const targetTag = tag || selectedTag.value || routePathMap.get(route.path);
if (!targetTag) return;

router.push(targetTag);
Expand All @@ -496,7 +502,7 @@ const closeOtherTags = () => {
/**
* 关闭所有标签
*/
const closeAllTags = (tag: TagView | null) => {
const closeAllTags = (tag?: TagView) => {
tagsViewStore.delAllViews().then((result: any) => {
tagsViewStore.toLastView(result.visitedViews, tag || undefined);
// 关闭所有标签后重置滚动状态
Expand All @@ -511,7 +517,7 @@ const closeAllTags = (tag: TagView | null) => {
*/
const handleAction = async (action: string) => {
// 总是使用当前路由对应的标签
const currentTag = routePathMap.value.get(route.path);
const currentTag = routePathMap.get(route.path);
if (!currentTag) return;

switch (action) {
Expand Down Expand Up @@ -557,7 +563,7 @@ const handleAction = async (action: string) => {
const onContextMenuVisibleChange = (visible: boolean, tag?: TagView) => {
if (visible) {
// 设置当前右键点击的标签
selectedTag.value = tag || routePathMap.value.get(route.path) || null;
selectedTag.value = tag || routePathMap.get(route.path) || null;
} else {
// 关闭菜单时清空选择
selectedTag.value = null;
Expand Down Expand Up @@ -713,6 +719,7 @@ watch(
}
addCurrentTag();
updateCurrentTag();
updateRoutePathMap();
},
{ immediate: true }
);
Expand All @@ -724,6 +731,9 @@ let resizeObserver: ResizeObserver | null = null;
watch(
() => visitedViews.value.length,
() => {
// 更新路由映射缓存
updateRoutePathMap();

// 只有在通过菜单添加新标签时才滚动
if (tagSwitchSource.value === "menu") {
nextTick(() => {
Expand All @@ -747,6 +757,9 @@ watch(
onMounted(() => {
initAffixTags();

// 初始化路由映射缓存
updateRoutePathMap();

// 监听容器大小变化
const tagsContainer = document.querySelector(".tags-container");
if (tagsContainer && window.ResizeObserver) {
Expand Down
18 changes: 9 additions & 9 deletions frontend/src/layouts/views/MixLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
<!-- 顶部菜单栏 -->
<div class="layout__header">
<div class="layout__header-content">
<!-- Logo区域 -->
<div v-if="isShowLogo" class="layout__header-logo">
<AppLogo :collapse="isLogoCollapsed" />
</div>

<!-- 顶部菜单区域 -->
<div class="layout__header-menu">
<!-- Logo区域 -->
<div v-if="isShowLogo" class="layout__header-logo">
<AppLogo :collapse="isLogoCollapsed" />
</div>
<MixTopMenu />
</div>

Expand Down Expand Up @@ -107,11 +108,10 @@ function resolvePath(routePath: string) {
return routePath;
}

// if (routePath.startsWith("/")) {
// return activeTopMenuPath.value + routePath;
// }
// return `${activeTopMenuPath.value}/${routePath}`;
return routePath;
if (routePath.startsWith("/")) {
return routePath;
}
return `${activeTopMenuPath.value}/${routePath}`;
}

watch(
Expand Down
Loading