v3-admin-vite 改造自动路由,view页面自解释Meta

news/2024/7/10 2:45:01 标签: vue, admin, v3-admin-vite, typescript

需求

v3-admin-vite是一款不错的后端管理模板,主要是pany一直都在维护,最近将后台管理也进行了升级,顺便完成一直没时间解决的小痛痒:

在不使用后端动态管理的情况下。我不希望单独维护一份路由定义,我希望页面是自解释的。就像HTML标记,一个页面的标题等信息由页面内<title>决定,而一个页面的访问地址(路由)由页面目录决定。很自然的思维是么?单独维护一份路由感觉就没那么自然了,我希望这一切都由页面自解释。访问路径我只需要移动页面的位置,Ctrl+CV目录的结构就好了

思路

之前实战过数个项目,大部分都轻车熟路了。但是对于v3-admin-vite系统,还是有几个地方需要调整 :

1.目录的定义

目录的定义除了名称外,还有图标等信息需要管理。因此需要采用文件补充信息,我的解决方案是将要输出成为左侧目录结构的目录(好绕口)下放一个index.ts文件,为了避免和其它的文件冲突,约定默认导出export default必须包含title这个string信息,表示目录名称(神马?title为空怎么办?有点正常业务思维吧),顺便把图标的定义也在导出解决。

2.View文件的定义

View文件的定义由于目录定义一样,只需要将你导出成为菜单的vue模块添加导出定义即可。把meta信息导出,自动输出路由配置。

3.Name约定

除了meta信息以外,admin-v3还要求name不能一致(没试过 ?改个一样的试试看😏),我们可以直接从文件名读取,至少一个目录下文件名是不会一致的。当然如果多个目录的话,就要注意一下了,功能页面名称唯一这个应该很容易办到。

4.递归扫描文件

webpack,vite等工具都提供了文件扫描的接口,只是不能使用变量进行路径扫描,必须字面量(常量),好在支持通配符。解决起来不难。对于后端很早例如spring框架就具备了自动扫描功能,对于前端,有对应方案但是应用的不是很多,用好了很舒服。

5.顺序问题的解决

由于工具扫描都是基于文件名称的,而实际需要显示的结构和文件顺序 不一定相同,例如我有a.vue,b.vue,按名称扫描a会出现在前面。因此我扩充了一下Meta定义,添加了一个position属性 ,没设置时,默认以100作为排序值,根据其对所有的目录递归排序,这样就OK了。

功能实现

看一下最终的对应效果

对于目录标记,我们只需要在目录下添加一个index.ts文件:

import { RouteMetaEx } from '@/router'

export default {
  title: '二级目录测试',
} as RouteMetaEx

对于View模块,我们只需要添加多一个typescript块导出meta:

<template>
  <div>测试节点3</div>
</template>
<script lang="ts">
import { RouteMetaEx } from '@/router'
const meta: RouteMetaEx = {
  title: '3级节点1', // 只有导出title的才会成为路由
  elIcon: 'Cpu', // element-ui的内置ICON,比svgIcon优先
  // svgIcon: 'dashboard',
  roles: ['role0'], // 哪些角色可以显示
  position: 100
  //keepAlive: true // 是否要keepAlive保持页面状态
  // hidden: true 默认为false,不会挂载到菜单
}
export default meta
</script>

使用是不很简单?哈哈哈哈。

这里添加了position的RouteMetaEx在后面有定义,其它结构和功能和Meta定义一致。注意vue的文件名在view下要唯一。

然后我们的src/router/index.ts里dynamicRoutes需要按照下面方式来导出:


export interface RouteMetaEx extends RouteMeta {
  position?: number //排序,不填写的话默认为100,用于控制菜单顺序
}

/**
 * admin-vite-v3 自动路由
 * 递归扫描views下的文件,识别导出title的页面加入路由,需要配置权限 (Roles 属性)
 * 注意二级目录产生要求在目录index.ts里导出含title的meta
 * @author Jim 2024/4/1
 */
const autoRoutes: Array<RouteRecordRaw> = []

const scanDir: Record<string, any> = import.meta.glob('../views/**/index.ts', { eager: true }) // 处理目录
const dirNodeCache = new Map<string, RouteRecordRaw>()
for (const key in scanDir) {
  const component = scanDir[key]
  if (component.default?.title) {
    // 通过默认导出title判断
    const groups = /\.\.\/views\/((\w+\/)+)index\.ts/.exec(key) || []
    const dirName = groups[2].slice(0, -1) // 提取目录名
    const perfix = groups[1].slice(0, -dirName.length - 1) // 提取前缀目录
    const currentNode: RouteRecordRaw = {
      path: dirName,
      name: dirName,
      children: [],
      meta: { ...component.default, alwaysShow: true } // 合并alwaysShow进去,保持目录结构
    }
    const upperNode = dirNodeCache.get(perfix)
    if (upperNode) {
      upperNode.children?.push(currentNode)
    } else {
      // 一级目录
      currentNode.path = `/${dirName}` // 更改根格式
      currentNode.component = Layouts
      autoRoutes.push(currentNode)
    }
    dirNodeCache.set(groups[1], currentNode)
  }
}
const scanModule: Record<string, any> = import.meta.glob('../views/**/*.vue', { eager: true }) // 处理节点
for (const key in scanModule) {
  const component = scanModule[key]
  if (component.default?.title) {
    // 通过默认导出title判断
    const groups = /\.\.\/views\/((\w+\/)*)(\w+)\.vue/.exec(key) || []
    const dirPath = groups[1]
    const moduleName = groups[3]
    if (!dirPath) {
      // 一级菜单特殊处理
      autoRoutes.push({
        path: `/${moduleName}`,
        name: moduleName,
        component: Layouts,
        redirect: `/${moduleName}/index`,
        meta: component.default,
        children: [
          {
            path: 'index',
            name: moduleName,
            component: () => component,
            meta: component.default
          }
        ]
      })
    } else {
      const currentNode: RouteRecordRaw = {
        path: moduleName,
        name: moduleName,
        component: () => component,
        meta: component.default
      }
      const upperNode = dirNodeCache.get(dirPath)
      if (upperNode) {
        // 挂上级目录下,没有定义就不要挂了
        upperNode.children?.push(currentNode)
      } else {
        console.error(`upper node ${dirPath} not found`)
      }
    }
  }
}

const sortFunc = (a: RouteRecordRaw, b: RouteRecordRaw) =>
  ((a.meta as RouteMetaEx).position ?? 100) - ((b.meta as RouteMetaEx).position ?? 100)
function sortByPosition(nodes: RouteRecordRaw[]) {
  nodes.forEach((node: RouteRecordRaw) => {
    if (node.children) {
      const sortedChildren = node.children.sort(sortFunc) // 排序所有children
      sortByPosition(sortedChildren)
    }
  })
}
autoRoutes.sort(sortFunc)
sortByPosition(autoRoutes)

export const dynamicRoutes: RouteRecordRaw[] = autoRoutes

其他部分可以不用动,不到100行代码,你便妥妥的拥有了高大上的自动路由功能。

副作用

这样处理虽然开发方便,但是也有它的局限和副作用。副作用就是分包问题,由于所有的模块获取菜单定义必须读文件,因此无法懒加载,会导致扫描过程需要加载全部的模块,这样一来当模块非常多的时候加载会比较耗资源,也无法细化的分包。但对于中小项目一个gzip包全部load下来还是问题不大。大型项目可能要采用其它方案例如自动化脚本来输出路由。


http://www.niftyadmin.cn/n/5471251.html

相关文章

vivado向赛灵思器件添加配置存储器器件

使用 Vivado IDE 右键单击 SVF 链中的赛灵思器件时 &#xff0c; 可以选择创建配置存储器器件 &#xff0c; 并将配置存储器器件与该器件关联。 这样会打开“添加配置存储器器件 (Add Configuration Memory Device) ”对话框 &#xff0c; 如下所示。 选择相应的存储器器件…

【linux】基础IO(二)

我们在基础IO&#xff08;一&#xff09;主要讲述了fd&#xff0c;一切皆文件&#xff0c;文件的系统调用与语言文件库函数的关系&#xff0c; 今天主要进行对重定向与缓冲区的理解与应用。另外&#xff0c;对系统调用的read进行一下使用。 read的使用&#xff1a; 再使用rea…

Linux 安装系统可视化监控工具 Netdata

目录 About 监控工具 NetdataLinux 系统安装 Netdata关于 openEuler1、查看内核信息2、查看主机信息3、查看 dnf 包管理器的版本 Netdata 安装1、更新系统环境相关 rpm 包2、查看 netdata 包信息3、安装 netdata 包4、编辑 netdata.conf 配置5、启动 netdata 服务6、查看 netda…

Linux基础二(工具篇)

Linux基础二 1. Linux编译器——vim1.1vim常用三种模式1.2 vim的操作指令 2. Linux编译器的 gcc / g2.1 在xshell中安装 gcc/g2.2 程序的编译的过程 Linux自动化构建工具 make/MakefileLinux调试器 - gdb 1. Linux编译器——vim 1.1vim常用三种模式 vim编译器有许多种模式&am…

安全防御产品—锐安盾重磅上线,助力更安全、更流畅的业务体验

在互联网时代&#xff0c;互联网技术蓬勃发展&#xff0c;然而&#xff0c;随之而来的网络安全问题也备受关注。诸如DDoS攻击、CC攻击、常见Web攻击等攻击手段突如其来&#xff0c;导致企业业务中断&#xff0c;严重影响企业业务正常运行。对此&#xff0c;锐成云重磅推出安全防…

网络安全 | 什么是单点登录SSO?

关注WX&#xff1a;CodingTechWork SSO-概念 单点登录 (SSO) 是一种身份认证方法&#xff0c;用户一次可通过一组登录凭证登入会话&#xff0c;在该次会话期间无需再次登录&#xff0c;即可安全访问多个相关的应用和服务。SSO 通常用于管理一些环境中的身份验证&#xff0c;包…

Vue3:组件间通信-$attrs的使用

一、情景说明 我们之前学习了通过props实现&#xff0c;父给子传数据 那么&#xff0c;如果&#xff0c;父组件给子组件传递多个数据&#xff0c;但是&#xff0c;子组件只用props声明了一个数据 其他数据去哪里了呢&#xff1f; 二、案例 1、父组件 <Child :a"a&…

Netty NioEventLoop详解

文章目录 前言类图主要功能NioEventLoop如何实现事件循环NioEventLoop如何处理多路复用Netty如何管理Channel和Selector管理Channel管理Selector注意事项 前言 Netty通过事件循环机制(EventLoop)处理IO事件和异步任务&#xff0c;简单来说&#xff0c;就是通过一个死循环&…