564 lines
15 KiB
Vue
564 lines
15 KiB
Vue
<template>
|
|
<div
|
|
class="layout-header"
|
|
:class="{
|
|
'layout-header-horizontal': navMode === 'horizontal',
|
|
}"
|
|
>
|
|
<!--顶部菜单-->
|
|
<div
|
|
v-if="navMode === 'horizontal' || (navMode === 'horizontal-mix' && mixMenu)"
|
|
class="layout-header-left"
|
|
>
|
|
<div v-if="navMode === 'horizontal'" class="logo">
|
|
<img alt="" src="~@/assets/images/logo.png" />
|
|
<h2 v-show="!collapsed" class="title">数据中台管理系统</h2>
|
|
</div>
|
|
</div>
|
|
<!--左侧菜单-->
|
|
<div v-else class="layout-header-left">
|
|
<!-- 菜单收起 -->
|
|
<div
|
|
id="collapsed-trigger"
|
|
class="ml-1 layout-header-trigger layout-header-trigger-min collapsed-trigger"
|
|
@click="() => $emit('update:collapsed')"
|
|
>
|
|
<el-icon class="el-input__icon" v-if="collapsed" :size="18">
|
|
<MenuUnfoldOutlined />
|
|
</el-icon>
|
|
<el-icon class="el-input__icon" v-else :size="18">
|
|
<MenuFoldOutlined />
|
|
</el-icon>
|
|
</div>
|
|
<!-- 刷新 -->
|
|
<div
|
|
v-if="headerSetting.isReload"
|
|
class="mr-1 layout-header-trigger layout-header-trigger-min"
|
|
@click="reloadPage"
|
|
>
|
|
<el-icon class="el-input__icon" :size="18">
|
|
<ReloadOutlined />
|
|
</el-icon>
|
|
</div>
|
|
<!-- 面包屑 -->
|
|
<el-breadcrumb v-if="crumbsSetting.show" class="flex items-center hidden-sm-only">
|
|
<template v-for="routeItem in breadcrumbList" :key="routeItem.name">
|
|
<el-breadcrumb-item v-if="routeItem.meta.breadcrumbView != false">
|
|
<el-dropdown v-if="routeItem.children.length" :options="routeItem.children">
|
|
<span class="flex items-center link-text">
|
|
<el-icon
|
|
class="mr-2 el-input__icon"
|
|
:size="18"
|
|
v-if="crumbsSetting.showIcon && routeItem.meta.icon"
|
|
>
|
|
<component :is="routeItem.meta.icon" />
|
|
</el-icon>
|
|
<Render
|
|
:ref="`renderDom_${routeItem.name}`"
|
|
:value="getRender(routeItem.meta.title)"
|
|
/>
|
|
</span>
|
|
<template #dropdown>
|
|
<el-dropdown-menu>
|
|
<el-dropdown-item v-for="item in routeItem.children" :key="item.name">
|
|
<el-icon
|
|
class="el-input__icon"
|
|
:size="18"
|
|
v-if="crumbsSetting.showIcon && item.meta.icon"
|
|
>
|
|
<component :is="item.meta.icon" />
|
|
</el-icon>
|
|
<Render :ref="`renderDom_${item.name}`" :value="getRender(item.meta.title)" />
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
<span v-else class="link-text">
|
|
<el-icon
|
|
class="el-input__icon"
|
|
:size="18"
|
|
v-if="crumbsSetting.showIcon && routeItem.meta.icon"
|
|
>
|
|
<component :is="routeItem.meta.icon" />
|
|
</el-icon>
|
|
<Render
|
|
:ref="`renderDom_${routeItem.name}`"
|
|
:value="getRender(routeItem.meta.title)"
|
|
/>
|
|
</span>
|
|
</el-breadcrumb-item>
|
|
</template>
|
|
</el-breadcrumb>
|
|
</div>
|
|
<div class="header-horizontal-menu">
|
|
<Sider
|
|
v-if="navMode === 'horizontal' || (navMode === 'horizontal-mix' && mixMenu)"
|
|
v-model:location="getMenuLocation"
|
|
v-bind="siderOption"
|
|
:inverted="getInverted"
|
|
mode="horizontal"
|
|
/>
|
|
</div>
|
|
<div class="layout-header-right">
|
|
<div
|
|
v-for="item in iconList"
|
|
:key="item.icon.name"
|
|
v-on="item.eventObject || {}"
|
|
class="layout-header-trigger layout-header-trigger-min"
|
|
>
|
|
<el-tooltip placement="bottom" :content="item.tips">
|
|
<el-icon class="el-input__icon" :size="18">
|
|
<component :is="item.icon" />
|
|
</el-icon>
|
|
</el-tooltip>
|
|
</div>
|
|
<!--切换全屏-->
|
|
<div class="layout-header-trigger layout-header-trigger-min">
|
|
<el-tooltip placement="bottom" :content="isFullscreen ? '还原' : '全屏'">
|
|
<el-icon class="el-input__icon" :size="18" v-if="isFullscreen" @click="toggleFullScreen">
|
|
<FullscreenExitOutlined />
|
|
</el-icon>
|
|
<el-icon class="el-input__icon" :size="18" v-else @click="toggleFullScreen">
|
|
<FullscreenOutlined />
|
|
</el-icon>
|
|
</el-tooltip>
|
|
</div>
|
|
<!--消息-->
|
|
<div class="layout-header-trigger layout-header-trigger-min notifier-plus">
|
|
<NotifierProPlus />
|
|
</div>
|
|
<!-- 个人中心 -->
|
|
<div class="layout-header-trigger layout-header-trigger-min">
|
|
<el-dropdown trigger="hover" @command="avatarSelect">
|
|
<div class="avatar">
|
|
<el-avatar round :src="userAvatar?userAvatar:schoolboy" />
|
|
</div>
|
|
<template #dropdown>
|
|
<el-dropdown-menu>
|
|
<el-dropdown-item command="1"
|
|
><el-icon class="el-input__icon" :size="18"><UserSwitchOutlined /></el-icon
|
|
>个人设置</el-dropdown-item
|
|
>
|
|
<el-dropdown-item command="3"
|
|
><el-icon class="el-input__icon" :size="18"><LockClosedOutline /></el-icon
|
|
>修改密码</el-dropdown-item
|
|
>
|
|
<el-dropdown-item command="2"
|
|
><el-icon class="el-input__icon" :size="18"><LogoutOutlined /></el-icon
|
|
>退出登录</el-dropdown-item
|
|
>
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
</div>
|
|
<!--设置-->
|
|
<div
|
|
id="setting-trigger"
|
|
class="layout-header-trigger layout-header-trigger-min setting-trigger"
|
|
@click="openSetting"
|
|
>
|
|
<el-tooltip placement="bottom-end" content="项目配置">
|
|
<el-icon class="el-input__icon" :size="18" style="font-weight: bold">
|
|
<SettingOutlined />
|
|
</el-icon>
|
|
</el-tooltip>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!--项目配置-->
|
|
<ProjectSetting ref="drawerSetting" />
|
|
|
|
<!-- 搜索 -->
|
|
<AppSearch ref="appSearchRef" />
|
|
|
|
<!--修改密码-->
|
|
<AmendPwd ref="amendPwdRef" />
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { useTabsViewStore } from '@/store/modules/tabsView';
|
|
import { computed, ref, unref, watch, inject } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { TABS_ROUTES,FIRST_ROUTE } from '@/store/mutation-types';
|
|
import { useUserStore } from '@/store/modules/user';
|
|
import { useLockscreenStore } from '@/store/modules/lockscreen';
|
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
|
import { AppSearch } from '@/components/Application/index';
|
|
import ProjectSetting from './ProjectSetting.vue';
|
|
import NotifierProPlus from './NotifierProPlus.vue';
|
|
import Sider from '../Sider/Sider.vue';
|
|
import AmendPwd from './AmendPwd.vue';
|
|
import { useGo, useRedo } from '@/hooks/web/usePage';
|
|
import {
|
|
FullscreenExitOutlined,
|
|
FullscreenOutlined,
|
|
GithubOutlined,
|
|
LockOutlined,
|
|
LogoutOutlined,
|
|
MenuFoldOutlined,
|
|
MenuUnfoldOutlined,
|
|
ReloadOutlined,
|
|
SearchOutlined,
|
|
SettingOutlined,
|
|
UserSwitchOutlined,
|
|
} from '@vicons/antd';
|
|
import { LockClosedOutline } from '@vicons/ionicons5';
|
|
import { PageEnum } from '@/enums/pageEnum';
|
|
import schoolboy from '@/assets/images/schoolboy.png';
|
|
import { useFullscreen } from '@vueuse/core';
|
|
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
|
|
import { ElMessageBox, ElMessage } from 'element-plus';
|
|
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
|
import { Render, getRender } from '@/components/Render';
|
|
|
|
defineEmits(['update:collapsed']);
|
|
|
|
const tabsViewStore = useTabsViewStore();
|
|
const userStore = useUserStore();
|
|
const useLockscreen = useLockscreenStore();
|
|
const appSearchRef = ref();
|
|
const isRefresh = ref(false);
|
|
const { getDarkTheme } = useDesignSetting();
|
|
const { getNavMode, getNavTheme, getHeaderSetting, getMenuSetting, getCrumbsSetting } =
|
|
useProjectSetting();
|
|
|
|
const props = defineProps({
|
|
inverted: {
|
|
type: Boolean,
|
|
},
|
|
});
|
|
|
|
const go = useGo();
|
|
|
|
const BASE_LOGIN_NAME = PageEnum.BASE_LOGIN_NAME;
|
|
|
|
const drawerSetting = ref();
|
|
|
|
const amendPwdRef = ref();
|
|
|
|
// const username = userStore?.info ? ref(userStore?.info.username) : '';
|
|
const userAvatar = userStore.getAvatar
|
|
|
|
const collapsed = inject('collapsed');
|
|
|
|
const navMode = getNavMode;
|
|
|
|
const headerSetting = getHeaderSetting;
|
|
|
|
const crumbsSetting = getCrumbsSetting;
|
|
|
|
const getInverted = computed(() => {
|
|
const navTheme = unref(getNavTheme);
|
|
return ['light', 'header-dark'].includes(navTheme) ? props.inverted : !props.inverted;
|
|
});
|
|
|
|
const siderOption = computed(() => {
|
|
const navTheme = unref(getNavTheme);
|
|
let backgroundColor = '#fff';
|
|
let textColor = '#333';
|
|
if (unref(getDarkTheme)) {
|
|
backgroundColor = '#18181c';
|
|
textColor = '#fff';
|
|
} else if (['header-dark'].includes(navTheme)) {
|
|
backgroundColor = '#001428';
|
|
textColor = '#bbb';
|
|
}
|
|
|
|
return {
|
|
backgroundColor,
|
|
textColor,
|
|
};
|
|
});
|
|
|
|
const mixMenu = computed(() => {
|
|
return unref(getMenuSetting).mixMenu;
|
|
});
|
|
|
|
const getMenuLocation = computed(() => {
|
|
return 'header';
|
|
});
|
|
|
|
const router = useRouter();
|
|
const route = useRoute();
|
|
const { isFullscreen, toggle } = useFullscreen();
|
|
const asyncRouteStore = useAsyncRouteStore();
|
|
|
|
const generator: any = (routerMap) => {
|
|
return routerMap
|
|
.filter((item) => {
|
|
return !item.meta?.hidden;
|
|
})
|
|
.map((item) => {
|
|
const currentMenu = {
|
|
...item,
|
|
label: item.meta.title,
|
|
key: item.name,
|
|
disabled: item.path === '/',
|
|
props: {
|
|
onClick: () => {
|
|
go(item, false);
|
|
},
|
|
},
|
|
};
|
|
// 是否有子菜单,并递归处理
|
|
if (item.children && item.children.length > 0) {
|
|
// Recursion
|
|
currentMenu.children = generator(item.children, currentMenu);
|
|
}
|
|
return currentMenu;
|
|
});
|
|
};
|
|
|
|
watch(
|
|
() => route.fullPath,
|
|
(to) => {
|
|
isRefresh.value = to.indexOf('/redirect/') != -1;
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
|
|
// eslint-disable-next-line vue/return-in-computed-property
|
|
const breadcrumbList = computed(() => {
|
|
if (!isRefresh.value) return generator(route.matched);
|
|
});
|
|
|
|
// 刷新页面
|
|
async function reloadPage() {
|
|
const redo = useRedo(router);
|
|
await redo();
|
|
}
|
|
|
|
// 退出登录
|
|
const doLogout = () => {
|
|
ElMessageBox.confirm('您确定要退出登录吗', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning',
|
|
}).then(() => {
|
|
userStore.logout().then(() => {
|
|
ElMessage({
|
|
type: 'success',
|
|
message: '成功退出登录',
|
|
});
|
|
// 移除标签页
|
|
tabsViewStore.closeAllTabs();
|
|
localStorage.removeItem(TABS_ROUTES);
|
|
localStorage.removeItem(FIRST_ROUTE);
|
|
asyncRouteStore.setDynamicAddedRoute(false);
|
|
window.location.reload();
|
|
router.replace({
|
|
name: BASE_LOGIN_NAME,
|
|
query: {
|
|
redirect: route.fullPath,
|
|
},
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
// 全屏切换
|
|
const toggleFullScreen = () => {
|
|
toggle();
|
|
};
|
|
|
|
// 图标列表
|
|
const iconList = [
|
|
{
|
|
icon: SearchOutlined,
|
|
tips: '搜索',
|
|
eventObject: {
|
|
click: () => openAppSearch(),
|
|
},
|
|
},
|
|
// {
|
|
// icon: GithubOutlined,
|
|
// tips: 'github',
|
|
// eventObject: {
|
|
// click: () => window.open('https://www.baidu.com'),
|
|
// },
|
|
// },
|
|
// {
|
|
// icon: LockOutlined,
|
|
// tips: '锁屏',
|
|
// eventObject: {
|
|
// click: () => useLockscreen.setLock(true),
|
|
// },
|
|
// },
|
|
];
|
|
|
|
//头像下拉菜单
|
|
const avatarSelect = (command) => {
|
|
switch (command) {
|
|
case '1':
|
|
router.push({ path: '/setting/profile' });
|
|
break;
|
|
case '2':
|
|
doLogout();
|
|
break;
|
|
case '3':
|
|
amendPwdRef.value.showModal();
|
|
break;
|
|
}
|
|
};
|
|
|
|
function openSetting() {
|
|
const { openDrawer } = drawerSetting.value;
|
|
openDrawer();
|
|
}
|
|
|
|
function openAppSearch() {
|
|
appSearchRef.value && appSearchRef.value.show();
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.layout-header {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0;
|
|
height: $header-height;
|
|
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
|
|
transition: all 0.2s ease-in-out;
|
|
flex: 1;
|
|
z-index: 11;
|
|
|
|
:deep(.n-menu--horizontal) {
|
|
width: calc(100%);
|
|
overflow-x: auto;
|
|
}
|
|
|
|
&-left {
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
.logo {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 64px;
|
|
line-height: 64px;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
padding-left: 10px;
|
|
min-width: 130px;
|
|
|
|
img {
|
|
width: auto;
|
|
height: 32px;
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.title {
|
|
margin-bottom: 0;
|
|
}
|
|
}
|
|
|
|
:deep(.ant-breadcrumb span:last-child .link-text) {
|
|
color: #515a6e;
|
|
}
|
|
|
|
.n-breadcrumb {
|
|
display: inline-block;
|
|
}
|
|
|
|
&-menu {
|
|
color: var(--n-text-color);
|
|
}
|
|
}
|
|
|
|
&-right {
|
|
display: flex;
|
|
align-items: center;
|
|
padding-right: 20px;
|
|
|
|
.avatar {
|
|
display: flex;
|
|
align-items: center;
|
|
height: 64px;
|
|
}
|
|
|
|
> * {
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
|
|
&-trigger {
|
|
display: inline-block;
|
|
width: 64px;
|
|
height: 64px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease-in-out;
|
|
|
|
.n-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
height: 64px;
|
|
line-height: 64px;
|
|
}
|
|
|
|
&:hover {
|
|
background: hsla(0, 0%, 100%, 0.08);
|
|
}
|
|
|
|
.anticon {
|
|
font-size: 16px;
|
|
color: #515a6e;
|
|
}
|
|
}
|
|
|
|
&-trigger-min {
|
|
width: auto;
|
|
padding: 0 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.header-horizontal-menu {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
}
|
|
}
|
|
|
|
.layout-header-horizontal {
|
|
:deep(.n-menu--horizontal) {
|
|
width: calc(100% - 130px);
|
|
overflow-x: auto;
|
|
}
|
|
}
|
|
|
|
.layout-header-light {
|
|
background: #fff;
|
|
color: #515a6e;
|
|
|
|
.n-icon {
|
|
color: #515a6e;
|
|
}
|
|
|
|
.layout-header-left {
|
|
:deep(.n-breadcrumb .n-breadcrumb-item:last-child .n-breadcrumb-item__link) {
|
|
color: #515a6e;
|
|
}
|
|
}
|
|
|
|
.layout-header-trigger {
|
|
&:hover {
|
|
background: #f8f8f9;
|
|
}
|
|
}
|
|
}
|
|
|
|
.layout-header-fix {
|
|
position: fixed;
|
|
top: 0;
|
|
right: 0;
|
|
left: 200px;
|
|
z-index: 11;
|
|
}
|
|
|
|
.notifier-plus {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
</style>
|