# 网关后台管理系统 **Repository Path**: ruanjingtao/KPS-management ## Basic Information - **Project Name**: 网关后台管理系统 - **Description**: 基于vue3全家桶+vite+element-plus+vue-i18n的后台管理系统 - **Primary Language**: Unknown - **License**: GPL-3.0 - **Default Branch**: feature/permission - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-08-16 - **Last Updated**: 2024-06-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # KPS网关后台管理系统 ## 技术栈 - [Vue3](https://vue3js.cn/docs/zh/) - [Element-Plus](https://element-plus.gitee.io/#/zh-CN) - [Vite](https://cn.vitejs.dev/) - [Vue-Router](https://next.router.vuejs.org/zh/index.html) - [Axios](http://www.axios-js.com/) - [ES6](https://www.bookstack.cn/read/es6-3rd/sidebar.md) axios基础 也是vue官方推荐的http的ajax请求方式,支持promise,也是基于XHR的进一步封装,好用且强大 https://juejin.cn/post/7084163923552780319 JS使用的是**ES6**及以上语言 **ECMAScript 6.0**(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。也需要大概了解下使用,比ES5之前要强大很多很多 Vue3 + Vite + Vue-Router + Element-Plus + Axios + vue-i18n 后台管理系统。 在开发之前最好了解一下vue的一些基本概念 生命周期,响应式 ,单文件组件 ,属性绑定,双向绑定,事件绑定,模板语法,模板引用,条件渲染,列表渲染,监听器,组件间通信(子传父props,父传子emits,兄弟组件间通信), 我会带大家用10分钟左右在vue3官方文档进行一个粗略了解,不做具体培训,知道怎么使用就可,具体后续可查看官方文档 表单的UI组件使用的是elment-plus,在开发过程中,遇到不知道如何使用那些表单组件可查看官方文档,上面链接可点击,点击右上方组件即可查看 ## 开发环境要求 node.js, vue3 ,包管理工具 npm或yarn (推荐) 开发工具选择,推荐使用jetbrain的webstorm 代码提示和报错以及git工具都很实用, 内网中有安装包,在web开发工具下,在下个vue插件即可,插件下载需要外网 同时也可以使用微软的vscode进行开发,这个看个人喜好,vscode也需要安装对应的vue插件才可以代码提示vue 运行项目命令, 在没有安装node依赖时,输入npm install或者 yarn install安装node_models,需要外网环境,如果内网开发的话,需要配置代理,详情询问JAVA后端刘海涛,代理服务器是他配置的,但是很容易出问题,有些依赖一直下不下来 依赖安装完成后,调试可以打开 `yarn dev` 或 npm run dev,运行成功后,控制台出现如下,进入如下url即可,配置好的为本地的3000端口,这个可以自己修改,在根目录的vite.config.js中的server属性,在开发过程中通过这种方式能更快的看到编码效果,避免总是打包,一般是完成一个需求迭代之后,再进行打包发布 ![image-20230619111510801](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230619111510801.png) 打包命令 ,完成开发后,需要将一个个的vue文件打包成服务器可以发布服务的html文件 `yarn build` 或 npm run build 打包成功后会根目录下生成/dist目录,该目录只有一个html和一堆js和css是一个单页面应用SPA, 此时这个就是一个可以部署的应用了,可以自己起一个web服务器或者nginx 部署到板子方式,打包完成后,复制/dist 打包目录下的所有文件到明哥的python的打包目录下安装python3环境,运行package.py 生成webPacketNew.bin 并通过ssh连接板子,烧写在板子的/usr目录下,同时拷贝webServerNew到同级目录,进行运行即可 ![image-20230619095459716](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230619095459716.png) - ## 页面展示 以下为 vue3-admin 系统的部分页面预览图: ![image-20230615112902922](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230615112902922.png) ![image-20230615112938824](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230615112938824.png) ![image-20230615112948210](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230615112948210.png) ![image-20230615112958674](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230615112958674.png) ![image-20230615113023275](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230615113023275.png) ![image-20230615113122992](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230615113122992.png) ![image-20230615113131859](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230615113131859.png) ## 项目目录结构 从上至下 /config 配置文件,存储发送请求的baseUrl 也可以根据需要存储其他东西 /dist yarn build打包命令后打包完成的html页面存放目录,为SPA单页应用 /node_modules node下载的依赖目录,不可编辑,不用去操心,全部有包管理器自动管理 /public 公用资源存放目录,打包后会存在dist目录下 /src 源码目录,也是最常用的开发目录,需要编辑的代码全部在里面 其中 api:存放封装的axios接口 assets:是用于存放着各种静态文件,比如图片。 components:用于存放我们自定义的公共组件,即**非路由组件**,区别views包下的page组件。 router:vue-router路由文件。index.js中引入views包下的.vue。 store:是vuex的文件,主要用于项目里边的一些状态保存。比如state、mutations、actions、getters、modules。 views:用于存放我们写好的各种页面,即**路由组件**,比如Login.vue,Home.vue。 App.vue:是主vue模块,主要是使用router-link引入其他模块,App.vue是项目的主组件,所有的页面都是在App.vue下切换的。 main.js:程序入口文件,主要作用是初始化vue实例,同时可以在此文件中引用某些组件库或者全局挂载一些变量。 https://blog.csdn.net/Kedaya_7017/article/details/123249471 https://juejin.cn/post/6945369337100238885 ## 多语言的配置 多语言框架我已部署完成,采用vue-i18n 文档可具体查看官方 我部署好之后,你们只需要维护src/language下的对应的cn.js和en.js,其中也是对象的形式,相同的属性名,在两个或者多个文件中找到的时候会对应进行翻译,如果包含子对象,例如状态信息,在使用时就要使用 menuStr.serialIfaceStr 多语言有两种使用的位置,一种是模板中,例如给组件或者标签的 label属性赋值,**注意!**label前要加 **:** 这是vue的v-bind简写语法,也就是把等号后面的值当成一个js表达式,这很重要,不加 **:** 的话会被当成字符串被解析,使用vue-i18n的$t() 方法去使用,这个$t就是被vue实例集成i18n后暴露出的翻译方法,当然这是最简单一个翻译方法,可以翻译值为字符串的情况, ```vue ``` 还有一种就是在标签体内部书写用双花括号 也就是胡子表达式,其中也是解析的js表达式 ```vue
{{$t('pageStr.enableStr')}}
``` 如果翻译的值是复杂类型,例如数组,配合vue的列表渲染,那个时区数组就是,这时候就要用$tm来翻译 ```vue ``` 如果在scripts中使用则需要进行引入,因为vue3后没有了组件的this对象,对于组件方法的调用更多的采用了封装的工厂函数,这样也可以减小打包体积,具体原理可查看vue3文档,这里不在赘述 引入vue-i18n的useI18n方法,为一个工厂方法,调用后返回一个对象,并只通过对象解析的形式只获得需要的对象就行,比如t方法 注意 这里不需要$t 其实t和$t本质是一个方法,但是因为在模板中暴露出的方法名称就是这个,可能是为了避免方法名重复,一般模板里暴露的方法都是有$符号的,这是基本统一的规范,例如路由组件在模板中是$router一样 ```js import {useI18n} from 'vue-i18n' const {t} = useI18n() ``` 使用时也方法一样 $t换成t就行 ```js ElMessage.info(t('alertStr.errorUploadStr')) ``` ## 异步ajax请求的封装和新建 在src/api目录下,存放一个个的子目录,为了便于辨识,与views目录下保持一致,可以清晰的看出是哪个页面的接口,如需要添加或者修改对应的接口,可新建一个js,并引入封装好的axios ![image-20230619112540633](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230619112540633.png) ```js /** * 基本告警设置 */ import request from '@/utils/axios' /** * 获取基本告警 * @returns {*} */ export function reqGetBasicAlarm() { return request.get('/status/getAlarmBasic') } /** * 设置基本告警 * @returns {*} */ export function reqSetBasicAlarm(data) { return request.post('/status/setAlarmBasic', data) } ``` 在每个方法前暴露出去 **export** 这是es6的模块化语法,详情可自己了解,在组建中使用时对应的**import** 并通过{} 解析出需要使用的对应方法就可以 ```js import {reqSetBasicAlarm, reqGetBasicAlarm} from '@/api/application/basic-alarm' ``` 然后在调用接口时,这个方法是一个**promise**(也是es6之后的异步任务封装,详情可自己查阅文档,可以避免回调地狱,代码逻辑也更加清晰) 请求成功则进入then的回调函数,失败就进入catch的 这里回调方法可以自己定义 ```js console.log('设置基本告警', form.value) reqSetBasicAlarm(form.value).then(() => { ElMessage.success(t('successTip')) }).catch(() => { ElMessage.error(t('errorTip')) }) ``` ## 新菜单的创建 如果想要在该应用中创建一个新的菜单或者子菜单,需要如下三个步骤 例如,需要在最外层菜单,新建一个选项,测试根菜单,在我封装好的组件/src/components/Menu.vue组件中在你想要的位置新建一个el-sub-menu 这个标签是包含子标签的,并可以重写title插槽来修改标题内容,具名插槽的调用方式为# 详情插槽概念可查看vue3文档, 使用的element组件也可以上官网查看, ![image-20230619150712019](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230619150712019.png) 需要添加子菜单,就在刚才的sub-mune下建一个menu-item子标签,并给**index属性**写入路由,到时候会根据你赋值的这个字符串来跳转路由 重要! 然后在router/index.js 路由的配置初始化文件中,createRouter方法中,routes数组增加对应的一条 其中path属性,与刚才的跳转路由一致,然后componet为渲染的组件, ![image-20230619150654635](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230619150654635.png) ## 公共组件的封装和使用 https://cn.vuejs.org/guide/essentials/component-basics.html 把项目中觉得很多地方都需要复用的地方,或者代码量比较大想拆分的地方可以进行组件化,这样也更便于代码的维护的管理,减少重复代码量,组件放在src/components下,组建的封装要尽量的考虑到可以复用的原则,可以多传递一些参数来配置不同的情况 ![image-20230619112913723](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230619112913723.png) Vue组件的[API](https://link.juejin.cn?target=https%3A%2F%2Fso.csdn.net%2Fso%2Fsearch%3Fq%3DAPI%26spm%3D1001.2101.3001.7020) 主要包含三部分:**prop、event、slot** - props表示组件接收的参数,最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,此外还可以通过type、validator等方式对输入进行验证 - slot可以给组件动态插入一些内容或组件,是实现高阶组件的重要途径;当需要多个插槽时,可以使用具名slot - event是子组件向父组件传递消息的重要途径 例如我在发现项目中有很多的文件上传地方,大体是相同的比如样式,逻辑,不同的只是上传的url,是否需要stop,是否需要分片,文件扩展名限制,以及上传按钮文字的不同,这时候我在封装之后,每个地方使用只需要一行代码,首先当然是进行组件的导入,在需要使用该组件的地方进行import,然后在模板中对应使用即可 ```vue ``` 给大家讲一下这个组件的实现过程和封装的思路,首先就是对文件上传的样式进行美化,同时对上传逻辑进行了一个适配,采用的也是原生的input 只是我把他隐藏了,用的外部的div调用input的click事件来实现点击的效果,在props中,父组件可以向其传很多值,来决定这上传文件组件需要干的事,这个后续如果业务有新需求,也可以考虑增加新的属性,这样也增加了其扩展性,一个组件可以导出使用,避免每个地方都是写几百行,不好维护。 ```js const props = defineProps({ // 上传路径 url: { type: String, default: '' }, // 上传按钮的文字 btnLabel: { type: String, default: '' }, // 过滤的文件类型 accept: { type: String, default: '' }, // 过滤的文件表单内名称 fileName: { type: String, default: 'file' }, // 是否需要stopApp isStop: { type: Boolean, default: false }, // 是否需要分片 isResumable: { type: Boolean, default: false }, isChecked: { type: Boolean, default: false } }) ``` 然后在onMounted生命周期中,如果需要分片的话,就进行分片的监听初始化,必须要在这个生命周期中,之前在onBeformMount生命周期中老是报错,原因是那时候dom元素还没加载 挂载到dom上的监听就会失败 然后再上传按钮的点击事件中,根据不同的选项进行不同的操作,同时判断是否选择了文件,没有就报错,进行验证,以及url是否传入,并且在成功上传后,向父组件抛出一个事件,并把请求成功返回的结果带给父组件,因为不同的请求返回的结果不同,抛给上级就可以把这个逻辑交给不同的父组件去判断 例如在刚才edps升级页面,成功之后的回调就可以这样写 ```js /** * 上传成功后的事件回调 */ function onUploaded(data) { console.log('上传完成的回调', data) if (data.updateInfo == -1) { ElMessage.error(t('errorTip')) } else { ElMessage.error(t('successTip')) } } ``` ## 工具类的使用的封装 为了把一些常用的,可复用的用法进行封装和复用,就有了工具类,工具类一般存放在src/utils目录下 在index中我首先封装了几个常用的方法 ```js function localGet(key) ``` 获取浏览器localStorage指定key的vaule ```js function formatBytes(bytes) ``` 输入指定字节数数字,根据大小转换成kb或者mb并返回 ```js /** * 下载文件方法 * @param url 下载文件路径 * @param fileName 保存文件的名称,如果不给就去响应头中content-disposition属性的文件名 */ export function downloadFile(url, fileName) /** * 使用des加密 * @param data 需要加密的数据 * @param cryptedKey 加密密钥 * @returns {*} 返回加密后结果 */ export function encrypt(data, cryptedKey = 'kyland.com') /** * 使用des解密 * @param data 需要解密数据 * @param cryptedKey 加密密钥 * @returns {*} 解密之后结果 */ export function decrypt(data, cryptedKey = 'kyland.com') /** * 验证IP的正则验证方法,加载rulse的validator属性中 * @param rule * @param value * @param callback */ export function validateIP(rule, value, callback) /** * @description 格式化时间 * @param date * @param format 转换的格式 */ export function dateFormat(date, format = 'YYYY-MM-DD HH:mm:ss') ``` 使用时,在对应组件的script内引入就可,例如登录很多地方对于密码的提交都要统一的进行三重DES加密 ```js import {localSet, encrypt} from '@/utils' const auth = encrypt(state.ruleForm.password) axios.post('/cn/cgi/login', {uname, auth}). ``` ## axios拦截 在进行ajax请求时,需要对每次来的响应和每次发出的请求做出一定的封装和拦截,这时候就需要拦截器,具体配置在src/utils/axios.js中 因为需要适配板子上后端的接口,所以对请求数据格式变为form格式,如果是post请求还需要字符串化拼接在Url后面, 同时由于响应的数据的不规范,还要对每个返回的数据做判断,在响应拦截器内可以自行配置,常用的几个我已经做好拦截和返回 ```js // request拦截器 service.interceptors.request.use( (config) => { // console.log('axios请求拦截器', config) if (config.method === 'post' && config.data) { config.data = qs.stringify(config.data) } return config }, (error) => { return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use(res => { // console.log('axios拦截器',res,typeof res.data) if (res.status === 200) { if (!res.data) { console.log('响应数据是空') return Promise.resolve('') } if (typeof res.data !== 'object') { if (res.data.startsWith('<')) { // 登录成功 if (res.data.indexOf('index.html') !== -1) { return Promise.resolve(res.data) } // 返回login登录失效 if (res.data.indexOf('login.html') !== -1) { localRemove('token') window.location.href = '/' // ElMessage.error('登录失效') return Promise.reject('登录失效') } // 响应的是xml if (res.data.indexOf(' { if (to.path == '/login') { // 如果路径是 /login 则正常执行 state.showMenu = false next() } else { // 如果不是 /login,判断是否有 token if (!localGet('token')) { // 如果没有,则跳至登录页面 next({path: '/login'}) state.showMenu = false ElMessage.error('登录状态已过期,请重新登陆!') } else { heartBeat() timer && clearInterval(timer) timer = setInterval(heartBeat, 5000) // 有token就是已登录,继续执行,显示菜单 state.showMenu = true next() } } // console.log('app的路由守卫,to', to) state.currentPath = to.path document.title = to.meta.title }) ``` ## 新页面创建 新页面创建主要分为几个流程,模板编写,样式编写,脚本编写,接口对接,自测,出于规范的目的项目流程一般要分为需求提出,需求评审,原型设计,接口文档,UI设计图等,好的规范化文档可以让开发事半功倍,磨刀不误砍柴工。 例如 宜化新材料其中一个接口的文档,请求参数,响应结果响应示例, 这是由后端工程师完成的,还有接口调试集成一起,像这个项目,由于很多接口不同人设计,返回字段,请求字段,和各个接口的返回结果没有一个规范,并且没有文档,所以会对接起来比较吃力,只能一个个对着原有页面,一个个发请求,字段靠猜,效率也会降低。 ![image-20230619150235926](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230619150235926.png) ![image-20230619150305180](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230619150305180.png) 原型图,展示页面的交互逻辑和大致布局等 ![image-20230619155320911](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230619155320911.png) UI设计稿给出详细的界面设计,每个地方的字体,颜色,像素大小,用到的图片等等,并尽量严格去等效的实现出来,通过html+css ![image-20230619155359188](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230619155359188.png) 拿到页面原型和UI设计后开始编写页面,首先在src/views下建立对应.vue文件,命名按照大驼峰,并在路由配置中对应配置,详情请见菜单创建 模板中语法几乎兼容所有html语法,就当做是html来编写即可,然后style中去做样式处理,默认就是css 也可以指定使用scss等,这些基础就不在赘述,主要是表单和UI控件,使用element-plus可以大大减少代码量,同时带来优秀的界面反馈和易用的组件等, 最常用的就是表单组件 el-form 在涉及到页面需要增删改查,调用接口等,需要用到,对数据进行回显,设置提交表单,表单验证等,实例可看我已编写页面 例如ftp设置,el-form的ref属性可以获取这个表单的示例,验证时需要用到,model绑定表单数据,rules绑定验证规则,label-width为表单label宽度等等,在element官方文档都有详解 ```vue ``` ## 表单的验证流程 首先在script中建立form响应式对象存放表单数据, formRef存放form的ref,rules为表单验证规则,为一个对象,其中每个属性都为一个数组,这个验证是一个最简单的非空验证,required为true,message为验证失败的提示,trigger为触发方式,还有其他的可文档查阅, ```js const form = ref({}) const formRef = ref(null) const rules = { username: [ {required: 'true', message: t('userEmptyTip'), trigger: 'blur'} ], userpassword: [ {required: 'true', message: t('pwdEmptyTip'), trigger: 'blur'} ] } ``` 稍复杂的 例如很多地方需要验证IP地址格式,或者密码规则的,可以使用正则表达式验证,这几乎可以满足所有的验证,自己去找出正确的匹配规则即可,我就拿IP验证做一个示例 并别忘了在对应的el-form-item中加入prop属性,这个和rules中的属性严格保持一致,否则接收不到验证,并和对应数据的form属性保持一致 ```vue ``` 这是工具类中的IP验证函数,逻辑已经非常清晰,包括入参和返回值,其他的规则,稍加变通,更换一下表达式即可 ```js /** * 验证IP的正则验证方法,加载rulse的validator属性中 * @param rule * @param value * @param callback */ export function validateIP(rule, value, callback) { if (value === '') { callback(new Error('Please enter IP address')); } else if (/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(value)) { const parts = value.split('.'); if (parts[0] > 255 || parts[1] > 255 || parts[2] > 255 || parts[3] > 255) { callback(new Error('Please enter a valid IP address')); } else { callback(); } } else { callback(new Error('Please enter a valid IP address')); } } ``` 而且这是一个复杂对象的验证,是对象中对象的属性,所以rules的key 不能是一个表达式,而是一个字符串,访问的其实就是form.staticProtoInfo.ipaddr, 然后值同样为一个数组,第一个是必填验证同上,第二个就是正则验证,在validator属性中给入验证函数,验证函数,我写在工具栏中,为了复用,这样所有用到这个验证的地方都引入就不用重复代码 ```js import {validateIP} from "@/utils"; const rules = reactive({ 'staticProtoInfo.ipaddr': [ {required: true, message: '必填项不能为空', trigger: 'blur'}, {validator: validateIP, message: '请输入正确的IP', trigger: 'change'}], }) ``` 最后在提交按钮的点击事件中去调用表单验证 ```js formEl?.validate(async (valid) => { // 如果验证不通过,直接返回函数,什么都不会操作 if (!valid) { return } //表单提交的操作,调用ajax接口等,并对响应结果做出判断 form.value.userpassword = encrypt(form.value.userpassword) console.log('设置ftp', form.value) reqSetFtp(form.value).then(() => { ElMessage.success(t('successTip')) }).catch(() => { ElMessage.error(t('errorTip')) }).finally(getData) }) ``` ## 权限配置管理 由于系统业务需要,登录角色暂被分为三种:只读,读写,管理员,权限等级分别为1,2,99,在src/utils/permission.js下建立枚举类,后期也好便于维护,不在代码中写这些魔法量,不便于统一管理 ```js export default { read: [1], write: [2], readAndWrite: [1, 2], adminAndWrite: [99, 2], admin: [99], all: [1, 2, 99] } ``` 使用方法,已封装自定义指令 **v-permissionShow** 在你需要控制不同的权限是否显示该元素时使用,使用该指令不需要引入,已经作为全局指令,该指令后可跟数组参数,当然不建议直接写数组,不好维护,直接引入权限枚举类后,来使用 例如,permission.admin 等价于 [99] 下面代码就完成了左侧菜单,用户管理菜单只有管理员可见, 系统-重启菜单,只有管理员和读写可见 ```vue ``` 除了菜单不可见,还有一种业务需求,在权限为只读的时候,表单是禁用的状态,这时通过指令不好实现,采用element自带的disabled属性去设置,然后通过一个方法,判断当前用户权限,如果为只读,就返回true来禁用表单 该方法在工具类utlis/index.js中,在要使用的组建中记得需要import ```js /** * 判断当前权限是否需要禁用,为只读就禁用表单,返回true,其他返回false */ export function permissionDisabled() { const level = localGet('token')?.level || 0 return level === 1 } ``` ```vue // 在你需要控制的表单或者控件中调用函数给disabled属性赋值,注意加: 是v-bind绑定的表达式,不是字符串 在script中引入该工具类的函数 ``` 然后表单提交下的按钮,在只读下也是隐藏的,这是同菜单,用自定义指令就可以,把你需要隐藏的按钮,或者整块提交的模块给隐藏,根据权限,也可自己配置参数 ```vue {{ $t('tabsStr.applyStr') }} ``` ## 动态路由的配置 在配置好不同权限的显隐和禁用后,还有一个问题,就是只读用户,如果知道了页面路由,例如不是他权限应该访问的,用户管理,user-manage,他在地址栏输入后,由于路由已经挂载,还是能匹配到对应组件,这时他还是能访问的, 所以这时候要把不同权限分配的路由给抽离出来,作为动态路由, 所有权限都可以访问的路由作为公共路由,就放在router/index.js的routes下, 动态路由的挂载使用vue-router的 addRoute方法,详情查看app.vue中的全局前置路由守卫中的方法,大概逻辑就是用户登录成功,有token后,判断当前登录用户的用户权限等级,然后为管理员时 挂载哪些路由,给一个变量使其只挂载一次就够, 然后为管理员或者读写时挂载哪些路由,这些都可以后期添加和维护,注意 一次添加多个路由时,addRoute方法不支持数组参数,所以要将数组遍历,然后循环的方式一个个添加其中的路由对象 ![image-20230627102339011](https://gitee.com/ruanjingtao/image1/raw/master/images/image-20230627102339011.png) 例如为读写和管理员时,挂载系统除日志外的其他页面 ```js else if (permission.adminAndWrite.includes(level) && !isAdd) { isAdd = true console.log('当前权限是读写或者管理员,添加系统路由', user) // 动态添加的路由数组 const routes = [ { path: '/backup', name: 'backup', meta: { title: '备份/恢复' }, component: () => import(/* webpackChunkName: "Log" */ './views/system/Backup.vue') }, { path: '/upgrade', name: 'upgrade', meta: { title: '升级' }, component: () => import(/* webpackChunkName: "Log" */ './views/system/Upgrade.vue') }, { path: '/system-reset', name: 'system-reset', meta: { title: '系统重置' }, component: () => import(/* webpackChunkName: "Log" */ './views/system/Reset.vue') }, { path: '/reboot', name: 'reboot', meta: { title: '重启' }, component: () => import(/* webpackChunkName: "Log" */ './views/system/Reboot.vue') } ] // 循环遍历要添加的路由数组,es6新语法,函数式编程,避免写for routes.forEach((route) => { router.addRoute(route) }) } ``` 进行如上操作后,在只读权限下,哪怕在地址栏知道了这些路由地址,输入,由于没有添加路由,找不到匹配组件,也是只会进入404页面,也就确保了页面的安全性。