Vue 动态路由接口数据结构化为符合VueRouter的声明结构及菜单导航结构、动态路由懒加载方法
Vue 动态路由接口数据结构化为符合VueRouter的声明结构及菜单导航结构、动态路由懒加载方法
实现目标
- 项目打包代码实现按需分割
- 路由懒加载按需打包,排除引入子组件的冗余打包(仅处理打包冗余现象,不影响生产部署)
- 解决路由懒加载
import
方法内引入变量报错问题
可能碰到的问题
1.ESLint: Cannot read properties of null (reading 'range') Occurred while linting
2.eslint 语法分析报错:Syntax Error: TypeError: Cannot read property 'value' of null.
// import 方法内不可直接使用模板字符串
return () => import(`@/views/${view}`).catch(() => import('@/views/error/notfound'))
3.动态路由按需加载-Cannot find module
4.不同系统环境代码分包路径匹配问题(路径分隔符不兼容)
个人最终解决方法
1.开发环境(本人实测)
- 系统环境:
Windows 11、Linux、MacOS
- node 版本:
v14.21.2
- npm 版本:
6.14.17
- vue:
@vue/cli 5.0.8
- webpack:
6.14.17
- 项目依赖 -
{"name": "v1","version": "1.0.0","description": "xxx","author": "xx <xx@gmail.com>","scripts": {"dev": "vue-cli-service serve","build:prod": "vue-cli-service build","build:stage": "vue-cli-service build --mode staging","svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml","lint": "eslint --ext .js,.vue src"},"dependencies": {"@riophae/vue-treeselect": "^0.4.0","axios": "^0.21.1","core-js": "^3.27.2","echarts": "^5.4.0","echarts-wordcloud": "^2.1.0","element-ui": "^2.15.10","js-cookie": "2.2.0","lodash.merge": "^4.6.2","monotone-chain-convex-hull": "^1.1.0","normalize.css": "7.0.0","nprogress": "0.2.0","ol": "^6.14.1","ol-ext": "^4.0.4","path-to-regexp": "2.4.0","screenfull": "^5.2.0","swiper": "^5.4.5","vue": "^2.7.13","vue-awesome-swiper": "^4.1.1","vue-cropper": "^0.5.8","vue-router": "^3.6.5","vuex": "^3.6.2"},"devDependencies": {"@vue/cli-plugin-babel": "4.4.6","@vue/cli-plugin-eslint": "4.4.6","@vue/cli-service": "4.4.6","babel-eslint": "10.1.0","chalk": "4.1.0","eslint": "7.15.0","eslint-plugin-vue": "7.2.0","sass": "1.32.13","sass-loader": "10.1.1","script-ext-html-webpack-plugin": "2.1.5","svg-sprite-loader": "5.1.1","vue-template-compiler": "2.6.12","autoprefixer": "9.5.1","sass-resources-loader": "^2.1.1","serve-static": "1.13.2","svgo": "1.2.2","worker-loader": "^3.0.8"},"browserslist": ["> 1%","last 2 versions"],"engines": {"node": ">=8.9","npm": ">= 3.0.0"},"license": "MIT"
}
- Babel 完整配置
module.exports = {presets: [// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app'@vue/cli-plugin-babel/preset'// https://blog.csdn.net/jayccx/article/details/128200440// ['@vue/cli-plugin-babel/preset', { 'exclude': ['proposal-dynamic-import'] }]]// 'env': {// 'development': {// // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().// // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.// 'plugins': ['dynamic-import-node']// }// 'production': {// // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().// // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.// 'plugins': ['dynamic-import-node']// }// }
}
- ESLint完整配置
.eslintrc.js
module.exports = {root: true,parserOptions: {parser: 'babel-eslint',sourceType: 'module'},env: {browser: true,node: true,es6: true},extends: ['plugin:vue/recommended', 'eslint:recommended'],// add your custom rules here// it is base on https://github.com/vuejs/eslint-config-vuerules: {'vue/html-closing-bracket-newline': 'off','vue/require-default-prop': 'off','vue/html-indent': 'off','vue/max-attributes-per-line': 'off','vue/singleline-html-element-content-newline': 'off','vue/multiline-html-element-content-newline': 'off','vue/component-definition-name-casing': ['error', 'PascalCase'],'vue/no-v-html': 'off','accessor-pairs': 2,'arrow-spacing': [2, {'before': true,'after': true}],'block-spacing': [2, 'always'],'brace-style': [2, '1tbs', {'allowSingleLine': true}],'camelcase': [0, {'properties': 'always'}],'comma-dangle': [2, 'never'],'comma-spacing': [2, {'before': false,'after': true}],'comma-style': [2, 'last'],'constructor-super': 2,'curly': [2, 'multi-line'],'dot-location': [2, 'property'],'eol-last': 2,'eqeqeq': ['error', 'always', { 'null': 'ignore' }],'generator-star-spacing': [2, {'before': true,'after': true}],'space-before-function-paren': ['error', {'anonymous': 'always','named': 'ignore','asyncArrow': 'always'}],'handle-callback-err': 'off','jsx-quotes': [2, 'prefer-single'],'key-spacing': [2, {'beforeColon': false,'afterColon': true}],'keyword-spacing': [2, {'before': true,'after': true}],'new-cap': [2, {'newIsCap': true,'capIsNew': false}],'new-parens': 2,'no-array-constructor': 2,'no-caller': 2,'no-console': 'off','no-class-assign': 2,'no-cond-assign': 2,'no-const-assign': 2,'no-control-regex': 0,'no-delete-var': 2,'no-dupe-args': 2,'no-dupe-class-members': 2,'no-dupe-keys': 2,'no-duplicate-case': 2,'no-empty-character-class': 2,'no-empty-pattern': 2,'no-eval': 2,'no-ex-assign': 2,'no-extend-native': 2,'no-extra-bind': 2,'no-extra-boolean-cast': 2,'no-extra-parens': [2, 'functions'],'no-fallthrough': 2,'no-floating-decimal': 2,'no-func-assign': 2,'no-implied-eval': 2,'no-inner-declarations': [2, 'functions'],'no-invalid-regexp': 2,'no-irregular-whitespace': 2,'no-iterator': 2,'no-label-var': 2,'no-labels': [2, {'allowLoop': false,'allowSwitch': false}],'no-lone-blocks': 2,'no-mixed-spaces-and-tabs': 2,'no-multi-spaces': 2,'no-multi-str': 2,'no-multiple-empty-lines': [2, {'max': 1}],'no-native-reassign': 2,'no-negated-in-lhs': 2,'no-new-object': 2,'no-new-require': 2,'no-new-symbol': 2,'no-new-wrappers': 2,'no-obj-calls': 2,'no-octal': 2,'no-octal-escape': 2,'no-path-concat': 2,'no-proto': 2,'no-redeclare': 2,'no-regex-spaces': 2,'no-return-assign': [2, 'except-parens'],'no-self-assign': 2,'no-self-compare': 2,'no-sequences': 2,'no-shadow-restricted-names': 2,'no-spaced-func': 2,'no-sparse-arrays': 2,'no-this-before-super': 2,'no-throw-literal': 2,'no-trailing-spaces': 2,'no-undef': 2,'no-undef-init': 2,'no-unexpected-multiline': 2,'no-unmodified-loop-condition': 2,'no-unneeded-ternary': [2, {'defaultAssignment': false}],'no-unreachable': 2,'no-unsafe-finally': 2,'no-unused-vars': [2, {'vars': 'all','args': 'none'}],'no-useless-call': 2,'no-useless-computed-key': 2,'no-useless-constructor': 2,'no-useless-escape': 0,'no-whitespace-before-property': 2,'no-with': 2,'one-var': [2, {'initialized': 'never'}],'operator-linebreak': [2, 'after', {'overrides': {'?': 'before',':': 'before'}}],'padded-blocks': [2, 'never'],'quotes': [2, 'single', {'avoidEscape': true,'allowTemplateLiterals': true}],'semi': [2, 'never'],'semi-spacing': [2, {'before': false,'after': true}],'space-before-blocks': [2, 'always'],'space-in-parens': [2, 'never'],'space-infix-ops': 2,'space-unary-ops': [2, {'words': true,'nonwords': false}],'spaced-comment': [2, 'always', {'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']}],'template-curly-spacing': [2, 'never'],'use-isnan': 2,'valid-typeof': 2,'wrap-iife': [2, 'any'],'yield-star-spacing': [2, 'both'],'yoda': [2, 'never'],'prefer-const': 2,'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,'object-curly-spacing': [2, 'always', {objectsInObjects: true}],'array-bracket-spacing': [2, 'never']}
}
2.项目中接口数据生成路由结构及菜单结构(重点关注以下代码中的loadView
函数)
import { constantRoutes } from '@/router'
import { getRouters } from '@/api/system/menu'
import pathToRegexp from 'path-to-regexp'
import Layout from '@/layout/index'
const route = {state: {routes: [],addRoutes: [],allSidebarRouters: [],sidebarRouters: []},mutations: {SET_ROUTES: (state, routes) => {state.addRoutes = routesstate.routes = constantRoutes.concat(routes)},SET_ALL_SIDEBAR_ROUTERS: (state, routers) => {state.allSidebarRouters = routers},SET_SIDEBAR_ROUTERS: (state, routers) => {state.sidebarRouters = routers}},actions: {// 生成路由GenerateRoutes({ commit }) {return new Promise(resolve => {// 向后端请求路由数据getRouters().then(res => {const sdata = JSON.parse(JSON.stringify(res.data))const rdata = JSON.parse(JSON.stringify(res.data))/* 符合菜单的数据结构 */const allSidebarRoutes = filterAsyncRouter(sdata)/* 符合路由的数据结构 */const rewriteRoutes = filterAsyncRouter(rdata, true)/* 路由通配符 */rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })commit('SET_ROUTES', rewriteRoutes)commit('SET_ALL_SIDEBAR_ROUTERS', allSidebarRoutes)// commit('SET_SIDEBAR_ROUTERS', allSidebarRoutes[0].children)resolve(rewriteRoutes)})})},/* 切换菜单 */SwitchSiderBar({ commit }, routes) {commit('SET_SIDEBAR_ROUTERS', routes)}}
}/* 匹配参数 */
const regParams = /\;[^\/]*/g
/* 匹配值 /user/:id;1 */
const regValue = /(?:\:)[^;]*(?:;)/g
/* 遍历后台传来的路由字符串,转换为组件对象(一级目录及一级菜单后端数据则自动添加根/路径) */
function filterAsyncRouter(asyncRouterMap, isRewrite = false/* 是否生成为路由标准 */, parentRoute) {return asyncRouterMap.filter(route => {if (parentRoute) {route.path = parentRoute.path + route.path}if (isRewrite) {route.path = route.path.replace(regParams, '')} else {route.path = route.path.replace(regValue, '')route._regex = pathToRegexp(route.path, pathToRegexp.parse(route.path), {sensitive: true,strict: true})}if (isRewrite && route.children) {route.children = filterChildren(route.children)}if (route.component && route.component !== 'ParentView') {if (route.component === 'Layout') {route.component = Layout} else {/* 记录源代码位置 */route.meta && (route.meta.src = route.component)route.component = loadView(route.component)}}if (route.children && route.children.length) {route.children = filterAsyncRouter(route.children, isRewrite, route)}return true})
}
/* 递归扁平化路由结构 */
function filterChildren(childrenMap, parentRoute) {var children = []var hasRoute = {}childrenMap.forEach((el, index) => {el.path = el.path.replace(regParams, '')if (parentRoute) {el.path = parentRoute.path}/* 当存在子路由时,将子路由添加到定义 */if (el.children && el.children.length) {/* ParentView 的处理使系统多级菜单的展现出现在Layout组件下成为可能 */let childs = []el.children.forEach(c => {c.path = el.path + c.path.replace(regParams, '')if (c.children && c.children.length) {childs = childs.concat(filterChildren(c.children, c))return}if (hasRoute[c.path]) returnhasRoute[c.path] = truechilds.push(c)})/* 父级路由明确为目录时(ParentView),不再将父路由加入到路由定义中 */if (el.component === 'ParentView') {children = children.concat(childs)} else {/* 否则将父路由作为嵌套路由加入到路由定义中 */el.children = childschildren = children.concat(el)}return/* 父级路由明确为目录时(ParentView),不存在子路由,则直接将路由组件设置为notfound组件 */} else if (el.component === 'ParentView') {el.component = 'error/notfound'}if (hasRoute[el.path]) returnhasRoute[el.path] = truechildren = children.concat(el)})return children
}/* 路由懒加载失败时重置为notfound页面 */
export const loadView = (view) => {if (process.env.NODE_ENV === 'development') {return (resolve) => {require([`@/views/${view}`], resolve, err => {require([`@/views/error/notfound`], resolve)console.log(err)})}} else {/*** 使用 import 实现生产环境的路由懒加载* !注意:import 方法内不可直接使用模板字符串 ,eslint 语法分析报错:Syntax Error: TypeError: Cannot read property 'value' of null。* !注意:正则匹配时应注意不同系统的路径分隔符区别,例如,Linux、MacOS 系统的路径分隔符为 "/",Windows 系统的路径分隔符为 "\"* 因此正则中路径分隔符的表达式,应匹配以上两种情况 [\/\\]。*/return () => import(/* webpackChunkName: "[request]",webpackInclude: /.+[\/\\][a-z0-9\-]+.vue$/ */'@/views/' + view).catch(() => import('@/views/error/notfound'))}
}export default route
参考文档
VueRouter 路由懒加载
Webpack import
Ruoyi-Vue issue
Ruoyi-Vue 路由逻辑
相关文章:
Vue 动态路由接口数据结构化为符合VueRouter的声明结构及菜单导航结构、动态路由懒加载方法
Vue 动态路由接口数据结构化为符合VueRouter的声明结构及菜单导航结构、动态路由懒加载方法 实现目标 项目打包代码实现按需分割路由懒加载按需打包,排除引入子组件的冗余打包(仅处理打包冗余现象,不影响生产部署)解决路由懒加载…...

Python----------字符串
1.转义字符 注:转义字符放在你所想效果字符前 2.原始字符串 print(r"D:\three\two\one\now") ->D:\three\two\one\now注: 在使用原始字符串时,转义字符不再有效,只能当作原始的字符,每个字符都没有特殊…...

日志收集笔记(架构设计、Log4j2项目初始化、Lombok)
1 架构设计 ELK 技术栈架构设计图: 从左往右看, Beats:主要是使用 Filebeat,用于收集日志,将收集后的日志数据发送给 Kafka,充当 Kafka 的生产者Kafka:高性能消息队列,主要起缓冲…...

一文教你玩转 Apache Doris 分区分桶新功能|新版本揭秘
数据分片(Sharding)是分布式数据库分而治之 (Divide And Conquer) 这一设计思想的体现。过去的单机数据库在大数据量下往往面临存储和 IO 的限制,而分布式数据库则通过数据划分的规则,将数据打散分布至不同的机器或节点上…...

数据挖掘,计算机网络、操作系统刷题笔记54
数据挖掘,计算机网络、操作系统刷题笔记54 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,可能很多算法学生都得去找开发,测开 测开的话,你就得学数据库,sql,orac…...

将数组中的每个元素四舍五入到指定的精度numpy.rint()
【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 将数组中的每个元素 四舍五入到指定的精度 numpy.rint() 选择题 请问np.rint(a)的输出结果是? import numpy as np anp.array([-1.72,-1.3,0.37,2.4]) print("【显示】a:\n…...
Web安全之服务器端请求伪造(SSRF)类漏洞详解及预防
如何理解服务器端请求伪造(SSRF)类漏洞当服务器向用户提交的未被严格校验的URL发起请求的时候,就有可能会发生服务器端请求伪造(SSRF,即Server-Side Request Forgery)攻击。SSRF是由攻击者构造恶意请求URL&…...
LeetCode:239. 滑动窗口最大值
239. 滑动窗口最大值 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1: 输入:nums [1,3,-…...
JS 函数参数(动态参数、剩余参数)
需求:求和函数 传入不同实参 求和出来1.动态参数 arguments 只存在于函数内function getSum() {//arguments 获取传递的所有参数 是一个伪数组let num 0for(let i0;i<arguments.length;i){num arguments[i]}return num}//调用console.log(getSum(1,2,3))consol…...

365天深度学习训练营-第J3周:DenseNet算法实战与解析
目录 一、前言 二、论文解读 1、DenseNet的优势 2、设计理念 3、网络结构 4、与其他算法进行对比 三、代码复现 1、使用Pytorch实现DenseNet 2、使用Tensorflow实现DenseNet网络 四、分析总结 一、前言 🍨 本文为🔗365天深度学习训练营 中的学习…...

Parisland NFT 作品集
该作品集用来自 Parisland 体验,共包含 11 个 NFT 资产,把你的土地装扮成一个眼花缭乱的热带天堂吧! 登上芭黎丝的爱情船和戴上豪华的螺旋爱情戒指,成为她在数位世界举办的真人秀的一部分吧!该系列还包含两个传奇级别的…...

uniapp: 基础开发官网文档
1、uniapp官网文档:https://uniapp.dcloud.net.cn/component/2、uView跨端UI组件库:http://v1.uviewui.com/components/intro.html3、lunch-request(类似axios的请求库):https://www.quanzhan.co/luch-request/handboo…...
mybatis中配置连接池的原理介绍分析
1.连接池:我们在实际开发中都会使用连接池。因为它可以减少我们获取连接所消耗的时间。2、mybatis中的连接池mybatis连接池提供了3种方式的配置:配置的位置:主配置文件SqlMapConfig.xml中的dataSource标签,type属性就是表示采用何…...

二叉树——路径总和
路径总和 链接 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。 叶子节点…...

WebDAV之π-Disk派盘+文件管理器
文件管理器 支持WebDAV方式连接π-Disk派盘。 推荐一款iOS上的免费文件管理器新秀。 文件管理器这是一款功能强大的文件管理工具,支持zip,rar,7z等压缩包的解压和压缩,支持小说,漫画,视频下载及播,极大提升日常办公,娱乐,文件管理的工作效率,使得文档的归档和管理随心…...
form表单单输入框回车提交事件处理
问题 form表单中如果只有一个输入框,在输入时按Enter回车键会出发默认事件自动提交表单,该交互是同步发生的,会导致页面刷新。 解决思路 有三种解决思路: 1. 增加input输入框的数量 如果form表单中不止一个input输入框&#…...

c++常用stl算法
1、头文件 这些算法通常包含在头文件<algorithm> <functional> <numeric>中。 2、常用遍历算法 for_each(v.begin(),v.end(), 元素处理函数/仿函数) 注意:在使用transform转存时,目标容器需要提取开辟合适的空间。 void printfunc(…...

非对称密钥PKCS#1和PKCS#8格式互相转换(Java)
目录一、序言二、代码示例1、Maven依赖2、工具类封装三、测试用例1、密钥文件2、公私钥PKCS1和PKCS8格式互相转换一、序言 之前在 《前后端RSA互相加解密、加签验签、密钥对生成》 中提到过PKCS#1格式和PKCS#8格式密钥的区别以及如何生成密钥。实际有些场景中有可能也会涉及到…...

java获取当前时间的方法:LocalDateTime、Date、Calendar,以及三者的比较
文章目录前言一、LocalDateTime1.1 获取当前时间LocalDate.now()1.2 获取当前时间的年、月、日、时分秒localDateTime.getYear()……1.3 给LocalDateTime赋值LocalDateTime.of()1.4 时间与字符串相互转换LocalDateTime.parse()1.5 时间运算——加上对应时间LocalDateTime.now()…...

npm link
正文npm link的用法假如我们想自己开发一个依赖包,以便在多个项目中使用。一种可行的方法,也是npm给我们提供的标准做法,那就是我们独立开发好这个 "依赖包",然后将它直接发布到 npm镜像站 上去,等以后想在其…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...