|
|
@@ -41,6 +41,69 @@ const activeKey = ref('/desktop/dashboard');
|
|
|
// 菜单节点映射表 - 存储路径与菜单节点的对应关系
|
|
|
const pathMenuMap = ref({});
|
|
|
|
|
|
+// 获取不含 query 的路径,用于标题解析等
|
|
|
+const getPathname = fullPath => fullPath.split('?')[0];
|
|
|
+
|
|
|
+// 解析 query 参数
|
|
|
+const parseQuery = fullPath => {
|
|
|
+ const queryIndex = fullPath.indexOf('?');
|
|
|
+ if (queryIndex < 0) {
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+ const params = {};
|
|
|
+ new URLSearchParams(fullPath.slice(queryIndex + 1)).forEach((value, key) => {
|
|
|
+ params[key] = value;
|
|
|
+ });
|
|
|
+ return params;
|
|
|
+};
|
|
|
+
|
|
|
+// 获取标签页稳定标识(忽略 uuid 等每次跳转都会变的参数)
|
|
|
+const getTabIdentity = fullPath => {
|
|
|
+ const pathname = getPathname(fullPath);
|
|
|
+ if (
|
|
|
+ pathname.includes('/desktop/window1/')
|
|
|
+ || pathname.includes('/desktop/window/')
|
|
|
+ || pathname.includes('/desktop/sheetWindow/')
|
|
|
+ ) {
|
|
|
+ return pathname;
|
|
|
+ }
|
|
|
+ if (pathname === '/desktop/launch') {
|
|
|
+ const query = parseQuery(fullPath);
|
|
|
+ if (query.key) {
|
|
|
+ return `${pathname}?key=${query.key}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return fullPath;
|
|
|
+};
|
|
|
+
|
|
|
+// 根据路由同步当前激活的标签
|
|
|
+const syncActiveTab = fullPath => {
|
|
|
+ const tabIdentity = getTabIdentity(fullPath);
|
|
|
+ const existTab = tabs.value.find(
|
|
|
+ tab => tab.key === tabIdentity || getTabIdentity(tab.path) === tabIdentity,
|
|
|
+ );
|
|
|
+ if (existTab) {
|
|
|
+ activeKey.value = existTab.key;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 规范化并去重本地恢复的标签页
|
|
|
+const normalizeTabs = parsedTabs => {
|
|
|
+ const tabMap = new Map();
|
|
|
+ parsedTabs.forEach(tab => {
|
|
|
+ const path = tab.path || tab.key;
|
|
|
+ const identity = getTabIdentity(path);
|
|
|
+ if (!tabMap.has(identity)) {
|
|
|
+ tabMap.set(identity, {
|
|
|
+ ...tab,
|
|
|
+ key: identity,
|
|
|
+ path: path,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return Array.from(tabMap.values());
|
|
|
+};
|
|
|
+
|
|
|
// 初始化标签页
|
|
|
onMounted(() => {
|
|
|
// 尝试从本地存储中恢复标签页
|
|
|
@@ -58,7 +121,8 @@ onMounted(() => {
|
|
|
closable: false,
|
|
|
});
|
|
|
}
|
|
|
- tabs.value = parsedTabs;
|
|
|
+ tabs.value = normalizeTabs(parsedTabs);
|
|
|
+ saveTabsToLocalStorage();
|
|
|
}
|
|
|
} catch (e) {
|
|
|
console.error('恢复标签页失败', e);
|
|
|
@@ -68,46 +132,50 @@ onMounted(() => {
|
|
|
// 如果当前路由不是首页,添加当前路由的标签页
|
|
|
if (route.path !== '/desktop/dashboard') {
|
|
|
setTimeout(() => {
|
|
|
- addTab(route.path);
|
|
|
+ addTab(route.fullPath);
|
|
|
}, 100);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 添加标签页方法
|
|
|
-const addTab = async path => {
|
|
|
+const addTab = async fullPath => {
|
|
|
+ const tabIdentity = getTabIdentity(fullPath);
|
|
|
// 如果标签页已存在,则激活该标签页
|
|
|
- const existTab = tabs.value.find(tab => tab.path === path);
|
|
|
+ const existTab = tabs.value.find(
|
|
|
+ tab => tab.key === tabIdentity || getTabIdentity(tab.path) === tabIdentity,
|
|
|
+ );
|
|
|
if (existTab) {
|
|
|
activeKey.value = existTab.key;
|
|
|
+ if (existTab.path !== fullPath) {
|
|
|
+ existTab.path = fullPath;
|
|
|
+ saveTabsToLocalStorage();
|
|
|
+ }
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
// 尝试获取菜单标题
|
|
|
- let title = await getMenuTitle(path);
|
|
|
-
|
|
|
- // if (!title) {
|
|
|
- // // 如果没有找到菜单标题,则从路由元数据中获取
|
|
|
- // const matchedRoute = router.getRoutes().find(route => route.path === path);
|
|
|
- // if (matchedRoute && matchedRoute.meta && matchedRoute.meta.title) {
|
|
|
- // title = matchedRoute.meta.title;
|
|
|
- // } else {
|
|
|
- // // 如果路由也没有定义标题,则从路径中提取
|
|
|
- // const pathSegments = path.split('/');
|
|
|
- // title = pathSegments[pathSegments.length - 1] || '未命名页面';
|
|
|
- // }
|
|
|
- // }
|
|
|
+ let title = await getMenuTitle(fullPath);
|
|
|
|
|
|
if (title) {
|
|
|
+ // 异步获取标题期间可能已添加,再次检查
|
|
|
+ const existAfter = tabs.value.find(
|
|
|
+ tab => tab.key === tabIdentity || getTabIdentity(tab.path) === tabIdentity,
|
|
|
+ );
|
|
|
+ if (existAfter) {
|
|
|
+ activeKey.value = existAfter.key;
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- // 创建新标签页
|
|
|
+ // 创建新标签页(key 使用稳定标识,path 保留最近一次完整路径)
|
|
|
const newTab = {
|
|
|
- key: path,
|
|
|
+ key: tabIdentity,
|
|
|
title: title,
|
|
|
- path: path,
|
|
|
+ path: fullPath,
|
|
|
closable: true, // 非首页标签可以关闭
|
|
|
};
|
|
|
|
|
|
tabs.value.push(newTab);
|
|
|
- activeKey.value = path;
|
|
|
+ activeKey.value = tabIdentity;
|
|
|
|
|
|
// 保存标签页到本地存储
|
|
|
saveTabsToLocalStorage();
|
|
|
@@ -116,8 +184,12 @@ const addTab = async path => {
|
|
|
};
|
|
|
|
|
|
// 获取菜单标题
|
|
|
-const getMenuTitle = async path => {
|
|
|
+const getMenuTitle = async fullPath => {
|
|
|
+ const path = getPathname(fullPath);
|
|
|
// 如果有缓存的菜单信息,直接使用
|
|
|
+ if (pathMenuMap.value[fullPath]) {
|
|
|
+ return pathMenuMap.value[fullPath].title;
|
|
|
+ }
|
|
|
if (pathMenuMap.value[path]) {
|
|
|
return pathMenuMap.value[path].title;
|
|
|
}
|
|
|
@@ -189,25 +261,30 @@ const removeTab = targetKey => {
|
|
|
}
|
|
|
console.log(activeKey.value, 'activeKey.value');
|
|
|
// 跳转到新激活标签页对应的路由
|
|
|
- if (activeKey.value.includes('/desktop/window1/')) {
|
|
|
- router.push(activeKey.value + '?uuid=' + Uuid.createUUID());
|
|
|
- } else {
|
|
|
- router.push(activeKey.value);
|
|
|
- }
|
|
|
+ router.push(getTabRoutePath(activeKey.value));
|
|
|
}
|
|
|
|
|
|
// 保存标签页到本地存储
|
|
|
saveTabsToLocalStorage();
|
|
|
};
|
|
|
|
|
|
+// 获取标签页跳转路径(window/window1 每次打开刷新 uuid,其余保留 query)
|
|
|
+const getTabRoutePath = tabKeyOrPath => {
|
|
|
+ const pathname = getPathname(tabKeyOrPath);
|
|
|
+ if (
|
|
|
+ pathname.includes('/desktop/window1/')
|
|
|
+ || pathname.includes('/desktop/window/')
|
|
|
+ || pathname.includes('/desktop/sheetWindow/')
|
|
|
+ ) {
|
|
|
+ return pathname + '?uuid=' + Uuid.createUUID();
|
|
|
+ }
|
|
|
+ return tabKeyOrPath;
|
|
|
+};
|
|
|
+
|
|
|
// 切换标签页
|
|
|
const onChange = newActiveKey => {
|
|
|
- let uidWindow = newActiveKey;
|
|
|
- if (newActiveKey.includes('/desktop/window1/')) {
|
|
|
- uidWindow = newActiveKey + '?uuid=' + Uuid.createUUID();
|
|
|
- }
|
|
|
activeKey.value = newActiveKey;
|
|
|
- router.push(uidWindow);
|
|
|
+ router.push(getTabRoutePath(newActiveKey));
|
|
|
};
|
|
|
|
|
|
// 保存标签页到本地存储
|
|
|
@@ -223,20 +300,23 @@ const saveTabsToLocalStorage = () => {
|
|
|
watch(menuClickedEvent, event => {
|
|
|
if (event && event.path && event.menuInfo) {
|
|
|
pathMenuMap.value[event.path] = event.menuInfo;
|
|
|
+ pathMenuMap.value[getPathname(event.path)] = event.menuInfo;
|
|
|
}
|
|
|
}, { deep: true, immediate: true });
|
|
|
|
|
|
|
|
|
-// 监听路由变化,添加新标签页
|
|
|
+// 监听路由变化,同步或添加标签页
|
|
|
watch(
|
|
|
- () => route.path,
|
|
|
- newPath => {
|
|
|
+ () => route.fullPath,
|
|
|
+ newFullPath => {
|
|
|
// 有一些特殊路由可能不需要添加标签页,可以在这里过滤
|
|
|
const ignorePaths = ['/login', '/loginNode', '/loginGraphic'];
|
|
|
- if (!ignorePaths.includes(newPath)) {
|
|
|
+ const pathname = getPathname(newFullPath);
|
|
|
+ if (!ignorePaths.includes(pathname)) {
|
|
|
nextTick(() => {
|
|
|
setTimeout(() => {
|
|
|
- addTab(newPath);
|
|
|
+ syncActiveTab(newFullPath);
|
|
|
+ addTab(newFullPath);
|
|
|
}, 100);
|
|
|
});
|
|
|
}
|
|
|
@@ -259,16 +339,18 @@ watch(
|
|
|
);
|
|
|
|
|
|
// 供外部组件调用,用于添加带有菜单信息的标签页
|
|
|
-const addTabWithMenuInfo = (path, menuInfo) => {
|
|
|
- // 缓存菜单信息
|
|
|
- pathMenuMap.value[path] = {
|
|
|
+const addTabWithMenuInfo = (fullPath, menuInfo) => {
|
|
|
+ // 缓存菜单信息(同时缓存含 query 与不含 query 的 key)
|
|
|
+ const menuData = {
|
|
|
title: menuInfo.title || menuInfo.name || '',
|
|
|
menuNodeType: menuInfo.menuNodeType,
|
|
|
no: menuInfo.no,
|
|
|
};
|
|
|
+ pathMenuMap.value[fullPath] = menuData;
|
|
|
+ pathMenuMap.value[getPathname(fullPath)] = menuData;
|
|
|
|
|
|
// 添加标签页
|
|
|
- addTab(path);
|
|
|
+ addTab(fullPath);
|
|
|
};
|
|
|
|
|
|
// 暴露方法给其他组件使用
|