최근에 프로젝트를 마치고 나서 고객의 다양한 어려움으로 인해 새로운 프로젝트 요구 사항이 추가되었습니다
. 현재 사용자의 해당 권한에 대한 라우팅 테이블을 백그라운드에서 보내고 프런트 엔드에서 후처리(백- 최종 처리 라우팅) 조정 인터페이스를 통해
vue 프로젝트는 동적 라우팅을 실현합니다. 방법은 크게 두 가지 유형으로 나눌 수 있습니다:
1. 첫 번째는 프론트 엔드에 라우팅을 작성하고 사용자의 역할에 따라 라우팅을 동적으로 표시하는 것입니다. 로그인 시 권한. 자세한
내용(프론트엔드 제어 라우팅)은 이 사람이 쓴 것을 볼 수
있습니다 . 대신의 다이내믹한 라우팅에는 여러 겹의 판단이 있고 다양한 vuex, Xiaobai가 산재되어 있기 때문에 약간의 논리를 이해했습니다.
나는 거의 혼란스럽고 많은 영감을 얻습니다. 2.
두 번째는 현재 사용자의 해당 권한의 라우팅 테이블을 백그라운드에서 전송하고 프런트 엔드는 조정 인터페이스를 통해 획득한다는 것입니다.후처리(백 엔드 처리 라우팅),
이 두 가지 방법은 나름의 장점이 있고 효과를 얻을 수 있습니다.우리 회사에서는 이번에 두 번째 방법이 필요합니다.이유는 회사의 프로젝트에 특별한 사용자 센터가 있고 내부의 논리가 매우 복잡하기 때문입니다. , 프론트엔드 사용자에게 권한을 돌려주는 것이 쉽지 않고, 프론트엔드
에 라우팅을 두는 것이 안전하지 않을까 걱정이 됩니다. 실은 나도 잘 모르겠고, 요구사항에 따라 항상 잘못된 것은 없다.) 그럼, 모든 것을 다 잡고 자신의 능력의 태도를 연습하고
, 두 번째 방법을 해보자.
아이디어:
1.后台同学返回一个json格式的路由表,我用easy mock造了一段:动态路由表,大家可参考(easymock就是个模拟后台数据接口的,你习惯用啥都可以。mock / RAP /等);
2.因为后端同学传回来的都是字符串格式的,但是前端这里需要的是一个组件对象,所以要写个方法遍历一下,将字符串转换为组件对象;
3.利用vue-router的beforeEach、addRoutes、localStorage来配合上边两步实现效果;
4.左侧菜单栏根据拿到转换好的路由列表进行展示;
操作的大体步骤:1.拦截路由->2.后台取到路由->3.保存路由到localStorage(用户登录进来只会从后台取一次,其余都从本地取,所以用户,只有退出在登录路由才会更新)
코드의 첫 번째 단계인 시뮬레이션된 백그라운드 json 데이터 라우팅 테이블:
각 경로는 전체 페이지 레이아웃인 Layout 구성 요소를 사용합니다. 왼쪽의 메뉴 열, 오른쪽의 페이지, 따라서 자식 아래의 첫 번째 수준 경로는 자신이 개발한 페이지이고 메타에는 경로 이름이 포함됩니다. 그리고 경로에 해당하는 아이콘, 다단계 메뉴가 있을 수 있기 때문에 자식이 자식 아래에 중첩되는 상황이 발생할 수 있습니다.
"data": {
"router": [
{
"path": "", // 路径
"component": "Layout", // 组件
"redirect": "dashboard", // 重定向
"children": [ // 子路由菜单
{
"path": "dashboard",
"component": "dashboard/index",
name: 'dashboard'
"meta": { // meta 标签
"title": "首页",
"icon": "dashboard" // 图标
noCache: true
}
}
]
},
{
"path": "/example",
"component": "Layout",
"redirect": "/example/table",
"name": "Example",
"meta": {
"title": "案例",
"icon": "example"
},
"children": [
{
"path": "table",
"name": "Table",
"component": "table/index",
"meta": {
"title": "表格",
"icon": "table"
}
},
{
"path": "tree",
"name": "Tree",
"component": "tree/index",
"meta": {
"title": "树形菜单",
"icon": "tree"
}
}
]
},
{
"path": "/form",
"component": "Layout",
"children": [
{
"path": "index",
"name": "Form",
"component": "form/index",
"meta": {
"title": "表单",
"icon": "form"
}
}
]
},
{
"path": "*",
"redirect": "/404",
"hidden": true
}
]
}
시뮬레이션 데이터가 있으면 일반 코드를 작성할 수 있습니다. 먼저 라우터 폴더에서 index.js 파일을 찾습니다. 회사 프로젝트에 캡슐화 계층이 여러 개 있는 경우를 제외하고 라우팅 구성은 일반적으로 라우터 폴더에 있지만 라우팅 파일을 찾는 데 충분합니다.
그런 다음 라우터/index.js 파일에 코드를 입력하고 빈 라우팅 테이블을 내보냅니다:
router/index.js
//清空路由表
export const asyncRouterMap = []
그런 다음 src 파일인 permission.js 파일에 코드를 작성합니다(권한 권한 파일은 src 폴더 바로 아래에 있으며 라우터와 동일한 수준입니다. 나중에 권한 파일을 메인에 추가하기만 하면 됩니다. js 파일.)
파일을 가져옵니다 .
我们将后端传回的"component": "Layout", 转为"component": Layout组件对象处理:
因为有多级路由的出现,所以要写成遍历递归方法,确保把每个component转成对象,
因为后台传回的是字符串,所以要把加载组件的过程 封装成一个方法,用这个方法在遍历中使用;详情查看项目里的router文件夹下的 _import_development.js和_import_production.js文件。
permission.js 파일의 코드는 다음과 같습니다.
(使用到的技术点 beforeEach、addRoutes、localStorage来配合实现
beforeEach路由拦截,进入判断,如果发现本地没有路由数据,那就利用axios后台取一次,取完以后,利用localStorage存储起来,利用addRoutes动态添加路由,
切记切记,得在一开始就加判断要不然一步小心就进入到了他的死循环,浏览器都tm崩了。
拿到路由了,就直接next(),
global.antRouter是为了传递数据给左侧菜单组件进行渲染)
import axios from 'axios'
const _import = require('./router/_import_' + process.env.NODE_ENV)//获取组件的方法
import Layout from '@/views/layout/Layout' // Layout 是架构组件,不在后台返回,在文件里单独引入
var getRouter;
// 路由拦截router.beforeEach
router.beforeEach((to, from, next) => {
if (!getRouter) {//不加这个判断,路由会陷入死循环
if (!getObjArr('router')) { (// getObjArr是封装的获取localStorage的方法
// axios.get('https://www.easy-mock.com/mock/5c70ba7331b3fb6533b94241/example/restful/:id/list').then(res => {
axios.get('实际的后台接口').then(res => {
getRouter = res.data.data.router // 后台拿到路由
saveObjArr('router', getRouter) // 存储路由到localStorage的封装方法
routerGo(to, next) //执行路由跳转方法(导航到一个新的路由,详细解说看官网: https://www.cntofu.com/book/132/api/go.md)
})
} else {//从localStorage拿到了路由
getRouter = getObjArr('router')//拿到路由
routerGo(to, next)
}
} else {
next()
}
}
// 导航到一个新的路由方法封装
function routerGo(to, next) {
getRouter = filterAsyncRouter(getRouter) // 过滤路由
router.options.routes = getRouter; //必须在addroutes前,使用router.options.routes=XXXXX的方法手动添加
router.addRoutes(getRouter) //动态添加路由
global.antRouter = getRouter //将路由数据传递给全局变量,做侧边栏菜单渲染工作
next({ ...to, replace: true })
}
// 存储 localStorage 数组对象的方法
function saveObjArr(name, data) { // localStorage 存储数组对象的方法
localStorage.setItem(name, JSON.stringify(data))
}
// 获取 localStorage数组对象的方法封装
function getObjArr(name) { // localStorage 获取数组对象的方法
return JSON.parse(window.localStorage.getItem(name));
}
//过滤路由方法封装
function filterAsyncRouter(asyncRouterMap) { //遍历后台传来的路由字符串,转换为组件对象
const accessedRouters = asyncRouterMap.filter(route => {
if (route.component) {
if (route.component === 'Layout') {//Layout组件特殊处理
route.component = Layout
} else {
route.component = _import(route.component)
}
}
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children)
}
return true
})
return accessedRouters
}
그런 다음 라우터 폴더에 두 개의 js 파일을 추가합니다. 즉, 지연 로딩을 위한
router/_import_production.js
module.exports = file => () => import('@/views/' + file + '.vue')
라우터/_import_development.js
module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+
동적 라우팅은 기본적으로 완료되었습니다!
트래버스된 경로를 얻은 후
왼쪽 메뉴의 permission.js 파일이 렌더링됩니다.코드 처리에서 global.antRouter에 전역 변수(vuex로 대체 가능)인 값이 할당되고 경로는 메뉴에서 가져와서 렌더링합니다.(경로 가로채기 부분에 대해 많은 최적화가 있어야 합니다.)
가능한 문제:
遇到了 Cannot find module '@/xx/xxx/xxx.vue'的错误.
아이디어는 이것
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
我在App.vue中进行初始化路由. 完了后执行类似动作
router.addRoutes(newRouters);
只是路由在寻找这个异步component的时候提示找不到模块.
app.js:890 Error: Cannot find module '_c/layout/Layout'
另外 这个异步加载点有点疑问. 按照我的理解, webpack 根据这个import去分离代码. 那么在build的时候 webpack肯定会扫描到这个import点. 进而为这个点去创建分离点. 当实际网络请求到这个组件的时候在根据这个分离点去后端拿组件并注入.
然后现在的思路却是build后(在此之前并不知道分离点) 动态的生成import. 这时候webpack显然是不知道这个分离点的. 因为已经build完成了. 如果连分离点都不知道 又凭 什么去取组件异步加载呢?
按照上面的理解, 做了个简单的测试. 在import的工具中 我这样定义
export const Layout = () => import('_c/layout/Layout');
//module.exports = fileName => () => import(${fileName}.vue)
export default (file) => {
return Layout
}
也就是说 不管是什么component 我暂时都返回 Layout的异步加载组件.
唯一的区别就是 Layout是我事先定义好的一个异步加载的分离点.
而且确实这样过后 就不会在提示找不到module了.
如果这个思路是正确的 是不是意味着我得为所有的页面组件定义一个map. 然后统一定义分离点. 根据后端的返回key 动态的去mao里面把这个分离点加进去
솔루션:
main.js 파일에서
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
그런 다음 APP.vue 파일에서
<template>
<div id="app">
<div v-if="sysLoading">
<i class="fa fa-spinner fa-pulse fa-3x fa-fw"></i>
</div>
<transition v-else name="fade" mode="out-in">
<el-row v-if="initError" type="flex" justify="center" align="middle">
<el-alert :style="{width:'180px'}" :closable="false" type="error" show-icon :title="initError"></el-alert>
</el-row>
<router-view v-else></router-view>
</transition>
</div>
</template>
<script>
import { resetRouter } from "./router.js";
import AsyncImport from "./utils/AsyncImport.js";
export default {
name: "app",
data() {
return {
sysLoading: true,
initError: null
};
},
created() { // 挂载完成执行钩子函数
this.axios.get("/common/public/menus").then(
response => {
const { data: menuList } = response;
console.log("menuList", menuList);
this.initRouters(menuList);
console.log("routers after init:", menuList);
//设置新的路由
resetRouter().then(router => {
console.log("then router:", router);
router.addRoutes(menuList);
router.options.routes = menuList;
});
this.sysLoading = false;
},
() => {
this.initError = "初始化菜单失败";
this.sysLoading = false;
}
);
},
methods: {
initRouters(menuList = []) {
menuList.forEach(item => {
if (item.component && item.component != "") {
item.component = AsyncImport(item.component);
}
if (item.children && item.children.length > 0) {
this.initRouters(item.children);
}
});
}
},
};
</script>
AsyncImport.js
const routerViewMap = {
Layout: () => import('_c/layout/Layout'),
SysModule: () => import('@/views/sys/SysModule')
}
export default (key) => {
return routerViewMap[key] || null
}
router.js 파일에서
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const createRouter = () => new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: []
})
let router = createRouter();
export function resetRouter() {
return new Promise((resolve) => {
const newRouter = createRouter();
router.matcher = newRouter.matcher;
resolve(router);
});
}
export default router;
核心思路依然是动态的路由挂载.
只是事先定义好了分离点.
然后根据后端返回的component字符串去匹配这个分离点.
分离点不存在会抛Cannot find module xxxx