feat: 优化路由逻辑
自动部署 / build (push) Successful in 1m27s Details

master
绝弹 2023-11-07 21:12:26 +08:00
parent b11d43a0a6
commit f23f8f53e6
18 changed files with 205 additions and 118 deletions

10
.env
View File

@ -5,8 +5,12 @@
VITE_TITLE = 绝弹项目管理
# 网站副标题
VITE_SUBTITLE = 快速开发web应用的模板工具
# 接口前缀 说明:参见 axios 的 baseURL
# 接口前缀:参见 axios 的 baseURL
VITE_API = /
# 首页路径
VITE_HOME_PATH = /home/home
# 路由模式web(路径) hash(锚点)
VITE_HISTORY = web
# =====================================================================================
# 开发设置
@ -19,9 +23,5 @@ VITE_PORT = 3020
VITE_PROXY = http://127.0.0.1:3030/
# API文档 说明:需返回符合 OPENAPI 规范的json内容
VITE_OPENAPI = http://127.0.0.1:3030/openapi.json
# =====================================================================================
# 构建设置
# =====================================================================================
# 文件后缀 说明设为dev时会优先加载index.dev.vue文件否则回退至index.vue文件
VITE_EXTENSION = dev

View File

@ -10,7 +10,7 @@ export default defineComponent({
watch(
() => route.path,
() => {
selectedKeys.value = route.matched.map((i) => i.path);
selectedKeys.value = route.matched.map((i) => i.aliasOf?.path ?? i.path);
},
{ immediate: true }
);
@ -31,7 +31,9 @@ export default defineComponent({
const icon = route.icon ? () => <i class={route.icon} /> : null;
const node: any = route.children?.length ? (
<>
<div class="px-2"><a-divider margin={6} class="!border-slate-100"></a-divider></div>
<div class="px-2">
<a-divider margin={6} class="!border-slate-100"></a-divider>
</div>
{this.renderItem(route?.children)}
</>
) : (

View File

@ -7,11 +7,11 @@
<script setup lang="tsx">
import { api } from "@/api";
import { createColumn, updateColumn, useAniTable } from "@/components";
import { MenuTypes, MenuType } from "@/constants/menu";
import { flatedMenus } from "@/router";
import { MenuType, MenuTypes } from "@/constants/menu";
import { flatMenus } from "@/router";
import { listToTree } from "@/utils/listToTree";
const menuArr = flatedMenus.map((i) => ({ label: i.title, value: i.id }));
const menuArr = flatMenus.map((i) => ({ label: i.title, value: i.id }));
const expanded = ref(false);
const toggleExpand = () => {

68
src/router/guards/auth.ts Normal file
View File

@ -0,0 +1,68 @@
import { api } from "@/api";
import { store, useUserStore } from "@/store";
import { useMenuStore } from "@/store/menu";
import { treeFind } from "@/utils/listToTree";
import { Notification } from "@arco-design/web-vue";
import { Router } from "vue-router";
import { menus } from "../menus";
import { APP_HOME_NAME } from "../routes/base";
import { APP_ROUTE_NAME, routes } from "../routes/page";
const WHITE_LIST = ["/:all(.*)*"];
const UNSIGNIN_LIST = ["/login"];
/**
*
* @param to
* @description store pinia-plugin-peristedstate
* @returns
*/
export function useAuthGuard(router: Router) {
api.expireHandler = () => {
const userStore = useUserStore(store);
const redirect = router.currentRoute.value.path;
userStore.clearUser();
router.push({ path: "/login", query: { redirect } });
};
router.beforeEach(async function (to) {
const userStore = useUserStore(store);
const menuStore = useMenuStore(store);
if (to.meta.auth?.some((i) => i === "*")) {
return true;
}
if (WHITE_LIST.includes(to.path)) {
return true;
}
if (UNSIGNIN_LIST.includes(to.path)) {
if (!userStore.accessToken) {
return true;
}
Notification.warning({
title: "跳转提示",
content: `您已登陆,如需重新登陆请退出后再操作!`,
});
return false;
}
if (!userStore.accessToken) {
return { path: "/login", query: { redirect: to.path } };
}
if (!menuStore.menus.length) {
menuStore.setMenus(menus);
menuStore.setHome(import.meta.env.VITE_HOME_PATH);
for (const route of routes) {
router.addRoute(route);
}
const home = treeFind(routes, (i) => i.path === menuStore.home);
if (home) {
const route = { ...home, name: APP_HOME_NAME, alias: "/" };
router.removeRoute(home.name!);
router.addRoute(APP_ROUTE_NAME, route);
return router.replace(to.path);
}
}
return true;
});
return router;
}

View File

@ -1,31 +0,0 @@
import { store, useUserStore } from "@/store";
import { Notification } from "@arco-design/web-vue";
import { NavigationGuardWithThis } from "vue-router";
const WHITE_LIST = ["/:all(.*)*"];
const UNSIGNIN_LIST = ["/login"];
// store不能放在外面否则 pinia-plugin-peristedstate 插件会失效
export const authGuard: NavigationGuardWithThis<undefined> = async function (to) {
const userStore = useUserStore(store);
if (to.meta.auth?.some((i) => i === "*")) {
return true;
}
if (WHITE_LIST.includes(to.path)) {
return true;
}
if (UNSIGNIN_LIST.includes(to.path)) {
if (userStore.accessToken) {
Notification.warning({
title: "跳转提示",
content: `提示:您已登陆,如需重新登陆请退出后再操作!`,
});
return false;
}
return true;
}
if (!userStore.accessToken) {
return { path: "/login", query: { redirect: to.path } };
}
return true;
};

View File

@ -1,31 +0,0 @@
import { NProgress } from "@/libs/nprogress";
import { useAppStore } from "@/store";
import { NavigationGuardWithThis, NavigationHookAfter } from "vue-router";
const routeMap = new Map<string, boolean>();
const before: NavigationGuardWithThis<undefined> = function (to) {
NProgress.start();
if (routeMap.get(to.fullPath)) {
return true;
}
const appStore = useAppStore();
appStore.setPageLoading(true);
};
const after: NavigationHookAfter = function (to) {
NProgress.done();
if (routeMap.get(to.fullPath)) {
return;
}
const appStore = useAppStore();
setTimeout(() => {
appStore.setPageLoading(false);
routeMap.set(to.fullPath, true);
}, 500);
};
export const progressGuard = {
before,
after,
};

View File

@ -1,9 +0,0 @@
import { store, useAppStore } from "@/store";
import { NavigationHookAfter } from "vue-router";
export const titleGuard: NavigationHookAfter = function (to) {
const appStore = useAppStore(store);
const title = to.meta.title || appStore.title;
const subtitle = appStore.subtitle;
document.title = `${title} | ${subtitle}`;
};

View File

@ -0,0 +1,30 @@
import { NProgress } from "@/libs/nprogress";
import { useAppStore } from "@/store";
import { Router } from "vue-router";
const routeMap = new Map<string, boolean>();
export function useProgressGard(router: Router) {
router.beforeEach(function (to) {
NProgress.start();
if (routeMap.get(to.fullPath)) {
return true;
}
const appStore = useAppStore();
appStore.setPageLoading(true);
});
router.afterEach(function (to) {
NProgress.done();
if (routeMap.get(to.fullPath)) {
return;
}
const appStore = useAppStore();
setTimeout(() => {
appStore.setPageLoading(false);
routeMap.set(to.fullPath, true);
}, 500);
});
return router;
}

View File

@ -0,0 +1,12 @@
import { store, useAppStore } from "@/store";
import { Router } from "vue-router";
export function useTitleGuard(router: Router) {
router.beforeEach(function (to) {
const appStore = useAppStore(store);
const title = to.meta.title || appStore.title;
const subtitle = appStore.subtitle;
document.title = `${title} | ${subtitle}`;
});
return router;
}

View File

@ -1,3 +1,4 @@
export * from "./menus";
export * from "./router";
export * from "./routes";
export * from "./routes/page";

View File

@ -1,5 +1,5 @@
import { RouteRecordRaw } from "vue-router";
import { appRoutes } from "../routes";
import { appRoutes } from "../routes/page";
/**
*

View File

@ -1,42 +1,29 @@
import { createRouter, createWebHashHistory } from "vue-router";
import { authGuard } from "../guards/guard-auth";
import { progressGuard } from "../guards/guard-progress";
import { titleGuard } from "../guards/guard-title";
import { routes } from "../routes";
import { createRouter } from "vue-router";
import { useAuthGuard } from "../guards/auth";
import { useProgressGard } from "../guards/progress";
import { useTitleGuard } from "../guards/title";
import { baseRoutes } from "../routes/base";
import { api } from "@/api";
import { store, useUserStore } from "@/store";
import { historyMode } from "./util";
/**
*
*/
export const router = createRouter({
history: createWebHashHistory(),
routes: [...baseRoutes, ...routes],
history: historyMode(),
routes: [...baseRoutes],
});
/**
*
*/
router.beforeEach(progressGuard.before);
router.afterEach(progressGuard.after);
useProgressGard(router);
/**
*
*/
router.beforeEach(authGuard);
useAuthGuard(router);
/**
*
*/
router.afterEach(titleGuard);
/**
*
*/
api.expireHandler = () => {
const userStore = useUserStore(store);
const redirect = router.currentRoute.value.path;
userStore.clearUser();
router.push({ path: "/login", query: { redirect } });
};
useTitleGuard(router);

17
src/router/router/util.ts Normal file
View File

@ -0,0 +1,17 @@
import { createWebHashHistory, createWebHistory } from "vue-router";
/**
*
*/
const HistoryMap = {
web: createWebHistory,
hash: createWebHashHistory,
};
/**
*
*/
export function historyMode() {
const mode = HistoryMap[import.meta.env.VITE_HISTORY];
return mode();
}

View File

@ -1,14 +1,14 @@
import { useMenuStore } from "@/store/menu";
import { RouteRecordRaw } from "vue-router";
export const APP_HOME_NAME = "__APP_HOME__";
/**
*
*/
export const baseRoutes: RouteRecordRaw[] = [
{
path: "/",
redirect: (to) => {
const { home } = useMenuStore();
return {
name: home,
};
},
name: APP_HOME_NAME,
component: () => "Home Page",
},
];

View File

@ -1,8 +1,8 @@
import generatedRoutes from "virtual:generated-pages";
import { RouteRecordRaw } from "vue-router";
const TOP_ROUTE_PREF = "_";
const APP_ROUTE_NAME = "_layout";
export const TOP_ROUTE_PREF = "_";
export const APP_ROUTE_NAME = "_layout";
/**
*

View File

@ -1,12 +1,12 @@
import { defineStore } from "pinia";
import { MenuItem } from "@/router";
import { defineStore } from "pinia";
export const useMenuStore = defineStore({
id: "menu",
state: (): MenuStore => {
return {
menus: [],
home: "/",
home: "",
};
},
actions: {
@ -16,6 +16,14 @@ export const useMenuStore = defineStore({
setMenus(menus: MenuItem[]) {
this.menus = menus;
},
/**
*
* @param path
*/
setHome(path: string) {
this.home = path;
}
},
});

9
src/types/env.d.ts vendored
View File

@ -1,4 +1,3 @@
import 'vite';
interface ImportMetaEnv {
/**
@ -25,6 +24,14 @@ interface ImportMetaEnv {
*
*/
VITE_PORT: number;
/**
*
*/
VITE_HOME_PATH: string;
/**
*
*/
VITE_HISTORY: "web" | "hash";
}
interface ImportMeta {

View File

@ -33,3 +33,29 @@ export function treeEach(tree: any[], fn: (item: any) => void, before = true) {
!before && fn(item);
}
}
/**
*
* @param tree
* @param fn
* @returns
*/
export function treeFind<T extends { children?: T[]; [key: string]: any } = any>(
tree: T[],
fn: (item: T) => boolean
): T | null {
let data: T | null = null;
for (const item of tree) {
if (fn(item)) {
data = item;
break;
}
if (item.children) {
data = treeFind(item.children, fn);
if (data) {
break;
}
}
}
return data;
}