vue+elementUI项目中使用NavMenu 导航菜单

在elementUI官网组件中NavMenu 导航菜单的使用只是简单地运用,在实践项目中还需要配合路由配置。一般后台管理系统不只是导航菜单的单独应用,一般会配合页面标签和面包屑功能,一步一步进行,做导航菜单功能。
网上也有好多开源的后台管理系统,github上可以直接下载。我在网上找了一个vue+elementui后台管理系统,配合功能详解,相信对你会有帮助。

1.准备工作

vue项目搭建,本人习惯用vue-cli脚手架进行快速搭建框架,大家可以直接在网上搜索,安装elementui,不太熟悉的可以点击这里,elementUI官方文档

(1)切换到项目下,安装element-ui
# 推荐使用 npm 的方式安装,它能更好地和 webpack 打包工具配合使用。
npm i element-ui -S
(2)在项目中使用element-ui:

在main.js引入,并使用

import Vue from 'vue'
import App from './App'
import router from './router'

/*引入下面三行*/
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);


Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

这是全局应用,这样项目中的页面中就可以用引用elementui下的所有组件了。

2.功能实现

根据需要写好组件,包含公共组件及各模块所需组件,并且配置路由

(1)公共布局组件

主要代码存放结构如下图
在这里插入图片描述
布局组件LayOut.vue代码(其中有导航菜单,页面标签)
公共布局组件-----layout布局组件中存放内容(具体布局及样式根据需要而定)

<template>
    <el-container style="width:100%;height:100%">
        <side-bar></side-bar>
        <el-main>
            <!-- <template> -->
                <div>
                    <nav-bar></nav-bar>
                    <tags-view></tags-view>
                </div>
                <app-main></app-main>
            <!-- </template> -->
        </el-main>
    </el-container>
    
</template>
<script>
import SideBar from "@/components/common/sidebar/index"
import AppMain from "@/components/common/AppMain"
import NavBar from "@/components/common/navBar"
import TagsView from "@/components/common/tagsview"
export default {
    data(){
        return{

        }
    },
    components:{
        SideBar,
        AppMain,
        NavBar,
        TagsView
    }

}
</script>
<style lang="stylus" scoped>

</style>

下边是页面布局图
在这里插入图片描述

(2) 公共路由承载组件

公共承载组件----AppMain组件中存放内容
其中用vue内置transition标签设置动画,具体怎么设置可以在网上查到.
由于项目中引入了国际化功能,导航菜单中的国际化设置,读者引用时可以去掉。

<template>
    <section class="app-main clearBoth">
        <transition name="fade-transform" mode="out-in">
            <keep-alive>
                <router-view v-if="isRouterAlive && $route.meta.keepAlive" />
            </keep-alive>
        </transition>
        <transition name="fade-transform" mode="out-in"> 
            <router-view v-if="isRouterAlive && !$route.meta.keepAlive" />
        </transition>
    </section>
</template>
<script>
export default {
    data(){
        return{
            isRouterAlive:true
        }
    }
}
</script>
<style lang="stylus" scoped>
.clearBoth{
    clear both
    /* fade-transform */
    .fade-transform-leave-active,
    .fade-transform-enter-active {
        transition: all .5s;
    }

    .fade-transform-enter {
        opacity: 0;
        transform: translateX(-30px);
    }

    .fade-transform-leave-to {
        opacity: 0;
        transform: translateX(30px);
    }
}
</style>

菜单渲染组件sidebar(index.vue)其中使用vueX,共享侧边导航栏的收缩、展开状态,此功能如果不需要可以去掉。

<template>
  <div class="sidebar">
    <logo v-if="!isCollapse"/>
    <el-scrollbar wrap-class="scrollbar-wrapper">
      <el-menu
        :show-timeout="200" 
        :default-active="$route.path" 
        :collapse="isCollapse"
        mode="vertical" 
        :unique-opened="false"
        :collapse-transition="true"
        background-color="rgb(48, 65, 86)"
        text-color="#fff"
        class="el-menu-vertical-demo"
      >
        <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
      </el-menu>
    </el-scrollbar>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
export default {
  components: { Logo,SidebarItem },
  computed: {
    ...mapGetters([
      'sidebar'
    ]),
    routes() {
      return this.$router.options.routes
      console.log(routes)
    },
    isCollapse() {
      return !this.sidebar.opened   //获取侧边栏收缩状态
    }
  }
}
</script>

菜单渲染组件SidebarItem.vue

<template>
  <fragment v-if="!item.hidden">
    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="generateTitle(onlyOneChild.meta.title)" />
        </el-menu-item>
      </app-link>
    </template>

    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
      <template slot="title">
        <i :class="item.meta.icon"></i>
        <span slot="title">{{generateTitle(item.name)}}</span>
        <!-- <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="generateTitle(item.meta.title)"/> -->
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      />
    </el-submenu>
  </fragment>
</template>

<script>
import path from 'path'
import { generateTitle } from '@/utils/i18n'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'


export default {
  name: 'SidebarItem',
  components: { Item, AppLink },
  props: {
    // route object
    item: {
      type: Object,
      required: true
    },
    isNest: {
      type: Boolean,
      default: false
    },
    basePath: {
      type: String,
      default: ''
    }
  },
  data() {
    this.onlyOneChild = null
    return {}
  },
  methods: {
    hasOneShowingChild(children = [], parent) {
      const showingChildren = children.filter(item => {
        if (item.hidden) {
          return false
        } else {
          // Temp set(will be used if only has one showing child)
          this.onlyOneChild = item
          return true
        }
      })

      // When there is only one child router, the child router is displayed by default
      if (showingChildren.length === 1) {
        return true
      }

      // Show parent if there are no child router to display
      if (showingChildren.length === 0) {
        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
        return true
      }

      return false
    },
    resolvePath(routePath) {
      if (isExternal(routePath)) {
        return routePath
      }
      if (isExternal(this.basePath)) {
        return this.basePath
      }
      return path.resolve(this.basePath, routePath)
    },

    generateTitle
  }
}
</script>

菜单渲染组件item.vue

<script>
export default {
  name: 'MenuItem',
  functional: true,
  props: {
    icon: {
      type: String,
      default: ''
    },
    title: {
      type: String,
      default: ''
    }
  },
  render(h, context) {
    const { icon, title } = context.props
    const vnodes = []

    if (icon) {
        vnodes.push(<i class={[icon, 'sub-el-icon']} />)
    }

    if (title) {
      vnodes.push(<span slot='title'>{(title)}</span>)
    }
    return vnodes
  }
}
</script>

路由配置(公共组件在头部引入,各模块组件按需引入)

import Vue from 'vue'
import Router from 'vue-router'
import login from '@/modules/login'
import register from '@/modules/register'
import home from '@/modules/home'
import LayOut from '@/components/common/LayOut'
import adduser from '@/modules/user-manage/adduser'
import deluser from '@/modules/user-manage/deluser'
import authorityManage from '@/modules/authority-manage'
import Tinymce from '@/modules/example/Tinymce'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'login',
      component: login,
      hidden: true ,     // (默认 false)当设置 true 的时候该路由不会在侧边栏出现 如401,login等页面
      meta: {
        keepAlive: false
      }
    },
    {
      path: '/register',
      name: 'register',
      hidden: false,  
      component: register,
      meta: {
        keepAlive: false
      }
    },
    {
      path: '/home',
      name: 'home',
      component: LayOut,
      redirect:'/home/index',
      meta: {
        keepAlive: false,
        title: 'home',
        icon: 'el-icon-s-home'
      },
      children:[
        {
          path: '/home/index',
          component:home,
          name: 'home',
          meta: {
            title: 'home',
            icon: 'el-icon-s-home'
          },
        }
      ]
    },
    {
      path: '/userManage',
      component: LayOut,
      name: 'userManage',
      redirect:'/userManage/adduser',
      meta: {
        title: 'userManage',
        icon: 'el-icon-user'
      },
      children: [
        {
            path: '/userManage/adduser',
            name: 'adduser',
            meta: {
                title: 'adduser',
            },
            component:adduser,
        },  
        {
            path: '/userManage/deluser',
            name: 'deluser',
            meta: {
                title: 'deluser',
            },
            component:deluser,
        },
      ]
    },
    {
        path: '/authority-manage',
        component: LayOut,
        name: 'authorityManage',
        children: [
          {
            path: '/authority-manage/index',
            component: authorityManage,
            name: 'authorityManage',
            meta: {
              title: 'authorityManage',
              icon: 'el-icon-lock'
            },
          }
        ]
    },
    {
      path: '/example',
      component: LayOut,
      redirect: '/example/tinymce',
      name: 'example',
      alwaysShow:true,      //如果菜单中只有一个子菜单时,true为显示此菜单,为false则只显示子菜单
      meta: {
        title: 'example',
        icon: 'el-icon-edit-outline'
      },
      children: [
        {
          path: '/example/tinymce',
          component: Tinymce,
          name: 'tinymce',
          meta: { title: 'tinymce', icon: 'edit' }
        },
      ]
    },
  ]
})

路由个配置项的详细讲解

(3)各模块对应组件

各模块组件这里不做介绍,根据需要写入内容,保持路由中引入正确即可
截图这是我写的小例子中的组件位置,看读者需要自行设计。
在这里插入图片描述
按照以上的操作可能在打包的时候element-UI的图标会不显示,出现报错如图
在这里插入图片描述

问题原因:

(1)查看 /build/webpack.base.conf.js 文件可以发现,woff 或 ttf 这些字体会经由 url-loader 处理后在 static/fonts 目录下生成相应的文件。
在这里插入图片描述
(2)也就是说实际应该通过 /static/fonts/** 路径来获取字体图标,而实际却是请求 /static/css/static/fonts/**,自然报 404 错误。
在这里插入图片描述

解决办法:

1、webpack module配置:(build目录下webpack.base.conf.js)

module: {
rules: [
...(config.dev.useEslint ? [createLintingRule()] : []),
{
    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
    loader: 'url-loader',
    options: {
        limit: 10000,
        name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
    }
}
]
},

2、webpack utils.js 修改:(build目录下utils.js)中添加 publicPath: ‘…/…/’

if (options.extract) {
    return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader',
        publicPath: '../../'   //添加这一句
    })
} else {
    return ['vue-style-loader'].concat(loaders)
}

这样就不会报错,图标也显示了.
以上只是侧边栏的配置希望对你有一些帮助,其他布局后续完善。

Logo

前往低代码交流专区

更多推荐