wms-elevue/src/layout/components/Header/index.vue
2024-09-24 19:13:55 +08:00

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>