Files
xiaozhi-esp32-server/main/manager-web/src/components/HeaderBar.vue
T

859 lines
25 KiB
Vue
Raw Normal View History

2025-03-14 23:48:59 +08:00
<template>
<el-header class="header">
2025-03-28 11:08:12 +08:00
<div class="header-container">
<!-- 左侧元素 -->
<div class="header-left" @click="goHome">
<img loading="lazy" alt="" src="@/assets/xiaozhi-logo.png" class="logo-img" />
<img loading="lazy" alt="" src="@/assets/xiaozhi-ai.png" class="brand-img" />
2025-03-28 11:08:12 +08:00
</div>
<!-- 中间导航菜单 -->
<div class="header-center">
2025-11-08 23:44:40 +08:00
<div class="equipment-management" :class="{
'active-tab':
$route.path === '/home' ||
$route.path === '/role-config' ||
$route.path === '/device-management',
}" @click="goHome">
<img loading="lazy" alt="" src="@/assets/header/robot.png" :style="{
filter:
2025-10-11 15:02:01 +08:00
$route.path === '/home' ||
$route.path === '/role-config' ||
$route.path === '/device-management'
2025-11-08 23:44:40 +08:00
? 'brightness(0) invert(1)'
: 'None',
}" />
2025-10-11 15:02:01 +08:00
<span class="nav-text">{{ $t("header.smartManagement") }}</span>
2025-03-14 23:48:59 +08:00
</div>
2025-10-07 23:54:42 +08:00
<!-- 普通用户显示音色克隆 -->
2025-11-08 23:44:40 +08:00
<div v-if="!isSuperAdmin" class="equipment-management"
:class="{ 'active-tab': $route.path === '/voice-clone-management' }" @click="goVoiceCloneManagement">
<img loading="lazy" alt="" src="@/assets/header/voice.png" :style="{
filter:
$route.path === '/voice-clone-management'
? 'brightness(0) invert(1)'
: 'None',
}" />
2025-10-11 15:02:01 +08:00
<span class="nav-text">{{ $t("header.voiceCloneManagement") }}</span>
2025-10-07 23:54:42 +08:00
</div>
<!-- 超级管理员显示音色克隆下拉菜单 -->
2025-11-08 23:44:40 +08:00
<el-dropdown v-if="isSuperAdmin" trigger="click" class="equipment-management more-dropdown" :class="{
'active-tab':
$route.path === '/voice-clone-management' ||
$route.path === '/voice-resource-management',
}" @visible-change="handleVoiceCloneDropdownVisibleChange">
2025-10-07 23:54:42 +08:00
<span class="el-dropdown-link">
2025-11-08 23:44:40 +08:00
<img loading="lazy" alt="" src="@/assets/header/voice.png" :style="{
filter:
$route.path === '/voice-clone-management' ||
2025-10-11 15:02:01 +08:00
$route.path === '/voice-resource-management'
2025-11-08 23:44:40 +08:00
? 'brightness(0) invert(1)'
: 'None',
}" />
2025-10-11 15:02:01 +08:00
<span class="nav-text">{{ $t("header.voiceCloneManagement") }}</span>
2025-11-08 23:44:40 +08:00
<i class="el-icon-arrow-down el-icon--right" :class="{ 'rotate-down': voiceCloneDropdownVisible }"></i>
2025-10-07 23:54:42 +08:00
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="goVoiceCloneManagement">
2025-10-11 15:02:01 +08:00
{{ $t("header.voiceCloneManagement") }}
2025-10-07 23:54:42 +08:00
</el-dropdown-item>
<el-dropdown-item @click.native="goVoiceResourceManagement">
2025-10-11 15:02:01 +08:00
{{ $t("header.voiceResourceManagement") }}
2025-10-07 23:54:42 +08:00
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
2025-11-08 23:44:40 +08:00
<div v-if="isSuperAdmin" class="equipment-management" :class="{ 'active-tab': $route.path === '/model-config' }"
@click="goModelConfig">
<img loading="lazy" alt="" src="@/assets/header/model_config.png" :style="{
filter:
$route.path === '/model-config' ? 'brightness(0) invert(1)' : 'None',
}" />
2025-10-11 15:02:01 +08:00
<span class="nav-text">{{ $t("header.modelConfig") }}</span>
2025-03-14 23:48:59 +08:00
</div>
2025-11-08 23:44:40 +08:00
<div class="equipment-management"
2025-11-04 16:21:01 +08:00
:class="{ 'active-tab': $route.path === '/knowledge-base-management' || $route.path === '/knowledge-file-upload' }"
2025-11-08 23:44:40 +08:00
@click="goKnowledgeBaseManagement">
<img loading="lazy" alt="" src="@/assets/header/knowledge_base.png" :style="{
filter:
$route.path === '/knowledge-base-management' || $route.path === '/knowledge-file-upload' ? 'brightness(0) invert(1)' : 'None',
}" />
2025-11-04 16:21:01 +08:00
<span class="nav-text">{{ $t("header.knowledgeBase") }}</span>
2025-04-05 20:16:02 +08:00
</div>
2025-11-08 23:44:40 +08:00
<el-dropdown v-if="isSuperAdmin" trigger="click" class="equipment-management more-dropdown" :class="{
'active-tab':
$route.path === '/dict-management' ||
$route.path === '/params-management' ||
$route.path === '/provider-management' ||
$route.path === '/server-side-management' ||
$route.path === '/agent-template-management' ||
$route.path === '/ota-management' ||
$route.path === '/user-management',
}" @visible-change="handleParamDropdownVisibleChange">
2025-05-01 16:57:58 +08:00
<span class="el-dropdown-link">
2025-11-08 23:44:40 +08:00
<img loading="lazy" alt="" src="@/assets/header/param_management.png" :style="{
filter:
$route.path === '/dict-management' ||
2025-10-11 15:02:01 +08:00
$route.path === '/params-management' ||
$route.path === '/provider-management' ||
$route.path === '/server-side-management' ||
$route.path === '/agent-template-management' ||
2025-11-04 16:21:01 +08:00
$route.path === '/ota-management' ||
$route.path === '/user-management'
2025-11-08 23:44:40 +08:00
? 'brightness(0) invert(1)'
: 'None',
}" />
2025-10-11 15:02:01 +08:00
<span class="nav-text">{{ $t("header.paramDictionary") }}</span>
2025-11-08 23:44:40 +08:00
<i class="el-icon-arrow-down el-icon--right" :class="{ 'rotate-down': paramDropdownVisible }"></i>
2025-05-01 16:57:58 +08:00
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="goParamManagement">
2025-10-11 15:02:01 +08:00
{{ $t("header.paramManagement") }}
2025-05-01 16:57:58 +08:00
</el-dropdown-item>
2025-11-08 23:44:40 +08:00
<el-dropdown-item @click.native="goUserManagement">
{{ $t("header.userManagement") }}
</el-dropdown-item>
2025-11-02 17:14:11 +08:00
<el-dropdown-item @click.native="goOtaManagement">
{{ $t("header.otaManagement") }}
</el-dropdown-item>
2025-05-01 16:57:58 +08:00
<el-dropdown-item @click.native="goDictManagement">
2025-10-11 15:02:01 +08:00
{{ $t("header.dictManagement") }}
2025-05-01 16:57:58 +08:00
</el-dropdown-item>
<el-dropdown-item @click.native="goProviderManagement">
2025-10-11 15:02:01 +08:00
{{ $t("header.providerManagement") }}
</el-dropdown-item>
2025-09-17 15:38:23 +08:00
<el-dropdown-item @click.native="goAgentTemplateManagement">
2025-10-11 15:02:01 +08:00
{{ $t("header.agentTemplate") }}
2025-09-17 15:38:23 +08:00
</el-dropdown-item>
2025-09-21 18:04:56 +08:00
<el-dropdown-item @click.native="goServerSideManagement">
2025-10-11 15:02:01 +08:00
{{ $t("header.serverSideManagement") }}
2025-09-21 18:04:56 +08:00
</el-dropdown-item>
2025-05-01 16:57:58 +08:00
</el-dropdown-menu>
</el-dropdown>
2025-03-14 23:48:59 +08:00
</div>
2025-09-21 18:04:56 +08:00
2025-03-28 11:08:12 +08:00
<!-- 右侧元素 -->
<div class="header-right">
2025-11-08 23:44:40 +08:00
<div class="search-container" v-if="$route.path === '/home' && !(isSuperAdmin && isSmallScreen)">
2025-10-01 02:52:32 +08:00
<div class="search-wrapper">
2025-11-08 23:44:40 +08:00
<el-input v-model="search" :placeholder="$t('header.searchPlaceholder')" class="custom-search-input"
@keyup.enter.native="handleSearch" @focus="showSearchHistory" @blur="hideSearchHistory" clearable
ref="searchInput">
<i slot="suffix" class="el-icon-search search-icon" @click="handleSearch"></i>
2025-10-01 02:52:32 +08:00
</el-input>
<!-- 搜索历史下拉框 -->
2025-11-08 23:44:40 +08:00
<div v-if="showHistory && searchHistory.length > 0" class="search-history-dropdown">
2025-10-01 02:52:32 +08:00
<div class="search-history-header">
2025-10-11 15:02:01 +08:00
<span>{{ $t("header.searchHistory") }}</span>
2025-11-08 23:44:40 +08:00
<el-button type="text" size="small" class="clear-history-btn" @click="clearSearchHistory">
2025-10-11 15:02:01 +08:00
{{ $t("header.clearHistory") }}
2025-10-01 02:52:32 +08:00
</el-button>
</div>
<div class="search-history-list">
2025-11-08 23:44:40 +08:00
<div v-for="(item, index) in searchHistory" :key="index" class="search-history-item"
@click.stop="selectSearchHistory(item)">
2025-10-01 02:52:32 +08:00
<span class="history-text">{{ item }}</span>
2025-11-08 23:44:40 +08:00
<i class="el-icon-close clear-item-icon" @click.stop="removeSearchHistory(index)"></i>
2025-10-01 02:52:32 +08:00
</div>
</div>
</div>
</div>
2025-03-14 23:48:59 +08:00
</div>
2025-09-12 18:09:09 +08:00
2025-11-08 23:44:40 +08:00
<img loading="lazy" alt="" src="@/assets/home/avatar.png" class="avatar-img" @click="handleAvatarClick" />
2025-10-07 23:54:42 +08:00
<span class="el-dropdown-link" @click="handleAvatarClick">
2025-10-11 15:02:01 +08:00
{{ userInfo.username || "加载中..." }}
2025-11-08 23:44:40 +08:00
<i class="el-icon-arrow-down el-icon--right" :class="{ 'rotate-down': userMenuVisible }"></i>
2025-10-07 23:54:42 +08:00
</span>
2025-11-08 23:44:40 +08:00
<el-cascader :options="userMenuOptions" trigger="click" :props="cascaderProps"
style="width: 0px; overflow: hidden" :show-all-levels="false" @change="handleCascaderChange"
@visible-change="handleUserMenuVisibleChange" ref="userCascader">
2025-10-07 23:54:42 +08:00
<template slot-scope="{ data }">
<span>{{ data.label }}</span>
</template>
</el-cascader>
2025-03-14 23:48:59 +08:00
</div>
</div>
2025-03-24 18:08:05 +08:00
<!-- 修改密码弹窗 -->
<ChangePasswordDialog v-model="isChangePasswordDialogVisible" />
2025-03-14 23:48:59 +08:00
</el-header>
</template>
<script>
2025-10-11 15:02:01 +08:00
import userApi from "@/apis/module/user";
import i18n, { changeLanguage } from "@/i18n";
import { mapActions, mapGetters } from "vuex";
import ChangePasswordDialog from "./ChangePasswordDialog.vue"; // 引入修改密码弹窗组件
2025-03-14 23:48:59 +08:00
export default {
2025-10-11 15:02:01 +08:00
name: "HeaderBar",
2025-03-24 18:08:05 +08:00
components: {
2025-10-11 15:02:01 +08:00
ChangePasswordDialog,
2025-03-24 18:08:05 +08:00
},
2025-10-11 15:02:01 +08:00
props: ["devices"], // 接收父组件设备列表
2025-03-14 23:48:59 +08:00
data() {
2025-03-19 18:13:03 +08:00
return {
2025-10-11 15:02:01 +08:00
search: "",
2025-03-19 18:13:03 +08:00
userInfo: {
2025-10-11 15:02:01 +08:00
username: "",
mobile: "",
2025-03-24 18:08:05 +08:00
},
isChangePasswordDialogVisible: false, // 控制修改密码弹窗的显示
2025-05-15 09:54:59 +08:00
paramDropdownVisible: false,
2025-10-07 23:54:42 +08:00
voiceCloneDropdownVisible: false,
2025-10-10 10:02:11 +08:00
userMenuVisible: false, // 添加用户菜单可见状态
2025-10-01 02:52:32 +08:00
isSmallScreen: false,
// 搜索历史相关
searchHistory: [],
showHistory: false,
2025-10-11 15:02:01 +08:00
SEARCH_HISTORY_KEY: "xiaozhi_search_history",
2025-10-07 23:54:42 +08:00
MAX_HISTORY_COUNT: 3,
// Cascader 配置
cascaderProps: {
2025-10-11 15:02:01 +08:00
expandTrigger: "click",
value: "value",
label: "label",
children: "children",
},
};
2025-03-19 18:13:03 +08:00
},
computed: {
2025-10-11 15:02:01 +08:00
...mapGetters(["getIsSuperAdmin"]),
isSuperAdmin() {
return this.getIsSuperAdmin;
},
// 获取当前语言
currentLanguage() {
2025-10-11 15:02:01 +08:00
return i18n.locale || "zh_CN";
2025-09-12 18:09:09 +08:00
},
// 获取当前语言显示文本
currentLanguageText() {
const currentLang = this.currentLanguage;
switch (currentLang) {
2025-10-11 15:02:01 +08:00
case "zh_CN":
return this.$t("language.zhCN");
case "zh_TW":
return this.$t("language.zhTW");
case "en":
return this.$t("language.en");
2025-11-15 17:24:47 +08:00
case "de":
return this.$t("language.de");
case "vi":
return this.$t("language.vi");
2025-09-12 18:09:09 +08:00
default:
2025-10-11 15:02:01 +08:00
return this.$t("language.zhCN");
2025-09-12 18:09:09 +08:00
}
2025-10-07 23:54:42 +08:00
},
// 用户菜单选项
userMenuOptions() {
return [
{
label: this.currentLanguageText,
2025-10-11 15:02:01 +08:00
value: "language",
2025-10-07 23:54:42 +08:00
children: [
{
2025-10-11 15:02:01 +08:00
label: this.$t("language.zhCN"),
value: "zh_CN",
2025-10-07 23:54:42 +08:00
},
{
2025-10-11 15:02:01 +08:00
label: this.$t("language.zhTW"),
value: "zh_TW",
2025-10-07 23:54:42 +08:00
},
{
2025-10-11 15:02:01 +08:00
label: this.$t("language.en"),
value: "en",
},
2025-11-15 17:24:47 +08:00
{
label: this.$t("language.de"),
value: "de",
},
{
label: this.$t("language.vi"),
value: "vi",
},
2025-10-11 15:02:01 +08:00
],
2025-10-07 23:54:42 +08:00
},
{
2025-10-11 15:02:01 +08:00
label: this.$t("header.changePassword"),
value: "changePassword",
2025-10-07 23:54:42 +08:00
},
{
2025-10-11 15:02:01 +08:00
label: this.$t("header.logout"),
value: "logout",
},
2025-10-07 23:54:42 +08:00
];
2025-10-11 15:02:01 +08:00
},
},
2025-03-19 18:13:03 +08:00
mounted() {
2025-05-15 09:54:59 +08:00
this.fetchUserInfo();
this.checkScreenSize();
2025-10-11 15:02:01 +08:00
window.addEventListener("resize", this.checkScreenSize);
2025-10-01 02:52:32 +08:00
// 从localStorage加载搜索历史
this.loadSearchHistory();
2025-05-15 09:54:59 +08:00
},
//移除事件监听器
beforeDestroy() {
2025-10-11 15:02:01 +08:00
window.removeEventListener("resize", this.checkScreenSize);
2025-03-14 23:48:59 +08:00
},
methods: {
goHome() {
// 跳转到首页
2025-10-11 15:02:01 +08:00
this.$router.push("/home");
2025-03-19 18:13:03 +08:00
},
2025-03-20 14:08:38 +08:00
goUserManagement() {
2025-10-11 15:02:01 +08:00
this.$router.push("/user-management");
2025-03-20 14:08:38 +08:00
},
2025-03-20 18:11:37 +08:00
goModelConfig() {
2025-10-11 15:02:01 +08:00
this.$router.push("/model-config");
2025-03-20 18:11:37 +08:00
},
2025-11-04 16:21:01 +08:00
goKnowledgeBaseManagement() {
this.$router.push("/knowledge-base-management");
},
2025-10-07 23:54:42 +08:00
goVoiceCloneManagement() {
2025-10-11 15:02:01 +08:00
this.$router.push("/voice-clone-management");
2025-10-07 23:54:42 +08:00
},
2025-04-09 13:36:59 +08:00
goParamManagement() {
2025-10-11 15:02:01 +08:00
this.$router.push("/params-management");
2025-04-09 13:36:59 +08:00
},
goOtaManagement() {
2025-10-11 15:02:01 +08:00
this.$router.push("/ota-management");
},
2025-05-01 16:57:58 +08:00
goDictManagement() {
2025-10-11 15:02:01 +08:00
this.$router.push("/dict-management");
2025-05-01 16:57:58 +08:00
},
goProviderManagement() {
2025-10-11 15:02:01 +08:00
this.$router.push("/provider-management");
},
goServerSideManagement() {
2025-10-11 15:02:01 +08:00
this.$router.push("/server-side-management");
},
2025-10-07 23:54:42 +08:00
// 跳转到音色资源管理
goVoiceResourceManagement() {
2025-10-11 15:02:01 +08:00
this.$router.push("/voice-resource-management");
2025-10-07 23:54:42 +08:00
},
2025-09-17 15:38:23 +08:00
// 添加默认角色模板管理导航方法
goAgentTemplateManagement() {
2025-10-11 15:02:01 +08:00
this.$router.push("/agent-template-management");
2025-09-17 15:38:23 +08:00
},
2025-03-19 18:13:03 +08:00
// 获取用户信息
fetchUserInfo() {
userApi.getUserInfo(({ data }) => {
2025-10-11 15:02:01 +08:00
this.userInfo = data.data;
if (data.data.superAdmin !== undefined) {
2025-10-11 15:02:01 +08:00
this.$store.commit("setUserInfo", data.data);
}
2025-10-11 15:02:01 +08:00
});
2025-03-19 18:13:03 +08:00
},
2025-05-15 09:54:59 +08:00
checkScreenSize() {
this.isSmallScreen = window.innerWidth <= 1386;
},
2025-03-19 18:13:03 +08:00
// 处理搜索
handleSearch() {
2025-04-01 10:59:35 +08:00
const searchValue = this.search.trim();
2025-03-19 18:13:03 +08:00
// 如果搜索内容为空,触发重置事件
2025-03-19 18:13:03 +08:00
if (!searchValue) {
2025-10-11 15:02:01 +08:00
this.$emit("search-reset");
return;
2025-03-19 18:13:03 +08:00
}
2025-10-01 02:52:32 +08:00
// 保存搜索历史
this.saveSearchHistory(searchValue);
try {
// 创建不区分大小写的正则表达式
2025-10-11 15:02:01 +08:00
const regex = new RegExp(searchValue, "i");
// 触发搜索事件,将正则表达式传递给父组件
2025-10-11 15:02:01 +08:00
this.$emit("search", regex);
} catch (error) {
2025-10-11 15:02:01 +08:00
console.error("正则表达式创建失败:", error);
this.$message.error({
2025-10-11 15:02:01 +08:00
message: this.$t("message.error"),
showClose: true,
});
}
2025-10-01 02:52:32 +08:00
// 搜索完成后让输入框失去焦点,从而触发blur事件隐藏搜索历史
if (this.$refs.searchInput) {
this.$refs.searchInput.blur();
}
},
// 显示搜索历史
showSearchHistory() {
this.showHistory = true;
},
// 隐藏搜索历史
hideSearchHistory() {
// 延迟隐藏,以便点击事件能够执行
setTimeout(() => {
this.showHistory = false;
}, 200);
},
// 加载搜索历史
loadSearchHistory() {
try {
const history = localStorage.getItem(this.SEARCH_HISTORY_KEY);
if (history) {
this.searchHistory = JSON.parse(history);
}
} catch (error) {
2025-10-11 15:02:01 +08:00
console.error("加载搜索历史失败:", error);
2025-10-01 02:52:32 +08:00
this.searchHistory = [];
}
},
// 保存搜索历史
saveSearchHistory(keyword) {
if (!keyword || this.searchHistory.includes(keyword)) {
return;
}
// 添加到历史记录开头
this.searchHistory.unshift(keyword);
// 限制历史记录数量
if (this.searchHistory.length > this.MAX_HISTORY_COUNT) {
this.searchHistory = this.searchHistory.slice(0, this.MAX_HISTORY_COUNT);
}
// 保存到localStorage
try {
localStorage.setItem(this.SEARCH_HISTORY_KEY, JSON.stringify(this.searchHistory));
} catch (error) {
2025-10-11 15:02:01 +08:00
console.error("保存搜索历史失败:", error);
2025-10-01 02:52:32 +08:00
}
},
// 选择搜索历史项
selectSearchHistory(keyword) {
this.search = keyword;
this.handleSearch();
},
// 移除单个搜索历史项
removeSearchHistory(index) {
this.searchHistory.splice(index, 1);
try {
localStorage.setItem(this.SEARCH_HISTORY_KEY, JSON.stringify(this.searchHistory));
} catch (error) {
2025-10-11 15:02:01 +08:00
console.error("更新搜索历史失败:", error);
2025-10-01 02:52:32 +08:00
}
},
// 清空所有搜索历史
clearSearchHistory() {
this.searchHistory = [];
try {
localStorage.removeItem(this.SEARCH_HISTORY_KEY);
} catch (error) {
2025-10-11 15:02:01 +08:00
console.error("清空搜索历史失败:", error);
2025-10-01 02:52:32 +08:00
}
2025-03-24 18:08:05 +08:00
},
// 显示修改密码弹窗
showChangePasswordDialog() {
this.isChangePasswordDialogVisible = true;
2025-10-11 15:02:01 +08:00
// 添加:显示修改密码弹窗后重置用户菜单可见状态
this.userMenuVisible = false;
},
// 退出登录
async handleLogout() {
try {
// 调用 Vuex 的 logout action
await this.logout();
this.$message.success({
2025-10-11 15:02:01 +08:00
message: this.$t("message.success"),
showClose: true,
});
} catch (error) {
2025-10-11 15:02:01 +08:00
console.error("退出登录失败:", error);
this.$message.error({
2025-10-11 15:02:01 +08:00
message: this.$t("message.error"),
showClose: true,
});
}
},
2025-10-07 23:54:42 +08:00
// 监听参数字典下拉菜单的可见状态变化
handleParamDropdownVisibleChange(visible) {
this.paramDropdownVisible = visible;
},
2025-10-07 23:54:42 +08:00
// 监听音色克隆下拉菜单的可见状态变化
handleVoiceCloneDropdownVisibleChange(visible) {
this.voiceCloneDropdownVisible = visible;
},
2025-10-11 09:26:08 +08:00
// 在data中添加一个key用于强制重新渲染组件
2025-10-07 23:54:42 +08:00
// 处理 Cascader 选择变化
handleCascaderChange(value) {
if (!value || value.length === 0) {
return;
}
const action = value[value.length - 1];
// 处理语言切换
2025-10-11 15:02:01 +08:00
if (value.length === 2 && value[0] === "language") {
2025-10-07 23:54:42 +08:00
this.changeLanguage(action);
2025-10-11 09:26:08 +08:00
} else {
// 处理其他操作
switch (action) {
2025-10-11 15:02:01 +08:00
case "changePassword":
2025-10-11 09:26:08 +08:00
this.showChangePasswordDialog();
break;
2025-10-11 15:02:01 +08:00
case "logout":
2025-10-11 09:26:08 +08:00
this.handleLogout();
break;
}
2025-10-07 23:54:42 +08:00
}
2025-10-11 15:02:01 +08:00
2025-10-11 09:26:08 +08:00
// 操作完成后立即清空选择
setTimeout(() => {
this.completeResetCascader();
}, 300);
},
2025-10-11 15:02:01 +08:00
// 切换语言
changeLanguage(lang) {
changeLanguage(lang);
this.$message.success({
message: this.$t("message.success"),
showClose: true,
});
// 添加:切换语言后重置用户菜单可见状态
this.userMenuVisible = false;
},
2025-10-11 09:26:08 +08:00
// 完全重置级联选择器
completeResetCascader() {
if (this.$refs.userCascader) {
try {
// 尝试所有可能的方法来清空选择
// 1. 尝试使用组件提供的clearValue方法
if (this.$refs.userCascader.clearValue) {
this.$refs.userCascader.clearValue();
}
2025-10-11 15:02:01 +08:00
2025-10-11 09:26:08 +08:00
// 2. 直接清空内部属性
if (this.$refs.userCascader.$data) {
this.$refs.userCascader.$data.selectedPaths = [];
this.$refs.userCascader.$data.displayLabels = [];
2025-10-11 15:02:01 +08:00
this.$refs.userCascader.$data.inputValue = "";
2025-10-11 09:26:08 +08:00
this.$refs.userCascader.$data.checkedValue = [];
this.$refs.userCascader.$data.showAllLevels = false;
}
2025-10-11 15:02:01 +08:00
2025-10-11 09:26:08 +08:00
// 3. 操作DOM清除选中状态
const menuElement = this.$refs.userCascader.$refs.menu;
if (menuElement && menuElement.$el) {
2025-10-11 15:02:01 +08:00
const activeItems = menuElement.$el.querySelectorAll(
".el-cascader-node.is-active"
);
activeItems.forEach((item) => item.classList.remove("is-active"));
const checkedItems = menuElement.$el.querySelectorAll(
".el-cascader-node.is-checked"
);
checkedItems.forEach((item) => item.classList.remove("is-checked"));
2025-10-11 09:26:08 +08:00
}
2025-10-11 15:02:01 +08:00
console.log("Cascader values cleared");
2025-10-11 09:26:08 +08:00
} catch (error) {
2025-10-11 15:02:01 +08:00
console.error("清空选择值失败:", error);
2025-10-11 09:26:08 +08:00
}
2025-10-07 23:54:42 +08:00
}
},
2025-10-11 15:02:01 +08:00
2025-10-07 23:54:42 +08:00
// 点击头像触发cascader下拉菜单
handleAvatarClick() {
if (this.$refs.userCascader) {
2025-10-10 10:02:11 +08:00
// 切换菜单可见状态
this.userMenuVisible = !this.userMenuVisible;
2025-10-11 15:02:01 +08:00
2025-10-11 09:26:08 +08:00
// 菜单收起时清空选择值
if (!this.userMenuVisible) {
this.completeResetCascader();
}
2025-10-11 15:02:01 +08:00
2025-10-10 10:02:11 +08:00
// 直接设置菜单的显隐状态
try {
// 尝试使用toggleDropDownVisible方法
this.$refs.userCascader.toggleDropDownVisible(this.userMenuVisible);
} catch (error) {
// 如果toggle方法失败,尝试直接设置属性
if (this.$refs.userCascader.$refs.menu) {
this.$refs.userCascader.$refs.menu.showMenu(this.userMenuVisible);
} else {
2025-10-11 15:02:01 +08:00
console.error("Cannot access menu component");
2025-10-10 10:02:11 +08:00
}
}
2025-10-07 23:54:42 +08:00
}
},
2025-10-11 15:28:53 +08:00
// 处理用户菜单可见性变化
handleUserMenuVisibleChange(visible) {
this.userMenuVisible = visible;
// 如果菜单关闭了,也要清空选择值
if (!visible) {
this.completeResetCascader();
}
},
2025-10-07 23:54:42 +08:00
// 使用 mapActions 引入 Vuex 的 logout action
2025-10-11 15:02:01 +08:00
...mapActions(["logout"]),
},
};
2025-03-14 23:48:59 +08:00
</script>
<style lang="scss" scoped>
2025-03-28 11:08:12 +08:00
.header {
background: #f6fcfe66;
border: 1px solid #fff;
height: 63px !important;
min-width: 900px;
/* 设置最小宽度防止过度压缩 */
overflow: visible;
2025-03-28 11:08:12 +08:00
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
padding: 0 10px;
}
.header-left {
display: flex;
align-items: center;
gap: 10px;
min-width: 120px;
}
.logo-img {
width: 42px;
height: 42px;
}
.brand-img {
height: 20px;
2025-03-28 11:08:12 +08:00
}
.header-center {
display: flex;
align-items: center;
gap: 25px;
position: absolute;
2025-10-07 23:54:42 +08:00
left: 50%;
2025-03-28 11:08:12 +08:00
transform: translateX(-50%);
}
.header-right {
display: flex;
align-items: center;
gap: 7px;
min-width: 300px;
justify-content: flex-end;
}
2025-03-14 23:48:59 +08:00
.equipment-management {
height: 30px;
border-radius: 15px;
2025-03-28 11:08:12 +08:00
background: #deeafe;
2025-03-14 23:48:59 +08:00
display: flex;
justify-content: center;
font-size: 14px;
2025-03-20 14:08:38 +08:00
font-weight: 500;
2025-03-18 21:49:57 +08:00
gap: 7px;
2025-03-14 23:48:59 +08:00
color: #3d4566;
2025-03-20 14:08:38 +08:00
margin-left: 1px;
2025-03-14 23:48:59 +08:00
align-items: center;
2025-03-22 00:22:12 +08:00
transition: all 0.3s ease;
2025-03-24 17:27:08 +08:00
cursor: pointer;
flex-shrink: 0;
/* 防止导航按钮被压缩 */
padding: 0 15px;
position: relative;
2025-03-22 00:22:12 +08:00
}
2025-03-24 17:27:08 +08:00
.equipment-management.active-tab {
2025-03-22 00:22:12 +08:00
background: #5778ff !important;
color: #fff !important;
2025-03-14 23:48:59 +08:00
}
2025-03-28 11:08:12 +08:00
.equipment-management img {
width: 15px;
height: 13px;
2025-03-14 23:48:59 +08:00
}
2025-03-28 11:08:12 +08:00
.search-container {
margin-right: 5px;
flex: 0.9;
min-width: 60px;
max-width: none;
2025-03-28 11:08:12 +08:00
}
2025-10-01 02:52:32 +08:00
.search-wrapper {
position: relative;
}
.search-history-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #e4e6ef;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
z-index: 1000;
margin-top: 2px;
}
.search-history-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
border-bottom: 1px solid #f0f0f0;
font-size: 12px;
color: #909399;
}
.clear-history-btn {
color: #909399;
font-size: 11px;
padding: 0;
height: auto;
}
.clear-history-btn:hover {
color: #606266;
}
.search-history-list {
max-height: 200px;
overflow-y: auto;
}
.search-history-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
cursor: pointer;
font-size: 12px;
color: #606266;
}
.search-history-item:hover {
background-color: #f5f7fa;
}
.clear-item-icon {
font-size: 10px;
color: #909399;
visibility: hidden;
}
2025-10-07 23:54:42 +08:00
.more-dropdown .el-dropdown-link {
display: flex;
align-items: center;
gap: 7px;
}
2025-10-01 02:52:32 +08:00
.search-history-item:hover .clear-item-icon {
visibility: visible;
}
.clear-item-icon:hover {
color: #ff4949;
}
2025-11-08 23:44:40 +08:00
.custom-search-input>>>.el-input__inner {
height: 18px;
border-radius: 9px;
background-color: #fff;
2025-03-18 21:49:57 +08:00
border: 1px solid #e4e6ef;
padding-left: 8px;
font-size: 9px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
2025-03-28 11:08:12 +08:00
width: 100%;
2025-03-14 23:48:59 +08:00
}
2025-03-20 18:11:37 +08:00
2025-03-28 11:08:12 +08:00
.search-icon {
cursor: pointer;
color: #909399;
margin-right: 3px;
font-size: 9px;
line-height: 18px;
2025-03-28 11:08:12 +08:00
}
.custom-search-input::v-deep .el-input__suffix-inner {
display: flex;
align-items: center;
height: 100%;
}
2025-03-28 11:08:12 +08:00
.avatar-img {
width: 21px;
height: 21px;
flex-shrink: 0;
2025-10-07 23:54:42 +08:00
cursor: pointer;
}
/* 导航文本样式 - 支持中英文换行 */
.nav-text {
white-space: normal;
text-align: center;
max-width: 80px;
line-height: 1.2;
}
2025-03-28 11:08:12 +08:00
/* 响应式调整 */
@media (max-width: 1200px) {
.header-center {
gap: 14px;
}
.equipment-management {
width: 79px;
2025-03-28 11:08:12 +08:00
font-size: 9px;
}
}
2025-05-01 16:57:58 +08:00
.equipment-management.more-dropdown {
position: relative;
}
.equipment-management.more-dropdown .el-dropdown-menu {
position: absolute;
right: 0;
min-width: 120px;
margin-top: 5px;
}
.el-dropdown-menu__item {
min-width: 60px;
padding: 8px 20px;
font-size: 14px;
color: #606266;
white-space: nowrap;
}
2025-11-08 23:44:40 +08:00
2025-10-10 10:02:11 +08:00
/* 添加倒三角旋转样式 */
.rotate-down {
transform: rotate(180deg);
transition: transform 0.3s ease;
}
.el-icon-arrow-down {
transition: transform 0.3s ease;
}
2025-10-11 15:02:01 +08:00
</style>