vue-element-admin后台项目总结(一)

1,575 阅读4分钟

参考了项目github地址
vue-element-admin官方 github.com/PanJiaChen/…
adminLTE(好看的样式库) github.com/ColorlibHQ/…


项目初始化

利用vue-cli3进行可视化项目的初始化,参考 blog.csdn.net/qq_40636998…

目录介绍


权限路由

权限路由主要作用是进行权限的认证,不同角色进入到系统给出不同的菜单以及路由,大部分权限路由是通过在router中设置路由拦截器 beforeEach 进行判断Cookie或者Session是否已经存在登录验证的Token,如果有就进行下一步的操作去后端获取路由信息,没有就弹出到登录页。
需要注意用户的信息和路由都是存储在vuex中,这样刷新页面,都会重新去获取,这样做是解决了在另一端修改了此用户的信息或者权限,本机能及时的更新信息。菜单以及路由都是存储在vuex中进行刷新的。

router.beforeEach((to, from, next) => {
  const token = store.state.user.token || '';
  if (token) { // 判断是否有token
    if (to.path === '/userlogin/login') {//判断是否跳转到登录页,如果有token把跳转登录页的请求直接重置到主页
      next({
        path: '/'
      });
    } else {
      if (store.getters.username.length === 0) { // 判断当前用户是否已拉取完user_info信息(在vuex中存储了用户信息)
        store.dispatch('user/GetInfo').then(() => { // 拉取用户信息
          store.dispatch('permission/GenerateRoutes').then(() => { // 生成可访问的路由表
            router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表(从vuex中获取)
            next({
              ...to,
              replace: true
            }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
          })
        }).catch(err => {
          console.log(err);
        });
      } else {
        next() //当有用户权限的时候,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面
      }
    }
  } else {
    if (to.path.includes('/userlogin')) {
      next();
    } else {
      next('/userlogin'); // 否则全部重定向到登录页
    }
  }
});

在动态路由中我们需要在router中配置,静态以及动态两个路由,静态路由是项目默认加载的路由,动态路由用于跟接口返回的路由数据进行拼接。

export const constantRouterMap = [//静态路由,默认加载
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path*',
        component:  () =>
        import( /* webpackChunkName: "redirect" */ '@/views/redirect/index'),
      }
    ]
  },
  {
    path: "/userlogin",
    name: "userlogin",
    redirect: "/userlogin/login",
    hidden: true,
    component: () =>
      import( /* webpackChunkName: "layout" */ "@/layout/user/userlayout.vue"),
    children: [{
        path: "/userlogin/login",
        name: "login",
        hidden: true,
        component: () =>
          import( /* webpackChunkName: "login" */ "@/views/user/login.vue")
      },
      {
        path: "/userlogin/register",
        name: "register",
        hidden: true,
        component: () =>
          import( /* webpackChunkName: "register" */ "@/views/user/register.vue")
      }
    ]
  }
];
export const asyncRouterMap = [//动态路由用于后面拼接
  {
    path: '/',
    name: 'home',
    redirect: '/home/main',
    component: Layout,
    children: [{
      path: '/home/main',
      name: 'mainhome',
      meta:{
        title: '首页'
      },
      component: () =>
        import( /* webpackChunkName: "main" */ "@/views/Home.vue")
    }]
  }
];

keep-alive多级缓存

后台项目中多级缓存是非常常见的,多是用于顶部的导航栏上,路由层级为三级的时候需要做特殊的处理

在需要缓存的router-view上进行keep-alive包裹,cachedView就是需要缓存的路由名称

<template>
 <keep-alive :include="cachedViews">
     <router-view :key="key"></router-view>
 </keep-alive>
</template>

<script>
 computed: {
    cachedViews() {
      return this.$store.state.tagsView.cachedViews;
    },
  }
</script>

在vuex中队需要缓存的页面进行存储,在多级的路由需要将路由的父级路由加入的缓存的cachedViews中,不然无法缓存成功,而且每一个vue页面都需要加上name:""这个属性,以此来识别需要缓存的页面。在清除缓存中需要将缓存中父级页面的名称也清除掉,不然页面还是会有缓存。

const state = {
  visitedViews: [],
  cachedViews: [],
  cachedViewsParent: []//存储每个路由的父级信息
}
const mutations = {
  ADD_VISITED_VIEW: (state, view) => {
    if (state.visitedViews.some(v => v.path === view.path)) return
    state.visitedViews.push(
      Object.assign({}, view, {
        title: view.meta.title || 'no-name'
      })
    )
  },
  ADD_CACHED_VIEW: (state, view) => {
    //多级缓存
    if (state.cachedViews.includes(view.name)) return
    view.matched.map(item => {
      //不缓存首页
      if (item.name.indexOf('home') == -1 && state.cachedViews.indexOf(item.name) == -1) {
        state.cachedViews.push(item.name)
      }
    });
    //不缓存首页
    if (!view.name.includes('home')) {
      state.cachedViewsParent.push({
        name: view.name,
        parent: view.matched.filter(item => item.name.indexOf('home') == -1).map(item => item.name)
      });
    }


  },
  DEL_VISITED_VIEW: (state, view) => {
    for (const [i, v] of state.visitedViews.entries()) {
      if (v.path === view.path) {
        state.visitedViews.splice(i, 1)
        break
      }
    }
  },
  DEL_CACHED_VIEW: (state, view) => {
    let arr = [];
    state.cachedViewsParent = state.cachedViewsParent.filter(item => item.name != view.name);
    //同级情况下不删除上级路由
    state.cachedViewsParent.map(i => [
      arr.push(...i.parent)
    ]);
    view.matched.map(item => {
      
      if (!arr.includes(item.name)) {
        const index = state.cachedViews.indexOf(item.name)
        index > -1 && state.cachedViews.splice(index, 1)
      }
    })
  },

  DEL_OTHERS_VISITED_VIEWS: (state, view) => {
    state.visitedViews = state.visitedViews.filter(v => {
      return v.meta.affix || v.path === view.path
    })
  },
  DEL_OTHERS_CACHED_VIEWS: (state, view) => {
    const index = state.cachedViews.indexOf(view.name)
    if (index > -1) {
      state.cachedViews = state.cachedViews.slice(index, index + 1)
    } else {
      // if index = -1, there is no cached tags
      state.cachedViews = []
    }
  },

  DEL_ALL_VISITED_VIEWS: state => {
    // keep affix tags
    const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
    state.visitedViews = affixTags
  },
  DEL_ALL_CACHED_VIEWS: state => {
    state.cachedViews = []
  },

  UPDATE_VISITED_VIEW: (state, view) => {
    for (let v of state.visitedViews) {
      if (v.path === view.path) {
        v = Object.assign(v, view)
        break
      }
    }
  }
}

const actions = {
  addView({
    dispatch
  }, view) {
    dispatch('addVisitedView', view)
    dispatch('addCachedView', view)
  },
  addVisitedView({
    commit
  }, view) {
    commit('ADD_VISITED_VIEW', view)
  },
  addCachedView({
    commit
  }, view) {
    commit('ADD_CACHED_VIEW', view)
  },


  delView({
    dispatch,
    state
  }, view) {
    return new Promise(resolve => {
      dispatch('delVisitedView', view)
      dispatch('delCachedView', view)
      resolve({
        visitedViews: [...state.visitedViews],
        cachedViews: [...state.cachedViews]
      })
    })
  },
  delVisitedView({
    commit,
    state
  }, view) {
    return new Promise(resolve => {
      commit('DEL_VISITED_VIEW', view)
      resolve([...state.visitedViews])
    })
  },
  delCachedView({
    commit,
    state
  }, view) {
    return new Promise(resolve => {
      commit('DEL_CACHED_VIEW', view)
      resolve([...state.cachedViews])
    })
  },

  delOthersViews({
    dispatch,
    state
  }, view) {
    return new Promise(resolve => {
      dispatch('delOthersVisitedViews', view)
      dispatch('delOthersCachedViews', view)
      resolve({
        visitedViews: [...state.visitedViews],
        cachedViews: [...state.cachedViews]
      })
    })
  },
  delOthersVisitedViews({
    commit,
    state
  }, view) {
    return new Promise(resolve => {
      commit('DEL_OTHERS_VISITED_VIEWS', view)
      resolve([...state.visitedViews])
    })
  },
  delOthersCachedViews({
    commit,
    state
  }, view) {
    return new Promise(resolve => {
      commit('DEL_OTHERS_CACHED_VIEWS', view)
      resolve([...state.cachedViews])
    })
  },
  delAllViews({
    dispatch,
    state
  }, view) {
    return new Promise(resolve => {
      dispatch('delAllVisitedViews', view)
      dispatch('delAllCachedViews', view)
      resolve({
        visitedViews: [...state.visitedViews],
        cachedViews: [...state.cachedViews]
      })
    })
  },
  delAllVisitedViews({
    commit,
    state
  }) {
    return new Promise(resolve => {
      commit('DEL_ALL_VISITED_VIEWS')
      resolve([...state.visitedViews])
    })
  },
  delAllCachedViews({
    commit,
    state
  }) {
    return new Promise(resolve => {
      commit('DEL_ALL_CACHED_VIEWS')
      resolve([...state.cachedViews])
    })
  },
  updateVisitedView({
    commit
  }, view) {
    commit('UPDATE_VISITED_VIEW', view)
  }
}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}

当然多级路由的缓存会出现坑,同时打开多个父级路由相同的页面时,关闭清除页面无法成功的去掉路由缓存,把多级路由过滤替换成两级路由,具体可以参照 这个解决方案 segmentfault.com/a/119000003…

let allChildren = [];
// 将多级路由转换成为二级路由方便使用keep-alive(找出所有的叶子节点,平展开)
export const flatAsyncRouter = (routers) => {
    searchchildren(routers)
    return allChildren;
}
let searchchildren = (arr) => {
    arr.map(item => {
        if (item.children && item.children.length > 0) {
            searchchildren(item.children)
        } else {
            allChildren.push(item) 
        }
    })
}