Files
Situation-Awareness-Platfor…/src/components/NavTabs/NewNavTabs.vue
2025-03-12 18:41:20 +08:00

300 lines
9.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div v-if="update" class=" h-[54px] g-menu-nav relative">
<div class="absolute b-0">
<div
v-if="mListMore.length === 0"
width="100%"
class="py-0 px-0 flex g-menu"
mode="horizontal"
:disable-overflow-style="false"
:default-select-keys="[id]"
>
<template v-for="item in menuList" :key="item.id">
<GLink
@mouseover="mouseEnter(item.id)"
@mouseout="mouseLeave"
v-if="item.label !== '社区'"
:key="item.id"
:href="getLink(item)"
:to="getTo(item)"
class="g-menu-item cursor-pointer whitespace-pre"
:class="{ 'menu-item-active': pType === item.id }"
>
<div class="p-2 m-2 menu-tab" :class="{ 'menu-tab-hover': item.id === hoverId }">
<div class="flex gap-2 items-center">
<GIcon v-if="(pType === item.id && item.activeIcon) || item.id === hoverId" :name="item.activeIcon" />
<GIcon :name="item.icon" v-else-if="item.icon" />
<span class="font-color-t2 font-semibold">{{ item.label }}</span>
<span class="font-color-t2 menu-tab-num px-1" v-if="item.num"><Number :number="item.num" /></span>
</div>
</div>
</GLink>
</template>
</div>
<template v-else>
<div
width="100%"
class="py-0 px-0 flex g-menu"
mode="horizontal"
:default-select-keys="[id]"
>
<template v-for="item in mList" :key="item.id">
<GLink
@mouseover="mouseEnter(item.id)"
@mouseout="mouseLeave"
v-if="item.label !== '社区'"
:key="item.id"
:href="getLink(item)"
:to="getTo(item)"
class="g-menu-item cursor-pointer whitespace-pre"
:class="{ 'menu-item-active': pType === item.id }"
>
<div class="p-2 m-2 menu-tab" :class="{ 'menu-tab-hover': item.id === hoverId }">
<div class="flex gap-2 items-center">
<GIcon v-if="(pType === item.id && item.activeIcon) || item.id === hoverId" :name="item.activeIcon" />
<GIcon :name="item.icon" v-else-if="item.icon" />
<span class="font-color-t2 font-semibold">{{ item.label }}</span>
<span class="font-color-t2 menu-tab-num px-1" v-if="item.num"><Number :number="item.num" /></span>
</div>
</div>
</GLink>
</template>
<d-dropdown trigger="hover" :position="['bottom-end']" align="end">
<div class="menu-item-more">
<GIcon name="gt-plane-more" />
</div>
<template #menu>
<template v-for="item in mListMore" :key="item.id">
<GLink
@mouseover="mouseEnter(item.id)"
@mouseout="mouseLeave"
v-if="item.label !== '社区'"
:href="getLink(item)"
:to="getTo(item)"
class="g-menu-item cursor-pointer"
:class="{ 'menu-item-active': pType === item.id }"
>
<div class="p-2 m-2 menu-tab" :class="{ 'menu-tab-hover': item.id === hoverId }">
<div class="flex gap-2 items-center">
<GIcon
v-if="(pType === item.id && item.activeIcon) || item.id === hoverId"
:name="item.activeIcon"
/>
<GIcon :name="item.icon" v-else-if="item.icon" />
<span class="font-color-t2 font-semibold">{{ item.label }}</span>
<span class="font-color-t2 menu-tab-num px-1" v-if="item.num"><Number :number="item.num" /></span>
</div>
</div>
</GLink>
</template>
</template>
</d-dropdown>
</div>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch, nextTick, onMounted, onUnmounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { orgInfoStore } from '@/stores/Org';
import { useGlobalInfoStore } from '@/stores/Global';
import { storeToRefs } from 'pinia';
import { useRepoId } from '@/utils/hooks/useRepoId';
import debounce from 'lodash/debounce';
import { repoInfoStore } from '@/stores/Repo';
import setting from '@/setting';
const globalInfoStore = useGlobalInfoStore();
const route = useRoute();
const router = useRouter();
const update = ref<boolean>(true);
const id = ref('');
const pType = computed(() => route.meta.pType); // 页面归属的tab高亮
const nameMap = new Map([
['WIKI', 'repoWiki'],
['ISSUE', 'repoIssues'],
['MERGE_REQUEST', 'repoMerge'],
['FORK', 'repoFork'],
['ANALYSIS', 'repoAnalysis'],
['DISCUSSION', 'repoDiscussion'],
['COMMUNITY', 'reqCommunity']
]);
const { menuInfo, customMenuInfo } = storeToRefs(globalInfoStore);
const { repoInfo, access_level } = storeToRefs(repoInfoStore());
const InvalidMenuItemSet = computed(() => {
const set = new Set();
(repoInfo.value.module_setting?.modules|| []).forEach(({ key, value }) => {
if (nameMap.get(key) && value === '0') set.add(nameMap.get(key));
});
return set;
})
const menuList = computed(() => {
let list: any;
if (globalInfoStore.menuType === 'org') {
list = [...menuInfo.value, ...customMenuInfo.value];
} else {
list = [...menuInfo.value];
}
const listToShow = list.filter((item) => {
const isAllowed = !item.permission || access_level.value >= setting.role[item.permission];
const isEnabled = !InvalidMenuItemSet.value.has(item.id);
// 导入失败项目只展示代码tab
const isShow = !(repoInfo.value?.import_status === 'failed' && item.id !== 'repo');
return isEnabled && isAllowed && isShow;
})
// 自定义菜单排序
if (customMenuInfo.value?.length) {
const index = listToShow.findIndex((item) => item.id === 'orgSetting');
if (index >= 0) {
const orgSetting = listToShow.splice(index, 1);
listToShow.push(orgSetting[0]);
}
}
return listToShow;
});
const mList = ref<any>([]);
const mListMore = ref<any>([]);
let seTime: any;
const computeW = () => {
if (seTime) clearTimeout(seTime);
seTime = setTimeout(() => {
mList.value = [];
mListMore.value = [];
nextTick(() => {
// 获取div元素的引用
const d1: any = document.querySelector('.g-menu-nav');
const d2: any = document.querySelector('.g-menu');
if (!d1.clientWidth) return;
let w = 0;
for (let i = 0; i < d2.children.length - 1; i++) {
w = w + d2.children[i].clientWidth;
if (w < d1.clientWidth - 60) {
mList.value.push(menuList.value[i]);
} else {
mListMore.value.push(menuList.value[i]);
}
}
});
}, 100);
};
const hoverId = ref('');
const mouseEnter = debounce((id) => {
if (hoverId.value === id) return;
hoverId.value = id;
}, 10);
const mouseLeave = debounce(() => {
hoverId.value = '';
}, 10);
const { repoId } = useRepoId();
watch(
[repoId],
() => {
globalInfoStore.setMenuType(route?.meta?.type);
id.value = route.name as string;
update.value = false; // 需要强制刷新 疑似devui Bug
nextTick(() => {
update.value = true;
computeW();
});
},
{ immediate: true, deep: true }
);
watch(
() => menuList.value,
() => {
computeW();
},
{ immediate: true, deep: true }
);
// // 点击tab跳转路由
// const handleNav = (val: any) => {
// if (/^\/organization/.test(val.key)) {
// // 社区相关路由
// const openUrl = val.key.replace(':namespace', orgStore.orgInfo.full_path);
// window.open(openUrl, '_self');
// }
// };
const getTo = (data: any) => {
const link = {
href: data.link || data.content || '', // 如有传入了链接,使用链接打开,此时新页面会全量加载
to: null // 未传入链接基于router-link打开此时路由切换走spa router局部加载
};
if (!link.href) {
try {
link.to = router.resolve({
name: data.id,
params: {
...route.params
}
}).fullPath;
} catch (err) {
// ignore
}
}
return link.to ? link.to : null;
};
const getLink = (data: any) => {
return data.link || data.content || null;
};
const init = () => {
computeW();
window.addEventListener('resize', computeW);
};
onMounted(() => {
init();
});
onUnmounted(() => {
window.removeEventListener('resize', computeW);
if (seTime) clearTimeout(seTime);
});
</script>
<style lang="scss" scoped>
.menu-item-active {
border-bottom: 2px solid #000;
span {
color: #000;
}
}
.menu-tab-hover {
border-radius: 4px;
background: rgba(#bcbcd0, 0.2);
span {
color: #000;
}
}
.menu-item-more {
border-radius: 4px;
border: 1px solid #e3e3ee;
background: #fff;
display: flex;
align-items: center;
height: 32px;
width: 32px;
padding: 0 8px;
margin: auto 0 auto 16px;
cursor: pointer;
}
.menu-tab-num {
border-radius: 4px;
background: #E3E3EE;
}
</style>