vue2使用过滤器实现菜单栏文字动态显示


前言

左侧菜单栏有缩进,所以不同级别的菜单名可显示的文字数不同。顶部菜单栏是下拉框,所以文字是固定的


一、过滤器filters

由于filters不能使用this为undefined,所以要在过滤器中使用data或者computed的数据,有两种方式。无论哪种方式都需要在export default的上面写上let that;
参考:https://blog.youkuaiyun.com/qq_41926464/article/details/131453022

<script>
let that;
export default {
	name: 'test',
	data(){}
}
</script>

1.data数据

data() {
    return {
      test: this.testFun()
    };
  },
  methods: {
    testFun() {
      // data在filters之前执行
      that = this;
    },
  },
  filters: {
    // that就是this
    ellipsis(str) {
      console.log(that);
    }
  }

2.beforeCreate

beforeCreate() {
    that = this;
  },
  filters: {
    // that就是this
    ellipsis(str) {
      console.log(that);
    }
  }

二、菜单栏文字动态显示

<!-- 字数超长显示提示信息  这个是含有子集的菜单,就是有>的(文字超长的话>会遮盖在文字下面)。ellipsis(numTooltip)这样的话会有两个参数,第一个参数默认是要改的数据-->
        <el-tooltip v-if="item.label.length > labelNUmnum" :content="item.label" effect="light" placement="top">
          <span>{{item.label | ellipsis(tip)}}</span>
        </el-tooltip>
computed: {
// 主要针对左侧菜单有缩进,三级菜单显示4个字,默认最多展示七个字。顶部菜单无缩进/左侧菜单栏收起之后,默认展示11个字
    numTooltip() {
      let num = 7;
      if (this.type === 'side-item') {
        num -= this.level;
      } else {
        num = 11;
      }
      return num;
    }
 },
filters: {
    // 截取菜单文字,超出部分显示省略号
    // 我这里是采用的传参的方式
    ellipsis(str, numTooltip) {
      if (str.length > numTooltip) {
        return `${str.slice(0, numTooltip)}...`;
      }
      return str;
    }
  }
<think>我们之前已经讨论过Vue2和ElementUI实现侧边导航栏与路由结合的基本方案。现在用户要求提供示例代码,并参考了站内引用[1]中提到的动态侧边导航栏实现,特别是涉及vuex和动态路由权限控制的部分。结合用户的需求和之前的回答,我们将提供更详细的实现方案,包括以下关键点:1.使用VueRouter的路由配置,并通过meta字段添加菜单信息2.创建可递归处理多级菜单的侧边栏组件(使用ElementUI的菜单组件)3.通过vuex管理菜单状态(如折叠状态)4.实现权限控制:根据用户权限动态生成可访问的路由菜单具体步骤:###1.路由配置(router/index.js)在路由配置中,我们使用meta字段来存储菜单的标题、图标和权限等信息。同时,我们将路由分为两种:常量路由(所有用户可见)和动态路由(根据权限动态添加)。```javascriptimportVuefrom'vue'importRouterfrom'vue-router'Vue.use(Router)//常量路由(所有用户可见)exportconstconstantRoutes=[{path:'/login',component:()=>import('@/views/login'),hidden:true//不在侧边栏显示},{path:'/',component:()=>import('@/layout/Layout'),redirect:'/dashboard',children:[{path:'dashboard',name:'Dashboard',component:()=>import('@/views/dashboard'),meta:{title:'控制台',icon:'dashboard',affix:true}}]}]//动态路由(需要根据权限动态添加)exportconstasyncRoutes=[{path:'/user',component:()=>import('@/layout/Layout'),redirect:'/user/list',name:'User',meta:{title:'用户管理',icon:'user',roles:['admin','editor']//允许访问的角色},children:[{path:'list',name:'UserList',component:()=>import('@/views/user/list'),meta:{title:'用户列表'}},{path:'detail/:id',name:'UserDetail',component:()=>import('@/views/user/detail'),hidden:true,//不在侧边栏显示meta:{title:'用户详情'}}]},//其他动态路由...]//创建路由实例constcreateRouter=()=>newRouter({mode:'history',routes:constantRoutes})constrouter=createRouter()//重置路由的函数(用于动态添加路由后刷新路由)exportfunctionresetRouter(){constnewRouter=createRouter()router.matcher=newRouter.matcher//重置路由匹配器}exportdefaultrouter```###2.侧边栏菜单组件(layout/components/Sidebar.vue)这个组件将根据路由生成菜单,并支持递归多级菜单。```vue<template><div><el-scrollbarwrap-class="scrollbar-wrapper"><el-menu:default-active="activeMenu":collapse="isCollapse":unique-opened="true"background-color="#304156"text-color="#bfcbd9"active-text-color="#409EFF"mode="vertical"router><sidebar-itemv-for="routeinpermission_routes":key="route.path":item="route":base-path="route.path"/></el-menu></el-scrollbar></div></template><script>import{mapGetters}from'vuex'importSidebarItemfrom'./SidebarItem'exportdefault{components:{SidebarItem},computed:{...mapGetters(['permission_routes','sidebar']),activeMenu(){constroute=this.$routeconst{meta,path}=route//如果设置了高亮的路径(如编辑页面需要高亮列表页),则返回该路径if(meta.activeMenu){returnmeta.activeMenu}returnpath},isCollapse(){return!this.sidebar.opened}}}</script>```###3.侧边栏菜单项组件(layout/components/SidebarItem.vue)这是一个递归组件,用于处理多级菜单。```vue<template><divv-if="!item.hidden"><!--只有一个子路由且该子路由没有children--><templatev-if="hasOneShowingChild(item.children,item)&&(!onlyOneChild.children||onlyOneChild.noShowingChildren)"><app-linkv-if="onlyOneChild.meta":to="resolvePath(onlyOneChild.path)"><el-menu-item:index="resolvePath(onlyOneChild.path)"><item:icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)":title="onlyOneChild.meta.title"/></el-menu-item></app-link></template><!--有多个子路由--><el-submenuv-else:index="resolvePath(item.path)"popper-append-to-body><templateslot="title"><itemv-if="item.meta":icon="item.meta.icon":title="item.meta.title"/></template><sidebar-itemv-for="childinitem.children":key="child.path":item="child":base-path="resolvePath(child.path)"/></el-submenu></div></template><script>importpathfrom'path'import{isExternal}from'@/utils/validate'importItemfrom'./Item'importAppLinkfrom'./Link'exportdefault{name:'SidebarItem',components:{Item,AppLink},props:{//路由对象item:{type:Object,required:true},//基础路径basePath:{type:String,default:''}},data(){//用于存储只有一个子路由的情况this.onlyOneChild=nullreturn{}},methods:{hasOneShowingChild(children=[],parent){constshowingChildren=children.filter(item=>{if(item.hidden){returnfalse}else{//临时设置(只有一个子路由时使用)this.onlyOneChild=itemreturntrue}})//当只有一个子路由时,显示该子路由if(showingChildren.length===1){returntrue}//没有子路由时,显示父路由if(showingChildren.length===0){this.onlyOneChild={...parent,path:'',noShowingChildren:true}returntrue}returnfalse},resolvePath(routePath){if(isExternal(routePath)){returnroutePath}returnpath.resolve(this.basePath,routePath)}}}</script>```###4.辅助组件(layout/components/Item.vue和Link.vue)-Item.vue:用于渲染菜单项的图标和标题```vue<template><div><iv-if="icon":class="icon"></i><spanv-if="title">{{title}}</span></div></template><script>exportdefault{name:'MenuItem',props:{icon:{type:String,default:''},title:{type:String,default:''}}}</script>```-Link.vue:处理外部链接和内部路由```vue<template><av-if="isExternal":href="to"target="_blank"rel="noopener"><slot></slot></a><router-linkv-else:to="to"><slot></slot></router-link></template><script>import{isExternalasexternal}from'@/utils/validate'exportdefault{name:'AppLink',props:{to:{type:String,required:true}},computed:{isExternal(){returnexternal(this.to)}}}</script>```###5.Vuex状态管理(store/modules/permission.js)用于存储根据权限过滤后的路由(即侧边栏菜单要显示的路由)```javascriptimport{asyncRoutes,constantRoutes}from'@/router'//判断用户是否有该路由的权限functionhasPermission(roles,route){if(route.meta&&route.meta.roles){returnroles.some(role=>route.meta.roles.includes(role))}else{returntrue}}//根据角色过滤路由exportfunctionfilterAsyncRoutes(routes,roles){constres=[]routes.forEach(route=>{consttmp={...route}if(hasPermission(roles,tmp)){if(tmp.children){tmp.children=filterAsyncRoutes(tmp.children,roles)}res.push(tmp)}})returnres}conststate={routes:[],//完整路由(常量路由+动态路由)addRoutes:[]//动态添加的路由}constmutations={SET_ROUTES:(state,routes)=>{state.addRoutes=routesstate.routes=constantRoutes.concat(routes)//将动态路由和常量路由合并}}constactions={generateRoutes({commit},roles){returnnewPromise(resolve=>{letaccessedRoutes//如果角色包含admin,则拥有所有权限if(roles.includes('admin')){accessedRoutes=asyncRoutes||[]}else{//根据角色过滤动态路由accessedRoutes=filterAsyncRoutes(asyncRoutes,roles)}//提交mutation,将过滤后的动态路由添加到vuexcommit('SET_ROUTES',accessedRoutes)resolve(accessedRoutes)})}}exportdefault{namespaced:true,state,mutations,actions}```###6.权限控制流程(在登录后获取用户角色并动态添加路由)在用户登录成功后,获取用户角色,然后根据角色生成可访问的路由,并动态添加到路由器中。```javascript//在登录成功的回调中importrouterfrom'@/router'importstorefrom'@/store'//获取用户角色(假设从后台获取到角色数组)constroles=['admin']//示例角色//生成路由store.dispatch('permission/generateRoutes',roles).then(accessedRoutes=>{//动态添加路由accessedRoutes.forEach(route=>{router.addRoute(route)})})```###7.布局组件(layout/Layout.vue)整合侧边栏和主内容区```vue<template><el-containerclass="app-wrapper"><!--侧边栏--><el-aside:width="sidebar.opened?'210px':'64px'"><sidebar/></el-aside><el-container><!--顶部导航栏--><el-header><navbar/></el-header><!--内容区域--><el-main><app-main/></el-main></el-container></el-container></template><script>import{Navbar,Sidebar,AppMain}from'./components'exportdefault{name:'Layout',components:{Navbar,Sidebar,AppMain},computed:{sidebar(){returnthis.$store.state.app.sidebar}}}</script>```###总结以上实现方案结合了Vue2、ElementUI和VueRouter,实现了动态侧边导航栏与路由的集成。关键点包括:-使用路由的meta字段存储菜单信息-递归组件处理多级菜单-通过vuex管理菜单状态和权限路由-动态路由加载这种方案可以灵活地根据用户权限动态生成菜单,并且支持多级嵌套菜单。§§相关问题§§1.Vue2中如何根据用户角色动态生成侧边栏菜单?2.在ElementUI中如何实现侧边栏菜单的折叠与展开?3.如何处理动态添加路由后刷新页面路由丢失的问题?4.如何实现侧边栏菜单的高亮(包括子菜单激活)?5.如何优化递归组件在大型菜单中的性能?</think>下面我将为你提供一个完整的Vue2 + ElementUI侧边导航栏与路由集成的实现方案,包含示例代码和最佳实践: ### 一、完整实现方案 ```vue <!-- src/layout/Layout.vue --> <template> <el-container style="height: 100vh;"> <!-- 侧边栏 --> <el-aside :width="isCollapse ? '64px' : '200px'"> <div class="logo-container"> <h3 v-show="!isCollapse">管理系统</h3> </div> <Sidebar :routes="permissionRoutes" :isCollapse="isCollapse" /> </el-aside> <!-- 主内容区 --> <el-container> <el-header> <div class="header-left"> <el-button @click="toggleCollapse" icon="el-icon-s-fold" /> <Breadcrumb /> </div> <UserDropdown /> </el-header> <el-main> <router-view /> </el-main> </el-container> </el-container> </template> <script> import Sidebar from './components/Sidebar' import Breadcrumb from './components/Breadcrumb' import UserDropdown from './components/UserDropdown' import { mapGetters } from 'vuex' export default { components: { Sidebar, Breadcrumb, UserDropdown }, data() { return { isCollapse: false } }, computed: { ...mapGetters(['permissionRoutes']) // 从Vuex获取权限过滤后的路由 }, methods: { toggleCollapse() { this.isCollapse = !this.isCollapse // 保存折叠状态到本地存储 localStorage.setItem('sidebarCollapse', JSON.stringify(this.isCollapse)) } }, mounted() { // 从本地存储加载折叠状态 const savedState = localStorage.getItem('sidebarCollapse') if (savedState) this.isCollapse = JSON.parse(savedState) } } </script> ``` ```vue <!-- src/layout/components/Sidebar.vue --> <template> <el-menu :default-active="activeMenu" :collapse="isCollapse" :collapse-transition="false" router unique-opened background-color="#304156" text-color="#bfcbd9" active-text-color="#409eff" > <template v-for="route in routes"> <MenuItem :key="route.path" :item="route" :base-path="route.path" :is-collapse="isCollapse" /> </template> </el-menu> </template> <script> import MenuItem from './MenuItem' export default { components: { MenuItem }, props: { routes: Array, isCollapse: Boolean }, computed: { activeMenu() { const route = this.$route const { meta, path } = route return meta.activeMenu || path } } } </script> ``` ```vue <!-- src/layout/components/MenuItem.vue --> <template> <div v-if="!item.hidden"> <!-- 无子路由 --> <el-menu-item v-if="!item.children || item.children.length === 0" :index="resolvePath(basePath, item.path)" > <i :class="item.meta.icon"></i> <span slot="title">{{ item.meta.title }}</span> </el-menu-item> <!-- 有子路由 --> <el-submenu v-else :index="item.path"> <template slot="title"> <i :class="item.meta.icon"></i> <span v-show="!isCollapse">{{ item.meta.title }}</span> </template> <MenuItem v-for="child in item.children" :key="child.path" :item="child" :base-path="resolvePath(basePath, item.path)" :is-collapse="isCollapse" /> </el-submenu> </div> </template> <script> import path from 'path' export default { name: 'MenuItem', props: { item: { type: Object, required: true }, basePath: { type: String, default: '' }, isCollapse: Boolean }, methods: { resolvePath(basePath, routePath) { return path.resolve(basePath, routePath) } } } </script> ``` ### 二、路由配置示例 ```javascript // src/router/index.js import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export const constantRoutes = [ { path: '/login', component: () => import('@/views/login'), hidden: true }, { path: '/404', component: () => import('@/views/404'), hidden: true }, { path: '/', redirect: '/dashboard', hidden: true }, { path: '/dashboard', component: () => import('@/layout'), meta: { title: '控制台', icon: 'el-icon-s-data' } } ] export const asyncRoutes = [ { path: '/user', component: () => import('@/layout'), redirect: '/user/list', name: 'User', meta: { title: '用户管理', icon: 'el-icon-user', roles: ['admin'] // 权限控制 }, children: [ { path: 'list', name: 'UserList', component: () => import('@/views/user/list'), meta: { title: '用户列表' } }, { path: 'create', name: 'UserCreate', component: () => import('@/views/user/create'), meta: { title: '创建用户' } } ] }, // 其他动态路由... ] const createRouter = () => new Router({ mode: 'history', routes: constantRoutes }) const router = createRouter() // 重置路由函数(用于动态添加路由) export function resetRouter() { const newRouter = createRouter() router.matcher = newRouter.matcher } export default router ``` ### 三、权限控制实现 ```javascript // src/store/modules/permission.js import { asyncRoutes, constantRoutes } from '@/router' function hasPermission(roles, route) { if (route.meta && route.meta.roles) { return roles.some(role => route.meta.roles.includes(role)) } else { return true } } export function filterAsyncRoutes(routes, roles) { const res = [] routes.forEach(route => { const tmp = { ...route } if (hasPermission(roles, tmp)) { if (tmp.children) { tmp.children = filterAsyncRoutes(tmp.children, roles) } res.push(tmp) } }) return res } const state = { routes: [], addRoutes: [] } const mutations = { SET_ROUTES: (state, routes) => { state.addRoutes = routes state.routes = constantRoutes.concat(routes) } } const actions = { generateRoutes({ commit }, roles) { return new Promise(resolve => { let accessedRoutes if (roles.includes('admin')) { accessedRoutes = asyncRoutes || [] } else { accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) } commit('SET_ROUTES', accessedRoutes) resolve(accessedRoutes) }) } } export default { namespaced: true, state, mutations, actions } ``` ### 四、路由守卫配置 ```javascript // src/permission.js import router from './router' import store from './store' import { Message } from 'element-ui' // 白名单 const whiteList = ['/login'] router.beforeEach(async (to, from, next) => { // 显示页面加载进度条 NProgress.start() // 确定用户是否已登录 const hasToken = store.getters.token if (hasToken) { if (to.path === '/login') { next({ path: '/' }) NProgress.done() } else { // 检查用户是否已获取权限信息 const hasRoles = store.getters.roles && store.getters.roles.length > 0 if (hasRoles) { next() } else { try { // 获取用户信息 const { roles } = await store.dispatch('user/getInfo') // 根据角色生成可访问的路由 const accessRoutes = await store.dispatch('permission/generateRoutes', roles) // 动态添加路由 router.addRoutes(accessRoutes) // 使用replace确保导航成功 next({ ...to, replace: true }) } catch (error) { // 移除token并跳转到登录页 await store.dispatch('user/resetToken') Message.error(error || 'Has Error') next(`/login?redirect=${to.path}`) NProgress.done() } } } } else { /* 用户未登录 */ if (whiteList.includes(to.path)) { next() } else { next(`/login?redirect=${to.path}`) NProgress.done() } } }) router.afterEach(() => { // 完成进度条 NProgress.done() }) ``` ### 五、面包屑组件 ```vue <!-- src/layout/components/Breadcrumb.vue --> <template> <el-breadcrumb separator="/"> <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path" > <span v-if="index === levelList.length - 1" class="no-redirect"> {{ item.meta.title }} </span> <router-link v-else :to="item.path"> {{ item.meta.title }} </router-link> </el-breadcrumb-item> </el-breadcrumb> </template> <script> export default { data() { return { levelList: [] } }, watch: { $route() { this.getBreadcrumb() } }, created() { this.getBreadcrumb() }, methods: { getBreadcrumb() { // 过滤掉没有标题的路由 let matched = this.$route.matched.filter(item => item.meta && item.meta.title) this.levelList = matched } } } </script> ``` ### 最佳实践总结 1. **路由驱动菜单** 菜单数据直接从路由配置生成,保证菜单与路由同步更新[^1] 2. **权限控制** 使用路由守卫 + Vuex 实现动态路由加载和菜单过滤 3. **递归组件** 使用递归组件处理无限层级菜单,支持任意深度嵌套 4. **状态持久化** 将侧边栏折叠状态保存到 localStorage 5. **性能优化** - 路由懒加载:`component: () => import('@/views/dashboard')` - 按需加载 ElementUI 组件 - 使用 keep-alive 缓存页面状态 6. **用户体验** - 面包屑导航显示当前路径 - 菜单折叠时自动显示 Tooltip - 路由切换时显示加载进度条 > 项目结构建议: > ``` > src/ > ├── layout/ # 布局组件 > │ ├── Layout.vue # 主布局 > │ └── components/ # 布局子组件 > │ ├── Sidebar.vue > │ ├── MenuItem.vue > │ ├── Breadcrumb.vue > │ └── ... > ├── router/ # 路由配置 > │ └── index.js > ├── store/ # Vuex 状态管理 > │ └── modules/ > │ ├── permission.js # 权限模块 > │ └── ... > ├── views/ # 页面组件 > ├── permission.js # 路由守卫 > └── main.js # 入口文件 > ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值