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_TITLE = 绝弹项目管理
|
||||||
# 网站副标题
|
# 网站副标题
|
||||||
VITE_SUBTITLE = 快速开发web应用的模板工具
|
VITE_SUBTITLE = 快速开发web应用的模板工具
|
||||||
# 接口前缀 说明:参见 axios 的 baseURL
|
# 接口前缀:参见 axios 的 baseURL
|
||||||
VITE_API = /
|
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/
|
VITE_PROXY = http://127.0.0.1:3030/
|
||||||
# API文档 说明:需返回符合 OPENAPI 规范的json内容
|
# API文档 说明:需返回符合 OPENAPI 规范的json内容
|
||||||
VITE_OPENAPI = http://127.0.0.1:3030/openapi.json
|
VITE_OPENAPI = http://127.0.0.1:3030/openapi.json
|
||||||
|
|
||||||
# =====================================================================================
|
|
||||||
# 构建设置
|
|
||||||
# =====================================================================================
|
|
||||||
# 文件后缀 说明:设为dev时会优先加载index.dev.vue文件,否则回退至index.vue文件
|
# 文件后缀 说明:设为dev时会优先加载index.dev.vue文件,否则回退至index.vue文件
|
||||||
VITE_EXTENSION = dev
|
VITE_EXTENSION = dev
|
||||||
|
|
@ -10,7 +10,7 @@ export default defineComponent({
|
||||||
watch(
|
watch(
|
||||||
() => route.path,
|
() => route.path,
|
||||||
() => {
|
() => {
|
||||||
selectedKeys.value = route.matched.map((i) => i.path);
|
selectedKeys.value = route.matched.map((i) => i.aliasOf?.path ?? i.path);
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
@ -31,7 +31,9 @@ export default defineComponent({
|
||||||
const icon = route.icon ? () => <i class={route.icon} /> : null;
|
const icon = route.icon ? () => <i class={route.icon} /> : null;
|
||||||
const node: any = route.children?.length ? (
|
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)}
|
{this.renderItem(route?.children)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import { createColumn, updateColumn, useAniTable } from "@/components";
|
import { createColumn, updateColumn, useAniTable } from "@/components";
|
||||||
import { MenuTypes, MenuType } from "@/constants/menu";
|
import { MenuType, MenuTypes } from "@/constants/menu";
|
||||||
import { flatedMenus } from "@/router";
|
import { flatMenus } from "@/router";
|
||||||
import { listToTree } from "@/utils/listToTree";
|
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 expanded = ref(false);
|
||||||
const toggleExpand = () => {
|
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 "./menus";
|
||||||
export * from "./router";
|
export * from "./router";
|
||||||
export * from "./routes";
|
export * from "./routes/page";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { RouteRecordRaw } from "vue-router";
|
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 { createRouter } from "vue-router";
|
||||||
import { authGuard } from "../guards/guard-auth";
|
import { useAuthGuard } from "../guards/auth";
|
||||||
import { progressGuard } from "../guards/guard-progress";
|
import { useProgressGard } from "../guards/progress";
|
||||||
import { titleGuard } from "../guards/guard-title";
|
import { useTitleGuard } from "../guards/title";
|
||||||
import { routes } from "../routes";
|
|
||||||
import { baseRoutes } from "../routes/base";
|
import { baseRoutes } from "../routes/base";
|
||||||
import { api } from "@/api";
|
import { historyMode } from "./util";
|
||||||
import { store, useUserStore } from "@/store";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路由实例
|
* 路由实例
|
||||||
*/
|
*/
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: historyMode(),
|
||||||
routes: [...baseRoutes, ...routes],
|
routes: [...baseRoutes],
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 进度条守卫
|
* 进度条守卫
|
||||||
*/
|
*/
|
||||||
router.beforeEach(progressGuard.before);
|
useProgressGard(router);
|
||||||
router.afterEach(progressGuard.after);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 权限守卫
|
* 权限守卫
|
||||||
*/
|
*/
|
||||||
router.beforeEach(authGuard);
|
useAuthGuard(router);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标题守卫
|
* 标题守卫
|
||||||
*/
|
*/
|
||||||
router.afterEach(titleGuard);
|
useTitleGuard(router);
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置令牌过期处理函数
|
|
||||||
*/
|
|
||||||
api.expireHandler = () => {
|
|
||||||
const userStore = useUserStore(store);
|
|
||||||
const redirect = router.currentRoute.value.path;
|
|
||||||
userStore.clearUser();
|
|
||||||
router.push({ path: "/login", query: { redirect } });
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -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";
|
import { RouteRecordRaw } from "vue-router";
|
||||||
|
|
||||||
|
export const APP_HOME_NAME = "__APP_HOME__";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基本路由
|
||||||
|
*/
|
||||||
export const baseRoutes: RouteRecordRaw[] = [
|
export const baseRoutes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
redirect: (to) => {
|
name: APP_HOME_NAME,
|
||||||
const { home } = useMenuStore();
|
component: () => "Home Page",
|
||||||
return {
|
|
||||||
name: home,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import generatedRoutes from "virtual:generated-pages";
|
import generatedRoutes from "virtual:generated-pages";
|
||||||
import { RouteRecordRaw } from "vue-router";
|
import { RouteRecordRaw } from "vue-router";
|
||||||
|
|
||||||
const TOP_ROUTE_PREF = "_";
|
export const TOP_ROUTE_PREF = "_";
|
||||||
const APP_ROUTE_NAME = "_layout";
|
export const APP_ROUTE_NAME = "_layout";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换路由
|
* 转换路由
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { defineStore } from "pinia";
|
|
||||||
import { MenuItem } from "@/router";
|
import { MenuItem } from "@/router";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
export const useMenuStore = defineStore({
|
export const useMenuStore = defineStore({
|
||||||
id: "menu",
|
id: "menu",
|
||||||
state: (): MenuStore => {
|
state: (): MenuStore => {
|
||||||
return {
|
return {
|
||||||
menus: [],
|
menus: [],
|
||||||
home: "/",
|
home: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
|
@ -16,6 +16,14 @@ export const useMenuStore = defineStore({
|
||||||
setMenus(menus: MenuItem[]) {
|
setMenus(menus: MenuItem[]) {
|
||||||
this.menus = menus;
|
this.menus = menus;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置首页
|
||||||
|
* @param path 路径
|
||||||
|
*/
|
||||||
|
setHome(path: string) {
|
||||||
|
this.home = path;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import 'vite';
|
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,6 +24,14 @@ interface ImportMetaEnv {
|
||||||
* 开发服务器端口
|
* 开发服务器端口
|
||||||
*/
|
*/
|
||||||
VITE_PORT: number;
|
VITE_PORT: number;
|
||||||
|
/**
|
||||||
|
* 首页路径
|
||||||
|
*/
|
||||||
|
VITE_HOME_PATH: string;
|
||||||
|
/**
|
||||||
|
* 路由模式
|
||||||
|
*/
|
||||||
|
VITE_HISTORY: "web" | "hash";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
|
|
||||||
|
|
@ -33,3 +33,29 @@ export function treeEach(tree: any[], fn: (item: any) => void, before = true) {
|
||||||
!before && fn(item);
|
!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