mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-23 07:33:53 +08:00
646 lines
21 KiB
Vue
646 lines
21 KiB
Vue
<template>
|
|
<el-header class="header">
|
|
<div class="header-container">
|
|
<!-- 左侧元素 -->
|
|
<div class="header-left" @click="handleRouter('home')">
|
|
<img loading="lazy" alt="" src="@/assets/xiaozhi-logo.png" class="logo-img" />
|
|
<img loading="lazy" alt="" :src="xiaozhiAiIcon" class="brand-img" />
|
|
</div>
|
|
|
|
<!-- 中间导航菜单 -->
|
|
<div class="header-center">
|
|
<div class="equipment-management" :class="{
|
|
'active-tab':
|
|
$route.path === '/home' ||
|
|
$route.path === '/role-config' ||
|
|
$route.path === '/device-management',
|
|
}" @click="handleRouter('home')">
|
|
<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>
|
|
</div>
|
|
<!-- 普通用户显示音色克隆 -->
|
|
<div v-if="!userInfo.superAdmin && featureStatus.voiceClone" class="equipment-management"
|
|
:class="{ 'active-tab': $route.path === '/voice-clone-management' }"
|
|
@click="handleRouter('voiceCloneManagement')">
|
|
<img loading="lazy" alt="" src="@/assets/header/voice.png" :style="{
|
|
filter:
|
|
$route.path === '/voice-clone-management'
|
|
? 'brightness(0) invert(1)'
|
|
: 'None',
|
|
}" />
|
|
<span class="nav-text">{{ $t("header.voiceCloneManagement") }}</span>
|
|
</div>
|
|
|
|
<!-- 超级管理员显示音色克隆下拉菜单 -->
|
|
<el-dropdown v-if="userInfo.superAdmin && featureStatus.voiceClone" trigger="click"
|
|
class="equipment-management more-dropdown" :class="{
|
|
'active-tab':
|
|
$route.path === '/voice-clone-management' ||
|
|
$route.path === '/voice-resource-management',
|
|
}" @visible-change="handleVoiceCloneDropdownVisibleChange">
|
|
<span class="el-dropdown-link">
|
|
<img loading="lazy" alt="" src="@/assets/header/voice.png" :style="{
|
|
filter:
|
|
$route.path === '/voice-clone-management' ||
|
|
$route.path === '/voice-resource-management'
|
|
? 'brightness(0) invert(1)'
|
|
: 'None',
|
|
}" />
|
|
<span class="nav-text">{{ $t("header.voiceCloneManagement") }}</span>
|
|
<i class="el-icon-arrow-down el-icon--right" :class="{ 'rotate-down': voiceCloneDropdownVisible }"></i>
|
|
</span>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item @click.native="handleRouter('voiceCloneManagement')">
|
|
{{ $t("header.voiceCloneManagement") }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item @click.native="handleRouter('voiceResourceManagement')">
|
|
{{ $t("header.voiceResourceManagement") }}
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
|
|
<div v-if="userInfo.superAdmin" class="equipment-management"
|
|
:class="{ 'active-tab': $route.path === '/model-config' }" @click="handleRouter('modelConfig')">
|
|
<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>
|
|
</div>
|
|
<div v-if="featureStatus.knowledgeBase" class="equipment-management"
|
|
:class="{ 'active-tab': $route.path === '/knowledge-base-management' || $route.path === '/knowledge-file-upload' }"
|
|
@click="handleRouter('knowledgeBaseManagement')">
|
|
<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',
|
|
}" />
|
|
<span class="nav-text">{{ $t("header.knowledgeBase") }}</span>
|
|
</div>
|
|
<div v-if="featureStatus.addressBook" class="equipment-management"
|
|
:class="{ 'active-tab': $route.path === '/address-book-management' }"
|
|
@click="handleRouter('addressBookManagement')">
|
|
<img loading="lazy" alt="" src="@/assets/header/address_book.png" :style="{
|
|
filter:
|
|
$route.path === '/address-book-management' ? 'brightness(0) invert(1)' : 'None',
|
|
}" />
|
|
<span class="nav-text">{{ $t("header.addressBook") }}</span>
|
|
</div>
|
|
<el-dropdown v-if="userInfo.superAdmin" 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' ||
|
|
$route.path === '/feature-management' ||
|
|
$route.path === '/replacement-word-management'
|
|
}" @visible-change="handleParamDropdownVisibleChange">
|
|
<span class="el-dropdown-link">
|
|
<img loading="lazy" alt="" src="@/assets/header/param_management.png" :style="{
|
|
filter:
|
|
$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' ||
|
|
$route.path === '/feature-management' ||
|
|
$route.path === '/replacement-word-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>
|
|
</span>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item @click.native="handleRouter('paramManagement')">
|
|
{{ $t("header.paramManagement") }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item @click.native="handleRouter('userManagement')">
|
|
{{ $t("header.userManagement") }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item @click.native="handleRouter('otaManagement')">
|
|
{{ $t("header.otaManagement") }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item @click.native="handleRouter('dictManagement')">
|
|
{{ $t("header.dictManagement") }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item @click.native="handleRouter('providerManagement')">
|
|
{{ $t("header.providerManagement") }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item @click.native="handleRouter('agentTemplate')">
|
|
{{ $t("header.agentTemplate") }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item @click.native="handleRouter('replacementWordManagement')">
|
|
{{ $t("header.replacementWordManagement") }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item @click.native="handleRouter('serverSideManagement')">
|
|
{{ $t("header.serverSideManagement") }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item @click.native="handleRouter('featureManagement')">
|
|
{{ $t("header.featureManagement") }}
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
</div>
|
|
|
|
<!-- 右侧元素 -->
|
|
<div class="header-right">
|
|
<img loading="lazy" alt="" src="@/assets/home/avatar.png" class="avatar-img" @click="handleAvatarClick" />
|
|
<span class="el-user-dropdown" @click="handleAvatarClick">
|
|
{{ userInfo.username || "加载中..." }}
|
|
<i class="el-icon-arrow-down el-icon--right" :class="{ 'rotate-down': userMenuVisible }"></i>
|
|
</span>
|
|
<el-cascader :options="userMenuOptions" trigger="click" :props="cascaderProps"
|
|
style="width: 0px; overflow: hidden" :show-all-levels="false" @change="handleCascaderChange"
|
|
@visible-change="handleUserMenuVisibleChange" ref="userCascader">
|
|
<template slot-scope="{ data }">
|
|
<span>{{ data.label }}</span>
|
|
</template>
|
|
</el-cascader>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 修改密码弹窗 -->
|
|
<ChangePasswordDialog v-model="isChangePasswordDialogVisible" />
|
|
</el-header>
|
|
</template>
|
|
|
|
<script>
|
|
import i18n, { changeLanguage } from "@/i18n";
|
|
import featureManager from "@/utils/featureManager"; // 引入功能管理工具类
|
|
import { mapActions, mapState } from "vuex";
|
|
import ChangePasswordDialog from "./ChangePasswordDialog.vue"; // 引入修改密码弹窗组件
|
|
|
|
export default {
|
|
name: "HeaderBar",
|
|
components: {
|
|
ChangePasswordDialog,
|
|
},
|
|
props: ["devices"], // 接收父组件设备列表
|
|
data() {
|
|
return {
|
|
search: "",
|
|
isChangePasswordDialogVisible: false, // 控制修改密码弹窗的显示
|
|
paramDropdownVisible: false,
|
|
voiceCloneDropdownVisible: false,
|
|
userMenuVisible: false, // 添加用户菜单可见状态
|
|
menuVisibleTimer: null, // 菜单显示定时器,防止够快触发
|
|
// Cascader 配置
|
|
cascaderProps: {
|
|
expandTrigger: "click",
|
|
value: "value",
|
|
label: "label",
|
|
children: "children",
|
|
},
|
|
// 跳转页面配置
|
|
routerPaths: {
|
|
home: "/home",
|
|
modelConfig: "/model-config",
|
|
knowledgeBaseManagement: "/knowledge-base-management",
|
|
addressBookManagement: "/address-book-management",
|
|
voiceCloneManagement: "/voice-clone-management",
|
|
voiceResourceManagement: "/voice-resource-management",
|
|
paramManagement: "/params-management",
|
|
userManagement: "/user-management",
|
|
otaManagement: "/ota-management",
|
|
dictManagement: "/dict-management",
|
|
providerManagement: "/provider-management",
|
|
agentTemplate: "/agent-template-management",
|
|
replacementWordManagement: "/replacement-word-management",
|
|
serverSideManagement: "/server-side-management",
|
|
featureManagement: "/feature-management",
|
|
}
|
|
};
|
|
},
|
|
computed: {
|
|
...mapState({
|
|
featureStatus: (state) => ({
|
|
voiceClone: state.pubConfig.systemWebMenu?.features?.voiceClone?.enabled, // 音色克隆功能状态
|
|
knowledgeBase: state.pubConfig.systemWebMenu?.features?.knowledgeBase?.enabled, // 知识库功能状态
|
|
addressBook: state.pubConfig.systemWebMenu?.features?.addressBook?.enabled, // 通讯录功能状态
|
|
}),
|
|
userInfo: (state) => state.userInfo,
|
|
}),
|
|
// 获取当前语言
|
|
currentLanguage() {
|
|
return i18n.locale || "zh_CN";
|
|
},
|
|
// 获取当前语言显示文本
|
|
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");
|
|
case "de":
|
|
return this.$t("language.de");
|
|
case "vi":
|
|
return this.$t("language.vi");
|
|
case "pt_BR":
|
|
return this.$t("language.ptBR");
|
|
default:
|
|
return this.$t("language.zhCN");
|
|
}
|
|
},
|
|
// 根据当前语言获取对应的xiaozhi-ai图标
|
|
xiaozhiAiIcon() {
|
|
const currentLang = this.currentLanguage;
|
|
switch (currentLang) {
|
|
case "zh_CN":
|
|
return require("@/assets/xiaozhi-ai.png");
|
|
case "zh_TW":
|
|
return require("@/assets/xiaozhi-ai_zh_TW.png");
|
|
case "en":
|
|
return require("@/assets/xiaozhi-ai_en.png");
|
|
case "de":
|
|
return require("@/assets/xiaozhi-ai_de.png");
|
|
case "vi":
|
|
return require("@/assets/xiaozhi-ai_vi.png");
|
|
case "pt_BR":
|
|
return require("@/assets/xiaozhi-ai_en.png");
|
|
default:
|
|
return require("@/assets/xiaozhi-ai.png");
|
|
}
|
|
},
|
|
// 用户菜单选项
|
|
userMenuOptions() {
|
|
return [
|
|
{
|
|
label: this.currentLanguageText,
|
|
value: "language",
|
|
children: [
|
|
{
|
|
label: this.$t("language.zhCN"),
|
|
value: "zh_CN",
|
|
},
|
|
{
|
|
label: this.$t("language.zhTW"),
|
|
value: "zh_TW",
|
|
},
|
|
{
|
|
label: this.$t("language.en"),
|
|
value: "en",
|
|
},
|
|
{
|
|
label: this.$t("language.de"),
|
|
value: "de",
|
|
},
|
|
{
|
|
label: this.$t("language.vi"),
|
|
value: "vi",
|
|
},
|
|
{
|
|
label: this.$t("language.ptBR"),
|
|
value: "pt_BR",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: this.$t("header.changePassword"),
|
|
value: "changePassword",
|
|
},
|
|
{
|
|
label: this.$t("header.logout"),
|
|
value: "logout",
|
|
},
|
|
];
|
|
},
|
|
},
|
|
async mounted() {
|
|
// 等待featureManager初始化完成后再加载功能状态
|
|
await this.loadFeatureStatus();
|
|
},
|
|
methods: {
|
|
handleRouter(type) {
|
|
this.$router.push(this.routerPaths[type]);
|
|
},
|
|
// 加载功能状态
|
|
async loadFeatureStatus() {
|
|
// 等待featureManager初始化完成
|
|
await featureManager.waitForInitialization();
|
|
},
|
|
// 显示修改密码弹窗
|
|
showChangePasswordDialog() {
|
|
this.isChangePasswordDialogVisible = true;
|
|
// 添加:显示修改密码弹窗后重置用户菜单可见状态
|
|
this.userMenuVisible = false;
|
|
},
|
|
// 退出登录
|
|
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,
|
|
});
|
|
}
|
|
},
|
|
// 监听参数字典下拉菜单的可见状态变化
|
|
handleParamDropdownVisibleChange(visible) {
|
|
this.paramDropdownVisible = visible;
|
|
},
|
|
|
|
// 监听音色克隆下拉菜单的可见状态变化
|
|
handleVoiceCloneDropdownVisibleChange(visible) {
|
|
this.voiceCloneDropdownVisible = visible;
|
|
},
|
|
// 在data中添加一个key用于强制重新渲染组件
|
|
// 处理 Cascader 选择变化
|
|
handleCascaderChange(value) {
|
|
if (!value || value.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const action = value[value.length - 1];
|
|
|
|
// 处理语言切换
|
|
if (value.length === 2 && value[0] === "language") {
|
|
this.changeLanguage(action);
|
|
} else {
|
|
// 处理其他操作
|
|
switch (action) {
|
|
case "changePassword":
|
|
this.showChangePasswordDialog();
|
|
break;
|
|
case "logout":
|
|
this.handleLogout();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 操作完成后立即清空选择
|
|
setTimeout(() => {
|
|
this.completeResetCascader();
|
|
}, 300);
|
|
},
|
|
|
|
// 切换语言
|
|
changeLanguage(lang) {
|
|
changeLanguage(lang);
|
|
this.$message.success({
|
|
message: this.$t("message.success"),
|
|
showClose: true,
|
|
});
|
|
// 添加:切换语言后重置用户菜单可见状态
|
|
this.userMenuVisible = false;
|
|
},
|
|
|
|
// 完全重置级联选择器
|
|
completeResetCascader() {
|
|
if (this.$refs.userCascader) {
|
|
try {
|
|
// 尝试所有可能的方法来清空选择
|
|
// 1. 尝试使用组件提供的clearValue方法
|
|
if (this.$refs.userCascader.clearValue) {
|
|
this.$refs.userCascader.clearValue();
|
|
}
|
|
|
|
// 2. 直接清空内部属性
|
|
if (this.$refs.userCascader.$data) {
|
|
this.$refs.userCascader.$data.selectedPaths = [];
|
|
this.$refs.userCascader.$data.displayLabels = [];
|
|
this.$refs.userCascader.$data.inputValue = "";
|
|
this.$refs.userCascader.$data.checkedValue = [];
|
|
this.$refs.userCascader.$data.showAllLevels = false;
|
|
}
|
|
|
|
// 3. 操作DOM清除选中状态
|
|
const menuElement = this.$refs.userCascader.$refs.menu;
|
|
if (menuElement && menuElement.$el) {
|
|
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"));
|
|
}
|
|
|
|
console.log("Cascader values cleared");
|
|
} catch (error) {
|
|
console.error("清空选择值失败:", error);
|
|
}
|
|
}
|
|
},
|
|
|
|
// 点击头像触发cascader下拉菜单
|
|
handleAvatarClick() {
|
|
if (this.$refs.userCascader) {
|
|
// 切换菜单可见状态
|
|
this.userMenuVisible = !this.userMenuVisible;
|
|
|
|
// 菜单收起时清空选择值
|
|
if (!this.userMenuVisible) {
|
|
this.completeResetCascader();
|
|
}
|
|
|
|
// 直接设置菜单的显隐状态
|
|
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 {
|
|
console.error("Cannot access menu component");
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// 处理用户菜单可见性变化
|
|
handleUserMenuVisibleChange(visible) {
|
|
if (this.menuVisibleTimer) return;
|
|
this.menuVisibleTimer = setTimeout(() => {
|
|
this.userMenuVisible = visible;
|
|
clearTimeout(this.menuVisibleTimer);
|
|
this.menuVisibleTimer = null;
|
|
}, 100);
|
|
|
|
// 如果菜单关闭了,也要清空选择值
|
|
if (!visible) {
|
|
this.completeResetCascader();
|
|
}
|
|
},
|
|
|
|
// 使用 mapActions 引入 Vuex 的 logout action
|
|
...mapActions(["logout"]),
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.header {
|
|
background: linear-gradient(180deg, #dfeafe, #eff4ff);
|
|
height: 63px !important;
|
|
min-width: 900px;
|
|
overflow: visible;
|
|
}
|
|
|
|
.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: 130px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.logo-img {
|
|
width: 42px;
|
|
height: 42px;
|
|
}
|
|
|
|
.brand-img {
|
|
height: 20px;
|
|
}
|
|
|
|
.header-center {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 25px;
|
|
background: white;
|
|
border-radius: 30px;
|
|
box-shadow: 0 0 6px 0px #cfe1fb;
|
|
padding: 4px;
|
|
}
|
|
|
|
.header-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 7px;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.equipment-management {
|
|
padding: 8px 16px;
|
|
border-radius: 30px;
|
|
display: flex;
|
|
justify-content: center;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
gap: 7px;
|
|
color: #6c79a8;
|
|
margin-left: 1px;
|
|
align-items: center;
|
|
transition: all 0.3s ease;
|
|
cursor: pointer;
|
|
flex-shrink: 0;
|
|
position: relative;
|
|
}
|
|
|
|
.equipment-management.active-tab {
|
|
color: #fff !important;
|
|
background: linear-gradient(90deg, #2983fe 0%, #5251fc 100%);
|
|
box-shadow: 0 1px 8px rgba(41, 131, 254, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.equipment-management.active-tab::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 50%;
|
|
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.4) 0%, rgba(255, 255, 255, 0) 100%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.equipment-management img {
|
|
width: 15px;
|
|
height: 13px;
|
|
}
|
|
|
|
.avatar-img {
|
|
width: 21px;
|
|
height: 21px;
|
|
flex-shrink: 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.el-user-dropdown {
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* 导航文本样式 - 支持中英文换行 */
|
|
.nav-text {
|
|
white-space: normal;
|
|
text-align: center;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
/* 响应式调整 */
|
|
@media (max-width: 1200px) {
|
|
.header-center {
|
|
gap: 14px;
|
|
}
|
|
|
|
.equipment-management {
|
|
min-width: 80px;
|
|
font-size: 10px;
|
|
}
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
/* 添加倒三角旋转样式 */
|
|
.rotate-down {
|
|
transform: rotate(180deg);
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.el-icon-arrow-down {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
</style>
|