Ver Fonte

feat✨ 添加顶部菜单功能

wenjianzhang há 4 anos atrás
pai
commit
5063d7fdc7

+ 145 - 0
src/components/TopNav/index.vue

@@ -0,0 +1,145 @@
+<template>
+  <el-menu
+    :default-active="activeMenu"
+    mode="horizontal"
+    @select="handleSelect"
+  >
+    <template v-for="(item, index) in topMenus">
+      <el-menu-item
+        v-if="index < visibleNumber"
+        :key="index"
+        :index="item.path"
+      ><svg-icon :icon-class="item.meta.icon" />
+        {{ item.meta.title }}</el-menu-item>
+    </template>
+
+    <!-- 顶部菜单超出数量折叠 -->
+    <el-submenu v-if="topMenus.length > visibleNumber" index="more">
+      <template slot="title">更多菜单</template>
+      <template v-for="(item, index) in topMenus">
+        <el-menu-item
+          v-if="index >= visibleNumber"
+          :key="index"
+          :index="item.path"
+        ><svg-icon :icon-class="item.meta.icon" />
+          {{ item.meta.title }}</el-menu-item>
+      </template>
+    </el-submenu>
+  </el-menu>
+</template>
+
+<script>
+import { constantRoutes } from '@/router'
+
+export default {
+  data() {
+    return {
+      // 顶部栏初始数
+      visibleNumber: 5,
+      // 是否为首次加载
+      isFrist: false
+    }
+  },
+  computed: {
+    // 顶部显示菜单
+    topMenus() {
+      return this.routers.map((menu) => ({
+        ...menu,
+        children: undefined
+      }))
+    },
+    // 所有的路由信息
+    routers() {
+      console.log(this.$store.state)
+      return this.$store.state.permission.topbarRouters
+    },
+    // 设置子路由
+    childrenMenus() {
+      var childrenMenus = []
+      this.routers.map((router) => {
+        for (var item in router.children) {
+          if (router.children[item].parentPath === undefined) {
+            router.children[item].parentPath = router.path
+          }
+          childrenMenus.push(router.children[item])
+        }
+      })
+      return constantRoutes.concat(childrenMenus)
+    },
+    // 默认激活的菜单
+    activeMenu() {
+      const path = this.$route.path
+      let activePath = this.routers[0].path
+      if (path.lastIndexOf('/') > 0) {
+        const tmpPath = path.substring(1, path.length)
+        activePath = '/' + tmpPath.substring(0, tmpPath.indexOf('/'))
+      } else if (path === '/index' || path === '') {
+        if (!this.isFrist) {
+          // eslint-disable-next-line vue/no-side-effects-in-computed-properties
+          this.isFrist = true
+        } else {
+          activePath = 'index'
+        }
+      }
+      this.activeRoutes(activePath)
+      return activePath
+    }
+  },
+  mounted() {
+    this.setVisibleNumber()
+  },
+  methods: {
+    // 根据宽度计算设置显示栏数
+    setVisibleNumber() {
+      const width = document.body.getBoundingClientRect().width - 200
+      const elWidth = this.$el.getBoundingClientRect().width
+      const menuItemNodes = this.$el.children
+      const menuWidth = Array.from(menuItemNodes).map(
+        (i) => i.getBoundingClientRect().width
+      )
+      this.visibleNumber = (
+        parseInt(width - elWidth) / parseInt(menuWidth)
+      ).toFixed(0)
+    },
+    // 菜单选择事件
+    handleSelect(key, keyPath) {
+      if (key.indexOf('http://') !== -1 || key.indexOf('https://') !== -1) {
+        // http(s):// 路径新窗口打开
+        window.open(key, '_blank')
+      } else {
+        this.activeRoutes(key)
+      }
+    },
+    // 当前激活的路由
+    activeRoutes(key) {
+      var routes = []
+      if (this.childrenMenus && this.childrenMenus.length > 0) {
+        this.childrenMenus.map((item) => {
+          if (key === item.parentPath || (key === 'index' && item.path === '')) {
+            routes.push(item)
+          }
+        })
+      }
+      this.$store.commit('permission/SET_SIDEBAR_ROUTERS', routes)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-menu--horizontal > .el-menu-item {
+  float: left;
+  height: 50px;
+  line-height: 50px;
+  margin: 0;
+  border-bottom: 3px solid transparent;
+  color: #999093;
+  padding: 0 5px;
+  margin: 0 10px;
+}
+
+.el-menu--horizontal > .el-menu-item.is-active {
+  border-bottom: 3px solid #409eff;
+  color: #303133;
+}
+</style>

+ 22 - 2
src/layout/components/Navbar.vue

@@ -2,7 +2,8 @@
   <div class="navbar">
     <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
 
-    <breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
+    <breadcrumb v-if="!topNav" id="breadcrumb-container" class="breadcrumb-container" />
+    <top-nav v-if="topNav" id="topmenu-container" class="breadcrumb-container" />
 
     <div class="right-menu">
       <template v-if="device!=='mobile'">
@@ -33,6 +34,7 @@
 <script>
 import { mapGetters } from 'vuex'
 import Breadcrumb from '@/components/Breadcrumb'
+import TopNav from '@/components/TopNav'
 import Hamburger from '@/components/Hamburger'
 import Screenfull from '@/components/Screenfull'
 import Search from '@/components/HeaderSearch'
@@ -40,6 +42,7 @@ import Search from '@/components/HeaderSearch'
 export default {
   components: {
     Breadcrumb,
+    TopNav,
     Hamburger,
     Screenfull,
     Search
@@ -49,7 +52,24 @@ export default {
       'sidebar',
       'avatar',
       'device'
-    ])
+    ]),
+    setting: {
+      get() {
+        return this.$store.state.settings.showSettings
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'showSettings',
+          value: val
+        })
+      }
+    },
+    topNav: {
+      get() {
+        return this.$store.state.settings.topNav
+      }
+    }
+
   },
   methods: {
     toggleSideBar() {

+ 20 - 0
src/layout/components/Settings/index.vue

@@ -41,6 +41,12 @@
         <div class="setting-drawer-title">
           布局设置
         </div>
+
+        <div class="drawer-item">
+          <span>开启 TopNav</span>
+          <el-switch v-model="topNav" class="drawer-switch" />
+        </div>
+
         <div class="drawer-item">
           <span>开启任务栏</span>
           <el-switch v-model="tagsView" :active-color="activeColor" class="drawer-switch" />
@@ -88,6 +94,20 @@ export default {
         })
       }
     },
+    topNav: {
+      get() {
+        return this.$store.state.settings.topNav
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'topNav',
+          value: val
+        })
+        if (!val) {
+          this.$store.commit('permission/SET_SIDEBAR_ROUTERS', this.$store.state.permission.defaultRoutes)
+        }
+      }
+    },
     tagsView: {
       get() {
         return this.$store.state.settings.tagsView

+ 8 - 2
src/layout/components/Sidebar/index.vue

@@ -12,7 +12,13 @@
         :collapse-transition="true"
         mode="vertical"
       >
-        <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
+        <sidebar-item
+          v-for="(route) in sidebarRouters"
+          :key="route.path"
+          :item="route"
+          :base-path="route.path"
+        />
+
       </el-menu>
     </el-scrollbar>
   </div>
@@ -28,7 +34,7 @@ export default {
   components: { SidebarItem, Logo },
   computed: {
     ...mapGetters([
-      'permission_routes',
+      'sidebarRouters',
       'sidebar'
     ]),
     activeMenu() {

+ 1 - 1
src/router/index.js

@@ -108,7 +108,7 @@ export const asyncRoutes = [
 ]
 
 const createRouter = () => new Router({
-  // mode: 'history', // require service support
+  mode: 'history', // require service support
   scrollBehavior: () => ({ y: 0 }),
   routes: constantRoutes
 })

+ 5 - 0
src/settings.js

@@ -7,6 +7,11 @@ module.exports = {
    */
   showSettings: true,
 
+  /**
+   * 是否显示顶部导航
+   */
+  topNav: true,
+
   /**
    * @type {boolean} true | false
    * @description Whether need tagsView

+ 3 - 0
src/store/getters.js

@@ -11,6 +11,9 @@ const getters = {
   roles: state => state.user.roles,
   permisaction: state => state.user.permisaction,
   permission_routes: state => state.permission.routes,
+  topbarRouters: state => state.permission.topbarRouters,
+  defaultRoutes: state => state.permission.defaultRoutes,
+  sidebarRouters: state => state.permission.sidebarRouters,
   errorLogs: state => state.errorLog.logs,
   appInfo: state => state.system.info
 }

+ 25 - 4
src/store/modules/permission.js

@@ -44,7 +44,7 @@ export function generaMenu(routes, data) {
       meta: {
         title: item.title,
         icon: item.icon,
-        noCache: true
+        noCache: item.noCache
       }
     }
     if (item.children) {
@@ -55,8 +55,7 @@ export function generaMenu(routes, data) {
 }
 
 export const loadView = (view) => { // 路由懒加载
-  return (resolve) => require(['@/views' + view], resolve)
-  // return () => import(`@/views${view}`)
+  return (resolve) => require([`@/views${view}`], resolve)
 }
 
 /**
@@ -103,13 +102,30 @@ export function filterAsyncPathRoutes(routes, paths) {
 
 const state = {
   routes: [],
-  addRoutes: []
+  addRoutes: [],
+  defaultRoutes: [],
+  topbarRouters: [],
+  sidebarRouters: []
 }
 
 const mutations = {
   SET_ROUTES: (state, routes) => {
     state.addRoutes = routes
     state.routes = constantRoutes.concat(routes)
+  },
+  SET_DEFAULT_ROUTES: (state, routes) => {
+    state.defaultRoutes = constantRoutes.concat(routes)
+  },
+  SET_TOPBAR_ROUTES: (state, routes) => {
+    // 顶部导航菜单默认添加统计报表栏指向首页
+    const index = [{
+      path: 'index',
+      meta: { title: '统计报表', icon: 'dashboard' }
+    }]
+    state.topbarRouters = routes.concat(index)
+  },
+  SET_SIDEBAR_ROUTERS: (state, routes) => {
+    state.sidebarRouters = routes
   }
 }
 
@@ -133,6 +149,11 @@ const actions = {
           generaMenu(asyncRoutes, loadMenuData)
           asyncRoutes.push({ path: '*', redirect: '/', hidden: true })
           commit('SET_ROUTES', asyncRoutes)
+          const sidebarRoutes = []
+          generaMenu(sidebarRoutes, loadMenuData)
+          commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
+          commit('SET_DEFAULT_ROUTES', sidebarRoutes)
+          commit('SET_TOPBAR_ROUTES', sidebarRoutes)
           resolve(asyncRoutes)
         }
       }).catch(error => {

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

@@ -1,11 +1,12 @@
 import variables from '@/styles/element-variables.scss'
 import defaultSettings from '@/settings'
 
-const { showSettings, tagsView, fixedHeader, sidebarLogo, themeStyle } = defaultSettings
+const { showSettings, topNav, tagsView, fixedHeader, sidebarLogo, themeStyle } = defaultSettings
 
 const state = {
   theme: variables.theme,
   showSettings: showSettings,
+  topNav: topNav,
   tagsView: tagsView,
   fixedHeader: fixedHeader,
   sidebarLogo: sidebarLogo,

+ 285 - 231
src/styles/admin.scss

@@ -5,234 +5,288 @@
 
  /** 基础通用 **/
  .pt5 {
-   padding-top: 5px;
- }
-
- .pr5 {
-   padding-right: 5px;
- }
-
- .pb5 {
-   padding-bottom: 5px;
- }
-
- .mt5 {
-   margin-top: 5px;
- }
-
- .mr5 {
-   margin-right: 5px;
- }
-
- .mb5 {
-   margin-bottom: 5px;
- }
-
- .mb8 {
-   margin-bottom: 8px;
- }
-
- .ml5 {
-   margin-left: 5px;
- }
-
- .mt10 {
-   margin-top: 10px;
- }
-
- .mr10 {
-   margin-right: 10px;
- }
-
- .mb10 {
-   margin-bottom: 10px;
- }
-
- .ml0 {
-   margin-left: 10px;
- }
-
- .mt20 {
-   margin-top: 20px;
- }
-
- .mr20 {
-   margin-right: 20px;
- }
-
- .mb20 {
-   margin-bottom: 20px;
- }
-
- .m20 {
-   margin-left: 20px;
- }
-
- .el-dialog {
-   margin-top: 6vh !important;
- }
-
- .el-table .el-table__header-wrapper th {
-   word-break: break-word;
-   background-color: #f8f8f9;
-   color: #515a6e;
-   height: 40px;
-   font-size: 13px;
- }
-
- /** 表单布局 **/
- .form-header {
-   font-size: 15px;
-   color: #6379bb;
-   border-bottom: 1px solid #ddd;
-   margin: 8px 10px 25px 10px;
-   padding-bottom: 5px
- }
-
- /** 表格布局 **/
- .pagination-container {
-   position: relative;
-   height: 25px;
-   margin-bottom: 10px;
-   margin-top: 15px;
-   padding: 10px 20px !important;
- }
-
- .pagination-container .el-pagination {
-   right: 0;
-   position: absolute;
- }
-
- .el-table .fixed-width .el-button--mini {
-   color: #409EFF;
-   padding-left: 0;
-   padding-right: 0;
-   width: inherit;
- }
-
- .el-tree-node__content>.el-checkbox {
-   margin-right: 8px;
- }
-
- .list-group-striped>.list-group-item {
-   border-left: 0;
-   border-right: 0;
-   border-radius: 0;
-   padding-left: 0;
-   padding-right: 0;
- }
-
- .list-group {
-   padding-left: 0px;
-   list-style: none;
- }
-
- .list-group-item {
-   border-bottom: 1px solid #e7eaec;
-   border-top: 1px solid #e7eaec;
-   margin-bottom: -1px;
-   padding: 11px 0px;
-   font-size: 13px;
- }
-
- .pull-right {
-   float: right !important;
- }
-
- .el-card__header {
-   padding: 14px 15px 7px;
-   min-height: 40px;
- }
-
- .el-card__body {
-   padding: 15px 20px 20px 20px;
- }
-
- .card-box {
-   padding-right: 15px;
-   padding-left: 15px;
-   margin-bottom: 10px;
- }
-
- /* text color */
- .text-navy {
-   color: #1ab394;
- }
-
- .text-primary {
-   color: inherit;
- }
-
- .text-success {
-   color: #1c84c6;
- }
-
- .text-info {
-   color: #23c6c8;
- }
-
- .text-warning {
-   color: #f8ac59;
- }
-
- .text-danger {
-   color: #ed5565;
- }
-
- .text-muted {
-   color: #888888;
- }
-
- /* image */
- .img-circle {
-   border-radius: 50%;
- }
-
- .img-lg {
-   width: 120px;
-   height: 120px;
- }
-
- .avatar-upload-preview {
-   position: absolute;
-   top: 50%;
-   transform: translate(50%, -50%);
-   width: 180px;
-   height: 180px;
-   border-radius: 50%;
-   box-shadow: 0 0 4px #ccc;
-   overflow: hidden;
- }
-
- .el-tabs__item {
-   font-size: 13px;
- }
-
-
- .demo-drawer__content {
-   display: flex;
-   flex-direction: column;
-   height: 100%
- }
-
- .demo-drawer__content form {
-   flex: 1
- }
-
- .demo-drawer__footer {
-   display: flex
- }
-
- .demo-drawer__footer button {
-   flex: 1
- }
-
- .el-drawer__body {
-   padding: 20px
- }
-
- // Drawer 抽屉增加滚动条
- .el-drawer {
-   overflow-y: scroll;
-   
- }
+  padding-top: 5px;
+}
+
+.pr5 {
+  padding-right: 5px;
+}
+
+.pb5 {
+  padding-bottom: 5px;
+}
+
+.mt5 {
+  margin-top: 5px;
+}
+
+.mr5 {
+  margin-right: 5px;
+}
+
+.mb5 {
+  margin-bottom: 5px;
+}
+
+.mb8 {
+  margin-bottom: 8px;
+}
+
+.ml5 {
+  margin-left: 5px;
+}
+
+.mt10 {
+  margin-top: 10px;
+}
+
+.mr10 {
+  margin-right: 10px;
+}
+
+.mb10 {
+  margin-bottom: 10px;
+}
+
+.ml0 {
+  margin-left: 10px;
+}
+
+.mt20 {
+  margin-top: 20px;
+}
+
+.mr20 {
+  margin-right: 20px;
+}
+
+.mb20 {
+  margin-bottom: 20px;
+}
+
+.m20 {
+  margin-left: 20px;
+}
+
+.el-dialog:not(.is-fullscreen) {
+  margin-top: 6vh !important;
+}
+
+.el-table {
+
+  .el-table__header-wrapper,
+  .el-table__fixed-header-wrapper {
+    th {
+      word-break: break-word;
+      background-color: #f8f8f9;
+      color: #515a6e;
+      height: 40px;
+      font-size: 13px;
+    }
+  }
+
+  .el-table__body-wrapper {
+    .el-button [class*="el-icon-"]+span {
+      margin-left: 1px;
+    }
+  }
+}
+
+/** 表单布局 **/
+.form-header {
+  font-size: 15px;
+  color: #6379bb;
+  border-bottom: 1px solid #ddd;
+  margin: 8px 10px 25px 10px;
+  padding-bottom: 5px
+}
+
+/** 表格布局 **/
+.pagination-container {
+  position: relative;
+  height: 25px;
+  margin-bottom: 10px;
+  margin-top: 15px;
+  padding: 10px 20px !important;
+}
+
+/* tree border */
+.tree-border {
+  margin-top: 5px;
+  border: 1px solid #e5e6e7;
+  background: #FFFFFF none;
+  border-radius: 4px;
+}
+
+.pagination-container .el-pagination {
+  right: 0;
+  position: absolute;
+}
+
+.el-table .fixed-width .el-button--mini {
+  padding-left: 0;
+  padding-right: 0;
+  width: inherit;
+}
+
+.el-tree-node__content>.el-checkbox {
+  margin-right: 8px;
+}
+
+.list-group-striped>.list-group-item {
+  border-left: 0;
+  border-right: 0;
+  border-radius: 0;
+  padding-left: 0;
+  padding-right: 0;
+}
+
+.list-group {
+  padding-left: 0px;
+  list-style: none;
+}
+
+.list-group-item {
+  border-bottom: 1px solid #e7eaec;
+  border-top: 1px solid #e7eaec;
+  margin-bottom: -1px;
+  padding: 11px 0px;
+  font-size: 13px;
+}
+
+.pull-right {
+  float: right !important;
+}
+
+.el-card__header {
+  padding: 14px 15px 7px;
+  min-height: 40px;
+}
+
+.el-card__body {
+  padding: 15px 20px 20px 20px;
+}
+
+.card-box {
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-bottom: 10px;
+}
+
+/* button color */
+.el-button--cyan.is-active,
+.el-button--cyan:active {
+  background: #20B2AA;
+  border-color: #20B2AA;
+  color: #FFFFFF;
+}
+
+.el-button--cyan:focus,
+.el-button--cyan:hover {
+  background: #48D1CC;
+  border-color: #48D1CC;
+  color: #FFFFFF;
+}
+
+.el-button--cyan {
+  background-color: #20B2AA;
+  border-color: #20B2AA;
+  color: #FFFFFF;
+}
+
+/* submenu item */
+.el-menu--horizontal>.el-submenu .el-submenu__title {
+  height: 50px !important;
+  line-height: 50px !important;
+}
+
+/* text color */
+.text-navy {
+  color: #1ab394;
+}
+
+.text-primary {
+  color: inherit;
+}
+
+.text-success {
+  color: #1c84c6;
+}
+
+.text-info {
+  color: #23c6c8;
+}
+
+.text-warning {
+  color: #f8ac59;
+}
+
+.text-danger {
+  color: #ed5565;
+}
+
+.text-muted {
+  color: #888888;
+}
+
+/* image */
+.img-circle {
+  border-radius: 50%;
+}
+
+.img-lg {
+  width: 120px;
+  height: 120px;
+}
+
+.avatar-upload-preview {
+  position: absolute;
+  top: 50%;
+  transform: translate(50%, -50%);
+  width: 200px;
+  height: 200px;
+  border-radius: 50%;
+  box-shadow: 0 0 4px #ccc;
+  overflow: hidden;
+}
+
+/* 拖拽列样式 */
+.sortable-ghost {
+  opacity: .8;
+  color: #fff !important;
+  background: #42b983 !important;
+}
+
+.top-right-btn {
+  position: relative;
+  float: right;
+}
+
+
+.demo-drawer__content {
+  display: flex;
+  flex-direction: column;
+  height: 100%
+}
+
+.demo-drawer__content form {
+  flex: 1
+}
+
+.demo-drawer__footer {
+  display: flex
+}
+
+.demo-drawer__footer button {
+  flex: 1
+}
+
+.el-drawer__body {
+  padding: 20px
+}
+
+// Drawer 抽屉增加滚动条
+.el-drawer {
+  overflow-y: scroll;
+  
+}

+ 31 - 25
src/styles/sidebar.scss

@@ -8,6 +8,7 @@
   }
 
   .sidebar-container {
+    -webkit-transition: width .28s;
     transition: width 0.28s;
     width: $sideBarWidth !important;
     background-color: $menuBg;
@@ -19,7 +20,8 @@
     left: 0;
     z-index: 1001;
     overflow: hidden;
-    box-shadow: 2px 0 6px rgba(0,21,41,.35);
+    -webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
+    box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
 
     // reset element-ui css
     .horizontal-collapse-transition {
@@ -64,43 +66,42 @@
       width: 100% !important;
     }
 
+    .el-menu-item,
+    .el-submenu__title {
+      overflow: hidden !important;
+      text-overflow: ellipsis !important;
+      white-space: nowrap !important;
+    }
+
     // menu hover
     .submenu-title-noDropdown,
     .el-submenu__title {
       &:hover {
-        background-color: rgba(0,0,0,0.06)!important;
-        // background-color: transparent!important;
+        background-color: rgba(0, 0, 0, 0.06) !important;
       }
     }
 
-    .is-active>.el-submenu__title {
-      // color: $subMenuActiveText !important;
+    & .theme-dark .is-active>.el-submenu__title {
+      color: $subMenuActiveText !important;
     }
 
     & .nest-menu .el-submenu>.el-submenu__title,
     & .el-submenu .el-menu-item {
       min-width: $sideBarWidth !important;
-      // background-color: $subMenuBg !important;
-      position: relative;
 
-     &:hover {
-        background-color: rgba(0,0,0,0.06)!important;
-        // &::before{
-        //   content: "";
-        //   position: absolute;
-        //   top: 0;
-        //   bottom: 0;
-        //   width: 2px;
-        //   background-color: $menuActiveText;
-        //   height: 100%;
-        //   right: 0;
-        // }
+      &:hover {
+        background-color: rgba(0, 0, 0, 0.06) !important;
       }
     }
-  }
 
-  .el-submenu{
-    background-color: transparent!important;
+    & .theme-dark .nest-menu .el-submenu>.el-submenu__title,
+    & .theme-dark .el-submenu .el-menu-item {
+      background-color: $subMenuBg !important;
+
+      &:hover {
+        background-color: $subMenuHover !important;
+      }
+    }
   }
 
   .hideSidebar {
@@ -135,9 +136,6 @@
           margin-left: 20px;
         }
 
-        .el-submenu__icon-arrow {
-          display: none;
-        }
       }
     }
 
@@ -197,6 +195,14 @@
     }
   }
 
+  .nest-menu .el-submenu>.el-submenu__title,
+  .el-menu-item {
+    &:hover {
+      // you can use $subMenuHover
+      background-color: rgba(0, 0, 0, 0.06) !important;
+    }
+  }
+
   // the scroll bar appears when the subMenu is too long
   >.el-menu--popup {
     max-height: 100vh;