【摸鱼神器】一次搞定 vue3的 路由 + 菜单 + tabs
做一个管理后台,首先要设置路由,然后配置菜单(有时候还需要导航),再来一个动态tabs,最后加上权限判断。
这个是不是有点繁琐?尤其是路由的设置和菜单的配置,是不是很雷同?那么能不能简单一点呢?如果可以实现设置一次就全部搞定的话,那么是不会很香呢?
我们可以简单封装一下,实现这个愿望。
定义一个结构
我们可以参考 vue-router 的设置 和 el-menu 的参数,设置一个适合我们需求的结构:
- ./router.js
 
import { createRouter } from '@naturefw/ui-elp'
import home from '../views/home.vue'
const router = {
  /**
   * 基础路径
   */
  baseUrl: baseUrl,
  /**
   * 首页
   */
  home: home,
  menus: [
    {
      menuId: '1', // 相当于路由的 name
      title: '全局状态', // 浏览器的标题
      naviId: '0', // 导航ID
      path: 'global', // 相当于 路由 的path
      icon: FolderOpened, // 菜单里的图标
      childrens: [ // 子菜单,不是子路由。
        {
          menuId: '1010', // 相当于路由的 name
          title: '纯state',
          path: 'state',
          icon: Document,
          // 加载的组件
          component: () => import('../views/state-global/10-state.vue')
          // 还可以有子菜单。
        },
        {
          menuId: '1020',
          title: '一般的状态',
          path: 'standard',
          icon: Document,
          component: () => import('../views/state-global/20-standard.vue')
        }
      ]
    },
    {
      menuId: '2000',
      title: '局部状态',
      naviId: '0',
      path: 'loacl',
      icon: FolderOpened,
      childrens: [
        {
          menuId: '2010',
          title: '父子组件',
          path: 'parent-son',
          icon: Document,
          component: () => import('../views/state-loacl/10-parent.vue')
        }
      ]
    }
  ]
}
export default createRouter(router )
在 Router 的配置的基础上,加上 title、icon等菜单需要的属性,基本就搞定了。
- baseUrl:如果不能发布到根目录的话,需要设置一个基础URL。
 - home:默认显示的组件,比如大屏。
 - menus:路由、菜单集合。
- naviId:导航ID。
 - menuId:相当于路由的 name。
 - path:相当于 路由 的path。
 - title:浏览器的标题。
 - icon: 菜单里的图标。
 - childrens:子菜单,不是子路由。
 
 
main 里面加载。
设置之后,我们在main里面挂载一下即可。
import { createApp } from 'vue'
import App from './App.vue'
// 简易路由
import router from './router'
createApp(App)
  .use(router)
  .mount('#app')
- 看看效果
 

https://naturefw-code.gitee.io/nf-rollup-state/class/object
这样就搞定了,是不是很简单,因为我们把其他代码都封装成了组件。
封装 n级菜单
我们可以基于 el-menu,封装一个动态n级菜单组件(nf-menu)。
菜单组件可以基于 el-menu 封装,也可以基于其他组件封装,或者自己写一个,这里以el-menu为例,介绍一下封装方式:
- 父级菜单
 
  <el-menu
    ref="domMenu"
    class="el-menu-vertical-demo"
    @select="select"
    background-color="#6c747c"
    text-color="#fff"
    active-text-color="#ffd04b"
  >
    <sub-menu1
      :subMenu="menus"
    />
  </el-menu>
父级菜单比较简单,设置 el-menu 需要的属性,然后加载子菜单组件。
- n级子菜单
 
  <template v-for="(item, index) in subMenu">
    <!--树枝-->
    <template v-if="item.childrens && item.childrens.length > 0">
      <el-sub-menu
        :key="item.menuId + '_' + index"
        :index="item.menuId"
        style="vertical-align: middle;"
      >
        <template #title>
          <component
            :is="item.icon"
            style="width: 1.5em; height: 1.5em; margin-right: 8px;vertical-align: middle;"
          >
          </component>
          <span>{{item.title}}</span>
        </template>
        <!--递归子菜单-->
        <my-sub-menu2
          :subMenu="item.childrens"
        />
      </el-sub-menu>
    </template>
    <!--树叶-->
    <el-menu-item v-else
      :index="item.menuId"
      :key="item.menuId + 'son_' + index"
    >
      <template #title>
        <span style="float: left;">
          <component
            :is="item.icon"
            style="width: 1.5em; height: 1.5em; margin-right: 8px;vertical-align: middle;"
          >
          </component>
          <span >{{item.title}}</span>
        </span>
      </template>
    </el-menu-item>
  </template>
- 树枝:含有子菜单的菜单,使用 el-sub-menu 实现,不加载组件。
 - 树叶:没有子菜单,使用 el-menu-item 实现,加载组件的菜单。
 - 图标:使用 component 加载图标组件。
 
然后设置属性即可,这样一个n级菜单就搞定了。
封装一个动态tabs
菜单有了,下一步就是tabs,为了满足不同的需求,这里封装两个组件,一个单tab的,一个是动态多tabs的。
- 单 tab
参考 Router 的 router-view 封装一个组件 nf-router-view: 
  <component :is="$router.getComponent()">
  </component>
直接使用 component 加载组件即可。
- 动态多tabs
基于 el-tabs 封装一个动态多tabs组件 nf-router-view-tabs: 
  <el-tabs
    v-model="$router.currentRoute.key"
    type="border-card"
  >
    <el-tab-pane label="桌面" name="home">
      <component :is="$router.home">
      </component>
    </el-tab-pane>
    <el-tab-pane
      v-for="key in $router.tabs"
      :key="key"
      :label="$router.menuList[key].title"
      :name="key"
    >
     <template #label>
        <span>{{$router.menuList[key].title}}  
          <circle-close-filled
            style="width: 1.0em; height: 1.0em; margin-top: 8px;"
            @click.stop="$router.removeTab(key)" />
        </span>
      </template>
      <component :is="$router.menuList[key].component">
      </component>
    </el-tab-pane>
  </el-tabs>
为了保持状态,这里采用了一个笨办法,点击菜单加载的组件都放在 el-tab-pane 里面,通过切换 tab 的方式显示组件。
源码:https://gitee.com/naturefw-code/nf-rollup-ui-controller
做一个简单的路由
看了半天,你有没有发现,似乎缺少了一个重要环节?
你猜对了,路由的封装还没有介绍。
这里并不想设计一个像 vue-router那样的全能路由,而是设计一个适合管理后台的简易路由。
菜单是多级的,url 也是多级的和菜单对应,但是路由是单级的,不嵌套。
也就是说,点击任意一级的(树叶)菜单,加载的都是同级的组件。
另外暂时不考虑加载组件后的路由的设置。我觉得,这个可以交给加载的组件自行实现。
import { defineAsyncComponent, reactive, watch, inject } from 'vue'
const flag = Symbol('nf-router-menu___')
/**
 * 一个简单的路由
 * @param { string } baseUrl 基础路径
 * @param { components } home 基础路径
 * @param { array } menus 路由设置,数组,多级
 * * [{
 * * *  menuId: '菜单ID'
 * * *  naviId: '0', // 导航ID,可以不设置
 * * *  title: '标题',
 * * *  path: '路径',
 * * *  icon: Edit, // 图标组件
 * * *  component: () => import('./views/xxx.vue') // 要加载的组件,可以不设置
 * * *  childrens: [ // 子菜单,可以多级
 * * * *  {
 * * * * *  menuId: '菜单ID'
 * * * * *  title: '标题',
 * * * * *  path: '路径',
 * * * * *  icon: Edit, // 图标组件
 * * * * *  component: () => import('./views/xxx.vue') // 要加载的组件
 * * * * }
 * * * ]
 * * },
 * * 其他菜单
 * * ]
 * @returns
 */
class Router {
  constructor (info) {
    // 设置当前选择的路由
    this.currentRoute = reactive({
      key: 'home', // 默认的首页
      paths: [] // 记录打开的多级菜单的信息
    })
    this.baseUrl = info.baseUrl // 基础路径,应对网站的二级目录
    this.baseTitle = document.title // 初始的标题
    this.isRefresh = false // 是否刷新进入
    this.home = info.home // 默认的首页
    this.menus = reactive(info.menus) // 菜单集合,数组形式,支持多级,可以设置导航ID
    this.menuList = {} // 变成单层的树,便于用key查找。
    this.tabs = reactive(new Set([])) // 点击过且没有关闭的二级菜单,做成动态tab标签
    this.setup()
  }
  /**
   * 初始化设置
   */
  setup = () => {
    // 监听当前路由,设置 tabs 和标题、url
    watch(() => this.currentRoute.key, (key) => {
      略
    })
  }
   /**
   * 添加新路由,主要是实现根据用户权限加载对应的菜单。
   */
  addRoute = (newMenus, props = {}) => {
      略
  }
  /**
   * 删除路由
   * @param { array } path 菜单的路径,[] 表示根菜单
   * @param { string | number } id 要删除的菜单ID
   */
  removeRoute = (path = [], id = '') => {
      略
  }
  /**
   * 刷新时依据url加载组件
   */
  refresh = () => {
     略
  }
  /**
   * 加载路由指定的组件
   * @returns
   */
  getComponent = () => {
    if (this.currentRoute.key === '' || this.currentRoute.key === 'home') {
      return this.home
    } else {
      return this.menuList[this.currentRoute.key].component
    }
  }
  /**
   * 删除tab
   * @param { string } key
   * @returns
   */
  removeTab = (key) => {
    略
  }
  /**
   * 安装插件
   * @param {*} app
   */
  install = (app) => {
    // 便于模板获取
    app.config.globalProperties.$router = this
    // 便于代码获取
    app.provide(flag, this)
  }
}
/**
 * 创建简易路由
 */
const createRouter = (info) => {
  // 创建路由,
  const router = new Router(info)
  // 判断url,是否需要加载组件
  setTimeout(() => {
    router.refresh()
  }, 300)
  // 使用vue的插件,设置全局路由
  return router
}
/**
 * 获取路由
 * @returns
 */
const useRouter = () => {
  return inject(flag)
}
export {
  createRouter,
  useRouter
}
篇幅有限,这里只介绍了路由的整体结构,具体实现方式可以看源码:
源码:https://gitee.com/naturefw-code/nf-rollup-ui-controller
菜单与权限
上面是静态的路由和导航的设置方式,对于管理后台,必备的一个需求就是,根据用户的权限来加载路由和菜单。
所以我们提供了一个 addRoute 方法,实现动态添加路由的功能,这样可以等用户登录之后,得到用户的权限,然后按照权限加载路由和菜单。
  const router = useRouter()
   router.addRoute([
      {
        menuId: 'dt-100',
        title: '添加根菜单',
        naviId: '0',
        path: 'new-router',
        icon: FolderOpened,
        childrens: [
          {
            menuId: '100-10',
            title: '动态菜单',
            path: 'ui',
            icon: Document,
            component: () => import('../ui/base/c-01html.vue')
          }
        ]
      }
    ], { index: 1 })
同时也可以加上权限判断。菜单是基于 el-menu 实现的,可以加上 select 事件,然后在事件里面判断权限,如果没有权限可以跳转到登录组件。
const router = useRouter()
const myselect = (index, indexPath) => {
  // 验证权限,如果没有权限,加载登录组件
  if (没有权限) {
    router.currentRoute.paths = ''
    router.currentRoute.key = '登录组件的key'
  }
}
示例项目
https://gitee.com/naturefw-code/nf-rollup-state
【摸鱼神器】一次搞定 vue3的 路由 + 菜单 + tabs的更多相关文章
- 【摸鱼神器】UI库秒变LowCode工具——列表篇(一)设计与实现
		
内容摘要: 需求分析 定义 interface 定义 json 文件 定义列表控件的 props 基于 el-table 封装,实现依赖 json 渲染 实现内置功能:选择行(单选.多选),格式化.锁 ...
 - Thief-Book 上班摸鱼神器
		
Thief-Book 上班摸鱼神器 介绍 Thief-Book 是一款真正的摸鱼神器,可以更加隐秘性大胆的看小说. 隐蔽性 自定义透明背景,随意调整大小,完美融入各种软件界面 快捷性 三个快捷键,实现 ...
 - vscode插件(摸鱼神器-小霸王游戏机
		
vscode插件(摸鱼神器-小霸王游戏机 步骤 vscode扩展搜索小霸王,点击下载即可. 使用 默认有一个demo小游戏,即超级玛丽. 本地仓库 可以通过local菜单上的添加按钮添加本地nes r ...
 - 【转】让Chrome化身成为摸鱼神器,利用Chorme运行布卡漫画以及其他安卓APK应用教程
		
下周就是十一了,无论是学生党还是工作党,大家的大概都会有点心不在焉,为了让大家更好的心不在焉,更好的在十一前最后一周愉快的摸鱼,今天就写一个如何让Chrome(google浏览器)运行安卓APK应用的 ...
 - 【摸鱼神器】UI库秒变低代码工具——表单篇(二)子控件
		
上一篇介绍了表单控件,这一篇介绍一下表单里面的各种子控件的封装方式. 主要内容 需求分析 子控件的分类 子控件属性的分类 定义 interface. 定义子控件的的 props. 定义 json 文件 ...
 - 【摸鱼神器】基于SSM风格的Java源代码生成器 单表生成 一对一、一对多、多对多连接查询生成
		
一.序言 UCode Cms 是一款Maven版的Java源代码生成器,是快速构建项目的利器.代码生成器模块属于可拆卸模块,即按需引入.代码生成器生成SSM(Spring.SpringBoot.Myb ...
 - 【摸鱼神器】UCode Cms管理系统 内置超好用的代码生成器 解决多表连接痛点
		
一.序言 UCode Cms管理系统是面向企业级应用软件开发的脚手架.当前版本1.3.4.快速体验: git clone https://gitee.com/decsa/demo-cms.git (一 ...
 - 【摸鱼神器】UI库秒变LowCode工具——列表篇(二)维护json的小工具
		
上一篇介绍了一下如何实现一个可以依赖 json 渲染的列表控件,既然需要 json 文件,那么要如何维护这个 json 文件就成了重点,如果没有好的维护方案的话,那么还不如直接用UI库. 所以需要我们 ...
 - 【摸鱼神器】UI库秒变低代码工具——表单篇(一)设计
		
前面说了列表的低代码化的方法,本篇介绍一下表单的低代码化. 内容摘要 需求分析. 定义 interface. 定义表单控件的 props. 定义 json 文件. 基于 el-form 封装,实现依赖 ...
 
随机推荐
- ionic3 教程(五)基本的网络请求
			
链接: ionic3教程(一)安装和配置 ionic3教程(二)登录页制作 ionic3教程(三)设置页制作 ionic3教程(四)安卓硬件返回键处理ionic3 教程(五)基本的网络请求 这是最后一 ...
 - DIV 上下左右居中黑科技
			
<style> #info{height:0px; width:0px;top:50%; left:50%;position:absolute;} #center{background:# ...
 - 我的python学习记_01
			
一切的开始都是从打招呼开始的,python也不例外,首先和将要陪伴我后半生的朋友说句情话: print("不是在最美好的时光遇见你,而是因为遇见你才让我有了最美好的时光") 初写代 ...
 - this和super的区别和应用
			
A:this和super都代表什么 * this:代表当前对象的引用,谁来调用我,我就代表谁 * super:代表当前对象父类的引用B:this和super的使用区别 * a:调用成员变量 * th ...
 - 微信小程序如何把接口调用成功的回调函数返回的参数return出去?(promise就可以解决)
			
举个栗子//获取应用实例 //const app = getApp() //const util = require('../../utils/util.js') //const sign = uti ...
 - Cookie&&Session&&jsp入门
			
会话技术 会话:一次会话中包含多次请求和响应. 一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止 功能:在一次会话的范围内的多次请求间,共享数据 方式: 客户端会话技术:Coo ...
 - 另类终端「GitHub 热点速览 v.22.15」
			
作者:HelloGitHub-小鱼干 除了编译器之外,终端也是我们日常打交道的软件之一.但,你用它看过股票吗?OpenBBTerminal 不仅能让你看股票,还能让你用科学的方法进行股票投资.说到投资 ...
 - 『忘了再学』Shell基础 — 11、变量定义的规则和分类
			
目录 1.定义变量的规则 2.变量的分类 1.定义变量的规则 在定义变量时,有一些规则需要遵守 变量名称可以由字母.数字和下划线组成,但是不能以数字开头.如果变量名是2name则是错误的. 在Bash ...
 - k8s入门之基础环境准备(一)
			
一.在虚拟机中安装Ubuntu20.04.4系统 1.下载Ubuntu20.04.4服务器版本系统 下载链接地址如下: https://mirrors.tuna.tsinghua.edu.cn/ubu ...
 - PHP的Laravel与Composer部署项目时常见问题
			
我们在部署PHP项目时,其实大部分的PHP项目会创建环境检测与一键**Install**页面. 但是,有许多的项目还采用了Composer部署. 什么是Composer 至于什么是Composer,我 ...