Переглянути джерело

菜单导航设置支持纯顶部

RuoYi 5 місяців тому
батько
коміт
73efecc5c2

+ 12 - 0
src/assets/styles/ruoyi.scss

@@ -156,6 +156,18 @@
   width: inherit;
 }
 
+/* el menu */
+.el-menu-item,
+.el-sub-menu {
+  .svg-icon + span {
+    margin-left: 5px;
+  }
+}
+
+.el-menu--horizontal .el-menu--popup {
+  min-width: 120px !important;
+}
+
 /** 表格更多操作下拉样式 */
 .el-table .el-dropdown-link {
   cursor: pointer;

+ 1 - 1
src/assets/styles/sidebar.scss

@@ -61,7 +61,7 @@
     }
 
     .svg-icon {
-      margin-right: 16px;
+      margin-right: 10px !important;
     }
 
     .el-menu {

+ 0 - 1
src/components/Breadcrumb/index.vue

@@ -88,7 +88,6 @@ getBreadcrumb()
   display: inline-block;
   font-size: 14px;
   line-height: 50px;
-  margin-left: 8px;
 
   .no-redirect {
     color: #97a8be;

+ 2 - 4
src/components/TopNav/index.vue

@@ -175,7 +175,7 @@ onMounted(() => {
   float: left;
   height: 50px !important;
   line-height: 50px !important;
-  color: #999093 !important;
+  color: #303133 !important;
   padding: 0 5px !important;
   margin: 0 10px !important;
 }
@@ -190,7 +190,7 @@ onMounted(() => {
   float: left;
   height: 50px !important;
   line-height: 50px !important;
-  color: #999093 !important;
+  color: #303133 !important;
   padding: 0 5px !important;
   margin: 0 10px !important;
 }
@@ -212,6 +212,4 @@ onMounted(() => {
   margin-left: 8px;
   margin-top: 0px;
 }
-
-
 </style>

+ 35 - 6
src/layout/components/Navbar.vue

@@ -1,8 +1,12 @@
 <template>
-  <div class="navbar">
+  <div class="navbar" :class="'nav' + settingsStore.navType">
     <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
-    <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
-    <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />
+    <breadcrumb v-if="settingsStore.navType == 1" id="breadcrumb-container" class="breadcrumb-container" />
+    <top-nav v-if="settingsStore.navType == 2" id="topmenu-container" class="topmenu-container" />
+    <template v-if="settingsStore.navType == 3">
+      <logo v-show="settingsStore.sidebarLogo" :collapse="false"></logo>
+      <top-bar id="topbar-container" class="topbar-container" />
+    </template>
 
     <div class="right-menu">
       <template v-if="appStore.device !== 'mobile'">
@@ -57,6 +61,8 @@
 import { ElMessageBox } from 'element-plus'
 import Breadcrumb from '@/components/Breadcrumb'
 import TopNav from '@/components/TopNav'
+import TopBar from './TopBar'
+import Logo from './Sidebar/Logo'
 import Hamburger from '@/components/Hamburger'
 import Screenfull from '@/components/Screenfull'
 import SizeSelect from '@/components/SizeSelect'
@@ -111,20 +117,33 @@ function toggleTheme() {
 </script>
 
 <style lang='scss' scoped>
+.navbar.nav3 {
+  .hamburger-container {
+    display: none !important;
+  }
+}
+
 .navbar {
   height: 50px;
   overflow: hidden;
   position: relative;
   background: var(--navbar-bg);
   box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+  display: flex;
+  align-items: center;
+  // padding: 0 8px;
+  box-sizing: border-box;
 
   .hamburger-container {
     line-height: 46px;
     height: 100%;
-    float: left;
     cursor: pointer;
     transition: background 0.3s;
     -webkit-tap-highlight-color: transparent;
+    display: flex;
+    align-items: center;
+    flex-shrink: 0;
+    margin-right: 8px;
 
     &:hover {
       background: rgba(0, 0, 0, 0.025);
@@ -132,7 +151,7 @@ function toggleTheme() {
   }
 
   .breadcrumb-container {
-    float: left;
+    flex-shrink: 0;
   }
 
   .topmenu-container {
@@ -140,16 +159,26 @@ function toggleTheme() {
     left: 50px;
   }
 
+  .topbar-container {
+    flex: 1;
+    min-width: 0;
+    display: flex;
+    align-items: center;
+    overflow: hidden;
+    margin-left: 8px;
+  }
+
   .errLog-container {
     display: inline-block;
     vertical-align: top;
   }
 
   .right-menu {
-    float: right;
     height: 100%;
     line-height: 50px;
     display: flex;
+    align-items: center;
+    margin-left: auto;
 
     &:focus {
       outline: none;

+ 110 - 16
src/layout/components/Settings/index.vue

@@ -1,5 +1,26 @@
 <template>
   <el-drawer v-model="showSettings" :withHeader="false" :lock-scroll="false" direction="rtl" size="300px">
+    <div class="setting-drawer-title">
+      <h3 class="drawer-title">菜单导航设置</h3>
+    </div>
+    <div class="nav-wrap">
+      <el-tooltip content="左侧菜单" placement="bottom">
+        <div class="item left" @click="handleNavType(1)" :class="{ activeItem: navType == 1 }">
+          <b></b><b></b>
+        </div>
+      </el-tooltip>
+
+      <el-tooltip content="混合菜单" placement="bottom">
+        <div class="item mix" @click="handleNavType(2)" :class="{ activeItem: navType == 2 }">
+          <b></b><b></b>
+        </div>
+      </el-tooltip>
+      <el-tooltip content="顶部菜单" placement="bottom">
+        <div class="item top" @click="handleNavType(3)" :class="{ activeItem: navType == 3 }">
+          <b></b><b></b>
+        </div>
+      </el-tooltip>
+    </div>
     <div class="setting-drawer-title">
       <h3 class="drawer-title">主题风格设置</h3>
     </div>
@@ -35,13 +56,6 @@
 
     <h3 class="drawer-title">系统布局配置</h3>
 
-    <div class="drawer-item">
-      <span>开启 TopNav</span>
-      <span class="comp-style">
-        <el-switch v-model="settingsStore.topNav" @change="topNavChange" class="drawer-switch" />
-      </span>
-    </div>
-
     <div class="drawer-item">
       <span>开启 Tags-Views</span>
       <span class="comp-style">
@@ -103,19 +117,12 @@ const appStore = useAppStore()
 const settingsStore = useSettingsStore()
 const permissionStore = usePermissionStore()
 const showSettings = ref(false)
+const navType = ref(settingsStore.navType)
 const theme = ref(settingsStore.theme)
 const sideTheme = ref(settingsStore.sideTheme)
 const storeSettings = computed(() => settingsStore)
 const predefineColors = ref(["#409EFF", "#ff4500", "#ff8c00", "#ffd700", "#90ee90", "#00ced1", "#1e90ff", "#c71585"])
 
-/** 是否需要topnav */
-function topNavChange(val) {
-  if (!val) {
-    appStore.toggleSideBarHide(false)
-    permissionStore.setSidebarRouters(permissionStore.defaultRoutes)
-  }
-}
-
 /** 是否需要dynamicTitle */
 function dynamicTitleChange() {
   useSettingsStore().setTitle(useSettingsStore().title)
@@ -131,10 +138,34 @@ function handleTheme(val) {
   sideTheme.value = val
 }
 
+function handleNavType(val) {
+  settingsStore.navType = val
+  navType.value = val
+}
+
+/** 菜单导航设置 */
+watch(() => navType, val => {
+  if (val.value == 1) {
+    appStore.sidebar.opened = true
+    appStore.toggleSideBarHide(false)
+  }
+  if (val.value == 2) {
+    appStore.sidebar.opened = true
+  }
+  if (val.value == 3) {
+    appStore.sidebar.opened = false
+    appStore.toggleSideBarHide(true)
+  }
+  if ([1, 3].includes(val.value)) {
+      permissionStore.setSidebarRouters(permissionStore.defaultRoutes)
+  }
+  }, { immediate: true, deep: true }
+)
+
 function saveSetting() {
   proxy.$modal.loading("正在保存到本地,请稍候...")
   let layoutSetting = {
-    "topNav": storeSettings.value.topNav,
+    "navType": storeSettings.value.navType,
     "tagsView": storeSettings.value.tagsView,
     "tagsIcon": storeSettings.value.tagsIcon,
     "fixedHeader": storeSettings.value.fixedHeader,
@@ -218,4 +249,67 @@ defineExpose({
     margin: -3px 8px 0px 0px;
   }
 }
+
+// 导航模式
+.nav-wrap {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  margin-top: 10px;
+  margin-bottom: 20px;
+
+  .activeItem {
+    border: 2px solid var(--el-color-primary) !important;
+  }
+
+  .item {
+    position: relative;
+    margin-right: 16px;
+    cursor: pointer;
+    width: 56px;
+    height: 48px;
+    border-radius: 4px;
+    background: #f0f2f5;
+    border: 2px solid transparent;
+  }
+
+  .left {
+    b:first-child {
+      display: block;
+      height: 30%;
+      background: #fff;
+    }
+    b:last-child {
+      width: 30%;
+      background: #1b2a47;
+      position: absolute;
+      height: 100%;
+      top: 0;
+      border-radius: 4px 0 0 4px;
+    }
+  }
+  .mix {
+    b:first-child {
+      border-radius: 4px 4px 0 0;
+      display: block;
+      height: 30%;
+      background: #1b2a47;
+    }
+    b:last-child {
+      width: 30%;
+      background: #1b2a47;
+      position: absolute;
+      height: 70%;
+      border-radius: 0 0 0 4px;
+    }
+  }
+  .top {
+    b:first-child {
+      display: block;
+      height: 30%;
+      background: #1b2a47;
+      border-radius: 4px 4px 0 0;
+    }
+  }
+}
 </style>

+ 6 - 1
src/layout/components/Sidebar/Logo.vue

@@ -34,6 +34,9 @@ const getLogoBackground = computed(() => {
   if (settingsStore.isDark) {
     return 'var(--sidebar-bg)'
   }
+  if (settingsStore.navType == 3) {
+    return variables.menuLightBg
+  }
   return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBg
 })
 
@@ -42,6 +45,9 @@ const getLogoTextColor = computed(() => {
   if (settingsStore.isDark) {
     return 'var(--sidebar-text)'
   }
+  if (settingsStore.navType == 3) {
+    return variables.menuLightText
+  }
   return sideTheme.value === 'theme-dark' ? '#fff' : variables.menuLightText
 })
 </script>
@@ -58,7 +64,6 @@ const getLogoTextColor = computed(() => {
 
 .sidebar-logo-container {
   position: relative;
-  width: 100%;
   height: 50px;
   line-height: 50px;
   background: v-bind(getLogoBackground);

+ 106 - 0
src/layout/components/TopBar/index.vue

@@ -0,0 +1,106 @@
+<template>
+  <el-menu class="topbar-menu" :ellipsis="false" :default-active="activeMenu" :active-text-color="theme" mode="horizontal">
+    <sidebar-item :key="route.path + index" v-for="(route, index) in topMenus" :item="route" :base-path="route.path" />
+
+    <el-sub-menu index="more" class="el-sub-menu__hide-arrow" v-if="moreRoutes.length > 0">
+      <template #title>
+        <span>更多菜单</span>
+      </template>
+      <sidebar-item :key="route.path + index" v-for="(route, index) in moreRoutes" :item="route" :base-path="route.path" />
+    </el-sub-menu>
+  </el-menu>
+</template>
+
+<script setup>
+import SidebarItem from '../Sidebar/SidebarItem'
+import useAppStore from '@/store/modules/app'
+import useSettingsStore from '@/store/modules/settings'
+import usePermissionStore from '@/store/modules/permission'
+
+const route = useRoute()
+const appStore = useAppStore()
+const settingsStore = useSettingsStore()
+const permissionStore = usePermissionStore()
+
+const sidebarRouters = computed(() => permissionStore.sidebarRouters)
+const theme = computed(() => settingsStore.theme)
+const device = computed(() => appStore.device)
+const activeMenu = computed(() => {
+  const { meta, path } = route
+  if (meta.activeMenu) {
+    return meta.activeMenu
+  }
+  return path
+})
+
+const visibleNumber = ref(5)
+const topMenus = computed(() => {
+  return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(0, visibleNumber.value)
+})
+const moreRoutes = computed(() => {
+  return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(visibleNumber.value, sidebarRouters.value.length - visibleNumber.value)
+})
+function setVisibleNumber() {
+  const width = document.body.getBoundingClientRect().width / 3
+  visibleNumber.value = parseInt(width / 85)
+}
+
+onMounted(() => {
+  window.addEventListener('resize', setVisibleNumber)
+})
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', setVisibleNumber)
+})
+
+onMounted(() => {
+  setVisibleNumber()
+})
+</script>
+
+<style lang="scss">
+.topbar-menu.el-menu--horizontal > .el-menu-item {
+  float: left;
+  height: 50px !important;
+  line-height: 50px !important;
+  color: #303133 !important;
+  padding: 0 5px !important;
+  margin: 0 10px !important;
+}
+
+.el-sub-menu.is-active .svg-icon, .el-menu-item.is-active .svg-icon + span, .el-sub-menu.is-active .svg-icon + span, .el-sub-menu.is-active .el-sub-menu__title span {
+  color: v-bind(theme);
+}
+
+/* sub-menu item */
+.topbar-menu.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
+  float: left;
+  height: 62px !important;
+  line-height: 50px !important;
+  color: #303133 !important;
+  padding: 0 5px !important;
+  margin: 0 20px -4px!important;
+}
+
+/* 背景色隐藏 */
+.topbar-menu.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, .topbar-menu.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, .topbar-menu.el-menu--horizontal>.el-submenu .el-submenu__title:hover {
+  background-color: #ffffff;
+}
+
+/* 图标右间距 */
+.topbar-menu .svg-icon {
+  margin-right: 4px;
+}
+
+/* topbar more arrow */
+.topbar-menu .el-sub-menu .el-sub-menu__icon-arrow {
+  position: static;
+  margin-left: 8px;
+  margin-top: 0px;
+  display: block !important;
+}
+
+/* menu__title el-menu-item */
+.topbar-menu.el-menu--horizontal .el-sub-menu__title, .topbar-menu.el-menu--horizontal .el-menu-item {
+  height: 60px;
+}
+</style>

+ 2 - 2
src/settings.js

@@ -15,9 +15,9 @@ export default {
   showSettings: true,
 
   /**
-   * 是否显示顶部导航
+   * 菜单导航模式 1、纯左侧 2、混合(左侧+顶部) 3、纯顶部
    */
-  topNav: false,
+  navType: 1,
 
   /**
    * 是否显示 tagsView

+ 2 - 2
src/store/modules/settings.js

@@ -5,7 +5,7 @@ import { useDynamicTitle } from '@/utils/dynamicTitle'
 const isDark = useDark()
 const toggleDark = useToggle(isDark)
 
-const { sideTheme, showSettings, topNav, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
+const { sideTheme, showSettings, navType, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
 
 const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
 
@@ -17,7 +17,7 @@ const useSettingsStore = defineStore(
       theme: storageSetting.theme || '#409EFF',
       sideTheme: storageSetting.sideTheme || sideTheme,
       showSettings: showSettings,
-      topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
+      navType: storageSetting.navType === undefined ? navType : storageSetting.navType,
       tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
       tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon,
       fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,