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

479 lines
14 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-09-12 18:09:09 +08:00
<div class="equipment-management"
2025-05-01 16:57:58 +08:00
:class="{ 'active-tab': $route.path === '/home' || $route.path === '/role-config' || $route.path === '/device-management' }"
@click="goHome">
2025-09-12 18:09:09 +08:00
<img loading="lazy" alt="" src="@/assets/header/robot.png"
:style="{ filter: $route.path === '/home' || $route.path === '/role-config' || $route.path === '/device-management' ? 'brightness(0) invert(1)' : 'None' }" />
<span class="nav-text">{{ $t('header.smartManagement') }}</span>
2025-03-14 23:48:59 +08:00
</div>
<div v-if="isSuperAdmin" class="equipment-management" :class="{ 'active-tab': $route.path === '/model-config' }"
@click="goModelConfig">
2025-09-12 18:09:09 +08:00
<img loading="lazy" alt="" src="@/assets/header/model_config.png"
:style="{ filter: $route.path === '/model-config' ? 'brightness(0) invert(1)' : 'None' }" />
<span class="nav-text">{{ $t('header.modelConfig') }}</span>
2025-03-14 23:48:59 +08:00
</div>
2025-09-12 18:09:09 +08:00
<div v-if="isSuperAdmin" class="equipment-management"
:class="{ 'active-tab': $route.path === '/user-management' }" @click="goUserManagement">
2025-09-12 18:09:09 +08:00
<img loading="lazy" alt="" src="@/assets/header/user_management.png"
:style="{ filter: $route.path === '/user-management' ? 'brightness(0) invert(1)' : 'None' }" />
<span class="nav-text">{{ $t('header.userManagement') }}</span>
2025-04-05 20:16:02 +08:00
</div>
2025-09-12 18:09:09 +08:00
<div v-if="isSuperAdmin" class="equipment-management"
:class="{ 'active-tab': $route.path === '/ota-management' }" @click="goOtaManagement">
2025-09-12 18:09:09 +08:00
<img loading="lazy" alt="" src="@/assets/header/firmware_update.png"
:style="{ filter: $route.path === '/ota-management' ? 'brightness(0) invert(1)' : 'None' }" />
<span class="nav-text">{{ $t('header.otaManagement') }}</span>
</div>
2025-09-12 18:09:09 +08:00
<el-dropdown v-if="isSuperAdmin" trigger="click" class="equipment-management more-dropdown"
2025-09-17 15:38:23 +08:00
: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' }"
2025-05-22 10:41:56 +08:00
@visible-change="handleParamDropdownVisibleChange">
2025-05-01 16:57:58 +08:00
<span class="el-dropdown-link">
2025-09-12 18:09:09 +08:00
<img loading="lazy" alt="" src="@/assets/header/param_management.png"
2025-09-17 15:38:23 +08:00
:style="{ filter: $route.path === '/dict-management' || $route.path === '/params-management' || $route.path === '/provider-management' || $route.path === '/server-side-management' || $route.path === '/agent-template-management' ? 'brightness(0) invert(1)' : 'None' }" />
<span class="nav-text">{{ $t('header.paramDictionary') }}</span>
<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">
{{ $t('header.paramManagement') }}
2025-05-01 16:57:58 +08:00
</el-dropdown-item>
<el-dropdown-item @click.native="goDictManagement">
{{ $t('header.dictManagement') }}
2025-05-01 16:57:58 +08:00
</el-dropdown-item>
<el-dropdown-item @click.native="goProviderManagement">
{{ $t('header.providerManagement') }}
</el-dropdown-item>
<el-dropdown-item @click.native="goServerSideManagement">
{{ $t('header.serverSideManagement') }}
</el-dropdown-item>
2025-09-17 15:38:23 +08:00
<!-- 添加默认角色模板管理到参数字典下拉菜单 -->
<el-dropdown-item @click.native="goAgentTemplateManagement">
{{ $t('header.agentTemplate') }}
</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-17 15:38:23 +08:00
2025-03-28 11:08:12 +08:00
<!-- 右侧元素 -->
<div class="header-right">
2025-05-15 09:54:59 +08:00
<div class="search-container" v-if="$route.path === '/home' && !(isSuperAdmin && isSmallScreen)">
2025-09-12 18:09:09 +08:00
<el-input v-model="search" :placeholder="$t('header.searchPlaceholder')" class="custom-search-input"
@keyup.enter.native="handleSearch">
2025-03-28 11:08:12 +08:00
<i slot="suffix" class="el-icon-search search-icon" @click="handleSearch"></i>
</el-input>
2025-03-14 23:48:59 +08:00
</div>
2025-09-12 18:09:09 +08:00
<!-- 语言切换下拉菜单 -->
<el-dropdown trigger="click" class="language-dropdown" @visible-change="handleLanguageDropdownVisibleChange">
<span class="el-dropdown-link">
2025-09-12 18:09:09 +08:00
<span class="current-language-text">{{ currentLanguageText }}</span>
<i class="el-icon-arrow-down el-icon--right" :class="{ 'rotate-down': languageDropdownVisible }"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="changeLanguage('zh_CN')">
{{ $t('language.zhCN') }}
</el-dropdown-item>
<el-dropdown-item @click.native="changeLanguage('zh_TW')">
{{ $t('language.zhTW') }}
</el-dropdown-item>
<el-dropdown-item @click.native="changeLanguage('en')">
{{ $t('language.en') }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
2025-09-12 18:09:09 +08:00
<img loading="lazy" alt="" src="@/assets/home/avatar.png" class="avatar-img" />
<el-dropdown trigger="click" class="user-dropdown" @visible-change="handleUserDropdownVisibleChange">
<span class="el-dropdown-link">
{{ userInfo.username || '加载中...' }}
<i class="el-icon-arrow-down el-icon--right" :class="{ 'rotate-down': userDropdownVisible }"></i>
</span>
<el-dropdown-menu slot="dropdown">
2025-09-12 18:09:09 +08:00
<el-dropdown-item @click.native="showChangePasswordDialog">{{ $t('header.changePassword')
}}</el-dropdown-item>
<el-dropdown-item @click.native="handleLogout">{{ $t('header.logout') }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
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-03-24 18:08:05 +08:00
import userApi from '@/apis/module/user';
2025-09-12 18:09:09 +08:00
import i18n, { changeLanguage } from '@/i18n';
import { mapActions, mapGetters } from 'vuex';
2025-04-05 20:16:02 +08:00
import ChangePasswordDialog from './ChangePasswordDialog.vue'; // 引入修改密码弹窗组件
2025-03-14 23:48:59 +08:00
export default {
name: 'HeaderBar',
2025-03-24 18:08:05 +08:00
components: {
ChangePasswordDialog
},
2025-03-19 18:13:03 +08:00
props: ['devices'], // 接收父组件设备列表
2025-03-14 23:48:59 +08:00
data() {
2025-03-19 18:13:03 +08:00
return {
2025-04-01 10:59:35 +08:00
search: '',
2025-03-19 18:13:03 +08:00
userInfo: {
username: '',
2025-03-19 18:13:03 +08:00
mobile: ''
2025-03-24 18:08:05 +08:00
},
isChangePasswordDialogVisible: false, // 控制修改密码弹窗的显示
userDropdownVisible: false,
2025-05-15 09:54:59 +08:00
paramDropdownVisible: false,
languageDropdownVisible: false,
2025-05-15 09:54:59 +08:00
isSmallScreen: false
2025-03-19 18:13:03 +08:00
}
},
computed: {
...mapGetters(['getIsSuperAdmin']),
isSuperAdmin() {
return this.getIsSuperAdmin;
},
// 获取当前语言
currentLanguage() {
return i18n.locale || 'zh_CN';
2025-09-12 18:09:09 +08:00
},
// 获取当前语言显示文本
currentLanguageText() {
const currentLang = this.currentLanguage;
switch (currentLang) {
case 'zh_CN':
return this.$t('language.zhCN');
case 'zh_TW':
return this.$t('language.zhTW');
case 'en':
return this.$t('language.en');
default:
return this.$t('language.zhCN');
}
}
},
2025-03-19 18:13:03 +08:00
mounted() {
2025-05-15 09:54:59 +08:00
this.fetchUserInfo();
this.checkScreenSize();
window.addEventListener('resize', this.checkScreenSize);
},
//移除事件监听器
beforeDestroy() {
window.removeEventListener('resize', this.checkScreenSize);
2025-03-14 23:48:59 +08:00
},
methods: {
goHome() {
// 跳转到首页
this.$router.push('/home')
2025-03-19 18:13:03 +08:00
},
2025-03-20 14:08:38 +08:00
goUserManagement() {
this.$router.push('/user-management')
},
2025-03-20 18:11:37 +08:00
goModelConfig() {
this.$router.push('/model-config')
},
2025-04-09 13:36:59 +08:00
goParamManagement() {
this.$router.push('/params-management')
},
goOtaManagement() {
this.$router.push('/ota-management')
},
2025-05-01 16:57:58 +08:00
goDictManagement() {
this.$router.push('/dict-management')
},
goProviderManagement() {
this.$router.push('/provider-management')
},
goServerSideManagement() {
this.$router.push('/server-side-management')
},
2025-09-17 15:38:23 +08:00
// 添加默认角色模板管理导航方法
goAgentTemplateManagement() {
this.$router.push('/agent-template-management')
},
2025-03-19 18:13:03 +08:00
// 获取用户信息
fetchUserInfo() {
userApi.getUserInfo(({ data }) => {
2025-03-24 18:08:05 +08:00
this.userInfo = data.data
if (data.data.superAdmin !== undefined) {
this.$store.commit('setUserInfo', data.data);
}
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) {
this.$emit('search-reset');
return;
2025-03-19 18:13:03 +08:00
}
try {
// 创建不区分大小写的正则表达式
const regex = new RegExp(searchValue, 'i');
// 触发搜索事件,将正则表达式传递给父组件
this.$emit('search', regex);
} catch (error) {
console.error('正则表达式创建失败:', error);
this.$message.error({
message: this.$t('message.error'),
showClose: true
});
}
2025-03-24 18:08:05 +08:00
},
// 显示修改密码弹窗
showChangePasswordDialog() {
this.isChangePasswordDialogVisible = true;
},
// 退出登录
async handleLogout() {
try {
// 调用 Vuex 的 logout action
await this.logout();
this.$message.success({
message: this.$t('message.success'),
showClose: true
});
} catch (error) {
console.error('退出登录失败:', error);
this.$message.error({
message: this.$t('message.error'),
showClose: true
});
}
},
handleUserDropdownVisibleChange(visible) {
this.userDropdownVisible = visible;
},
// 监听第二个下拉菜单的可见状态变化
handleParamDropdownVisibleChange(visible) {
this.paramDropdownVisible = visible;
},
// 监听语言下拉菜单的可见状态变化
handleLanguageDropdownVisibleChange(visible) {
this.languageDropdownVisible = visible;
},
// 切换语言
changeLanguage(lang) {
changeLanguage(lang);
this.languageDropdownVisible = false;
this.$message.success({
message: this.$t('message.success'),
showClose: true
});
},
// 使用 mapActions 引入 Vuex 的 logout action
...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;
left: 44%;
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
}
.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;
}
.user-dropdown {
flex-shrink: 0;
}
.language-dropdown {
flex-shrink: 0;
margin-right: 5px;
}
2025-09-12 18:09:09 +08:00
.current-language-text {
margin-left: 4px;
margin-right: 4px;
font-size: 12px;
color: #3d4566;
}
.more-dropdown {
padding-right: 20px;
}
.more-dropdown .el-dropdown-link {
display: flex;
align-items: center;
gap: 7px;
}
.rotate-down {
transform: rotate(180deg);
transition: transform 0.3s ease;
}
.el-icon-arrow-down {
transition: transform 0.3s ease;
}
/* 导航文本样式 - 支持中英文换行 */
.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-03-24 18:08:05 +08:00
</style>