搜索结果列表页面开发
This commit is contained in:
23
src/layouts/DashboardLayout/index.vue
Normal file
23
src/layouts/DashboardLayout/index.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<DefaultLayout>
|
||||
<template #sidebar>
|
||||
<h3 class="title">Dashboard</h3>
|
||||
<NavSidebar :show-avatar="false" />
|
||||
</template>
|
||||
</DefaultLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DefaultLayout from '@/layouts/DefaultLayout/index.vue';
|
||||
import NavSidebar from '@/components/NavSidebar/index.vue';
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.title{
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: #2D2D2E;
|
||||
line-height: 32px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
</style>
|
||||
273
src/layouts/DefaultLayout/aside.vue
Normal file
273
src/layouts/DefaultLayout/aside.vue
Normal file
@@ -0,0 +1,273 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useAccountStore } from '@/stores/user';
|
||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||
import { emitEvent } from '@/utils/eventBus';
|
||||
import setting from '@/setting';
|
||||
const { copyright } = setting;
|
||||
|
||||
defineProps<{ asideDefaultWidth: number; }>();
|
||||
const emit = defineEmits<{
|
||||
onSelect: [];
|
||||
}>();
|
||||
|
||||
const userStore = useAccountStore();
|
||||
const sizeStore = usePageResize();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const defaultSelectKeys = ref<string[]>([]);
|
||||
/**
|
||||
* 跳转菜单,转向子应用路由 或 自身路由
|
||||
*/
|
||||
const onSelect = (e) => {
|
||||
const route: { microApp?: string, name: string; login?: boolean; } = JSON.parse(e.el.parentNode.getAttribute('data-route'));
|
||||
if (route) {
|
||||
if (route.login && !userStore.isLogin) {
|
||||
if (sizeStore.isMobile.value) {
|
||||
emit('onSelect');
|
||||
}
|
||||
emitEvent('login');
|
||||
return;
|
||||
}
|
||||
location.href = router.resolve(route).href;
|
||||
// if (route?.microApp) {
|
||||
// if (microApp.router.current.has(route?.microApp)) {
|
||||
// microApp.router.push({
|
||||
// name: 'micoro-app-homeweb-app',
|
||||
// path: router.resolve(route).href
|
||||
// });
|
||||
// } else {
|
||||
// router.push(route);
|
||||
// }
|
||||
// } else {
|
||||
// router.push(route);
|
||||
// }
|
||||
}
|
||||
emit('onSelect');
|
||||
};
|
||||
const menuItemRouteName = ['home', 'gStar', 'aiCopilot', 'contributed', 'issues', 'merge', 'discussionCreated', 'explore', 'userFollowing', 'userStars'];
|
||||
watchEffect(() => {
|
||||
// 回显高亮menuitem
|
||||
const name = route?.name as string;
|
||||
if (menuItemRouteName.includes(name)) {
|
||||
defaultSelectKeys.value = [name];
|
||||
} else {
|
||||
const basePath = route.meta.basePath as string;
|
||||
if (basePath) defaultSelectKeys.value = [basePath];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="side-bar-content">
|
||||
<d-menu @select="onSelect" :open-keys="['work']" mode="vertical" :default-select-keys="defaultSelectKeys" class="menu"
|
||||
:key="defaultSelectKeys">
|
||||
<!-- 首页 -->
|
||||
<d-menu-item v-for="item in [
|
||||
{ key: 'home', icon: 'gt-plane-home', activeIcon: 'gt-plane-home-red', label: '首页', route: { name: 'home', microApp: 'micoro-app-homeweb-app' } },
|
||||
// { key: 'gStar', icon: 'gt-plane-GStar', activeIcon: 'gt-plane-GStar-red', label: 'G-Star', route: { name: 'gStar', microApp: 'micoro-app-homeweb-app' } },
|
||||
// { key: 'my-project', icon: 'gt-plane-new', activeIcon: 'gt-plane-new-red', label: '资源中心', route: undefined },
|
||||
// { key: 'aiCopilot', icon: 'gt-plane-ai', activeIcon: 'gt-plane-ai-red', label: 'AI Copilot', route: { name: 'aiCopilot', microApp: 'copilot-app' } },
|
||||
]" :key="item.key" :data-route="item.route ? JSON.stringify(item.route) : ''">
|
||||
<template #icon>
|
||||
<g-icon :name="defaultSelectKeys.some(e => item.key === e) ? item.activeIcon : item.icon"
|
||||
class="default-icon"></g-icon>
|
||||
<g-icon :name="item.activeIcon" class="active-icon"></g-icon>
|
||||
</template>
|
||||
<span>{{ item.label }}</span>
|
||||
</d-menu-item>
|
||||
|
||||
<!-- 个人工作台 -->
|
||||
<!-- <hr class="line">-->
|
||||
<!-- <d-sub-menu title="个人工作台" key="work" class="my-work-platform submenu-item">-->
|
||||
<!-- <d-menu-item v-for="item in [-->
|
||||
<!-- { key: 'contributed', icon: 'gt-plane-project', activeIcon: 'gt-plane-project-red', label: '参与的项目', route: { name: 'contributed', login: true } },-->
|
||||
<!-- { key: 'issues', icon: 'gt-plane-issues', activeIcon: 'gt-plane-issues-red', label: 'Issues', route: { name: 'issues', login: true } },-->
|
||||
<!-- { key: 'merge', icon: 'gt-plane-pullRequest', activeIcon: 'gt-plane-pullRequest-red', label: 'PR', route: { name: 'merge', login: true } },-->
|
||||
<!-- { key: 'discussionCreated', icon: 'gt-plane-discussions', activeIcon: 'gt-plane-discussions-red', label: '讨论', route: { name: 'discussionCreated', login: true } },-->
|
||||
<!-- ]" :key="item.key" :data-route="item.route ? JSON.stringify(item.route) : ''">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <g-icon :name="defaultSelectKeys.some(e => item.key === e) ? item.activeIcon : item.icon"-->
|
||||
<!-- class="default-icon"></g-icon>-->
|
||||
<!-- <g-icon :name="item.activeIcon" class="active-icon"></g-icon>-->
|
||||
<!-- </template>-->
|
||||
<!-- <span>{{ item.label }}</span>-->
|
||||
<!-- </d-menu-item>-->
|
||||
<!-- </d-sub-menu>-->
|
||||
|
||||
<!-- <!– 发现 –>-->
|
||||
<!-- <hr class="line">-->
|
||||
<!-- <div class="py-[10px] px-[16px] text-[#1D1F3A] leading-[20px]">发现</div>-->
|
||||
<!-- <d-menu-item v-for="item in [-->
|
||||
<!-- // { key: 'explore', icon: 'gt-plane-search', activeIcon: 'gt-plane-search-red', label: '探索开源', route: { name: 'explore', microApp: 'micoro-app-homeweb-app' } },-->
|
||||
<!-- { key: 'userFollowing', icon: 'gt-plane-subscribed', activeIcon: 'gt-plane-subscribed-red', label: '我的关注', route: { name: 'userFollowing', microApp: 'user-center', params: { namespace: userStore.accountInfo.username }, login: true } },-->
|
||||
<!-- { key: 'userStars', icon: 'gt-plane-star', activeIcon: 'gt-plane-star-red', label: 'Starred', route: { name: 'userStars', microApp: 'user-center', params: { namespace: userStore.accountInfo.username }, login: true } },-->
|
||||
<!-- ]" :key="item.key" :data-route="item.route ? JSON.stringify(item.route) : ''">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <g-icon :name="defaultSelectKeys.some(e => item.key === e) ? item.activeIcon : item.icon"-->
|
||||
<!-- class="default-icon"></g-icon>-->
|
||||
<!-- <g-icon :name="item.activeIcon" class="active-icon"></g-icon>-->
|
||||
|
||||
<!-- </template>-->
|
||||
<!-- <span>{{ item.label }}</span>-->
|
||||
<!-- </d-menu-item>-->
|
||||
|
||||
<!-- <hr class="line">-->
|
||||
<!-- <g-link class="menu-item-link" target="_blank" href="https://gitcode.com/Gitcode-offical-team/GitCode-Docs/wiki">-->
|
||||
<!-- <g-icon name="gt-plane-document" class="default-icon" /><g-icon name="gt-plane-document-red"-->
|
||||
<!-- class="active-icon" /> -->
|
||||
<!-- <span>帮助文档</span> -->
|
||||
<!-- <g-icon name="gt-line-open" class="!inline-flex align-middle" />-->
|
||||
<!-- </g-link>-->
|
||||
<!-- <g-link class="menu-item-link" target="_blank" href="https://gitcode.com/Gitcode-offical-team/GitCode-Docs/issues">-->
|
||||
<!-- <g-icon name="gt-plane-feedback" class="default-icon" /><g-icon name="gt-plane-feedback-red"-->
|
||||
<!-- class="active-icon" /> -->
|
||||
<!-- <span>反馈中心</span> -->
|
||||
<!-- <g-icon name="gt-line-open" class="!inline-flex align-middle" />-->
|
||||
<!-- </g-link>-->
|
||||
</d-menu>
|
||||
|
||||
<!-- <hr class="line">-->
|
||||
<!-- <div class="aside-footer">-->
|
||||
<!-- <p class="whitespace-nowrap">© {{ copyright.digestAbbr }}</p>-->
|
||||
<!-- <p class="whitespace-nowrap">-->
|
||||
<!-- <a href="https://beian.miit.gov.cn/#/Integrated/index" target="_blank">{{ copyright.icp }}</a>-->
|
||||
<!-- </p>-->
|
||||
<!-- <p class="whitespace-nowrap mt-[16px]">技术支持:</p>-->
|
||||
<!-- <p class="whitespace-nowrap">-->
|
||||
<!-- <a href="https://www.csdn.net/" target="_blank" rel="noopener noreferrer">CSDN</a>、<a-->
|
||||
<!-- href="https://www.huaweicloud.com/" target="_blank" rel="noopener noreferrer">华为云</a>-->
|
||||
<!-- </p>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.side-bar-content {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
@apply h-[100%] py-[32px] px-[16px] flex flex-col justify-between;
|
||||
|
||||
.menu {
|
||||
background: transparent !important;
|
||||
@apply border-r-0 flex-1;
|
||||
|
||||
:deep(.devui-menu-item-vertical-wrapper:first-of-type) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
:deep(.devui-menu-item-vertical-wrapper) {
|
||||
margin-top: 4px !important;
|
||||
}
|
||||
|
||||
:deep(.devui-menu-item) {
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
padding: 10px 16px !important;
|
||||
white-space: nowrap;
|
||||
|
||||
.active-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.devui-menu-item:hover) {
|
||||
background: #9699B633;
|
||||
|
||||
.active-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.default-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.devui-menu-item.devui-menu-item-select) {
|
||||
background: #9699B633 !important;
|
||||
}
|
||||
|
||||
:deep(.devui-menu-item>span) {
|
||||
flex: none;
|
||||
color: #1D1F3A;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.my-work-platform .devui-submenu-title-content ~ .icon) {
|
||||
//background-color: #E3E3EE;
|
||||
@apply rounded-[4px] w-[20px] h-[20px] text-center leading-[20px];
|
||||
}
|
||||
|
||||
:deep(.my-work-platform .devui-submenu-title-content ~ .icon:hover) {
|
||||
background-color: #D1D1E0;
|
||||
}
|
||||
|
||||
.submenu-item :deep(.devui-submenu-title) {
|
||||
padding: 10px 16px !important;
|
||||
|
||||
.devui-submenu-title-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item-link {
|
||||
@apply inline-block px-[16px] py-[10px] w-[100%] whitespace-nowrap leading-[20px] mt-[4px] first-of-type:mt-0;
|
||||
|
||||
&:hover {
|
||||
color: #DA203E;
|
||||
|
||||
.icon {
|
||||
color: #DA203E !important;
|
||||
}
|
||||
|
||||
.active-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.default-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.active-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.line {
|
||||
border-color: #F1F1F8;
|
||||
@apply ml-[10px] mr-[10px] my-[16px];
|
||||
}
|
||||
|
||||
.aside-footer {
|
||||
padding: 0px 16px;
|
||||
color: #9C9DB4;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.devui-menu-vertical ul.devui-submenu div.devui-submenu-title i.icon-chevron-up.is-opened) {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
:deep(.devui-menu-vertical ul.devui-submenu div.devui-submenu-title i.icon-chevron-up) {
|
||||
transform: none;
|
||||
|
||||
&::before {
|
||||
transform: rotate(0deg) !important;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: transform var(--devui-animation-duration-slow, .3s) var(--devui-animation-ease-in-out-smooth, cubic-bezier(.645, .045, .355, 1));
|
||||
}
|
||||
|
||||
&.is-opened::before {
|
||||
transform: rotate(180deg) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
195
src/layouts/DefaultLayout/index.vue
Normal file
195
src/layouts/DefaultLayout/index.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<d-layout class="d-layout h-full">
|
||||
<d-header class="d-header">
|
||||
<!-- header -->
|
||||
<Header @clickMenu="handleClickMenu" :asideSetShow="asideSetShow" ref="refHeader">
|
||||
<!-- 二级导航左侧内容 -->
|
||||
<template v-if="$slots.headerLeft && !route?.meta?.hideNav" #info>
|
||||
<slot name="headerLeft"></slot>
|
||||
</template>
|
||||
|
||||
<!-- 二级导航右侧内容 -->
|
||||
<template v-if="$slots.headerRight && !route?.meta?.hideNav" #menu>
|
||||
<slot name="headerRight"></slot>
|
||||
</template>
|
||||
|
||||
<template v-if="$slots.bodyHeader && !route?.meta?.hideNav" #bodyHeader>
|
||||
<slot name="bodyHeader"></slot>
|
||||
</template>
|
||||
<template v-if="$slots.headerBottom && !route?.meta?.hideNav" #headerBottom>
|
||||
<slot name="headerBottom"></slot>
|
||||
</template>
|
||||
</Header>
|
||||
</d-header>
|
||||
<!-- 顶栏信息 -->
|
||||
<div v-if="$slots.contentBanner">
|
||||
<slot name="contentBanner"></slot>
|
||||
</div>
|
||||
<!-- 主体 -->
|
||||
<d-layout class="flex-col">
|
||||
<d-aside class="left-side-bar" v-if="asideSetShow" :style="{ width: mainMarginLeftWidth + 'px' }">
|
||||
<Aside :asideDefaultWidth="mainMarginLeftWidth"></Aside>
|
||||
</d-aside>
|
||||
<d-drawer v-model="asideVisible" position="left" lock-scroll style="max-height: 100%; top: 0;"
|
||||
:style="{ width: '240px', overflowY: 'hidden' }">
|
||||
<div class="p-[16px] flex justify-between items-center h-[60px]">
|
||||
<GLink class="g-toolbar-left-logo" href="/" />
|
||||
<d-icon class="cursor-pointer" name="close" @click="asideVisible = false"></d-icon>
|
||||
</div>
|
||||
<Aside :asideDefaultWidth="240" @on-select="handleClickMenu" style="height: calc(100% - 60px);padding-top: 0px;"></Aside>
|
||||
</d-drawer>
|
||||
<d-layout :style="{ marginLeft: mainMarginLeftWidth + 'px', transition: 'margin-left ease 0.1s' }">
|
||||
<d-layout :class="responsiveClass" :style="{ minHeight: `calc(100% - ${footerSize?.height.value || 0}px)` }">
|
||||
<!-- sidebar -->
|
||||
<d-aside class="d-aside mr-[32px] pt-24" v-if="route?.meta?.sidebarType">
|
||||
<slot name="sidebar"></slot>
|
||||
</d-aside>
|
||||
<!-- 内容 -->
|
||||
<d-content>
|
||||
<slot name="content">
|
||||
<RouterView />
|
||||
</slot>
|
||||
</d-content>
|
||||
</d-layout>
|
||||
<!-- footer -->
|
||||
<!-- <d-footer v-if="!route?.meta?.hiddenFooter" ref="refFooter">
|
||||
<slot name="footer">
|
||||
<Footer />
|
||||
</slot>
|
||||
</d-footer> -->
|
||||
</d-layout>
|
||||
</d-layout>
|
||||
|
||||
<!-- 浮动工具栏(移动端不显示) -->
|
||||
<!-- <ToolsFloat v-if="showToolsFloat" />-->
|
||||
|
||||
</d-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'DefaultLayout' });
|
||||
import { RouterView, useRoute, useRouter } from 'vue-router';
|
||||
import { computed, ref, watch, watchEffect, onMounted, onUnmounted } from 'vue';
|
||||
import Header from '@/components/Header/index.vue';
|
||||
import Footer from '@/components/Footer/index.vue';
|
||||
import Aside from './aside.vue';
|
||||
import { useGlobalInfoStore } from '@/stores/Global';
|
||||
|
||||
import ToolsFloat from '@/components/ToolsFloat/index.vue';
|
||||
import { useStorage, useElementSize } from '@vueuse/core';
|
||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||
import microApp from '@micro-zoe/micro-app';
|
||||
defineProps<{ customAside?: boolean; }>();
|
||||
const router = useRouter();
|
||||
const solt = defineSlots();
|
||||
const sizeStore = usePageResize();
|
||||
const globalStore = useGlobalInfoStore();
|
||||
const route = useRoute();
|
||||
const asideVisible = ref(false);
|
||||
const refFooter = ref(null);
|
||||
const refHeader: any = ref(null);
|
||||
const footerSize = useElementSize(refFooter);
|
||||
|
||||
// aside 设置可见性 header有两层,有顶栏信息,两层sidebar 都不可见
|
||||
const asideSetShow = computed(() => {
|
||||
if (route.meta.asideMenu === 'drawer') return false;
|
||||
return refHeader.value?.height <= 56 && !solt.contentBanner && !route.meta.sidebarType;
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
clickMenu: [];
|
||||
}>();
|
||||
watch(route, () => {
|
||||
globalStore.setIsNotFound(false); // 切换路由默认不显示404,防止一直显示404
|
||||
}, { deep: true });
|
||||
|
||||
const asideDefaultWidth = useStorage('asideDefaultWidth', sizeStore.isMobile.value ? 0 : 240);
|
||||
const mainMarginLeftWidth = computed(() => {
|
||||
if (!asideSetShow.value || sizeStore.isMobile.value) {
|
||||
return 0;
|
||||
} else {
|
||||
return asideDefaultWidth.value;
|
||||
}
|
||||
});
|
||||
|
||||
// 是否展示浮动工具栏(非移动端 || 未设置隐藏)
|
||||
const showToolsFloat = computed(() => {
|
||||
return sizeStore.widthType.value !== 'sm' && !route.meta.hiddenToolsFloat;
|
||||
});
|
||||
|
||||
const responsiveClass = computed(() => { // 控制移动和pc布局
|
||||
let className = 'm-auto max-w-normal px-normal w-full';
|
||||
let viewportContent = 'width=device-width,initial-scale=0,user-scalable=yes,maximum-scale=1.0';
|
||||
|
||||
if (sizeStore.isMobile.value) { // 设备是否为手机
|
||||
if (route?.meta?.hasMobile) { // 是否有手机端页面
|
||||
className = 'w-full';
|
||||
viewportContent = 'width=device-width, initial-scale=1.0';
|
||||
}
|
||||
} else {
|
||||
if (route?.meta?.className) {
|
||||
className = `${route?.meta?.className as string}`;
|
||||
}
|
||||
if (route.name === 'homepage' && globalStore.namespaceType === 1) { // 组织主页样式特殊处理
|
||||
className = 'm-auto min-w-sm px-normal max-w-[1280px] w-full pt-[12px]';
|
||||
}
|
||||
}
|
||||
document?.querySelector("meta[name='viewport']")?.setAttribute('content', viewportContent);
|
||||
return className;
|
||||
});
|
||||
|
||||
const handleClickMenu = () => {
|
||||
if (sizeStore.isMobile.value || !asideSetShow.value) { // 不可见时弹框
|
||||
asideVisible.value = !asideVisible.value;
|
||||
} else { // 可见时收起
|
||||
asideDefaultWidth.value = asideDefaultWidth.value >= 240 ? 0 : 240;
|
||||
}
|
||||
};
|
||||
|
||||
const outOfSearch = ref(true); // 在search页面隐藏footer
|
||||
router.afterEach((to, form) => {
|
||||
outOfSearch.value = to.name !== 'search';
|
||||
});
|
||||
microApp.router.afterEach({
|
||||
'micoro-app-homeweb-app': (to, from) => {
|
||||
outOfSearch.value = to.pathname !== '/search';
|
||||
}
|
||||
});
|
||||
const checkPage = (data: any) => {
|
||||
if (data.type === 'checkPage') {
|
||||
return 'ready';
|
||||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
microApp.addDataListener('micoro-app-homeweb-app', checkPage, false);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
microApp.removeDataListener('micoro-app-homeweb-app', checkPage);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.left-side-bar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
top: 56px;
|
||||
background-color: #fff;
|
||||
z-index: 10;
|
||||
border-right: 1px solid #F1F1F8;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
transition: width ease 0.1s;
|
||||
}
|
||||
|
||||
.g-toolbar-left-logo {
|
||||
display: inline-block;
|
||||
width: 102px;
|
||||
height: 28px;
|
||||
background: url(../../assets/imgs/logo/logo.png) no-repeat;
|
||||
background-size: contain;
|
||||
vertical-align: middle;
|
||||
background-position: 0 center;
|
||||
}
|
||||
|
||||
</style>
|
||||
30
src/layouts/NoticeLayout/index.vue
Normal file
30
src/layouts/NoticeLayout/index.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<d-layout class="dlayout">
|
||||
<d-header class="dheader">
|
||||
<Header />
|
||||
</d-header>
|
||||
<d-layout class="dlayout-box">
|
||||
<d-content class="main-content m-auto max-w-normal min-w-sm px-normal w-full">
|
||||
<RouterView />
|
||||
</d-content>
|
||||
</d-layout>
|
||||
<d-footer class="dfooter"><Footer></Footer></d-footer>
|
||||
</d-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Header from '@/components/Header/index.vue';
|
||||
import Footer from '@/components/Footer/index.vue';
|
||||
import { RouterView } from 'vue-router';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dlayout,
|
||||
.dlayout-box {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.dcontent {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
193
src/layouts/OrgLayout/index.vue
Normal file
193
src/layouts/OrgLayout/index.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<DefaultLayout class="org-layout">
|
||||
<template v-if="!isMobile && hasPermission" #headerLeft>
|
||||
<InfoLeft :imgUrl="orgStore.orgInfo?.avatar" :infoText="orgStore.orgInfo?.name" :isFollow="orgStore.isFollow"
|
||||
@followClick="followClickEvent" :infoSubText="`@${orgStore.orgInfo?.full_path}`" />
|
||||
</template>
|
||||
<template v-if="!isMobile && hasPermission" #headerRight>
|
||||
<div class="pl-[32px] w-full h-full">
|
||||
<NavTabs :filter="filterFn" />
|
||||
</div>
|
||||
</template>
|
||||
<template #headerBottom v-if="tabPath && !isMobile">
|
||||
<OrgHeaderInfo v-if="hasPermission" :hideAvatar="false"> </OrgHeaderInfo>
|
||||
</template>
|
||||
<template #bodyHeader v-if="isMobile && hasPermission">
|
||||
<div class="w-full">
|
||||
<InfoLeft :imgUrl="orgStore.orgInfo?.avatar" :infoText="orgStore.orgInfo?.name"
|
||||
:infoSubText="`@${orgStore.orgInfo?.full_path}`" :isFollow="orgStore.isFollow"
|
||||
@followClick="followClickEvent" />
|
||||
<NavTabs :filter="filterFn" />
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<slot v-if="hasPermission && !globalInfo.isNotFound" name="content"></slot>
|
||||
<ErrorPage :code="globalInfo.isNotFound ? 404 : 403" v-else />
|
||||
</template>
|
||||
<template v-if="hasPermission" #sidebar>
|
||||
<NavSidebar :showAvatar="true" :avatarParam="{
|
||||
avatar: orgStore.orgInfo.avatar,
|
||||
name: orgStore.orgInfo.name,
|
||||
infoSubText: `@${orgStore.orgInfo.path}`
|
||||
}" />
|
||||
</template>
|
||||
</DefaultLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import OrgHeaderInfo from '@/views/Org/components/OrgHeaderInfo.vue';
|
||||
import { onMounted, reactive, ref, computed, watch } from 'vue';
|
||||
import DefaultLayout from '@/layouts/DefaultLayout/index.vue';
|
||||
import NavTabs from '@/components/NavTabs/index.vue';
|
||||
import NavSidebar from '@/components/NavSidebar/index.vue';
|
||||
import InfoLeft from '@/components/InfoLeft/index.vue';
|
||||
import ErrorPage from '@/views/ErrorPage/index.vue';
|
||||
import { orgInfoStore } from '@/stores/Org';
|
||||
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
|
||||
import { useAccountStore } from '@/stores/user';
|
||||
import setting from '@/setting';
|
||||
import { usePagePermission } from '@/layouts/hooks/usePagePermission';
|
||||
import { emitEvent } from '@/utils/eventBus';
|
||||
import { useGlobalInfoStore } from '@/stores/Global';
|
||||
import { reqCatch } from '@/utils/catch';
|
||||
import * as types from '@/api/org/types';
|
||||
import { getCommunityInfo } from '@/api/org/index';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||
|
||||
const { isMobile } = usePageResize();
|
||||
const user = useAccountStore();
|
||||
const orgStore = orgInfoStore();
|
||||
const { namespace, followDevCommunity, checkHasFollowedFun, getDevAdvertisement, getDevLogin, getArticleAndFans } = getOrgInfo();
|
||||
const { hasPermission } = usePagePermission(orgStore.access_level, orgStore.isPrivate);
|
||||
const globalInfo = useGlobalInfoStore();
|
||||
globalInfo.updateMenuNum('orgRepos', orgStore.orgInfo.project_count);
|
||||
// globalInfo.updateMenuNum('orgMember', orgStore.orgInfo.member_count);
|
||||
|
||||
const router = useRouter();
|
||||
const tabPath = computed(() => {
|
||||
const ary = router.currentRoute.value.path.split('/');
|
||||
let discussDetail = true;
|
||||
const disIndex = ary.findIndex((item) => item === 'discussion');
|
||||
if (disIndex > 0 && disIndex < ary.length - 1) {
|
||||
discussDetail = false;
|
||||
}
|
||||
return !ary.find((item) => item === 'setting') && discussDetail;
|
||||
});
|
||||
// navTabs卡片过滤
|
||||
const filterObj: {
|
||||
[x: string]: any;
|
||||
} = {
|
||||
讨论: 'DISCUSSION',
|
||||
社区: 'COMMUNITY'
|
||||
};
|
||||
|
||||
const filterFn: any = ref((item: any) => {
|
||||
return true;
|
||||
});
|
||||
const communityHome = ref('');
|
||||
// 监听组织路由变化
|
||||
const { accountInfo } = useAccountStore();
|
||||
// 社区基本信息
|
||||
let lastNameSpace = '';
|
||||
|
||||
const getCommunityInfoFun = async () => {
|
||||
if (lastNameSpace !== namespace.value) {
|
||||
const params: types.commonGroupReqType = {
|
||||
orgId: namespace.value
|
||||
};
|
||||
const { data, error } = await reqCatch(getCommunityInfo, params);
|
||||
if (!error && data) {
|
||||
orgStore.setCommunityUrl({
|
||||
communityUrl: `https://${data.data.custom_domain}`,
|
||||
communityactivityUrl: `https://${data.data.custom_domain}/activelist`
|
||||
});
|
||||
communityHome.value = data.data.custom_domain ? `//${data.data.custom_domain}` : '';
|
||||
orgStore.setCommunityInfo(data.data);
|
||||
// 获取dev社区登陆状态
|
||||
getDevLogin();
|
||||
// 获取社区关注状态
|
||||
if (user.isLogin) {
|
||||
checkHasFollowedFun();
|
||||
}
|
||||
// 获取社区广告
|
||||
getDevAdvertisement();
|
||||
// 获取dev社区文章粉丝数量
|
||||
getArticleAndFans();
|
||||
}
|
||||
}
|
||||
lastNameSpace = namespace.value;
|
||||
};
|
||||
|
||||
// 监听module_setting权限控制,权限变化,项目切换
|
||||
watch(
|
||||
() => [orgStore.orgInfo?.module_setting?.modules, namespace],
|
||||
() => {
|
||||
getCommunityInfoFun();
|
||||
// const obj = orgStore.orgInfo?.module_setting?.modules.find(mod => mod.key === 'COMMUNITY');
|
||||
// if (obj && obj.value === '1') getCommunityInfoFun();// 社区开启
|
||||
|
||||
filterFn.value = (item: any) => {
|
||||
// 判断用户权限
|
||||
const isAllowed = !item.permission || orgStore.access_level >= setting.role[item.permission];
|
||||
// 判断模块是否打开(讨论,社区等)
|
||||
// const param = orgStore.orgInfo?.module_setting?.modules.find(mod => mod.key === filterObj[item.label]);
|
||||
// const isEnabled = !filterObj[item.label] || !!(param && (param as any).value === '1');
|
||||
// return isEnabled && isAllowed;
|
||||
return isAllowed;
|
||||
};
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
flush: 'post',
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
// 关注点击
|
||||
const followClickEvent = (val: boolean) => {
|
||||
if (!user.isLogin) {
|
||||
emitEvent('login', { triggerType: '关注组织' });
|
||||
return;
|
||||
}
|
||||
followDevCommunity(val);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.back-idex {
|
||||
display: block;
|
||||
height: 68px;
|
||||
padding: 12px 32px;
|
||||
font-size: 16px;
|
||||
line-height: 44px;
|
||||
font-weight: bold;
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: ' ';
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #c7c9d9;
|
||||
vertical-align: top;
|
||||
margin-top: 10px;
|
||||
margin-right: 10px;
|
||||
background: url('@/assets/imgs/icon/ic-back@2x.png') center no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.org-layout {
|
||||
:deep(.g-header-body-right) {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
187
src/layouts/RepoLayout/index.vue
Normal file
187
src/layouts/RepoLayout/index.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<DefaultLayout class="g-repo-layout">
|
||||
<template #contentBanner v-if="hasPermission">
|
||||
<div class="g-repo-header">
|
||||
<ContentLayout>
|
||||
<div class="pt-8 g-repo-dashboard-header">
|
||||
<div class="g-repo-dashboard-width">
|
||||
<RepoHeaderInfo :widthType="widthType" />
|
||||
</div>
|
||||
</div>
|
||||
</ContentLayout>
|
||||
</div>
|
||||
<div class="g-archived" v-if="isArchived">此项目已归档</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<ErrorPage :code="errorCode" v-if="errorCode" />
|
||||
</template>
|
||||
<template v-if="hasPermission" #sidebar>
|
||||
<NavSidebar :key="route.fullPath" :showAvatar="false" />
|
||||
</template>
|
||||
</DefaultLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onUnmounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { getSettingsValues } from '@/api/repo';
|
||||
import { reqCatch } from '@/utils/catch';
|
||||
import { useRepoId } from '@/utils/hooks/useRepoId';
|
||||
import { repoInfoStore } from '@/stores/Repo';
|
||||
import { useGlobalInfoStore } from '@/stores/Global';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import DefaultLayout from '@/layouts/DefaultLayout/index.vue';
|
||||
import NavSidebar from '@/components/NavSidebar/index.vue';
|
||||
import { addEventListener, offEvent } from '@/utils/eventBus';
|
||||
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
|
||||
import * as types from '@/api/org/types';
|
||||
import { getCommunityInfo } from '@/api/org/index';
|
||||
import { orgInfoStore } from '@/stores/Org/index';
|
||||
import setting from '@/setting';
|
||||
import ErrorPage from '@/views/ErrorPage/index.vue';
|
||||
import { usePagePermission } from '@/layouts/hooks/usePagePermission';
|
||||
import RepoHeaderInfo from '@/components/Repo/RepoHeaderInfo/index.vue';
|
||||
import ContentLayout from '@/components/ContentLayout/index.vue';
|
||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||
|
||||
const { repoId } = useRepoId();
|
||||
const { widthType } = usePageResize();
|
||||
const { access_level, isPrivate, repoInfo, isArchived } = storeToRefs(repoInfoStore()); // pinia直接解构会失去响应式
|
||||
const { hasPermission } = usePagePermission(access_level.value, isPrivate.value);
|
||||
const globalInfo = useGlobalInfoStore();
|
||||
globalInfo.updateMenuNum('repoIssues', repoInfo.value.open_issues_count);
|
||||
globalInfo.updateMenuNum('repoMerge', repoInfo.value.open_merge_requests_count);
|
||||
globalInfo.updateMenuNum('repoMember', repoInfo.value.member_count);
|
||||
|
||||
// 根据权限及是否允许显示设置错误页面
|
||||
const errorCode = computed(() => {
|
||||
if (globalInfo.isNotFound) {
|
||||
return 404;
|
||||
}
|
||||
if (!hasPermission.value) {
|
||||
return 403;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
|
||||
// 获取组织namespace
|
||||
const { namespace } = getOrgInfo();
|
||||
const route = useRoute();
|
||||
|
||||
// 获取仓库
|
||||
const orgStore = orgInfoStore();
|
||||
// 社区基本信息
|
||||
const getCommunityInfoFun = async() => {
|
||||
const params: types.commonGroupReqType = {
|
||||
orgId: namespace.value
|
||||
};
|
||||
const { data, error } = await reqCatch(getCommunityInfo, params);
|
||||
if (!error && data) {
|
||||
orgStore.setCommunityUrl({
|
||||
communityUrl: `https://${data.data.custom_domain}`,
|
||||
communityactivityUrl: `https://${data.data.custom_domain}/activelist`
|
||||
});
|
||||
orgStore.setCommunityInfo(data.data);
|
||||
}
|
||||
};
|
||||
|
||||
const filterFn = ref<Function | null>(null);
|
||||
const nameMap = new Map([
|
||||
['WIKI', 'repoWiki'],
|
||||
['ISSUE', 'repoIssues'],
|
||||
['MERGE_REQUEST', 'repoMerge'],
|
||||
['FORK', 'repoFork'],
|
||||
['ANALYSIS', 'repoAnalysis'],
|
||||
['DISCUSSION', 'repoDiscussion'],
|
||||
['COMMUNITY', 'reqCommunity']
|
||||
]);
|
||||
async function getModuleState() {
|
||||
const res = await getSettingsValues({
|
||||
repoId: repoId.value,
|
||||
group_id: namespace.value
|
||||
});
|
||||
if (res.data) {
|
||||
const { modules } = res.data.data;
|
||||
const vModules = new Set();
|
||||
modules.forEach(({ key, value }) => {
|
||||
// 判断是否获取社区基本信息,拿到社区跳转链接
|
||||
if (key === 'COMMUNITY' && value === '1') {
|
||||
getCommunityInfoFun();
|
||||
}
|
||||
if (nameMap.get(key) && value === '0') vModules.add(nameMap.get(key));
|
||||
});
|
||||
filterFn.value = (item: any) => {
|
||||
const isAllowed = !item.permission || access_level.value >= setting.role[item.permission];
|
||||
if (item.id === 'reqCommunity') {
|
||||
const par = modules.find((item: any) => item.key === 'COMMUNITY');
|
||||
return !!(par && par.value === '1');
|
||||
}
|
||||
const isEnabled = !vModules.has(item.id);
|
||||
return isEnabled && isAllowed;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO 此处只用于repo切换时,读取项目模块配置信息。是否直接从repo详情获取?
|
||||
watch(repoId, () => getModuleState(), { flush: 'post', immediate: true });
|
||||
|
||||
addEventListener('updateNaviBar', getModuleState);
|
||||
onUnmounted(() => offEvent('updateNaviBar', getModuleState));
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.breadcrumb {
|
||||
:deep(.g-link) {
|
||||
font-weight: 500;
|
||||
color: var(--color-CG600);
|
||||
line-height: 20px;
|
||||
|
||||
&:last-child {
|
||||
color: var(--color-G900);
|
||||
}
|
||||
}
|
||||
|
||||
&-splitter {
|
||||
font-weight: 500;
|
||||
color: var(--color-CG600);
|
||||
line-height: 20px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
}
|
||||
.g-archived {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background-color: var(--color-Y100);
|
||||
font-weight: bold;
|
||||
color: var(--color-G900);
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--color-Y200);
|
||||
}
|
||||
.g-repo-header {
|
||||
background-color: #F9F9FB;
|
||||
border-bottom: 1px solid #F1F1F8;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.g-repo-content {
|
||||
.g-repo-content-width {
|
||||
max-width: 1536px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.g-repo-content-header {
|
||||
border-bottom: 1px solid #f1f1f8;
|
||||
background: #f9f9fb;
|
||||
}
|
||||
}
|
||||
.g-repo-layout {
|
||||
:deep(.devui-layout) {
|
||||
background-color: #fff;
|
||||
}
|
||||
:deep(.devui-layout__content) {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
125
src/layouts/UserLayout/index.vue
Normal file
125
src/layouts/UserLayout/index.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<DefaultLayout>
|
||||
<template v-if="!route?.meta?.hideNav && !isSelf && !isMe && !isMobile" #headerLeft>
|
||||
<InfoLeft :imgUrl="accountInfo?.avatarUrl" :infoText="accountInfo?.name" :isFollow="accountInfo.isFollow"
|
||||
@followClick="followClickEvent" :isRound="true" userDashBoard/>
|
||||
</template>
|
||||
<template v-if="!route?.meta?.hideNav && !isMobile" #headerRight>
|
||||
<NavTabs :filter="filterFn" />
|
||||
</template>
|
||||
<template #bodyHeader v-if="isMobile">
|
||||
<div class="w-full h-full">
|
||||
<InfoLeft
|
||||
v-if="!isSelf && !isMe"
|
||||
:imgUrl="accountInfo?.avatarUrl"
|
||||
:infoText="accountInfo?.name"
|
||||
:isFollow="accountInfo.isFollow"
|
||||
@followClick="followClickEvent"
|
||||
:isRound="true"
|
||||
/>
|
||||
<NavTabs :filter="filterFn" />
|
||||
</div>
|
||||
</template>
|
||||
<template #content v-if="$slots.content">
|
||||
<slot name="content"></slot>
|
||||
</template>
|
||||
<template #sidebar>
|
||||
<NavSidebar
|
||||
isRound
|
||||
:avatarParam="{
|
||||
avatar: user.accountInfo.avatar,
|
||||
name: user.accountInfo.nickname,
|
||||
infoSubText: `@${user.accountInfo.username}`
|
||||
}"
|
||||
>
|
||||
<template #sidebarInfo>
|
||||
<div>
|
||||
<span></span>
|
||||
</div>
|
||||
</template>
|
||||
</NavSidebar>
|
||||
</template>
|
||||
</DefaultLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, watch, onMounted, onBeforeMount } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import DefaultLayout from '@/layouts/DefaultLayout/index.vue';
|
||||
import NavTabs from '@/components/NavTabs/index.vue';
|
||||
import NavSidebar from '@/components/v2/NavSidebar/index.vue';
|
||||
import InfoLeft from '@/components/InfoLeft/index.vue';
|
||||
import { useUserInfo } from '@/views/User/hooks/useUserInfo';
|
||||
import { useAccountStore, otherAccountStore } from '@/stores/user';
|
||||
import { useStarFollow } from '@/views/User/hooks/useStarFollow';
|
||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||
import { getAtomgitIdentity } from '@/api/user';
|
||||
import { addEventListener, offEvent } from '@/utils/eventBus';
|
||||
import { useAccount } from '@/utils/hooks/useAccount';
|
||||
import debounce from 'lodash/debounce';
|
||||
const { saveAtomgitUserName } = useAccountStore();
|
||||
const { isMobile } = usePageResize();
|
||||
const user = useAccountStore();
|
||||
const other = otherAccountStore();
|
||||
const { accountInfo } = other;
|
||||
const { isSelf, userInfo, namespace } = useUserInfo();
|
||||
const route = useRoute();
|
||||
const { RecordInfo } = useAccount();
|
||||
|
||||
const filterFn = computed(() => (x) => {
|
||||
if (!accountInfo?.profile) return false;
|
||||
if (!isSelf && accountInfo?.profile?.setting_private) {
|
||||
if (x.id === 'userStars') return false;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const isMe = computed(() => {
|
||||
return other.accountInfo.username === user.accountInfo.username;
|
||||
});
|
||||
const { hasFollowed, toggleStar } = useStarFollow(
|
||||
{
|
||||
namespace,
|
||||
username: userInfo.username
|
||||
},
|
||||
false
|
||||
);
|
||||
watch(
|
||||
() => hasFollowed.value,
|
||||
(val) => {
|
||||
other.saveFollowed(val);
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => accountInfo.isFollow,
|
||||
(val) => {
|
||||
other.saveFollowed(val);
|
||||
}
|
||||
);
|
||||
const followClickEvent = (item: any) => {
|
||||
toggleStar(userInfo.username || '', accountInfo.username || '', !accountInfo.isFollow);
|
||||
};
|
||||
|
||||
const onShowPipeLine = async() => {
|
||||
const res = await getAtomgitIdentity();
|
||||
if (res.data) {
|
||||
saveAtomgitUserName(res.data?.data?.extern_username);
|
||||
}
|
||||
};
|
||||
if (route.meta.sidebarType === 'userSetting') {
|
||||
onShowPipeLine();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
addEventListener('updateUserInfo', debounce((payload: Record<string, any>) => {
|
||||
RecordInfo(payload as any);
|
||||
}, 500));
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
offEvent('updateUserInfo');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
28
src/layouts/hooks/usePagePermission.ts
Normal file
28
src/layouts/hooks/usePagePermission.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import setting from '@/setting';
|
||||
|
||||
/**
|
||||
* 判断页面权限
|
||||
* @param access_level
|
||||
* @param isPrivate
|
||||
* @returns
|
||||
*/
|
||||
export function usePagePermission(access_level: number = 0, isPrivate: boolean) {
|
||||
const route = useRoute();
|
||||
const hasPermission = ref(true);
|
||||
const permission = route.meta?.permission as string;
|
||||
|
||||
// 判断页面访问权限
|
||||
if (permission) {
|
||||
hasPermission.value = access_level >= setting.role[permission];
|
||||
} else {
|
||||
if (isPrivate) { // 访问私有项目至少是成员
|
||||
hasPermission.value = access_level >= setting.role['visitor'];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hasPermission
|
||||
};
|
||||
};
|
||||
273
src/layouts/v2/DefaultLayout/aside.vue
Normal file
273
src/layouts/v2/DefaultLayout/aside.vue
Normal file
@@ -0,0 +1,273 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useAccountStore } from '@/stores/user';
|
||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||
import { emitEvent } from '@/utils/eventBus';
|
||||
import setting from '@/setting';
|
||||
const { copyright } = setting;
|
||||
|
||||
defineProps<{ asideDefaultWidth: number; }>();
|
||||
const emit = defineEmits<{
|
||||
onSelect: [];
|
||||
}>();
|
||||
|
||||
const userStore = useAccountStore();
|
||||
const sizeStore = usePageResize();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const defaultSelectKeys = ref<string[]>([]);
|
||||
/**
|
||||
* 跳转菜单,转向子应用路由 或 自身路由
|
||||
*/
|
||||
const onSelect = (e) => {
|
||||
const route: { microApp?: string, name: string; login?: boolean; } = JSON.parse(e.el.parentNode.getAttribute('data-route'));
|
||||
if (route) {
|
||||
if (route.login && !userStore.isLogin) {
|
||||
if (sizeStore.isMobile.value) {
|
||||
emit('onSelect');
|
||||
}
|
||||
emitEvent('login');
|
||||
return;
|
||||
}
|
||||
location.href = router.resolve(route).href;
|
||||
// if (route?.microApp) {
|
||||
// if (microApp.router.current.has(route?.microApp)) {
|
||||
// microApp.router.push({
|
||||
// name: 'micoro-app-homeweb-app',
|
||||
// path: router.resolve(route).href
|
||||
// });
|
||||
// } else {
|
||||
// router.push(route);
|
||||
// }
|
||||
// } else {
|
||||
// router.push(route);
|
||||
// }
|
||||
}
|
||||
emit('onSelect');
|
||||
};
|
||||
const menuItemRouteName = ['home', 'gStar', 'aiCopilot', 'contributed', 'issues', 'merge', 'discussionCreated', 'explore', 'userFollowing', 'userStars'];
|
||||
watchEffect(() => {
|
||||
// 回显高亮menuitem
|
||||
const name = route?.name as string;
|
||||
if (menuItemRouteName.includes(name)) {
|
||||
defaultSelectKeys.value = [name];
|
||||
} else {
|
||||
const basePath = route.meta.basePath as string;
|
||||
if (basePath) defaultSelectKeys.value = [basePath];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="side-bar-content">
|
||||
<d-menu @select="onSelect" :open-keys="['work']" mode="vertical" :default-select-keys="defaultSelectKeys" class="menu"
|
||||
:key="defaultSelectKeys">
|
||||
<!-- 首页 -->
|
||||
<d-menu-item v-for="item in [
|
||||
{ key: 'home', icon: 'gt-plane-home', activeIcon: 'gt-plane-home-red', label: '首页', route: { name: 'home', microApp: 'micoro-app-homeweb-app' } },
|
||||
{ key: 'gStar', icon: 'gt-plane-GStar', activeIcon: 'gt-plane-GStar-red', label: 'G-Star', route: { name: 'gStar', microApp: 'micoro-app-homeweb-app' } },
|
||||
// { key: 'my-project', icon: 'gt-plane-new', activeIcon: 'gt-plane-new-red', label: '资源中心', route: undefined },
|
||||
{ key: 'aiCopilot', icon: 'gt-plane-ai', activeIcon: 'gt-plane-ai-red', label: 'AI Copilot', route: { name: 'aiCopilot', microApp: 'copilot-app' } },
|
||||
]" :key="item.key" :data-route="item.route ? JSON.stringify(item.route) : ''">
|
||||
<template #icon>
|
||||
<g-icon :name="defaultSelectKeys.some(e => item.key === e) ? item.activeIcon : item.icon"
|
||||
class="default-icon"></g-icon>
|
||||
<g-icon :name="item.activeIcon" class="active-icon"></g-icon>
|
||||
</template>
|
||||
<span>{{ item.label }}</span>
|
||||
</d-menu-item>
|
||||
|
||||
<!-- 个人工作台 -->
|
||||
<hr class="line">
|
||||
<d-sub-menu title="个人工作台" key="work" class="my-work-platform submenu-item">
|
||||
<d-menu-item v-for="item in [
|
||||
{ key: 'contributed', icon: 'gt-plane-project', activeIcon: 'gt-plane-project-red', label: '参与的项目', route: { name: 'contributed', login: true } },
|
||||
{ key: 'issues', icon: 'gt-plane-issues', activeIcon: 'gt-plane-issues-red', label: 'Issues', route: { name: 'issues', login: true } },
|
||||
{ key: 'merge', icon: 'gt-plane-pullRequest', activeIcon: 'gt-plane-pullRequest-red', label: 'PR', route: { name: 'merge', login: true } },
|
||||
{ key: 'discussionCreated', icon: 'gt-plane-discussions', activeIcon: 'gt-plane-discussions-red', label: '讨论', route: { name: 'discussionCreated', login: true } },
|
||||
]" :key="item.key" :data-route="item.route ? JSON.stringify(item.route) : ''">
|
||||
<template #icon>
|
||||
<g-icon :name="defaultSelectKeys.some(e => item.key === e) ? item.activeIcon : item.icon"
|
||||
class="default-icon"></g-icon>
|
||||
<g-icon :name="item.activeIcon" class="active-icon"></g-icon>
|
||||
</template>
|
||||
<span>{{ item.label }}</span>
|
||||
</d-menu-item>
|
||||
</d-sub-menu>
|
||||
|
||||
<!-- 发现 -->
|
||||
<hr class="line">
|
||||
<div class="py-[10px] px-[16px] text-[#1D1F3A] leading-[20px]">发现</div>
|
||||
<d-menu-item v-for="item in [
|
||||
// { key: 'explore', icon: 'gt-plane-search', activeIcon: 'gt-plane-search-red', label: '探索开源', route: { name: 'explore', microApp: 'micoro-app-homeweb-app' } },
|
||||
{ key: 'userFollowing', icon: 'gt-plane-subscribed', activeIcon: 'gt-plane-subscribed-red', label: '我的关注', route: { name: 'userFollowing', microApp: 'user-center', params: { namespace: userStore.accountInfo.username }, login: true } },
|
||||
{ key: 'userStars', icon: 'gt-plane-star', activeIcon: 'gt-plane-star-red', label: 'Starred', route: { name: 'userStars', microApp: 'user-center', params: { namespace: userStore.accountInfo.username }, login: true } },
|
||||
]" :key="item.key" :data-route="item.route ? JSON.stringify(item.route) : ''">
|
||||
<template #icon>
|
||||
<g-icon :name="defaultSelectKeys.some(e => item.key === e) ? item.activeIcon : item.icon"
|
||||
class="default-icon"></g-icon>
|
||||
<g-icon :name="item.activeIcon" class="active-icon"></g-icon>
|
||||
|
||||
</template>
|
||||
<span>{{ item.label }}</span>
|
||||
</d-menu-item>
|
||||
|
||||
<hr class="line">
|
||||
<g-link class="menu-item-link" target="_blank" href="https://gitcode.com/Gitcode-offical-team/GitCode-Docs/wiki">
|
||||
<g-icon name="gt-plane-document" class="default-icon" /><g-icon name="gt-plane-document-red"
|
||||
class="active-icon" />
|
||||
<span>帮助文档</span>
|
||||
<g-icon name="gt-line-open" class="!inline-flex align-middle" />
|
||||
</g-link>
|
||||
<g-link class="menu-item-link" target="_blank" href="https://gitcode.com/Gitcode-offical-team/GitCode-Docs/issues">
|
||||
<g-icon name="gt-plane-feedback" class="default-icon" /><g-icon name="gt-plane-feedback-red"
|
||||
class="active-icon" />
|
||||
<span>反馈中心</span>
|
||||
<g-icon name="gt-line-open" class="!inline-flex align-middle" />
|
||||
</g-link>
|
||||
</d-menu>
|
||||
|
||||
<hr class="line">
|
||||
<div class="aside-footer">
|
||||
<p class="whitespace-nowrap">© {{ copyright.digestAbbr }}</p>
|
||||
<p class="whitespace-nowrap">
|
||||
<a href="https://beian.miit.gov.cn/#/Integrated/index" target="_blank">{{ copyright.icp }}</a>
|
||||
</p>
|
||||
<p class="whitespace-nowrap mt-[16px]">技术支持:</p>
|
||||
<p class="whitespace-nowrap">
|
||||
<a href="https://www.csdn.net/" target="_blank" rel="noopener noreferrer">CSDN</a>、<a
|
||||
href="https://www.huaweicloud.com/" target="_blank" rel="noopener noreferrer">华为云</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.side-bar-content {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
@apply h-[100%] py-[32px] px-[16px] flex flex-col justify-between;
|
||||
|
||||
.menu {
|
||||
background: transparent !important;
|
||||
@apply border-r-0 flex-1;
|
||||
|
||||
:deep(.devui-menu-item-vertical-wrapper:first-of-type) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
:deep(.devui-menu-item-vertical-wrapper) {
|
||||
margin-top: 4px !important;
|
||||
}
|
||||
|
||||
:deep(.devui-menu-item) {
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
padding: 10px 16px !important;
|
||||
white-space: nowrap;
|
||||
|
||||
.active-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.devui-menu-item:hover) {
|
||||
background: #9699B633;
|
||||
|
||||
.active-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.default-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.devui-menu-item.devui-menu-item-select) {
|
||||
background: #9699B633 !important;
|
||||
}
|
||||
|
||||
:deep(.devui-menu-item>span) {
|
||||
flex: none;
|
||||
color: #1D1F3A;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.my-work-platform .devui-submenu-title-content ~ .icon) {
|
||||
background-color: #E3E3EE;
|
||||
@apply rounded-[4px] w-[20px] h-[20px] text-center leading-[20px];
|
||||
}
|
||||
|
||||
:deep(.my-work-platform .devui-submenu-title-content ~ .icon:hover) {
|
||||
background-color: #D1D1E0;
|
||||
}
|
||||
|
||||
.submenu-item :deep(.devui-submenu-title) {
|
||||
padding: 10px 16px !important;
|
||||
|
||||
.devui-submenu-title-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item-link {
|
||||
@apply inline-block px-[16px] py-[10px] w-[100%] whitespace-nowrap leading-[20px] mt-[4px] first-of-type:mt-0;
|
||||
|
||||
&:hover {
|
||||
color: #DA203E;
|
||||
|
||||
.icon {
|
||||
color: #DA203E !important;
|
||||
}
|
||||
|
||||
.active-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.default-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.active-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.line {
|
||||
border-color: #F1F1F8;
|
||||
@apply ml-[10px] mr-[10px] my-[16px];
|
||||
}
|
||||
|
||||
.aside-footer {
|
||||
padding: 0px 16px;
|
||||
color: #9C9DB4;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.devui-menu-vertical ul.devui-submenu div.devui-submenu-title i.icon-chevron-up.is-opened) {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
:deep(.devui-menu-vertical ul.devui-submenu div.devui-submenu-title i.icon-chevron-up) {
|
||||
transform: none;
|
||||
|
||||
&::before {
|
||||
transform: rotate(0deg) !important;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: transform var(--devui-animation-duration-slow, .3s) var(--devui-animation-ease-in-out-smooth, cubic-bezier(.645, .045, .355, 1));
|
||||
}
|
||||
|
||||
&.is-opened::before {
|
||||
transform: rotate(180deg) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
199
src/layouts/v2/DefaultLayout/index.vue
Normal file
199
src/layouts/v2/DefaultLayout/index.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<d-layout class="d-layout h-full">
|
||||
<d-header class="d-header">
|
||||
<!-- header -->
|
||||
<Header @clickMenu="handleClickMenu" :asideSetShow="asideSetShow" ref="refHeader">
|
||||
<!-- 二级导航左侧内容 -->
|
||||
<template v-if="$slots.headerLeft && !route?.meta?.hideNav" #info>
|
||||
<slot name="headerLeft"></slot>
|
||||
</template>
|
||||
|
||||
<!-- 二级导航右侧内容 -->
|
||||
<template v-if="$slots.headerRight && !route?.meta?.hideNav" #menu>
|
||||
<slot name="headerRight"></slot>
|
||||
</template>
|
||||
|
||||
<template v-if="$slots.bodyHeader && !route?.meta?.hideNav" #bodyHeader>
|
||||
<slot name="bodyHeader"></slot>
|
||||
</template>
|
||||
<template v-if="$slots.headerBottom && !route?.meta?.hideNav" #headerBottom>
|
||||
<slot name="headerBottom"></slot>
|
||||
</template>
|
||||
</Header>
|
||||
</d-header>
|
||||
<!-- 顶栏信息 -->
|
||||
<div v-if="$slots.contentBanner">
|
||||
<slot name="contentBanner"></slot>
|
||||
</div>
|
||||
<!-- 主体 -->
|
||||
<d-layout class="flex-col">
|
||||
<d-aside class="left-side-bar" v-if="asideSetShow" :style="{ width: mainMarginLeftWidth + 'px' }">
|
||||
<Aside :asideDefaultWidth="mainMarginLeftWidth"></Aside>
|
||||
</d-aside>
|
||||
<d-drawer v-model="asideVisible" position="left" lock-scroll style="max-height: 100%; top: 0;"
|
||||
:style="{ width: '240px', overflowY: 'hidden' }">
|
||||
<div class="p-[16px] flex justify-between items-center h-[60px]">
|
||||
<GLink class="g-toolbar-left-logo" href="/" />
|
||||
<d-icon class="cursor-pointer" name="close" @click="asideVisible = false"></d-icon>
|
||||
</div>
|
||||
<Aside :asideDefaultWidth="240" @on-select="handleClickMenu" style="height: calc(100% - 60px);padding-top: 0px;"></Aside>
|
||||
</d-drawer>
|
||||
<d-layout :style="{ marginLeft: mainMarginLeftWidth + 'px', transition: 'margin-left ease 0.1s' }">
|
||||
<d-layout :class="responsiveClass" :style="{ minHeight: `calc(100% - ${footerSize?.height.value || 0}px)` }">
|
||||
<!-- sidebar -->
|
||||
<d-aside class="d-aside pr-[32px] pt-32 g-side-border" v-if="route?.meta?.sidebarType">
|
||||
<slot name="sidebar"></slot>
|
||||
</d-aside>
|
||||
<!-- 内容 -->
|
||||
<d-content class="pl-[32px]">
|
||||
<slot name="content">
|
||||
<RouterView />
|
||||
</slot>
|
||||
</d-content>
|
||||
</d-layout>
|
||||
<!-- footer -->
|
||||
<!-- <d-footer v-if="!route?.meta?.hiddenFooter" ref="refFooter">
|
||||
<slot name="footer">
|
||||
<Footer />
|
||||
</slot>
|
||||
</d-footer> -->
|
||||
</d-layout>
|
||||
</d-layout>
|
||||
|
||||
<!-- 浮动工具栏(移动端不显示) -->
|
||||
<ToolsFloat v-if="showToolsFloat" />
|
||||
|
||||
</d-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'DefaultLayout' });
|
||||
import { RouterView, useRoute, useRouter } from 'vue-router';
|
||||
import { computed, ref, watch, watchEffect, onMounted, onUnmounted } from 'vue';
|
||||
import Header from '@/components/Header/index.vue';
|
||||
import Footer from '@/components/Footer/index.vue';
|
||||
import Aside from './aside.vue';
|
||||
import { useGlobalInfoStore } from '@/stores/Global';
|
||||
|
||||
import ToolsFloat from '@/components/ToolsFloat/index.vue';
|
||||
import { useStorage, useElementSize } from '@vueuse/core';
|
||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||
import microApp from '@micro-zoe/micro-app';
|
||||
defineProps<{ customAside?: boolean; }>();
|
||||
const router = useRouter();
|
||||
const solt = defineSlots();
|
||||
const sizeStore = usePageResize();
|
||||
const globalStore = useGlobalInfoStore();
|
||||
const route = useRoute();
|
||||
const asideVisible = ref(false);
|
||||
const refFooter = ref(null);
|
||||
const refHeader: any = ref(null);
|
||||
const footerSize = useElementSize(refFooter);
|
||||
|
||||
// aside 设置可见性 header有两层,有顶栏信息,两层sidebar 都不可见
|
||||
const asideSetShow = computed(() => {
|
||||
if (route.meta.asideMenu === 'drawer') return false;
|
||||
return refHeader.value?.height <= 56 && !solt.contentBanner && !route.meta.sidebarType;
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
clickMenu: [];
|
||||
}>();
|
||||
watch(route, () => {
|
||||
globalStore.setIsNotFound(false); // 切换路由默认不显示404,防止一直显示404
|
||||
}, { deep: true });
|
||||
|
||||
const asideDefaultWidth = useStorage('asideDefaultWidth', sizeStore.isMobile.value ? 0 : 240);
|
||||
const mainMarginLeftWidth = computed(() => {
|
||||
if (!asideSetShow.value || sizeStore.isMobile.value) {
|
||||
return 0;
|
||||
} else {
|
||||
return asideDefaultWidth.value;
|
||||
}
|
||||
});
|
||||
|
||||
// 是否展示浮动工具栏(非移动端 || 未设置隐藏)
|
||||
const showToolsFloat = computed(() => {
|
||||
return sizeStore.widthType.value !== 'sm' && !route.meta.hiddenToolsFloat;
|
||||
});
|
||||
|
||||
const responsiveClass = computed(() => { // 控制移动和pc布局
|
||||
let className = 'm-auto max-w-normal px-normal w-full';
|
||||
let viewportContent = 'width=device-width,initial-scale=0,user-scalable=yes,maximum-scale=1.0';
|
||||
|
||||
if (sizeStore.isMobile.value) { // 设备是否为手机
|
||||
if (route?.meta?.hasMobile) { // 是否有手机端页面
|
||||
className = 'w-full';
|
||||
viewportContent = 'width=device-width, initial-scale=1.0';
|
||||
}
|
||||
} else {
|
||||
if (route?.meta?.className) {
|
||||
className = `${route?.meta?.className as string}`;
|
||||
}
|
||||
if (route.name === 'homepage' && globalStore.namespaceType === 1) { // 组织主页样式特殊处理
|
||||
className = 'm-auto min-w-sm px-normal max-w-[1280px] w-full pt-[12px]';
|
||||
}
|
||||
}
|
||||
document?.querySelector("meta[name='viewport']")?.setAttribute('content', viewportContent);
|
||||
return className;
|
||||
});
|
||||
|
||||
const handleClickMenu = () => {
|
||||
if (sizeStore.isMobile.value || !asideSetShow.value) { // 不可见时弹框
|
||||
asideVisible.value = !asideVisible.value;
|
||||
} else { // 可见时收起
|
||||
asideDefaultWidth.value = asideDefaultWidth.value >= 240 ? 0 : 240;
|
||||
}
|
||||
};
|
||||
|
||||
const outOfSearch = ref(true); // 在search页面隐藏footer
|
||||
router.afterEach((to, form) => {
|
||||
outOfSearch.value = to.name !== 'search';
|
||||
});
|
||||
microApp.router.afterEach({
|
||||
'micoro-app-homeweb-app': (to, from) => {
|
||||
outOfSearch.value = to.pathname !== '/search';
|
||||
}
|
||||
});
|
||||
const checkPage = (data: any) => {
|
||||
if (data.type === 'checkPage') {
|
||||
return 'ready';
|
||||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
microApp.addDataListener('micoro-app-homeweb-app', checkPage, false);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
microApp.removeDataListener('micoro-app-homeweb-app', checkPage);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.left-side-bar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
top: 56px;
|
||||
background-color: #fff;
|
||||
z-index: 10;
|
||||
border-right: 1px solid #F1F1F8;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
transition: width ease 0.1s;
|
||||
}
|
||||
|
||||
.g-toolbar-left-logo {
|
||||
display: inline-block;
|
||||
width: 102px;
|
||||
height: 28px;
|
||||
background: url(../../assets/imgs/logo/logo.png) no-repeat;
|
||||
background-size: contain;
|
||||
vertical-align: middle;
|
||||
background-position: 0 center;
|
||||
}
|
||||
|
||||
.g-side-border {
|
||||
border-right: 1px solid var(--devui-dividing-line);
|
||||
}
|
||||
|
||||
</style>
|
||||
187
src/layouts/v2/RepoLayout/index.vue
Normal file
187
src/layouts/v2/RepoLayout/index.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<DefaultLayout class="g-repo-layout">
|
||||
<template #contentBanner v-if="hasPermission">
|
||||
<div class="g-repo-header">
|
||||
<ContentLayout>
|
||||
<div class="pt-8 g-repo-dashboard-header">
|
||||
<div class="g-repo-dashboard-width">
|
||||
<RepoHeaderInfo :widthType="widthType" />
|
||||
</div>
|
||||
</div>
|
||||
</ContentLayout>
|
||||
</div>
|
||||
<div class="g-archived" v-if="isArchived">此项目已归档</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<ErrorPage :code="errorCode" v-if="errorCode" />
|
||||
</template>
|
||||
<template v-if="hasPermission" #sidebar>
|
||||
<NavSidebar :key="route.fullPath" :showAvatar="false" />
|
||||
</template>
|
||||
</DefaultLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onUnmounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { getSettingsValues } from '@/api/repo';
|
||||
import { reqCatch } from '@/utils/catch';
|
||||
import { useRepoId } from '@/utils/hooks/useRepoId';
|
||||
import { repoInfoStore } from '@/stores/Repo';
|
||||
import { useGlobalInfoStore } from '@/stores/Global';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import DefaultLayout from '@/layouts/v2/DefaultLayout/index.vue';
|
||||
import NavSidebar from '@/components/v2/NavSidebar/index.vue';
|
||||
import { addEventListener, offEvent } from '@/utils/eventBus';
|
||||
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
|
||||
import * as types from '@/api/org/types';
|
||||
import { getCommunityInfo } from '@/api/org/index';
|
||||
import { orgInfoStore } from '@/stores/Org/index';
|
||||
import setting from '@/setting';
|
||||
import ErrorPage from '@/views/ErrorPage/index.vue';
|
||||
import { usePagePermission } from '@/layouts/hooks/usePagePermission';
|
||||
import RepoHeaderInfo from '@/components/Repo/RepoHeaderInfo/index.vue';
|
||||
import ContentLayout from '@/components/ContentLayout/index.vue';
|
||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||
|
||||
const { repoId } = useRepoId();
|
||||
const { widthType } = usePageResize();
|
||||
const { access_level, isPrivate, repoInfo, isArchived } = storeToRefs(repoInfoStore()); // pinia直接解构会失去响应式
|
||||
const { hasPermission } = usePagePermission(access_level.value, isPrivate.value);
|
||||
const globalInfo = useGlobalInfoStore();
|
||||
globalInfo.updateMenuNum('repoIssues', repoInfo.value.open_issues_count);
|
||||
globalInfo.updateMenuNum('repoMerge', repoInfo.value.open_merge_requests_count);
|
||||
globalInfo.updateMenuNum('repoMember', repoInfo.value.member_count);
|
||||
|
||||
// 根据权限及是否允许显示设置错误页面
|
||||
const errorCode = computed(() => {
|
||||
if (globalInfo.isNotFound) {
|
||||
return 404;
|
||||
}
|
||||
if (!hasPermission.value) {
|
||||
return 403;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
|
||||
// 获取组织namespace
|
||||
const { namespace } = getOrgInfo();
|
||||
const route = useRoute();
|
||||
|
||||
// 获取仓库
|
||||
const orgStore = orgInfoStore();
|
||||
// 社区基本信息
|
||||
const getCommunityInfoFun = async() => {
|
||||
const params: types.commonGroupReqType = {
|
||||
orgId: namespace.value
|
||||
};
|
||||
const { data, error } = await reqCatch(getCommunityInfo, params);
|
||||
if (!error && data) {
|
||||
orgStore.setCommunityUrl({
|
||||
communityUrl: `https://${data.data.custom_domain}`,
|
||||
communityactivityUrl: `https://${data.data.custom_domain}/activelist`
|
||||
});
|
||||
orgStore.setCommunityInfo(data.data);
|
||||
}
|
||||
};
|
||||
|
||||
const filterFn = ref<Function | null>(null);
|
||||
const nameMap = new Map([
|
||||
['WIKI', 'repoWiki'],
|
||||
['ISSUE', 'repoIssues'],
|
||||
['MERGE_REQUEST', 'repoMerge'],
|
||||
['FORK', 'repoFork'],
|
||||
['ANALYSIS', 'repoAnalysis'],
|
||||
['DISCUSSION', 'repoDiscussion'],
|
||||
['COMMUNITY', 'reqCommunity']
|
||||
]);
|
||||
async function getModuleState() {
|
||||
const res = await getSettingsValues({
|
||||
repoId: repoId.value,
|
||||
group_id: namespace.value
|
||||
});
|
||||
if (res.data) {
|
||||
const { modules } = res.data.data;
|
||||
const vModules = new Set();
|
||||
modules.forEach(({ key, value }) => {
|
||||
// 判断是否获取社区基本信息,拿到社区跳转链接
|
||||
if (key === 'COMMUNITY' && value === '1') {
|
||||
getCommunityInfoFun();
|
||||
}
|
||||
if (nameMap.get(key) && value === '0') vModules.add(nameMap.get(key));
|
||||
});
|
||||
filterFn.value = (item: any) => {
|
||||
const isAllowed = !item.permission || access_level.value >= setting.role[item.permission];
|
||||
if (item.id === 'reqCommunity') {
|
||||
const par = modules.find((item: any) => item.key === 'COMMUNITY');
|
||||
return !!(par && par.value === '1');
|
||||
}
|
||||
const isEnabled = !vModules.has(item.id);
|
||||
return isEnabled && isAllowed;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO 此处只用于repo切换时,读取项目模块配置信息。是否直接从repo详情获取?
|
||||
watch(repoId, () => getModuleState(), { flush: 'post', immediate: true });
|
||||
|
||||
addEventListener('updateNaviBar', getModuleState);
|
||||
onUnmounted(() => offEvent('updateNaviBar', getModuleState));
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.breadcrumb {
|
||||
:deep(.g-link) {
|
||||
font-weight: 500;
|
||||
color: var(--color-CG600);
|
||||
line-height: 20px;
|
||||
|
||||
&:last-child {
|
||||
color: var(--color-G900);
|
||||
}
|
||||
}
|
||||
|
||||
&-splitter {
|
||||
font-weight: 500;
|
||||
color: var(--color-CG600);
|
||||
line-height: 20px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
}
|
||||
.g-archived {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background-color: var(--color-Y100);
|
||||
font-weight: bold;
|
||||
color: var(--color-G900);
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--color-Y200);
|
||||
}
|
||||
.g-repo-header {
|
||||
background-color: #F9F9FB;
|
||||
border-bottom: 1px solid #F1F1F8;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.g-repo-content {
|
||||
.g-repo-content-width {
|
||||
max-width: 1536px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.g-repo-content-header {
|
||||
border-bottom: 1px solid #f1f1f8;
|
||||
background: #f9f9fb;
|
||||
}
|
||||
}
|
||||
.g-repo-layout {
|
||||
:deep(.devui-layout) {
|
||||
background-color: #fff;
|
||||
}
|
||||
:deep(.devui-layout__content) {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user