如果对 vue3 的语法不熟悉的,可以移步Vue3.0 基础入门,快速入门。
由 menu,面包屑,用户信息,页面标签,页面内容构建
创建 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 页面内容 组件
后台管理系统
根据后端返回动态路由数据,构建导航 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);
// });
});
},
},
});
模仿后端返回动态路由数据结构
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,
},
],
},
];
点击左侧导航路由,页面标签变化
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;
},
},
});
路由监听动态加载路由
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;
{{ item }}
{{ userName }}
{{ item.title }}
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
页面基础构建如 demo.vue
demo
layout 动态路由布局构建完成。下一章:基础框架搭建完了,后续完善到哪更新哪