feat: mock路由数据,整体路由逻辑调整,包括:生成、加载、跳转

This commit is contained in:
WANGFAN\wangf 2024-11-02 19:26:25 +08:00
parent 1c27fab1b2
commit 32fd8caa50
21 changed files with 1318 additions and 164 deletions

View File

@ -4,7 +4,6 @@ import { Message } from "@arco-design/web-vue";
// 是否开启本地mock
const MOCK_FLAG = import.meta.env.VITE_APP_OPEN_MOCK === "true";
// 创建axios实例
const service = axios.create({
baseURL: MOCK_FLAG ? "" : "/api"
@ -14,9 +13,13 @@ service.interceptors.request.use(
function (config: any) {
// 发送请求之前做什么
// 获取token鉴权
if (localStorage.getItem("AdminToken")) {
let userInfo: any = {};
if (localStorage.getItem("user-info")) {
userInfo = JSON.parse(localStorage.getItem("user-info") as string);
}
if (userInfo?.AdminToken) {
// 有token在请求头中携带token
config.headers.Authorization = localStorage.getItem("AdminToken");
config.headers.Authorization = userInfo.AdminToken;
}
return config;
},

View File

@ -0,0 +1,9 @@
import axios from "@/api";
// 获取菜单数据
export const getMenuListAPI = () => {
return axios({
url: "/mock/menu/list",
method: "get"
});
};

View File

@ -9,12 +9,10 @@ export const loginAPI = (data: any) => {
});
};
// 获取用户信息
export const getUserInfoAPI = () => {
return axios({
url: "/mock/user/info",
method: "get"
});
};
return axios({
url: "/mock/user/info",
method: "get"
});
};

View File

@ -1,6 +1,6 @@
import pinia from "@/store/index";
import { storeToRefs } from "pinia";
import { useRoutesListStore } from "@/store/modules/route-list";
import { useRoutesConfigStore } from "@/store/modules/route-config";
/**
* hooks
* @returns
@ -12,7 +12,7 @@ export const useRoutingMethod = () => {
* @returns undefined则表示未找到
*/
const findLinearArray = (key: string) => {
const routerStore = useRoutesListStore(pinia);
const routerStore = useRoutesConfigStore(pinia);
const { routeList } = storeToRefs(routerStore);
return routeList.value.find((item: Menu.MenuOptions) => item.name == key);
};
@ -23,7 +23,7 @@ export const useRoutingMethod = () => {
* @returns undefined则表示未找到
*/
const findTagsList = (key: string) => {
const routerStore = useRoutesListStore(pinia);
const routerStore = useRoutesConfigStore(pinia);
const { tabsList } = storeToRefs(routerStore);
return tabsList.value.find((item: Menu.MenuOptions) => item.name == key);
};

View File

@ -12,10 +12,10 @@ import Logo from "@/layout/components/Logo/index.vue";
import Menu from "@/layout/components/Menu/index.vue";
import { storeToRefs } from "pinia";
import { useThemeConfig } from "@/store/modules/theme-config";
import { useRoutesListStore } from "@/store/modules/route-list";
import { useRoutesConfigStore } from "@/store/modules/route-config";
const themeStore = useThemeConfig();
const { collapsed, asideDark } = storeToRefs(themeStore);
const routerStore = useRoutesListStore();
const routerStore = useRoutesConfigStore();
const { routeTree } = storeToRefs(routerStore);
</script>

View File

@ -62,11 +62,11 @@
<script setup lang="ts">
import PickColors from "vue-pick-colors";
import { storeToRefs } from "pinia";
import { useRoutesListStore } from "@/store/modules/route-list";
import { useRoutesConfigStore } from "@/store/modules/route-config";
import { useThemeConfig } from "@/store/modules/theme-config";
import { currentlyRoute } from "@/router/route-output";
const themeStore = useThemeConfig();
const routerStore = useRoutesListStore();
const routerStore = useRoutesConfigStore();
const {
collapsed,
isAccordion,

View File

@ -17,10 +17,10 @@
import Tabs from "@/layout/components/Tabs/index.vue";
import { storeToRefs } from "pinia";
import { useThemeConfig } from "@/store/modules/theme-config";
import { useRoutesListStore } from "@/store/modules/route-list";
import { useRoutesConfigStore } from "@/store/modules/route-config";
const themeStore = useThemeConfig();
let { refreshPage, isTabs, watermark, watermarkStyle, watermarkRotate, watermarkGap } = storeToRefs(themeStore);
const routerStore = useRoutesListStore();
const routerStore = useRoutesConfigStore();
const { cacheRoutes } = storeToRefs(routerStore);
//

View File

@ -18,10 +18,10 @@
import MenuItem from "@/layout/components/Menu/menu-item.vue";
import { storeToRefs } from "pinia";
import { useThemeConfig } from "@/store/modules/theme-config";
import { useRoutesListStore } from "@/store/modules/route-list";
import { useRoutesConfigStore } from "@/store/modules/route-config";
import { useRoutingMethod } from "@/hooks/useRoutingMethod";
const router = useRouter();
const routerStore = useRoutesListStore();
const routerStore = useRoutesConfigStore();
const { currentRoute } = storeToRefs(routerStore);
const themeStore = useThemeConfig();
const { collapsed, isAccordion, layoutType, asideDark } = storeToRefs(themeStore);

View File

@ -56,11 +56,11 @@
<script setup lang="ts">
import { useRouter } from "vue-router";
import { storeToRefs } from "pinia";
import { useRoutesListStore } from "@/store/modules/route-list";
import { useRoutesConfigStore } from "@/store/modules/route-config";
import { useRoutingMethod } from "@/hooks/useRoutingMethod";
import { useThemeConfig } from "@/store/modules/theme-config";
const router = useRouter();
const routerStore = useRoutesListStore();
const routerStore = useRoutesConfigStore();
const { tabsList, currentRoute } = storeToRefs(routerStore);
//

View File

@ -47,13 +47,13 @@ import Footer from "@/layout/components/Footer/index.vue";
import MenuItem from "@/layout/components/Menu/menu-item.vue";
import MenuItemIcon from "@/layout/components/Menu/menu-item-icon.vue";
import { storeToRefs } from "pinia";
import { useRoutesListStore } from "@/store/modules/route-list";
import { useRoutesConfigStore } from "@/store/modules/route-config";
import { useRoutingMethod } from "@/hooks/useRoutingMethod";
import { useThemeConfig } from "@/store/modules/theme-config";
import { useMenuMethod } from "@/hooks/useMenuMethod";
defineOptions({ name: "LayoutHead" });
const router = useRouter();
const routerStore = useRoutesListStore();
const routerStore = useRoutesConfigStore();
const themeStore = useThemeConfig();
const { routeTree, currentRoute } = storeToRefs(routerStore);
const { isFooter, language } = storeToRefs(themeStore);

View File

@ -49,7 +49,7 @@ import Footer from "@/layout/components/Footer/index.vue";
import Menu from "@/layout/components/Menu/index.vue";
import HeaderRight from "@/layout/components/Header/components/header-right/index.vue";
import MenuItemIcon from "@/layout/components/Menu/menu-item-icon.vue";
import { useRoutesListStore } from "@/store/modules/route-list";
import { useRoutesConfigStore } from "@/store/modules/route-config";
import { useRoutingMethod } from "@/hooks/useRoutingMethod";
import { storeToRefs } from "pinia";
import { useThemeConfig } from "@/store/modules/theme-config";
@ -57,7 +57,7 @@ import { useMenuMethod } from "@/hooks/useMenuMethod";
defineOptions({ name: "LayoutMixing" });
const route = useRoute();
const router = useRouter();
const routerStore = useRoutesListStore();
const routerStore = useRoutesConfigStore();
const themeStore = useThemeConfig();
const { isFooter, collapsed, asideDark, language } = storeToRefs(themeStore);
const { routeTree } = storeToRefs(routerStore);

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,8 @@ import { createProdMockServer } from "vite-plugin-mock/es/createProdMockServer";
import testModule from "./test/index";
import userModule from "./user/index";
// import tableModule from './person/index'
// import systemModule from './system/index'
// import fileModule from './file/index'
// import cateModule from './cate/index'
// import areaModule from './area/index'
import systemModule from "./system/index";
export function setupProdMockServer() {
createProdMockServer([...testModule, ...userModule]);
createProdMockServer([...testModule, ...userModule, ...systemModule]);
}

3
src/mock/system/index.ts Normal file
View File

@ -0,0 +1,3 @@
import menu from "./menu";
export default [...menu];

95
src/mock/system/menu.ts Normal file
View File

@ -0,0 +1,95 @@
import type { MockMethod } from "vite-plugin-mock";
import { resultSuccess, resultError } from "../_utils";
import systemMenu from "../_data/system_menu";
/**
*
* TODO
* 1token判断角色
* 2
* 3
* 4
*
* TODO
* 1
* 2
* 3
* 4 +
* 5
* 6
*/
// post请求body,get请求query
export default [
{
url: "/mock/menu/list",
method: "get",
timeout: 300,
response: ({ headers }: any) => {
let token = headers.authorization;
// 这里模拟两个角色admin、common
let userRoles = token === "Admin-Token" ? ["admin"] : ["common"];
systemMenu[0].children = treeSort(filterByRole(systemMenu[0].children, userRoles));
return resultSuccess(systemMenu);
}
}
] as MockMethod[];
/**
*
* 1
* 2children则递归排序
* @param {array} tree
* @returns
*/
export const treeSort = (tree: Menu.MenuOptions[]) => {
if (!tree || tree.length == 0) return [];
tree.sort((a: Menu.MenuOptions, b: Menu.MenuOptions) => {
// a和b都是undefined则相等
if (a.meta.sort != 0 && !a.meta.sort && b.meta.sort != 0 && !b.meta.sort) {
return 0;
}
// a是undefined则a被排在b之后
if (a.meta.sort != 0 && !a.meta.sort) return 1;
// b是undefined则b被排在a之后
if (b.meta.sort != 0 && !b.meta.sort) return -1;
return a.meta.sort - b.meta.sort;
});
// 深层递归
return tree.map((item: any) => {
if (item?.children?.length > 0) {
item.children = treeSort(item.children);
}
return item;
});
};
/**
*
* 1访
* 2
* @param {array} tree
* @returns
*/
export const filterByRole = (tree: any, userRoles: Array<string>) => {
return tree.filter((item: any) => {
// 过滤角色权限
if (item?.meta?.roles) {
if (!roleBase(item.meta.roles, userRoles)) return false;
}
// 过滤是否禁用
if (item?.meta?.disable) return false;
if (item.children) item.children = filterByRole(item.children, userRoles);
return true;
});
};
/**
*
* @param {array} roles
* @returns true是 false否
*/
export const roleBase = (roles: Array<string>, userRoles: Array<string>) => {
return userRoles.some((item: string) => roles.includes(item));
};

View File

@ -10,8 +10,10 @@ export default [
response: ({ body }) => {
let { username, password } = body;
if (username === "admin" && password === "123456") {
// 请求成功返回token
return resultSuccess({ token: "Your-Token" });
return resultSuccess({ token: "Admin-Token" });
}
if(username === "common" && password === "123456"){
return resultSuccess({ token: "Common-Token" });
}
return resultError(null, "账号或者密码错误", 500);
}

View File

@ -2,11 +2,12 @@ import NProgress from "@/config/nprogress";
import pinia from "@/store/index";
import { createRouter, createWebHashHistory } from "vue-router";
import { staticRoutes, notFoundAndNoPower } from "@/router/route.ts";
import { initSetRouter, currentlyRoute } from "@/router/route-output";
import { currentlyRoute } from "@/router/route-output";
import { storeToRefs } from "pinia";
import { useUserInfoStore } from "@/store/modules/user-info";
import { useRoutesListStore } from "@/store/modules/route-list";
import { useRoutesConfigStore } from "@/store/modules/route-config";
import { useRoutingMethod } from "@/hooks/useRoutingMethod";
import { loadingPage } from "@/utils/loading-page";
/**
* vue的路由示例
@ -55,7 +56,7 @@ router.beforeEach(async (to, from, next) => {
currentlyRoute(to.name as string);
} else {
// 4、去非登录页有token校验是否动态添加过路由添加过则放行未添加则执行路由初始化
const routeStore = useRoutesListStore(pinia);
const routeStore = useRoutesConfigStore(pinia);
const { routeTree } = storeToRefs(routeStore);
// 获取外链路由的处理函数
@ -66,7 +67,8 @@ router.beforeEach(async (to, from, next) => {
// 如果缓存的路由是0则说明未动态添加路由先添加再跳转
// 解决刷新页面404的问题
if (routeTree.value.length == 0) {
await initSetRouter();
loadingPage.start();
await routeStore.initSetRouter();
// 处理外链跳转
openExternalLinks(to);
// 处理完重新跳转

View File

@ -1,51 +1,10 @@
import pinia from "@/store/index";
import router from "@/router/index.ts";
import { RouteRecordRaw } from "vue-router";
import { dynamicRoutes } from "@/router/route";
import { storeToRefs } from "pinia";
import { useUserInfoStore } from "@/store/modules/user-info";
import { useRoutesListStore } from "@/store/modules/route-list";
import { useRoutesConfigStore } from "@/store/modules/route-config";
import { useThemeConfig } from "@/store/modules/theme-config";
import { deepClone, arrayFlattened } from "@/utils/index";
import { useRoutingMethod } from "@/hooks/useRoutingMethod";
import { loadingPage } from "@/utils/loading-page";
/**
*
* 1store
* 2
* 3 + addRoute动态添加路由KeepAlive支持二级路由缓存
*/
export async function initSetRouter() {
// 初始化路由渲染loading
loadingPage.start();
const store = useRoutesListStore(pinia);
// 根据角色权限过滤树并将其排序
const originTree: RouteRecordRaw[] = deepClone(dynamicRoutes);
let filteredTree = treeSort(filterByRole(originTree[0].children));
await store.setRouteTree(filteredTree);
// 根据树生成一维路由数组
const flattenedArray = linearArray(filteredTree);
// 设置完整的路由,二维路由:顶层路由 + 二级的一维路由
const addBeforeTree = originTree.map(item => {
if (flattenedArray.length > 0) item.redirect = flattenedArray[0].path;
item.children = flattenedArray;
return item;
});
// 动态添加路由
addBeforeTree.forEach((route: RouteRecordRaw) => router.addRoute(route));
// 设置一维路由
setRouting(flattenedArray);
}
/**
* 访
* @param {array} flattenedArray
*/
export function setRouting(flattenedArray: any) {
const store = useRoutesListStore(pinia);
store.setRouteList(flattenedArray); // 缓存一维路由
}
import Layout from "@/layout/index.vue";
/**
*
@ -57,67 +16,6 @@ export function linearArray(tree: any) {
return arrayFlattened(nodes, "children");
}
/**
*
* 1访
* 2
* @param {array} tree
* @returns
*/
export const filterByRole = (tree: any) => {
return tree.filter((item: any) => {
// 过滤角色权限
if (item?.meta?.roles) {
if (!roleBase(item.meta.roles)) return false;
}
// 过滤是否禁用
if (item?.meta?.disable) return false;
if (item.children) item.children = filterByRole(item.children);
return true;
});
};
/**
*
* 1
* 2children则递归排序
* @param {array} tree
* @returns
*/
export const treeSort = (tree: Menu.MenuOptions[]) => {
if (!tree || tree.length == 0) return [];
tree.sort((a: Menu.MenuOptions, b: Menu.MenuOptions) => {
// a和b都是undefined则相等
if (a.meta.sort != 0 && !a.meta.sort && b.meta.sort != 0 && !b.meta.sort) {
return 0;
}
// a是undefined则a被排在b之后
if (a.meta.sort != 0 && !a.meta.sort) return 1;
// b是undefined则b被排在a之后
if (b.meta.sort != 0 && !b.meta.sort) return -1;
return a.meta.sort - b.meta.sort;
});
// 深层递归
return tree.map((item: any) => {
if (item?.children?.length > 0) {
item.children = treeSort(item.children);
}
return item;
});
};
/**
*
* @param {array} roles
* @returns true是 false否
*/
export const roleBase = (roles: Array<string>) => {
const store = useUserInfoStore(pinia);
const { account } = storeToRefs(store);
return account.value.roles.some((item: string) => roles.includes(item));
};
/**
* tabs栏数据
* tabs标签栏数据
@ -127,7 +25,7 @@ export const roleBase = (roles: Array<string>) => {
export const currentlyRoute = (name: string) => {
const themeStore = useThemeConfig();
const { isTabs } = storeToRefs(themeStore);
const store = useRoutesListStore(pinia);
const store = useRoutesConfigStore(pinia);
const { tabsList, routeList } = storeToRefs(store);
// tabs无数据则默认添加首页
if (tabsList.value.length == 0 && routeList.value.length != 0) {
@ -149,3 +47,46 @@ export const currentlyRoute = (name: string) => {
if (!find.meta.keepAlive || !isTabs.value) return;
store.setRouteNames(find.name); // 缓存路由name
};
/**
*
* @param {array} tree
*/
export const moduleReplacement = (tree: any) => {
if (tree?.length == 0) return [];
tree.map((item: any) => {
// 模块匹配以及转换
moduleMatch(item);
if (item?.children?.length > 0) {
item.children = moduleReplacement(item.children);
}
});
return tree;
};
/**
*
* 1 views .vue
* 2layoutlayout为应用的基础结构
* 3views下的所有文件路径
* 4
*/
// 匹配views里面所有的.vue文件
const modules = import.meta.glob("@/views/**/*.vue");
export const moduleMatch = (item: any) => {
// 若是layout则直接给予顶层layout
if (item.component === "layout") {
// 布局组件是应用的基础结构,在应用启动时就需要被加载,因此不需要按需引入
return (item.component = Layout);
}
// 其它情况下匹配每个views文件夹下的文件路径
for (const key in modules) {
const dir = key.split("views/")[1].replace(".vue", "");
// 若匹配上,则替换真实模块
if (item.component === dir) {
// 按需引入modules
// 将模块的导入操作和实际使用操作解耦,使得我们可以在需要的时候才执行导入操作
item.component = () => modules[key]();
}
}
};

View File

@ -1,15 +1,19 @@
import { defineStore } from "pinia";
import router from "@/router/index.ts";
import { RouteRecordRaw } from "vue-router";
import { getMenuListAPI } from "@/api/modules/system/index";
import { moduleReplacement, linearArray } from "@/router/route-output";
/**
*
* @methods setRouteTree
* @methods setRoutesList
* @methods setRouteNames
* @methods setTabs tabs标签页
* @methods setCurrentRoute
* @methods removeTabsList tabs页的指定路由
* @methods removeRouteName
* @methods removeRouteNames
* @methods initSetRouter
*/
export const useRoutesListStore = defineStore("route-list", {
export const useRoutesConfigStore = defineStore("route-config", {
state: (): any => ({
routeTree: [], // 有访问权限的路由树
routeList: [], // 有访问权限的一维路由数组
@ -18,20 +22,6 @@ export const useRoutesListStore = defineStore("route-list", {
currentRoute: {} // 当前路由
}),
actions: {
/**
* 访
* @param {Array} data
*/
async setRouteTree(data: Menu.MenuOptions) {
this.routeTree = data;
},
/**
* 访
* @param {Array} data
*/
setRouteList(data: any) {
this.routeList = data;
},
/**
*
* @param {string} name
@ -65,6 +55,7 @@ export const useRoutesListStore = defineStore("route-list", {
*/
removeTabsList(key: string) {
const index = this.tabsList.findIndex((item: Menu.MenuOptions) => item.name === key);
if (this.tabsList[index].meta.affix) return;
if (index === -1) return;
this.tabsList.splice(index, 1);
},
@ -83,6 +74,32 @@ export const useRoutesListStore = defineStore("route-list", {
*/
removeRouteNames(list: Array<string>) {
this.cacheRoutes = this.cacheRoutes.filter((item: string) => !list.includes(item));
},
/**
*
* 1
* 2
* 3
* 4 +
* 5
* 6
*/
async initSetRouter() {
// 1、获取过滤角色权限后的树后端做排序处理
let { data } = await getMenuListAPI();
// 2、将模块设置为真实模块
let tree = moduleReplacement(data);
// 3、存储路由树用于生成菜单
this.routeTree = tree[0].children;
// 4、根据树生成一维路由数组
tree[0].children = linearArray(tree[0].children);
// 5、设置完整的路由二维路由顶层路由 + 二级的一维路由
tree[0].redirect = tree[0].children[0].path;
// 6、动态添加路由
tree.forEach((route: RouteRecordRaw) => router.addRoute(route));
console.log("最终路由", tree);
// 7、缓存一维路由
this.routeList = tree[0].children;
}
}
});

View File

@ -42,7 +42,6 @@ import { Message } from "@arco-design/web-vue";
import { useRouter } from "vue-router";
import { useUserInfoStore } from "@/store/modules/user-info";
import { loginAPI, getUserInfoAPI } from "@/api/modules/user/index";
const router = useRouter();
const form = ref({
username: "admin",
@ -86,7 +85,7 @@ const onSubmit = async ({ errors }: any) => {
if (errors) return;
let res = await loginAPI(form.value);
let stores = useUserInfoStore();
stores.setToken(res.data.token);
await stores.setToken(res.data.token);
let account = await getUserInfoAPI();
stores.setAccount(account.data); //
Message.success("登录成功");

1
src/vite-env.d.ts vendored
View File

@ -17,3 +17,4 @@ declare module "nprogress";
declare module "@wangeditor/editor-for-vue";
declare module "@/directives/modules/custom";
declare module "mockjs";
declare module "@/store/modules/route-config";