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