Personally, I think Vben Admin is a good project, complete and well-documented, and the technology stack is always up to date. When I was following through with the documentation, I didn’t immediately use it because I didn’t understand a lot of things.

Of course, it is also necessary to use it after all understanding it. After all, it is much better to use it and watch it. The guide part of the document on how to use it is very clear and the source code is highly encapsulated, and this part is often the need to modify according to the actual business, so I decided to take a look at this part of the source code before using.

  • Vben Admin in-depth understanding of plug-in, environment variable design

Doubt point

This part mainly analyzes the relationship between routing, menu and permissions. The following code is mainly the content of the process. Some relevant data processing functions and branches can be read by themselves.

Routes can affect the generation of menus. Menus are processed differently according to the permission mode. Permissions affect route registration and login verification.

  • How do routes automatically load and generate menus?
  • What are the differences between menu permission modes and how to distinguish and deal with them?
  • How is the authentication process and initialization of permissions done?

Initialization of the project

Since the overall structure is involved, let’s take a look at what the project initializes.

// src/main.ts
async function bootstrap() {
  const app = createApp(App);

  // Configure storage
  setupStore(app);

  // Initialize system configurations, project configurations, style themes, persistent caches, and so on
  initAppConfigStore();

  // Register the global component
  registerGlobComp(app);

  // Multi-language configuration
  await setupI18n(app);

  // Configure the route
  setupRouter(app);

  // Route guard, permission judgment, initialization cache data
  setupRouterGuard(router);

  // Register the global directive
  setupGlobDirectives(app);

  // Configure global error handling
  setupErrorHandle(app);

  await router.isReady();

  app.mount("#app".true);
}
Copy the code

The routing configuration

Realize automatic load modules under the routing file and generate routing configuration information and some general configuration.

// src/router/routes/index.ts
import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from "/@/router/routes/basic";
import { mainOutRoutes } from "./mainOut";

// Automatically loads the routing module in 'modules'
const modules = import.meta.globEager("./modules/**/*.ts");
const routeModuleList: AppRouteModule[] = []; // routeModuleList = modules

// The read route is not registered immediately, but is added to the route instance through router.addRoutes after permission authentication to implement permission filtering
export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList];

export const RootRoute: AppRouteRecordRaw = {};
export const LoginRoute: AppRouteRecordRaw = {};

export const basicRoutes = [
  // login to route /login
  LoginRoute,
  // Root route /
  RootRoute,
  // New page /main-out. mainOutRoutes,// from definition /redirect
  REDIRECT_ROUTE,
  // 404 /:path(.*)*
  PAGE_NOT_FOUND_ROUTE
];
Copy the code

Login principal Flow

Click Login to get user information and store the pinia implementation used.

// src/views/sys/login/LoginForm.vue
const userInfo = await userStore.login(
  toRaw({
    password: data.password,
    username: data.account,
    mode: "none"}));Copy the code
// src/store/modules/user.ts
login(params){
  // 1. Invoke login interface
  const data = await loginApi(loginParams, mode); // mock
  const { token } = data;

  // 2. Set token and store local cache.
  this.setToken(token);

  // 3. Obtain user information
  const userInfo = await this.getUserInfoAction();

  // 4. Obtain route configuration and add route configuration dynamically
  const routes = await permissionStore.buildRoutesAction();
  routes.forEach((route) = > {
    router.addRoute(route);
  });
  return userInfo;
}
Copy the code

Obtaining User information

After obtaining the token, call getUserInfoAction to get the user information.

// src/store/modules/user.ts
getUserInfoAction() {
  const userInfo = await getUserInfo(); // mock
  const { roles } = userInfo;
  const roleList = roles.map((item) = > item.value);
  // Set user information and store local cache
  this.setUserInfo(userInfo);
  // Set the permission list and store the local cache
  this.setRoleList(roleList);
  return userInfo;
}
Copy the code

Generate routing

After successful login, call buildRoutesAction to get route configuration and generate menu configuration.

// src/store/modules/permission.ts
buildRoutesAction() {
  // Get permission mode
  const permissionMode = appStore.getProjectConfig.permissionMode;
  // Separate permission modes
  switch (permissionMode) {
    // Front-end mode control (menu and route configuration separately)
    case PermissionModeEnum.ROLE:
      // Filter routes by permission
      routes = filter(asyncRoutes, routeFilter);
      routes = routes.filter(routeFilter);
      // Convert multi-level routes to level-2 routes
      routes = flatMultiLevelRoutes(routes);
      break;

    // Front-end mode control (menu automatically generated by routing configuration)
    case PermissionModeEnum.ROUTE_MAPPING:
      // Filter routes by permission
      routes = filter(asyncRoutes, routeFilter);
      routes = routes.filter(routeFilter);
      // Generate the menu by transforming the route
      const menuList = transformRouteToMenu(routes, true);
      // Set the save menu list
      this.setFrontMenuList(menuList);
      // Convert multi-level routes to level-2 routes
      routes = flatMultiLevelRoutes(routes);
      break;

    // Background mode control
    case PermissionModeEnum.BACK:
      // Mock /sys/menu.ts
      let routeList: AppRouteRecordRaw[] = [];
      routeList = await getMenuList();
      // Generate the menu by transforming the route
      const backMenuList = transformRouteToMenu(routeList);
      // Set the menu list
      this.setBackMenuList(backMenuList);
      // Set the save menu list
      routeList = flatMultiLevelRoutes(routeList);
      routes = [PAGE_NOT_FOUND_ROUTE, ...routeList];
      break; }}Copy the code

Generated menu

Get menus from different data sources based on different permission modes.

// src/router/menus/index.ts

// Automatically loads menu modules in 'modules'
const modules = import.meta.globEager("./modules/**/*.ts");
const staticMenus = transformMenuModule(modules); // simplify processing

async function getAsyncMenus() {
  const permissionStore = usePermissionStore();
  // BACK mode
  if (isBackMode()) {
    // Get the menu of this.setBackmenulist (menuList) Settings
    return permissionStore.getBackMenuList.filter(item= >! item.meta? .hideMenu && ! item.hideMenu); }// Front-end mode (the menu is automatically generated by route configuration) ROUTE_MAPPING
  if (isRouteMappingMode()) {
    // Get the menu of this.setFrontMenuList(menuList) Settings
    return permissionStore.getFrontMenuList.filter(item= >! item.hideMenu); }// Front-end mode (menus and routes are configured separately) ROLE
  return staticMenus;
}
Copy the code

Get the menu configuration rendering in the menu component.

// src/layouts/default/menu/index.vue
function renderMenu() {
  const{ menus, ... menuProps } = unref(getCommonProps);if(! menus || ! menus.length)return null;
  return! props.isHorizontal ? (<SimpleMenu {. menuProps} isSplitMenu={unref(getSplit)} items={menus} />
  ) : (
    <BasicMenu
      {.(menuProps as any)}
      isHorizontal={props.isHorizontal}
      type={unref(getMenuType)}
      showLogo={unref(getIsShowLogo)}
      mode={unref(getComputedMenuMode as any)}
      items={menus}
    />
  );
}
Copy the code

Routing guard

Determine whether to log in and initialize after refreshing.

// src/router/guard/permissionGuard.ts
export function createPermissionGuard(route) {
  const userStore = useUserStoreWithOut();
  const permissionStore = usePermissionStoreWithOut();
  router.beforeEach(async (to, from, next) => {
    / / white list
    if (whitePathList.includes(to.path)) {
      next();
      return;
    }

    // If the token does not exist, redirect to the login page
    const token = userStore.getToken;
    if(! token) {if (to.meta.ignoreAuth) {
        next();
        return;
      }

      // redirect login page
      const redirectData: { path: string; replace: boolean; query? : Recordable<string>} = {path: LOGIN_PATH,
        replace: true
      };
      if(to.path) { redirectData.query = { ... redirectData.query,redirect: to.path
        };
      }
      next(redirectData);
      return;
    }

    // Get user information userInfo/roleList
    if (userStore.getLastUpdateTime === 0) {
      try {
        await userStore.getUserInfoAction();
      } catch (err) {
        next();
        return; }}// Determine whether to obtain a dynamic route again
    if (permissionStore.getIsDynamicAddedRoute) {
      next();
      return;
    }
    const routes = await permissionStore.buildRoutesAction();
    routes.forEach(route= > {
      router.addRoute(route);
    });
    router.addRoute(PAGE_NOT_FOUND_ROUTE);
    permissionStore.setDynamicAddedRoute(true);
  });
}
Copy the code

conclusion

We know the permission authentication through permission filtering configured routing table and dynamically add routes, the generation of menu according to the different permission mode to obtain different data sources render menu bar. This process will change in real projects, so we should know to update the existing process.