Browse Source

(headerfeat-bar): 实现三级菜单功能

- 在 HeaderBar 组件中添加了二级和三级菜单的支持
- 实现了菜单的折叠和展开功能
- 优化了菜单项的样式和布局
- 新增了 submenuOpen 和 thirdLevelOpen 状态管理
- 添加了 toggleSubmenu 和 toggleThirdLevel 方法
panqiuyao 5 months ago
parent
commit
e6bc909b00
1 changed files with 201 additions and 70 deletions
  1. 201 70
      frontend/src/components/header-bar/index.vue

+ 201 - 70
frontend/src/components/header-bar/index.vue

@@ -1,21 +1,78 @@
 <template>
   <div class="header-bar">
     <div class="header-bar__menu">
-      <div v-for="(item, index) in menu" :key="index" class="header-bar__menu-item" @click="getItemClick(item)">
-        <img v-if="getItemIcon(item)" :src="getItemIcon(item)" class="header-bar__menu-icon" />
-        <span class="header-bar__menu-name">{{ getItemName(item) }}</span>
+      <div v-for="(item, index) in menu" :key="index" class="header-bar__menu-item">
+        <div
+          v-if="!item.children || item.children.length === 0"
+          @click="getItemClick(item)"
+          class="header-bar__menu-item-content flex"
+        >
+          <img v-if="getItemIcon(item)" :src="getItemIcon(item)" class="header-bar__menu-icon" />
+          <span class="header-bar__menu-name">{{ getItemName(item) }}</span>
+        </div>
+        <div v-else class="header-bar__submenu">
+          <div
+            class="header-bar__submenu-header"
+            @click.stop="toggleSubmenu(index)"
+          >
+            <img v-if="getItemIcon(item)" :src="getItemIcon(item)" class="header-bar__menu-icon" />
+            <span class="header-bar__menu-name">{{ getItemName(item) }}</span>
+          </div>
+          <!-- 二级菜单 -->
+          <div
+            class="header-bar__submenu-body"
+            :class="{ 'submenu-open': submenuOpen[index] }"
+          >
+            <div
+              v-for="(child, childIndex) in item.children"
+              :key="childIndex"
+              class="header-bar__submenu-item"
+              @click="getItemClick(child)"
+            >
+              <div
+                v-if="!child.children || child.children.length === 0"
+                class="header-bar__submenu-item-content flex left"
+              >
+                <img v-if="getItemIcon(child)" :src="getItemIcon(child)" class="header-bar__menu-icon" />
+                <span class="header-bar__menu-name">{{ getItemName(child) }}</span>
+              </div>
+              <div v-else class="header-bar__submenu-third-level">
+                <div
+                  class="header-bar__submenu-third-header"
+                  @click.stop="toggleThirdLevel(index, childIndex)"
+                >
+                  <img v-if="getItemIcon(child)" :src="getItemIcon(child)" class="header-bar__menu-icon" />
+                  <span class="header-bar__menu-name">{{ getItemName(child) }}</span>
+                </div>
+                <!-- 三级菜单 -->
+                <div
+                  class="header-bar__submenu-third-body"
+                  :class="{ 'submenu-open': thirdLevelOpen[`${index}-${childIndex}`] }"
+                >
+                  <div
+                    v-for="(grandChild, grandChildIndex) in child.children"
+                    :key="grandChildIndex"
+                    class="header-bar__submenu-third-item"
+                    @click="getItemClick(grandChild)"
+                  >
+                    <img v-if="getItemIcon(grandChild)" :src="getItemIcon(grandChild)" class="header-bar__menu-icon" />
+                    <span class="header-bar__menu-name">{{ getItemName(grandChild) }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
       </div>
     </div>
     <div class="header-bar__title">
       <span class="header-bar__text">{{ title }}</span>
     </div>
-    <div class="header-bar__buttons" >
-      <div class="header-bar__button header-bar__button__user" v-if="showUser">
-
-
+    <div class="header-bar__buttons" v-if="showUser">
+      <div class="header-bar__button header-bar__button__user">
         <el-dropdown>
           <span class="el-dropdown-link">
-           {{useUserInfoStore.userInfo.account_name || useUserInfoStore.userInfo.real_name || useUserInfoStore.userInfo.login_name}}
+            {{ useUserInfoStore.userInfo.account_name || useUserInfoStore.userInfo.real_name || useUserInfoStore.userInfo.login_name }}
           </span>
           <template #dropdown>
             <el-dropdown-menu>
@@ -30,26 +87,26 @@
 </template>
 
 <script setup lang="ts">
-import { defineProps, reactive } from 'vue';
-import useUserInfo from "@/stores/modules/user";
-import { useRouter} from "vue-router";
+import { defineProps, reactive } from 'vue'
+import useUserInfo from '@/stores/modules/user'
+import { useRouter } from 'vue-router'
 import iconsz from './assets/shezhi@2x.png'
 import iconykq from './assets/yaokong@2x.png'
 import gengxin from './assets/gengxin.svg'
-/*import iconMinimize from './assets/suoxiao@2x.png' // 新增
-import iconClose from './assets/guanbi@2x.png' // 新增*/
 import icpList from '@/utils/ipc'
 import { getRouterUrl } from '@/utils/appfun'
-import client from "@/stores/modules/client";
-const clientStore = client();
-const useUserInfoStore = useUserInfo();
+import client from '@/stores/modules/client'
+
+const clientStore = client()
+const useUserInfoStore = useUserInfo()
 
 // 定义 menu 项的类型
 interface MenuItem {
-  name?: string;    //名称
-  icon?: string;    // 图标
-  click?: () => void;   // 点击事件
-  type?: keyof typeof menuType; // 类型 menuType里面的值
+  name?: string //名称
+  icon?: string // 图标
+  click?: () => void // 点击事件
+  type?: keyof typeof menuType // 类型 menuType里面的值
+  children?: MenuItem[] // 支持嵌套菜单
 }
 
 // 定义 props
@@ -62,12 +119,26 @@ const props = defineProps({
     type: Array as () => MenuItem[],
     default: () => []
   },
-  showUser:{
+  showUser: {
     type: Boolean,
     default: false
   }
-});
+})
+
 const Router = useRouter()
+
+const submenuOpen = reactive<{ [key: number]: boolean }>({})
+const thirdLevelOpen = reactive<{ [key: string]: boolean }>({})
+
+const toggleSubmenu = (index: number) => {
+  submenuOpen[index] = !submenuOpen[index]
+}
+
+const toggleThirdLevel = (parentIndex: number, childIndex: number) => {
+  const key = `${parentIndex}-${childIndex}`
+  thirdLevelOpen[key] = !thirdLevelOpen[key]
+}
+
 const menuType = reactive({
   setting: {
     name: '设置',
@@ -89,124 +160,108 @@ const menuType = reactive({
     icon: gengxin,
     click: openOTA
   }
-});
+})
 
 function getItemClick(item: MenuItem) {
-  const menuItem = item.type ? { ...menuType[item.type], ...item } : item;
+  const menuItem = item.type ? { ...menuType[item.type], ...item } : item
   if (menuItem && menuItem.click) {
-    menuItem.click();
+    menuItem.click()
   }
 }
 
 function getItemName(item: MenuItem) {
-  const menuItem = item.type ? { ...menuType[item.type], ...item } : item;
-  return menuItem.name;
+  const menuItem = item.type ? { ...menuType[item.type], ...item } : item
+  return menuItem.name
 }
 
 function getItemIcon(item: MenuItem) {
-  const menuItem = item.type ? { ...menuType[item.type], ...item } : item;
-  return menuItem.icon;
+  const menuItem = item.type ? { ...menuType[item.type], ...item } : item
+  return menuItem.icon
 }
 
 function openSetting() {
-
   const { href } = Router.resolve({
     name: 'setting',
-    query:{
-      type:0,
+    query: {
+      type: 0
     }
   })
 
-  clientStore.ipc.removeAllListeners(icpList.utils.openMain);
+  clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
     title: '设置',
     width: 1920,
     height: 1080,
     frame: true,
-    id: "seeting",
+    id: 'seeting',
     url: getRouterUrl(href)
   }
-  clientStore.ipc.send(icpList.utils.openMain, params);
+  clientStore.ipc.send(icpList.utils.openMain, params)
 }
 
-
-function openRemoteControl(){
-
+function openRemoteControl() {
   const { href } = Router.resolve({
-    name: 'RemoteControl',
+    name: 'RemoteControl'
   })
 
-  clientStore.ipc.removeAllListeners(icpList.utils.openMain);
+  clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
     title: '模拟遥控器',
     width: 350,
     height: 600,
     frame: true,
-    id: "RemoteControl",
+    id: 'RemoteControl',
     url: getRouterUrl(href)
   }
-  clientStore.ipc.send(icpList.utils.openMain, params);
+  clientStore.ipc.send(icpList.utils.openMain, params)
 }
 
-
-function openDeveloper(){
-
+function openDeveloper() {
   const { href } = Router.resolve({
-    name: 'developer',
+    name: 'developer'
   })
 
-  clientStore.ipc.removeAllListeners(icpList.utils.openMain);
+  clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
     title: '初始设备调频设置',
     width: 900,
     height: 700,
     frame: true,
-    id: "developer",
+    id: 'developer',
     url: getRouterUrl(href)
   }
-  clientStore.ipc.send(icpList.utils.openMain, params);
+  clientStore.ipc.send(icpList.utils.openMain, params)
 }
 
-
-function openOTA(){
-
+function openOTA() {
   const { href } = Router.resolve({
-    name: 'ota',
+    name: 'ota'
   })
 
-  clientStore.ipc.removeAllListeners(icpList.utils.openMain);
+  clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
     title: '版本更新',
     width: 900,
     height: 700,
     frame: true,
-    id: "ota",
+    id: 'ota',
     url: getRouterUrl(href)
   }
-  clientStore.ipc.send(icpList.utils.openMain, params);
+  clientStore.ipc.send(icpList.utils.openMain, params)
 }
 
-function loginOut(){
-  useUserInfoStore.loginOut();
+function loginOut() {
+  useUserInfoStore.loginOut()
   useUserInfoStore.updateLoginShow(true)
 }
-
-// 新增
-function minimizeWindow() {
-  clientStore.ipc.send(icpList.utils.minimizeWindow);
-}
-
-// 新增
-function closeWindow() {
-  clientStore.ipc.send(icpList.utils.closeWindow);
-}
 </script>
 
-<style  lang="scss" scoped>
+<style lang="scss" scoped>
 .header-bar_blank {
   width: 100%;
-  height:30px;
+  height: 30px;
 }
+
 .header-bar {
   position: fixed;
   app-drag: drag;
@@ -295,6 +350,7 @@ function closeWindow() {
 .header-bar__button__user {
   padding: 0 10px;
 }
+
 .header-bar__button:hover {
   background-color: #e0e0e0;
 }
@@ -303,4 +359,79 @@ function closeWindow() {
   width: 16px;
   height: 16px;
 }
+
+.header-bar__submenu {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+}
+
+.header-bar__submenu-header {
+  display: flex;
+  align-items: center;
+  padding: 2px 10px;
+  cursor: pointer;
+}
+
+.header-bar__submenu-body {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  background: white;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+  z-index: 1000;
+  min-width: 160px;
+  border-radius: 4px;
+  overflow: hidden;
+  display: none;
+
+  &.submenu-open {
+    display: block;
+  }
+}
+
+.header-bar__submenu-item {
+  display: flex;
+  align-items: center;
+  padding: 6px 12px;
+  white-space: nowrap;
+
+  &:hover {
+    background-color: #f0f0f0;
+  }
+}
+
+.header-bar__submenu-third-level {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+}
+
+.header-bar__submenu-third-header {
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+
+  &:hover {
+    background-color: #f0f0f0;
+  }
+}
+
+.header-bar__submenu-third-body {
+  position: fixed;
+  margin-left: 150px;
+  top: 0;
+  left: 100%;
+  background: white;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+  z-index: 1000;
+  min-width: 160px;
+  border-radius: 4px;
+  overflow: hidden;
+  display: none;
+
+  &.submenu-open {
+    display: block;
+  }
+}
 </style>