feat: 优化路由逻辑
自动部署 / build (push) Successful in 1m27s
Details
自动部署 / build (push) Successful in 1m27s
Details
parent
b11d43a0a6
commit
f23f8f53e6
10
.env
10
.env
|
|
@ -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
|
||||
|
|
@ -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)}
|
||||
</>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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}`;
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./menus";
|
||||
export * from "./router";
|
||||
export * from "./routes";
|
||||
export * from "./routes/page";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { RouteRecordRaw } from "vue-router";
|
||||
import { appRoutes } from "../routes";
|
||||
import { appRoutes } from "../routes/page";
|
||||
|
||||
/**
|
||||
* 菜单项类型
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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",
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
/**
|
||||
* 转换路由
|
||||
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue