| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594 |
- <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">NaiveAdmin</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-dropdown trigger="hover" @command="avatarSelect">
- <div class="flex items-center">
- <!-- <div class="avatar">
- <el-avatar round :src="schoolboy" />
- </div> -->
- <img src="@/assets/images/chongqing-icon.png" />
- <h4 class="username">{{ getUsername }}</h4>
- <!-- <el-divider direction="vertical" /> -->
- <!-- <h4 class="mr-1 username">{{ getTenantName }}</h4> -->
- </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 class="layout-header-trigger layout-header-trigger-min">
- <QRcodePopover />
- </div>
- <!-- 安全管控平台 -->
- <div class="layout-header-trigger layout-header-trigger-min">
- <el-button style="background-color: #e6f7ff; border: none" type="primary">
- <a href="/skyeye-world" target="_blank">返回平台</a>
- </el-button>
- </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
- 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 { computed, ref, unref, watch, inject } from 'vue';
- import { useRoute, useRouter } from 'vue-router';
- import { TABS_ROUTES } 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 QRcodePopover from './QRcodePopover.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 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 collapsed = inject('collapsed');
- const navMode = getNavMode;
- const headerSetting = getHeaderSetting;
- const crumbsSetting = getCrumbsSetting;
- const getUsername = computed(() => {
- return userStore.getUserInfo.username;
- });
- const getTenantName = computed(() => {
- return userStore.getUserInfo.tenantName;
- });
- 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: '成功退出登录',
- });
- // 移除标签页
- localStorage.removeItem(TABS_ROUTES);
- asyncRouteStore.setDynamicAddedRoute(false);
- 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://github.com/jekip/naive-ui-admin'),
- // },
- // },
- // {
- // icon: LockOutlined,
- // tips: '锁屏',
- // eventObject: {
- // click: () => useLockscreen.setLock(true),
- // },
- // },
- // ];
- //头像下拉菜单
- const avatarSelect = (command) => {
- switch (command) {
- case '1':
- router.push({ name: 'Setting' });
- 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;
- .username {
- padding: 0px 10px;
- font-family: PingFangSC, PingFang SC;
- font-weight: 400;
- font-size: 14px;
- color: rgba(0, 0, 0, 0.65);
- line-height: 22px;
- text-align: left;
- font-style: normal;
- }
- .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>
|