348 lines
8.9 KiB
Vue
348 lines
8.9 KiB
Vue
<template>
|
||
<div :class="['navbar-actions', navbarActionsClass]">
|
||
<!-- 桌面端工具项 -->
|
||
<template v-if="isDesktop">
|
||
<!-- 搜索 -->
|
||
<div class="navbar-actions__item">
|
||
<CommandPalette />
|
||
</div>
|
||
|
||
<!-- 全屏 -->
|
||
<div class="navbar-actions__item">
|
||
<Fullscreen />
|
||
</div>
|
||
|
||
<!-- 布局大小 -->
|
||
<div class="navbar-actions__item">
|
||
<SizeSelect />
|
||
</div>
|
||
|
||
<!-- 语言选择 -->
|
||
<div class="navbar-actions__item">
|
||
<LangSelect />
|
||
</div>
|
||
|
||
<!-- 通知 -->
|
||
<div class="navbar-actions__item">
|
||
<NoticeDropdown />
|
||
</div>
|
||
|
||
<!-- 租户选择(如果启用多租户)-->
|
||
<div v-if="showTenantSelect" class="navbar-actions__item">
|
||
<TenantSwitcher @change="handleTenantChange" />
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 用户菜单 -->
|
||
<div class="navbar-actions__item">
|
||
<el-dropdown trigger="click">
|
||
<div class="user-profile">
|
||
<div style="width: 28px; height: 28px; overflow: hidden; border-radius: 50%">
|
||
<img
|
||
:src="userStore.userInfo.avatar"
|
||
class="user-profile__avatar"
|
||
style="width: 100%; height: 100%; object-fit: cover; object-position: center"
|
||
/>
|
||
</div>
|
||
<span class="user-profile__name">{{ userStore.userInfo.username }}</span>
|
||
</div>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item @click="handleProfileClick">
|
||
{{ t("navbar.profile") }}
|
||
</el-dropdown-item>
|
||
<el-dropdown-item divided @click="logout">
|
||
{{ t("navbar.logout") }}
|
||
</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</div>
|
||
|
||
<!-- 系统设置 -->
|
||
<div v-if="defaults.showSettings" class="navbar-actions__item" @click="handleSettingsClick">
|
||
<div class="i-svg:setting" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { useI18n } from "vue-i18n";
|
||
import { useRoute, useRouter } from "vue-router";
|
||
import { defaults } from "@/settings";
|
||
import { DeviceEnum, SidebarColor, ThemeMode, LayoutMode } from "@/enums/settings";
|
||
import { useAppStore, useSettingsStore, useUserStore } from "@/store";
|
||
|
||
// 导入子组件
|
||
import CommandPalette from "@/components/CommandPalette/index.vue";
|
||
import Fullscreen from "@/components/Fullscreen/index.vue";
|
||
import SizeSelect from "@/components/SizeSelect/index.vue";
|
||
import LangSelect from "@/components/LangSelect/index.vue";
|
||
import NoticeDropdown from "@/components/NoticeDropdown/index.vue";
|
||
import TenantSwitcher from "@/components/TenantSwitcher/index.vue";
|
||
import { useTenantStoreHook } from "@/store/modules/tenant";
|
||
|
||
const { t } = useI18n();
|
||
const appStore = useAppStore();
|
||
const settingStore = useSettingsStore();
|
||
const userStore = useUserStore();
|
||
const tenantStore = useTenantStoreHook();
|
||
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
|
||
// 是否为桌面设备
|
||
const isDesktop = computed(() => appStore.device === DeviceEnum.DESKTOP);
|
||
|
||
// 是否显示租户选择(如果用户有多个租户,则显示租户选择器)
|
||
// 最小侵入:只有在多租户模式下(租户列表长度 > 1)才显示
|
||
const showTenantSelect = computed(() => {
|
||
// 如果租户列表为空,不显示
|
||
if (tenantStore.tenantList.length === 0) {
|
||
return false;
|
||
}
|
||
// 如果只有一个租户,也不显示(单租户模式,用户无感知)
|
||
if (tenantStore.tenantList.length === 1) {
|
||
return false;
|
||
}
|
||
// 多个租户时才显示
|
||
return true;
|
||
});
|
||
|
||
function handleTenantChange(tenantId: number) {
|
||
tenantStore
|
||
.switchTenant(tenantId)
|
||
.then(() => {
|
||
ElMessage.success("切换租户成功");
|
||
window.location.reload();
|
||
})
|
||
.catch((error: any) => {
|
||
ElMessage.error(error?.message || "切换租户失败");
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 打开个人中心页面
|
||
*/
|
||
function handleProfileClick() {
|
||
router.push({ name: "Profile" });
|
||
}
|
||
|
||
// 根据主题和侧边栏配色方案选择样式类
|
||
const navbarActionsClass = computed(() => {
|
||
const { theme, sidebarColorScheme, layout } = settingStore;
|
||
|
||
// 暗黑主题下,所有布局都使用白色文字
|
||
if (theme === ThemeMode.DARK) {
|
||
return "navbar-actions--white-text";
|
||
}
|
||
|
||
// 明亮主题下
|
||
if (theme === ThemeMode.LIGHT) {
|
||
// 顶部布局和混合布局的顶部区域:
|
||
// - 如果侧边栏是经典蓝色,使用白色文字
|
||
// - 如果侧边栏是极简白色,使用深色文字
|
||
if (layout === LayoutMode.TOP || layout === LayoutMode.MIX) {
|
||
if (sidebarColorScheme === SidebarColor.CLASSIC_BLUE) {
|
||
return "navbar-actions--white-text";
|
||
} else {
|
||
return "navbar-actions--dark-text";
|
||
}
|
||
}
|
||
}
|
||
|
||
return "navbar-actions--dark-text";
|
||
});
|
||
|
||
/**
|
||
* 退出登录
|
||
*/
|
||
function logout() {
|
||
ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
|
||
confirmButtonText: "确定",
|
||
cancelButtonText: "取消",
|
||
type: "warning",
|
||
lockScroll: false,
|
||
}).then(() => {
|
||
userStore.logout().then(() => {
|
||
router.push(`/login?redirect=${route.fullPath}`);
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 打开系统设置页面
|
||
*/
|
||
function handleSettingsClick() {
|
||
settingStore.settingsVisible = true;
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.navbar-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
min-height: 44px;
|
||
|
||
&__item {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 44px; /* 增加最小点击区域到44px,符合人机交互标准 */
|
||
height: 44px;
|
||
padding: 0 8px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
|
||
// 确保子元素居中
|
||
> * {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
// 确保 Element Plus 组件可以正常工作
|
||
:deep(.el-dropdown),
|
||
:deep(.el-tooltip) {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
height: 44px;
|
||
}
|
||
|
||
:deep(.i-svg\:language) {
|
||
flex-shrink: 0;
|
||
width: 18px;
|
||
height: 18px;
|
||
font-size: 18px;
|
||
line-height: 18px;
|
||
background-size: 18px 18px;
|
||
}
|
||
|
||
// 图标样式
|
||
:deep([class^="i-svg:"]) {
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
color: var(--el-text-color-regular);
|
||
transition: color 0.3s;
|
||
}
|
||
|
||
&:hover {
|
||
background: rgba(0, 0, 0, 0.04);
|
||
|
||
:deep([class^="i-svg:"]) {
|
||
color: var(--el-color-primary);
|
||
}
|
||
}
|
||
}
|
||
|
||
.user-profile {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 44px;
|
||
padding: 0 8px;
|
||
|
||
&__avatar {
|
||
flex-shrink: 0;
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
&__name {
|
||
margin-left: 8px;
|
||
color: var(--el-text-color-regular);
|
||
white-space: nowrap;
|
||
transition: color 0.3s;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 白色文字样式(用于深色背景:暗黑主题、顶部布局、混合布局等)
|
||
.navbar-actions--white-text {
|
||
.navbar-actions__item {
|
||
:deep([class^="i-svg:"]) {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
|
||
&:hover {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
|
||
:deep([class^="i-svg:"]) {
|
||
color: #fff;
|
||
}
|
||
}
|
||
}
|
||
|
||
.user-profile__name {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
|
||
// 租户选择器在白色文字模式下的样式
|
||
::v-deep(.tenant-switcher__trigger) {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
::v-deep(.tenant-switcher__trigger .tenant-switcher__icon) {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
::v-deep(.tenant-switcher__trigger:hover) {
|
||
color: #fff;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
}
|
||
::v-deep(.tenant-switcher__trigger:hover .tenant-switcher__icon) {
|
||
color: #fff;
|
||
}
|
||
}
|
||
|
||
// 深色文字样式(用于浅色背景:明亮主题下的左侧布局等)
|
||
.navbar-actions--dark-text {
|
||
.navbar-actions__item {
|
||
:deep([class^="i-svg:"]) {
|
||
color: var(--el-text-color-regular) !important;
|
||
}
|
||
|
||
&:hover {
|
||
background: rgba(0, 0, 0, 0.04);
|
||
|
||
:deep([class^="i-svg:"]) {
|
||
color: var(--el-color-primary) !important;
|
||
}
|
||
}
|
||
}
|
||
|
||
.user-profile__name {
|
||
color: var(--el-text-color-regular) !important;
|
||
}
|
||
|
||
// 租户选择器在深色文字模式下的样式
|
||
::v-deep(.tenant-switcher__trigger) {
|
||
color: var(--el-text-color-regular) !important;
|
||
}
|
||
::v-deep(.tenant-switcher__trigger .tenant-switcher__icon) {
|
||
color: var(--el-text-color-regular) !important;
|
||
}
|
||
::v-deep(.tenant-switcher__trigger:hover) {
|
||
color: var(--el-color-primary) !important;
|
||
background: rgba(0, 0, 0, 0.04);
|
||
}
|
||
::v-deep(.tenant-switcher__trigger:hover .tenant-switcher__icon) {
|
||
color: var(--el-color-primary) !important;
|
||
}
|
||
}
|
||
|
||
// 确保下拉菜单中的图标不受影响
|
||
::v-deep(.el-dropdown-menu) {
|
||
[class^="i-svg:"] {
|
||
color: var(--el-text-color-regular) !important;
|
||
|
||
&:hover {
|
||
color: var(--el-color-primary) !important;
|
||
}
|
||
}
|
||
}
|
||
</style>
|