255 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			255 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|   <div ref="sideMenu" class="side-menu">
 | |
|     <div
 | |
|       v-for="(item, index) in menuItems"
 | |
|       :key="index"
 | |
|       :ref="(el) => setMenuItemRef(el, index)"
 | |
|       class="menu-item"
 | |
|       @mouseenter="showSubMenu(index)"
 | |
|       @mouseleave="hideSubMenu"
 | |
|     >
 | |
|       <!-- @click="handleSubmenuClick(item)" -->
 | |
|       {{ item.name }}
 | |
|       <!-- 悬浮浮窗 -->
 | |
|       <div v-if="activeIndex === index" class="submenu-panel" :style="{ top: submenuTop + 'px' }" @mouseenter="keepSubmenuVisible" @mouseleave="hideSubMenu">
 | |
|         <div class="submenu-content">
 | |
|           <!-- <div class="text-right">更多</div> -->
 | |
|           <div class="submenu-group">
 | |
|             <template v-for="group in item.children" :key="group.id">
 | |
|               <div class="submenu-group-title" @click.stop="handleSubmenuClick(group)">
 | |
|                 {{ group.name }}
 | |
|               </div>
 | |
|               <div class="submenu-group-items">
 | |
|                 <div v-for="sub in group.children" :key="sub.id" class="submenu-item" @click.stop="handleSubmenuClick(sub)">
 | |
|                   {{ sub.name }}
 | |
|                 </div>
 | |
|               </div>
 | |
|             </template>
 | |
|           </div>
 | |
|         </div>
 | |
|       </div>
 | |
|     </div>
 | |
|   </div>
 | |
| </template>
 | |
| 
 | |
| <script setup lang="ts">
 | |
|   import { ref, onMounted } from 'vue'
 | |
|   import type { ComponentPublicInstance } from 'vue'
 | |
|   import { tab2 } from '~/api/home/index'
 | |
|   import type { ProjectDictNodeVO } from '~/api/home/type'
 | |
| 
 | |
|   const activeIndex = ref(-1)
 | |
|   const submenuTop = ref(0)
 | |
|   // const submenuLeft = ref(0)
 | |
|   const sideMenu = ref()
 | |
|   const menuItemRefs = ref<HTMLElement[]>([])
 | |
| 
 | |
|   // 等待数据加载完成再进行渲染 :courseData 对data进行别名赋值
 | |
|   const {
 | |
|     data: menuItems,
 | |
|     pending,
 | |
|     error,
 | |
|   } = useAsyncData('tab2-list', async () => {
 | |
|     const res = await tab2()
 | |
|     const arr = []
 | |
|     for (let i = 0; i < res.data?.length; i += 2) {
 | |
|       arr.push({
 | |
|         children: res.data.slice(i, i + 2),
 | |
|         name: getName(res.data.slice(i, i + 2)),
 | |
|       })
 | |
|     }
 | |
|     return arr
 | |
|   })
 | |
| 
 | |
|   const showSubMenu = (index: number) => {
 | |
|     activeIndex.value = index
 | |
|     const dom = menuItemRefs.value[index].getBoundingClientRect()
 | |
|     console.log(dom)
 | |
|     // submenuTop.value = window.scrollY
 | |
|   }
 | |
| 
 | |
|   const hideSubMenu = () => {
 | |
|     activeIndex.value = -1
 | |
|   }
 | |
| 
 | |
|   const handleSubmenuClick = (primary: ProjectDictNodeVO) => {
 | |
|     navigateTo(`/drawe/${primary.id}/1/12/-1`)
 | |
|   }
 | |
| 
 | |
|   const keepSubmenuVisible = () => {
 | |
|     // activeIndex.value = activeIndex.value // 保持当前索引
 | |
|   }
 | |
| 
 | |
|   const setMenuItemRef = (el: Element | ComponentPublicInstance | null, index: number) => {
 | |
|     if (el && 'offsetTop' in el) {
 | |
|       menuItemRefs.value[index] = el as HTMLElement
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const getName = (arr: any[]) => {
 | |
|     if (arr.length === 1) {
 | |
|       return arr[0].name
 | |
|     } else {
 | |
|       return arr[0].name + ' / ' + arr[1].name
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const init = () => {
 | |
|     // 获取标签
 | |
|     // getLabel()
 | |
|   }
 | |
| 
 | |
|   onMounted(() => {
 | |
|     // init();
 | |
|   })
 | |
| </script>
 | |
| 
 | |
| <style scoped>
 | |
|   .side-menu {
 | |
|     width: 221px;
 | |
|     background-color: #fff;
 | |
|     /* padding: 10px 0; */
 | |
|     /* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
 | |
|     /* overflow: visible; */
 | |
|     box-sizing: border-box;
 | |
|     position: relative; /* 添加定位上下文 */
 | |
|     /* overflow-y: auto; */
 | |
|     /* 隐藏滚动条 */
 | |
|     scrollbar-width: none; /* Firefox */
 | |
|     -ms-overflow-style: none; /* IE and Edge */
 | |
|     z-index: 9;
 | |
|     /* padding-top: 10px; */
 | |
|   }
 | |
| 
 | |
|   /* 隐藏 Webkit 浏览器的滚动条 */
 | |
|   .side-menu::-webkit-scrollbar {
 | |
|     display: none;
 | |
|   }
 | |
| 
 | |
|   /* 鼠标悬停时显示滚动条 */
 | |
|   .side-menu:hover {
 | |
|     scrollbar-width: none; /* Firefox */
 | |
|     -ms-overflow-style: none; /* IE and Edge */
 | |
|   }
 | |
| 
 | |
|   /* .side-menu:hover::-webkit-scrollbar {
 | |
|     display: block;
 | |
|     width: 8px !important;
 | |
|   }
 | |
| 
 | |
|   .side-menu:hover::-webkit-scrollbar-thumb {
 | |
|     background-color: #e6e8eb;
 | |
|     border-radius: 6px !important;
 | |
|   }
 | |
| 
 | |
|   .side-menu:hover::-webkit-scrollbar-track {
 | |
|     background-color: #fff;
 | |
|   } */
 | |
| 
 | |
|   .menu-item {
 | |
|     /* position: relative; */
 | |
|     text-align: center;
 | |
|     padding: 9px 24px;
 | |
|     cursor: pointer;
 | |
|     /* transition: all 0.3s ease; */
 | |
|     color: #333;
 | |
|     font-size: 14px;
 | |
|     border: 1px solid transparent;
 | |
|     z-index: 9;
 | |
|   }
 | |
| 
 | |
|   .menu-item:hover {
 | |
|     background-color: #fff;
 | |
|     color: #1890ff;
 | |
|     border: 1px solid #1890ff;
 | |
|     border-right: transparent !important;
 | |
|   }
 | |
| 
 | |
|   .submenu-panel {
 | |
|     position: absolute;
 | |
|     left: 220px;
 | |
|     top: 0;
 | |
|     width: 957px;
 | |
|     background: #fff;
 | |
|     /* box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); */
 | |
|     border: 1px solid #1890ff;
 | |
|     /* border-radius: 4px; */
 | |
|     display: flex;
 | |
|     padding: 20px;
 | |
|     z-index: -1;
 | |
|     box-sizing: border-box;
 | |
|     min-height: 480px;
 | |
|     overflow-y: auto;
 | |
|   }
 | |
| 
 | |
|   .submenu-panel::before {
 | |
|     /* content: '';
 | |
|     position: absolute;
 | |
|     left: -6px;
 | |
|     top: 50%;
 | |
|     transform: translateY(-50%);
 | |
|     width: 0;
 | |
|     height: 0;
 | |
|     border-top: 8px solid transparent;
 | |
|     border-bottom: 8px solid transparent;
 | |
|     border-right: 8px solid #fff;
 | |
|     filter: drop-shadow(-2px 0 2px rgba(0, 0, 0, 0.1)); */
 | |
|   }
 | |
| 
 | |
|   .submenu-content {
 | |
|     /* flex: 1; */
 | |
|     /* display: grid; */
 | |
|     /* grid-template-columns: repeat(3, 1fr); */
 | |
|     /* gap: 15px; */
 | |
|     width: 100%;
 | |
|     display: flex;
 | |
|     flex-direction: column;
 | |
|     .submenu-group {
 | |
|       display: flex;
 | |
|       flex-direction: column;
 | |
|       align-items: flex-start;
 | |
|       flex-wrap: wrap;
 | |
|       -webkit-column-break-inside: avoid; /* 兼容webkit内核 */
 | |
|       /* border: 1px solid #e6e8eb; */
 | |
|       .submenu-group-title {
 | |
|         font-size: 15px;
 | |
|         /* font-weight: 700; */
 | |
|         padding: 10px 16px;
 | |
|         color: #000;
 | |
|         font-weight: 600;
 | |
|         transition: all 0.2s;
 | |
|         /* border-bottom: 1px solid #e6e8eb; */
 | |
|       }
 | |
|       .submenu-group-title:hover {
 | |
|         color: #1890ff;
 | |
|         /* border-bottom: 1px solid #e6e8eb; */
 | |
|       }
 | |
|       .submenu-group-items {
 | |
|         display: flex !important;
 | |
|         flex-direction: row;
 | |
|         align-items: flex-start;
 | |
|         flex-wrap: wrap;
 | |
|         color: #666;
 | |
|         padding: 0px 16px;
 | |
|         gap: 10px;
 | |
|         .submenu-item {
 | |
|           cursor: pointer;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   .submenu-item {
 | |
|     cursor: pointer;
 | |
|     transition: all 0.2s;
 | |
|     display: inline-block; /* 改为行内块元素 */
 | |
|     /* padding: 8px 12px; */
 | |
|     padding-right: 12px;
 | |
|     /* padding-bottom: 8px; */
 | |
|   }
 | |
| 
 | |
|   .submenu-item:hover {
 | |
|     color: #1890ff;
 | |
|   }
 | |
| </style>
 | 
