Vue3における動的ルーティング権限制御の実装

権限制御の実装アプローチ

バックエンドから提供される権限コードとローカルルート設定のIDを照合することで、ユーザー権限に基づく動的ルーティングを実装します。ロール毎に表示可能な操作項目を制御し、ルート設定を正規化後にaddRouteメソッドで動的に追加します。

ルーティング設定構造

// 基本ルート設定
export const baseRoutes: RouteConfig[] = [
  {
    path: "/admin",
    meta: { title: "管理システム", icon: "system", alwaysVisible: true },
    component: "Layout",
    routeId: "sys",
    parentId: "root"
  }
];

// 子ルート設定
export const childRoutes: RouteConfig[] = [
  {
    path: "users",
    name: "UserManagement",
    meta: { title: "ユーザー管理", icon: "user" },
    component: "admin/users/index",
    routeId: "user_mgmt",
    parentId: "sys"
  },
  {
    path: "settings",
    name: "SystemSettings",
    meta: { title: "システム設定", icon: "config" },
    component: "admin/settings/index",
    routeId: "sys_config",
    parentId: "sys"
  }
];

ルートツリー生成関数

export function generateRouteHierarchy(
  items: any[], 
  identifier = 'routeId', 
  parentRef = 'parentId', 
  childrenProp = 'children', 
  rootValue = 'root'
) {
  const hierarchy = [];
  items.forEach(item => {
    if (item[parentRef] === rootValue) {
      const descendants = generateRouteHierarchy(items, identifier, parentRef, childrenProp, item[identifier]);
      if (descendants.length) item[childrenProp] = descendants;
      hierarchy.push(item);
    }
  });
  return hierarchy;
}

動的ルート生成ストア

import { defineStore } from "pinia";
import Layout from "@/layouts/MainLayout.vue";

const loadView = (viewPath: string) => () => import(`@/views/${viewPath}.vue`);

function resolveComponents(routes: any[]) {
  return routes.map(route => {
    route.component = route.component === "Layout" ? Layout : loadView(route.component);
    if (route.children) resolveComponents(route.children);
    return route;
  });
}

export const useRouteStore = defineStore("routeManager", {
  state: () => ({
    dynamicRoutes: []
  }),
  actions: {
    async initializeRoutes() {
      const permissionCodes = localStorage.getItem("authCodes")?.split(",") || [];
      if (!permissionCodes.length) return [];
      
      const authorizedRoutes = childRoutes.filter(child => 
        permissionCodes.some(code => child.routeId.includes(code))
      );
      
      const routeTree = generateRouteHierarchy([
        ...baseRoutes, 
        ...authorizedRoutes
      ]);
      
      const validRoutes = routeTree.filter(branch => branch.children?.length);
      this.dynamicRoutes = resolveComponents(validRoutes);
      return this.dynamicRoutes;
    }
  }
});

ルーターガード処理

router.beforeEach(async (to, from, next) => {
  const authToken = getAuthToken();
  const routeManager = useRouteStore();
  
  if (!authToken) {
    next("/auth/login");
  } else if (!routeManager.dynamicRoutes.length) {
    try {
      const dynamicRoutes = await routeManager.initializeRoutes();
      const staticRoutes = router.getRoutes();
      
      dynamicRoutes.forEach(route => {
        router.addRoute(route);
      });
      
      next({ ...to, replace: true });
    } catch {
      next("/");
    }
  } else {
    to.name === "Login" ? next(from.name ? from.path : "/") : next();
  }
});

タグ: Vue3 Pinia 動的ルーティング 権限制御 フロントエンド

8月2日 18:01 投稿