Files
kaidi/frontend/src/layouts/components/Header.vue
T
2026-08-17 23:03:03 +08:00

290 lines
6.2 KiB
Vue

<template>
<div :class="layoutCls">
<t-head-menu :class="menuCls" :theme="menuTheme" expand-type="popup" :value="active">
<template #logo>
<span v-if="showLogo" class="header-logo-container" @click="handleNav(user.workbenchRoute)">
<span class="header-brand">财务系统</span>
</span>
<div v-else class="header-operate-left">
<span class="current-role">{{ user.roleName || '尚未选择身份' }}</span>
</div>
</template>
<template v-if="layout !== 'side'" #default>
<menu-content class="header-menu" :nav-data="menu" />
</template>
<template #operations>
<div class="operations-container">
<t-dropdown :min-column-width="120" trigger="click">
<template #dropdown>
<t-dropdown-item class="operations-dropdown-container-item" @click="handleNav('/profile')">
<user-circle-icon />{{ t('layout.header.user') }}
</t-dropdown-item>
<t-dropdown-item class="operations-dropdown-container-item" @click="handleLogout">
<poweroff-icon />{{ t('layout.header.signOut') }}
</t-dropdown-item>
</template>
<t-button class="header-user-btn" theme="default" variant="text">
<template #icon>
<t-icon class="header-user-avatar" name="user-circle" />
</template>
<div class="header-user-account">{{ user.displayName }}</div>
<template #suffix><chevron-down-icon /></template>
</t-button>
</t-dropdown>
</div>
</template>
</t-head-menu>
</div>
</template>
<script setup lang="ts">
import { ChevronDownIcon, PoweroffIcon, UserCircleIcon } from 'tdesign-icons-vue-next';
import type { PropType } from 'vue';
import { computed } from 'vue';
import { useRouter } from 'vue-router';
import { prefix } from '@/config/global';
import { t } from '@/locales';
import { getActive } from '@/router';
import { usePermissionStore, useUserStore } from '@/store';
import type { MenuRoute, ModeType } from '@/types/interface';
import MenuContent from './MenuContent.vue';
const { theme, layout, showLogo, menu, isFixed, isCompact } = defineProps({
theme: {
type: String,
default: 'light',
},
layout: {
type: String,
default: 'top',
},
showLogo: {
type: Boolean,
default: true,
},
menu: {
type: Array as PropType<MenuRoute[]>,
default: () => [],
},
isFixed: {
type: Boolean,
default: false,
},
isCompact: {
type: Boolean,
default: false,
},
maxLevel: {
type: Number,
default: 3,
},
});
const router = useRouter();
const user = useUserStore();
const permissionStore = usePermissionStore();
const active = computed(() => getActive());
const layoutCls = computed(() => [`${prefix}-header-layout`]);
const menuCls = computed(() => {
return [
{
[`${prefix}-header-menu`]: !isFixed,
[`${prefix}-header-menu-fixed`]: isFixed,
[`${prefix}-header-menu-fixed-side`]: layout === 'side' && isFixed,
[`${prefix}-header-menu-fixed-side-compact`]: layout === 'side' && isFixed && isCompact,
},
];
});
const menuTheme = computed(() => theme as ModeType);
const handleNav = (url: string) => {
router.push(url);
};
const handleLogout = async () => {
await user.logout();
permissionStore.restoreRoutes();
await router.replace('/login');
};
</script>
<style lang="less" scoped>
.@{starter-prefix}-header {
&-menu-fixed {
position: fixed;
top: 0;
z-index: 1001;
:deep(.t-head-menu__inner) {
padding-right: var(--td-comp-margin-xl);
}
&-side {
left: 232px;
right: 0;
z-index: 10;
width: auto;
transition: all 0.3s;
&-compact {
left: 64px;
}
}
}
&-logo-container {
cursor: pointer;
display: inline-flex;
}
}
.header-menu {
flex: 1 1 auto;
display: inline-flex;
:deep(.t-menu__item) {
min-width: unset;
}
}
.operations-container {
min-width: 0;
display: flex;
align-items: center;
.t-popup__reference {
display: flex;
align-items: center;
justify-content: center;
}
.t-button {
margin-left: var(--td-comp-margin-l);
}
}
.header-operate-left {
display: flex;
align-items: normal;
line-height: 0;
}
.current-role {
color: var(--td-text-color-secondary);
font-size: 13px;
line-height: 24px;
}
.header-logo-container {
width: 184px;
height: 26px;
display: flex;
margin-left: 24px;
color: var(--td-text-color-primary);
.t-logo {
width: 100%;
height: 100%;
&:hover {
cursor: pointer;
}
}
&:hover {
cursor: pointer;
}
}
.header-user-account {
min-width: 0;
max-width: 160px;
display: inline-flex;
align-items: center;
overflow: hidden;
color: var(--td-text-color-primary);
text-overflow: ellipsis;
white-space: nowrap;
}
.header-brand {
color: var(--td-brand-color);
font-size: 16px;
font-weight: 600;
}
@media (width <= 640px) {
.operations-container {
max-width: calc(100vw - 96px);
.t-button {
min-width: 0;
margin-left: var(--td-comp-margin-s);
}
}
.header-user-account {
max-width: 104px;
}
}
:deep(.t-head-menu__inner) {
border-bottom: 1px solid var(--td-component-stroke);
}
.t-menu--light {
.header-user-account {
color: var(--td-text-color-primary);
}
}
.t-menu--dark {
.t-head-menu__inner {
border-bottom: 1px solid var(--td-gray-color-10);
}
.header-user-account {
color: rgb(255 255 255 / 55%);
}
}
.operations-dropdown-container-item {
width: 100%;
display: flex;
align-items: center;
:deep(.t-dropdown__item-text) {
display: flex;
align-items: center;
}
.t-icon {
font-size: var(--td-comp-size-xxxs);
margin-right: var(--td-comp-margin-s);
}
:deep(.t-dropdown__item) {
width: 100%;
margin-bottom: 0;
}
&:last-child {
:deep(.t-dropdown__item) {
margin-bottom: 8px;
}
}
}
</style>
<!-- eslint-disable-next-line vue-scoped-css/enforce-style-type -->
<style lang="less">
.operations-dropdown-container-item {
.t-dropdown__item-text {
display: flex;
align-items: center;
}
}
</style>