Vue3+Vite+Pinia+Naive后台管理系统搭建之九:layout 动态路由布局

前言

如果对 vue3 的语法不熟悉的,可以移步Vue3.0 基础入门,快速入门。

1. 系统页面结构

由 menu,面包屑,用户信息,页面标签,页面内容构建

Vue3+Vite+Pinia+Naive后台管理系统搭建之九:layout 动态路由布局_第1张图片

 2. 创建页面

创建 src/pages/layout.vue 布局页

创建 src/pages/components/layout-menu.vue menu 组件

创建 src/pages/components/layout-crumbs.vue 面包屑 组件

创建 src/pages/components/layout-user.vue 用户信息 组件

创建 src/pages/components/layout-tag.vue 页面标签 组件

创建 src/pages/components/layout-content.vue 页面内容 组件

Vue3+Vite+Pinia+Naive后台管理系统搭建之九:layout 动态路由布局_第2张图片

 3. 构建 src/pages/layout.vue 布局页





4. 构建 src/pages/components/layout-menu.vue menu 组件





4.1 构建 src/store/permission.js 权限状态管理

根据后端返回动态路由数据,构建导航 menu 和动态路由。

import { defineStore } from "pinia";
import { h } from "vue";
import { RouterLink } from "vue-router";
// 接口获取路由 自己对接口
// import { getRouters } from "@/api/menu.js";
import SvgIcon from "@/components/SvgIcon.vue";
import { routerData } from "@/mock/datas.js";

const modules = import.meta.glob("../pages/*.vue");

//  icon 标签
let renderIcon = (name) => {
  return () => h(SvgIcon, { name }, null);
};

// 单个路由
let getRouterItem = (item) => {
  let { name, path, meta, component } = item;
  let obj = {
    path,
    name,
    meta,
    component: modules[`../pages/${component}`],
  };
  return obj;
};

// 获取异步路由
// 所有异步路由都是layout的子路由,并且routers只有一层children,没有考虑很复杂的情况。
// 将所有异步路由都存放在rmenu数组中,返回。
let getAayncRouter = (routers) => {
  let rmenu = [];
  routers.forEach((item) => {
    if (item.children && item.children.length) {
      item.children.map((_item) => {
        let obj = getRouterItem(_item);
        obj.meta.parentTitle = item.meta.title;
        rmenu.push(obj);
      });
    } else {
      rmenu.push(getRouterItem(item));
    }
  });
  return rmenu;
};

// 获取侧边栏导航
let getSiderMenu = (routers) => {
  let smenu = [];

  routers.forEach((item) => {
    let children = [];
    let obj = {};

    if (item.children && item.children.length) {
      // 二级 menu
      item.children.map((_item) => {
        if (!_item.hidden) {
          children.push({
            label: () =>
              h(
                RouterLink,
                { to: _item.path },
                { default: () => _item.meta.title }
              ),
            title: _item.meta.title,
            key: _item.name,
            icon: renderIcon(_item.meta.icon),
          });
        }
      });

      obj = {
        label: item.meta.title,
        title: item.meta.title,
        key: item.name,
        icon: renderIcon(item.meta.icon),
        children,
      };
    } else {
      // 一级 menu
      obj = {
        label: () =>
          h(RouterLink, { to: item.path }, { default: () => item.meta.title }),
        title: item.meta.title,
        key: item.name,
        icon: renderIcon(item.meta.icon),
      };
    }

    smenu.push(obj);
  });
  return smenu;
};

export const usePermissionStore = defineStore({
  id: "permissionStore",
  state: () => {
    return {
      siderMenu: [],
      activeMenuValue: "",
    };
  },
  actions: {
    getRouters() {
      return new Promise((resolve, reject) => {
        this.siderMenu = getSiderMenu(routerData);
        resolve(getAayncRouter(routerData));
        // getRouters()
        //   .then(({ data }) => {
        //     this.siderMenu = getSiderMenu(data);
        //     resolve(data);
        //   })
        //   .catch((err) => {
        //     reject(err);
        //   });
      });
    },
  },
});

4.1.1 构建 src/mock/datas.js 虚拟路由数据

模仿后端返回动态路由数据结构

export const routerData = [
  {
    name: "home",
    path: "/home",
    hidden: false,
    component: "home.vue",
    meta: {
      title: "首页",
      icon: "home",
    },
    children: null,
  },
  {
    name: "system",
    path: "/system",
    hidden: false,
    component: null,
    meta: {
      title: "系统管理",
      icon: "system",
    },
    children: [
      {
        name: "system-menu",
        path: "/system-menu",
        hidden: false,
        component: "system-menu.vue",
        meta: {
          title: "系统菜单",
          icon: "system-menu",
        },
        children: null,
      },
      {
        name: "system-dict",
        path: "/system-dict",
        hidden: false,
        component: "system-dict.vue",
        meta: {
          title: "系统字典",
          icon: "system-dict",
        },
        children: null,
      },
    ],
  },
  {
    name: "user",
    path: "/user",
    hidden: false,
    component: null,
    meta: {
      title: "用户管理",
      icon: "user",
    },
    children: [
      {
        name: "user-user",
        path: "/user-user",
        hidden: false,
        component: "user-user.vue",
        meta: {
          title: "用户管理",
          icon: "user-user",
        },
        children: null,
      },
      {
        name: "user-role",
        path: "/user-role",
        hidden: false,
        component: "user-role.vue",
        meta: {
          title: "角色管理",
          icon: "user-role",
        },
        children: null,
      },
    ],
  },
];


4.1.2 新增 src/assets/svg 路由图标

Vue3+Vite+Pinia+Naive后台管理系统搭建之九:layout 动态路由布局_第3张图片

 

4.2 构建 src/store/tag.js 页面标签状态管理

点击左侧导航路由,页面标签变化

import { defineStore } from "pinia";

export const useTagStore = defineStore({
  id: "tag",
  state: () => {
    return {
      tags: [{ title: "首页", key: "home" }],
      activeTagIndex: 0,
    };
  },
  getters: {
    tagsKey(state) {
      let arr = [];
      state.tags.map((tag) => {
        arr.push(tag.key);
      });
      return arr;
    },
  },
  actions: {
    addTag(tag) {
      if (!this.tagsKey.includes(tag.key)) {
        this.tags.push(tag);
      }
    },
    removeTag(key) {
      let index = this.tagsKey.indexOf(key);
      this.tags.splice(index, 1);
      this.activeTagIndex = index - 1;
    },
  },
});

4.3 完善 src/router/index.js 路由

路由监听动态加载路由

import { createRouter, createWebHistory } from "vue-router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import baseRouters from "./baseRouter.js";
import { getToken } from "@/utils/cookie.js";
import { useUserStore } from "@/store/user.js";
import { usePermissionStore } from "@/store/permission.js";

const whiteList = ["/", "/login"];

const routes = [...baseRouters];
const _createRouter = () =>
  createRouter({
    history: createWebHistory(),
    routes,
    scrollBehavior() {
      return {
        el: "#app",
        top: 0,
        behavior: "smooth",
      };
    },
  });

export function resetRouter() {
  const newRouter = _createRouter();
  router.matcher = newRouter.matcher;
}

const router = _createRouter();

// 路由监听
router.beforeEach((to, from, next) => {
  NProgress.start();
  let userStore = useUserStore();
  let permissionStore = usePermissionStore();

  // 判断是否登录
  if (!!getToken()) {
    // 已登录,跳转登录页,转跳首页
    if (to.path === "/login") {
      next("");
      NProgress.done();
    } else {
      if (userStore.roles.length === 0) {
        userStore
          .getInfo()
          .then((res) => {
            // 获取动态路由
            permissionStore.getRouters().then((_res) => {
              let resetRouters = {
                path: "/layout",
                name: "layout",
                component: () => import("@/pages/layout.vue"),
                children: _res,
              };
              router.addRoute(resetRouters);

              // 这句代码,重要!重要!重要!
              // 来确保addRoute()时动态添加的路由已经被完全加载上去。没有这句,动态路由加载后无效
              next({ ...to, replace: true });
            });
          })
          .catch((err) => {
            window.$msg.error(err);
            userStore.logout().then(() => {
              next({ name: "login" });
            });
          });
      } else {
        next();
      }
    }
    NProgress.done();
  } else {
    // 判断路由是否在白名单,是直接跳转
    if (whiteList.indexOf(to.path) !== -1) {
      next();
      // 未登录页面跳转,直接跳转到登录页
    } else {
      next(`/login?redirect=${to.fullPath}`);
    }
    NProgress.done();
  }
});

export default router;

5. 构建 src/pages/components/layout-crumbs.vue 面包屑组件





6. 构建 src/pages/components/layout-user.vue 用户信息 组件





7. 构建 src/pages/components/layout-tag.vue 页面标签 组件





8. 构建 src/pages/components/layout-content.vue 页面内容 组件





9. 创建如下内容页

src/pages/404.vue

src/pages/demo.vue

src/pages/eidtpassword.vue

src/pages/userinfo.vue

src/pages/system-dict.vue

src/pages/system-menu.vue

src/pages/user-user.vue

src/pages/user-role.vue

Vue3+Vite+Pinia+Naive后台管理系统搭建之九:layout 动态路由布局_第4张图片

 页面基础构建如 demo.vue





 综上

layout 动态路由布局构建完成。下一章:基础框架搭建完了,后续完善到哪更新哪

你可能感兴趣的:(前端,vue.js,javascript)