feat: 优化路由逻辑
parent
c648519d42
commit
b11d43a0a6
|
|
@ -41,7 +41,7 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: 300;
|
font-weight: 400;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.loading-tip {
|
.loading-tip {
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,139 @@
|
||||||
|
/**
|
||||||
|
* 请求函数
|
||||||
|
* @see "src/api/instance/useRequest.ts"
|
||||||
|
*/
|
||||||
|
export function useRequest<T extends PromiseFn, E = unknown>(fn: T, options: Options<T> = {}) {
|
||||||
|
type Data = Awaited<ReturnType<T>>;
|
||||||
|
type Args = Parameters<T>;
|
||||||
|
const { initialParams, initialData, retry = 0, retryDelay = 0, interval = 0 } = options;
|
||||||
|
const { onBefore, onSuccess, onError, onFinally } = options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回数据
|
||||||
|
*/
|
||||||
|
const data = ref<Data | null>(initialData ?? null);
|
||||||
|
/**
|
||||||
|
* 请求错误
|
||||||
|
*/
|
||||||
|
const error = ref<E | null>(null);
|
||||||
|
/**
|
||||||
|
* 是否请求中
|
||||||
|
*/
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
let isCanceled = false;
|
||||||
|
let retryCount = 0;
|
||||||
|
let retryTimer = 0;
|
||||||
|
let interTimer = 0;
|
||||||
|
let latestArgs = initialParams ?? [];
|
||||||
|
|
||||||
|
const _send = async (...args: Args) => {
|
||||||
|
try {
|
||||||
|
onBefore?.(args);
|
||||||
|
loading.value = true;
|
||||||
|
const res = await fn(...args);
|
||||||
|
retryCount = 0;
|
||||||
|
if (isCanceled) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
onSuccess?.(res);
|
||||||
|
data.value = res;
|
||||||
|
error.value = null;
|
||||||
|
} catch (err: any) {
|
||||||
|
if (isCanceled) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
onError?.(err);
|
||||||
|
data.value = null;
|
||||||
|
error.value = err;
|
||||||
|
if (retry > 0 && retryCount < retry) {
|
||||||
|
retryCount++;
|
||||||
|
retryTimer = setTimeout(() => _send(...args), retryDelay) as any;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
if (isCanceled) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
onFinally?.();
|
||||||
|
if (!retryCount && interval > 0) {
|
||||||
|
interTimer = setTimeout(() => _send(...args), interval) as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [error.value, data.value];
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearAllTimer = () => {
|
||||||
|
clearTimeout(retryTimer);
|
||||||
|
clearTimeout(interTimer);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消请求
|
||||||
|
*/
|
||||||
|
const cancel = () => {
|
||||||
|
isCanceled = true;
|
||||||
|
clearAllTimer();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送请求
|
||||||
|
*/
|
||||||
|
const send = (...args: Args) => {
|
||||||
|
isCanceled = false;
|
||||||
|
retryCount = 0;
|
||||||
|
latestArgs = args;
|
||||||
|
clearAllTimer();
|
||||||
|
return _send(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => initialParams && send(...initialParams));
|
||||||
|
onUnmounted(cancel);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
|
send,
|
||||||
|
cancel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
type PromiseFn = (...args: any[]) => Promise<any>;
|
type PromiseFn = (...args: any[]) => Promise<any>;
|
||||||
|
|
||||||
type Options<T extends PromiseFn = PromiseFn> = {
|
interface Options<T extends PromiseFn = PromiseFn> {
|
||||||
|
/**
|
||||||
|
* 初始请求参数
|
||||||
|
* @description 传递此参数会在挂载后立即执行
|
||||||
|
*/
|
||||||
|
initialParams?: Parameters<T>;
|
||||||
|
/**
|
||||||
|
* 默认值
|
||||||
|
* @description 与请求函数的返回值一致
|
||||||
|
*/
|
||||||
|
initialData?: Awaited<ReturnType<T>>;
|
||||||
/**
|
/**
|
||||||
* 是否显示全局的 loading
|
* 是否显示全局的 loading
|
||||||
|
* @default false
|
||||||
*/
|
*/
|
||||||
toast?: boolean | string;
|
toast?: boolean | string;
|
||||||
/**
|
|
||||||
* 是否立即执行
|
|
||||||
*/
|
|
||||||
initialParams?: boolean | Parameters<T>;
|
|
||||||
/**
|
|
||||||
* 默认值
|
|
||||||
*/
|
|
||||||
initialData?: Partial<Awaited<ReturnType<T>>>;
|
|
||||||
/**
|
/**
|
||||||
* 请求失败后重试的次数
|
* 请求失败后重试的次数
|
||||||
|
* @default 0
|
||||||
*/
|
*/
|
||||||
retry?: number;
|
retry?: number;
|
||||||
/**
|
/**
|
||||||
* 请求失败后重试的间隔(ms)
|
* 请求失败后重试的间隔(ms)
|
||||||
|
* @default 0
|
||||||
*/
|
*/
|
||||||
retryDelay?: number;
|
retryDelay?: number;
|
||||||
/**
|
/**
|
||||||
* 轮询间隔(ms)
|
* 轮询间隔(ms)
|
||||||
|
* @default 0
|
||||||
*/
|
*/
|
||||||
interval?: number;
|
interval?: number;
|
||||||
/**
|
/**
|
||||||
* 请求前回调
|
* 请求前的回调
|
||||||
*/
|
*/
|
||||||
onBefore?: (args: Parameters<T>) => void;
|
onBefore?: (args: Parameters<T>) => void;
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,139 +148,4 @@ type Options<T extends PromiseFn = PromiseFn> = {
|
||||||
* 请求结束回调
|
* 请求结束回调
|
||||||
*/
|
*/
|
||||||
onFinally?: () => void;
|
onFinally?: () => void;
|
||||||
};
|
|
||||||
|
|
||||||
type State<T extends PromiseFn = PromiseFn, D = Awaited<ReturnType<T>>> = {
|
|
||||||
/**
|
|
||||||
* 请求返回的数据
|
|
||||||
*/
|
|
||||||
data: D | undefined;
|
|
||||||
/**
|
|
||||||
* 请求返回的错误
|
|
||||||
*/
|
|
||||||
error: unknown;
|
|
||||||
/**
|
|
||||||
* 请求是否中
|
|
||||||
*/
|
|
||||||
loading: boolean;
|
|
||||||
/**
|
|
||||||
* 发送请求
|
|
||||||
*/
|
|
||||||
send: (...args: Parameters<T>) => Promise<[unknown, undefined] | [undefined, D]>;
|
|
||||||
/**
|
|
||||||
* 取消请求
|
|
||||||
*/
|
|
||||||
cancel: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const log = (...args: any[]) => {
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
|
||||||
console.log(...args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 包装请求函数,返回响应式状态和请求方法
|
|
||||||
* @see src/api/instance/useRequest.ts
|
|
||||||
*/
|
|
||||||
export function useRequest<T extends PromiseFn>(fn: T, options: Options<T> = {}) {
|
|
||||||
const {
|
|
||||||
initialParams,
|
|
||||||
retry,
|
|
||||||
retryDelay = 0,
|
|
||||||
interval,
|
|
||||||
initialData,
|
|
||||||
onBefore,
|
|
||||||
onSuccess,
|
|
||||||
onError,
|
|
||||||
onFinally,
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const state = reactive<State<T>>({
|
|
||||||
data: initialData,
|
|
||||||
error: null,
|
|
||||||
loading: false,
|
|
||||||
send: null,
|
|
||||||
cancel: null,
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
const inner = {
|
|
||||||
canceled: false,
|
|
||||||
retryCount: 0,
|
|
||||||
retryTimer: 0 as any,
|
|
||||||
intervalTimer: 0 as any,
|
|
||||||
latestParams: (initialParams || []) as any,
|
|
||||||
clearAllTimer: () => {
|
|
||||||
inner.retryTimer && clearTimeout(inner.retryTimer);
|
|
||||||
inner.intervalTimer && clearTimeout(inner.intervalTimer);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const _send: any = async (...args: Parameters<T>) => {
|
|
||||||
let data;
|
|
||||||
let error;
|
|
||||||
inner.retryCount && log(`retry: ${inner.retryCount}`);
|
|
||||||
try {
|
|
||||||
state.loading = true;
|
|
||||||
onBefore?.(args);
|
|
||||||
const res = await fn(...args);
|
|
||||||
inner.retryCount = 0;
|
|
||||||
if (!inner.canceled) {
|
|
||||||
onSuccess?.(res.data);
|
|
||||||
data = res.data;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (!inner.canceled) {
|
|
||||||
error = err;
|
|
||||||
onError?.(err);
|
|
||||||
if (retry && retry > 0 && inner.retryCount < retry) {
|
|
||||||
inner.retryCount++;
|
|
||||||
inner.retryTimer = setTimeout(() => {
|
|
||||||
_send(...args);
|
|
||||||
}, retryDelay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
log("finally");
|
|
||||||
state.loading = false;
|
|
||||||
state.error = error;
|
|
||||||
if (!error) {
|
|
||||||
state.data = data;
|
|
||||||
}
|
|
||||||
if (!inner.canceled) {
|
|
||||||
onFinally?.();
|
|
||||||
if (!inner.retryCount && interval && interval > 0) {
|
|
||||||
inner.intervalTimer = setTimeout(() => {
|
|
||||||
_send(...args);
|
|
||||||
}, interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [error, data];
|
|
||||||
};
|
|
||||||
|
|
||||||
state.cancel = () => {
|
|
||||||
inner.canceled = true;
|
|
||||||
inner.clearAllTimer();
|
|
||||||
};
|
|
||||||
|
|
||||||
state.send = (...args: Parameters<T>) => {
|
|
||||||
inner.canceled = false;
|
|
||||||
inner.retryCount = 0;
|
|
||||||
inner.latestParams = args;
|
|
||||||
inner.clearAllTimer();
|
|
||||||
return _send(...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (initialParams) {
|
|
||||||
state.send(...(Array.isArray(initialParams) ? initialParams : ([] as any)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
state.cancel();
|
|
||||||
});
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ const onSubmitForm = async () => {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await api.auth.login(model);
|
const res = await api.auth.login(model);
|
||||||
userStore.setAccessToken(res.data.data as unknown as string);
|
userStore.setAccessToken(res.data.data);
|
||||||
Notification.success({
|
Notification.success({
|
||||||
title: "登陆提示",
|
title: "登陆提示",
|
||||||
content: `欢迎,您已成功登陆系统!`,
|
content: `欢迎,您已成功登陆系统!`,
|
||||||
|
|
@ -124,7 +124,7 @@ const onSubmitForm = async () => {
|
||||||
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
.login-left {
|
.login-left {
|
||||||
background: rgb(var(--primary-6)) url(/src/pages/_login/image-br.svg) no-repeat center center/90% auto;
|
background: rgb(var(--primary-6)) url(./image-br.svg) no-repeat center center/90% auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,31 @@
|
||||||
import { store, useUserStore } from "@/store";
|
import { store, useUserStore } from "@/store";
|
||||||
import { Message } from "@arco-design/web-vue";
|
import { Notification } from "@arco-design/web-vue";
|
||||||
import { NavigationGuardWithThis } from "vue-router";
|
import { NavigationGuardWithThis } from "vue-router";
|
||||||
|
|
||||||
const whitelist = ["/:all(.*)*"];
|
const WHITE_LIST = ["/:all(.*)*"];
|
||||||
const signoutlist = ["/login"];
|
const UNSIGNIN_LIST = ["/login"];
|
||||||
|
|
||||||
|
// store不能放在外面,否则 pinia-plugin-peristedstate 插件会失效
|
||||||
export const authGuard: NavigationGuardWithThis<undefined> = async function (to) {
|
export const authGuard: NavigationGuardWithThis<undefined> = async function (to) {
|
||||||
// 放在外面,pinia-plugin-peristedstate 插件会失效
|
|
||||||
const userStore = useUserStore(store);
|
const userStore = useUserStore(store);
|
||||||
if (whitelist.includes(to.path) || to.name === "_all") {
|
if (to.meta.auth?.some((i) => i === "*")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (signoutlist.includes(to.path)) {
|
if (WHITE_LIST.includes(to.path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (UNSIGNIN_LIST.includes(to.path)) {
|
||||||
if (userStore.accessToken) {
|
if (userStore.accessToken) {
|
||||||
Message.warning(`提示:您已登陆,如需重新请退出后再操作!`);
|
Notification.warning({
|
||||||
|
title: "跳转提示",
|
||||||
|
content: `提示:您已登陆,如需重新登陆请退出后再操作!`,
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!userStore.accessToken) {
|
if (!userStore.accessToken) {
|
||||||
return {
|
return { path: "/login", query: { redirect: to.path } };
|
||||||
path: "/login",
|
|
||||||
query: {
|
|
||||||
redirect: to.path,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { appRoutes } from "../routes";
|
||||||
/**
|
/**
|
||||||
* 菜单项类型
|
* 菜单项类型
|
||||||
*/
|
*/
|
||||||
interface MenuItem {
|
export interface MenuItem {
|
||||||
id: string;
|
id: string;
|
||||||
parentId?: string;
|
parentId?: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
|
@ -23,38 +23,32 @@ interface MenuItem {
|
||||||
function routesToItems(routes: RouteRecordRaw[]): MenuItem[] {
|
function routesToItems(routes: RouteRecordRaw[]): MenuItem[] {
|
||||||
const items: MenuItem[] = [];
|
const items: MenuItem[] = [];
|
||||||
|
|
||||||
routes.forEach((route) => {
|
for (const route of routes) {
|
||||||
|
const { meta = {}, parentMeta, path } = route as any;
|
||||||
|
const { title, sort, icon } = meta;
|
||||||
|
let id = path;
|
||||||
let paths = route.path.split("/");
|
let paths = route.path.split("/");
|
||||||
let id = route.path;
|
|
||||||
let parentId = paths.slice(0, -1).join("/");
|
let parentId = paths.slice(0, -1).join("/");
|
||||||
|
if (parentMeta) {
|
||||||
if ((route as any).parentMeta) {
|
const { title, icon, sort } = parentMeta;
|
||||||
id = `${route.path}/index`;
|
id = `${path}/index`;
|
||||||
parentId = route.path;
|
parentId = path;
|
||||||
items.push({
|
items.push({
|
||||||
id: route.path,
|
title,
|
||||||
|
icon,
|
||||||
|
sort,
|
||||||
|
path,
|
||||||
|
id: path,
|
||||||
parentId: paths.slice(0, -1).join("/"),
|
parentId: paths.slice(0, -1).join("/"),
|
||||||
path: `${route.path}`,
|
|
||||||
title: (route as any).parentMeta.title,
|
|
||||||
icon: (route as any).parentMeta.icon,
|
|
||||||
sort: (route as any).parentMeta.sort,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const p = paths.slice(0, -1).join("/");
|
const p = paths.slice(0, -1).join("/");
|
||||||
if (routes.some((i) => i.path === p && (i as any).parentMeta)) {
|
if (routes.some((i) => i.path === p) && parentMeta) {
|
||||||
parentId = p;
|
parentId = p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
items.push({ id, title, parentId, path, icon, sort });
|
||||||
items.push({
|
}
|
||||||
id,
|
|
||||||
parentId,
|
|
||||||
path: route.path,
|
|
||||||
sort: route.meta?.sort,
|
|
||||||
title: route.meta?.title,
|
|
||||||
icon: route.meta?.icon,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
@ -68,18 +62,18 @@ function listToTree(list: MenuItem[]) {
|
||||||
const map: Record<string, MenuItem> = {};
|
const map: Record<string, MenuItem> = {};
|
||||||
const tree: MenuItem[] = [];
|
const tree: MenuItem[] = [];
|
||||||
|
|
||||||
list.forEach((item) => {
|
for (const item of list) {
|
||||||
map[item.id] = item;
|
map[item.id] = item;
|
||||||
});
|
}
|
||||||
|
|
||||||
list.forEach((item) => {
|
for (const item of list) {
|
||||||
const parent = map[item.parentId as string];
|
const parent = map[item.parentId as string];
|
||||||
if (parent) {
|
if (parent) {
|
||||||
(parent.children || (parent.children = [])).push(item);
|
(parent.children || (parent.children = [])).push(item);
|
||||||
} else {
|
} else {
|
||||||
tree.push(item);
|
tree.push(item);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
@ -102,31 +96,17 @@ function sort<T extends { children?: T[]; [key: string]: any }>(routes: T[], key
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换路由为树形菜单项,并排序
|
|
||||||
* @param routes 路由配置
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function transformToMenuItems(routes: RouteRecordRaw[]) {
|
|
||||||
const menus = routesToItems(routes);
|
|
||||||
const tree = listToTree(menus);
|
|
||||||
return sort(tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扁平化的菜单
|
* 扁平化的菜单
|
||||||
*/
|
*/
|
||||||
const flatedMenus = routesToItems(appRoutes);
|
export const flatMenus = routesToItems(appRoutes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 树结构菜单
|
* 树结构菜单
|
||||||
*/
|
*/
|
||||||
const treeMenus = listToTree(flatedMenus);
|
export const treeMenus = listToTree(flatMenus);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 排序过的树级菜单
|
* 排序过的树级菜单
|
||||||
*/
|
*/
|
||||||
const menus = sort(treeMenus);
|
export const menus = sort(treeMenus);
|
||||||
|
|
||||||
export { menus, treeMenus, flatedMenus };
|
|
||||||
export type { MenuItem };
|
|
||||||
|
|
|
||||||
|
|
@ -3,28 +3,40 @@ import { authGuard } from "../guards/guard-auth";
|
||||||
import { progressGuard } from "../guards/guard-progress";
|
import { progressGuard } from "../guards/guard-progress";
|
||||||
import { titleGuard } from "../guards/guard-title";
|
import { titleGuard } from "../guards/guard-title";
|
||||||
import { routes } from "../routes";
|
import { routes } from "../routes";
|
||||||
|
import { baseRoutes } from "../routes/base";
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import { store, useUserStore } from "@/store";
|
import { store, useUserStore } from "@/store";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路由实例
|
||||||
|
*/
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes: [
|
routes: [...baseRoutes, ...routes],
|
||||||
{
|
|
||||||
path: "/",
|
|
||||||
redirect: "/home/home",
|
|
||||||
},
|
|
||||||
...routes,
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进度条守卫
|
||||||
|
*/
|
||||||
router.beforeEach(progressGuard.before);
|
router.beforeEach(progressGuard.before);
|
||||||
router.afterEach(progressGuard.after);
|
router.afterEach(progressGuard.after);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限守卫
|
||||||
|
*/
|
||||||
router.beforeEach(authGuard);
|
router.beforeEach(authGuard);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标题守卫
|
||||||
|
*/
|
||||||
router.afterEach(titleGuard);
|
router.afterEach(titleGuard);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置令牌过期处理函数
|
||||||
|
*/
|
||||||
api.expireHandler = () => {
|
api.expireHandler = () => {
|
||||||
const userStore = useUserStore(store);
|
const userStore = useUserStore(store);
|
||||||
userStore.clearUser();
|
|
||||||
const redirect = router.currentRoute.value.path;
|
const redirect = router.currentRoute.value.path;
|
||||||
|
userStore.clearUser();
|
||||||
router.push({ path: "/login", query: { redirect } });
|
router.push({ path: "/login", query: { redirect } });
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { useMenuStore } from "@/store/menu";
|
||||||
|
import { RouteRecordRaw } from "vue-router";
|
||||||
|
|
||||||
|
export const baseRoutes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
redirect: (to) => {
|
||||||
|
const { home } = useMenuStore();
|
||||||
|
return {
|
||||||
|
name: home,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
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 = "_";
|
||||||
const APP_ROUTE_NAME = "_layout";
|
const APP_ROUTE_NAME = "_layout";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换一维路由为二维路由
|
* 转换路由
|
||||||
* @description 以 _ 开头的路由为顶级路由,其余为应用路由
|
* @description 以 _ 开头的路由为顶级路由,其余为应用路由
|
||||||
*/
|
*/
|
||||||
const transformRoutes = (routes: RouteRecordRaw[]) => {
|
const transformRoutes = (routes: RouteRecordRaw[]) => {
|
||||||
|
|
@ -12,11 +13,11 @@ const transformRoutes = (routes: RouteRecordRaw[]) => {
|
||||||
const appRoutes: RouteRecordRaw[] = [];
|
const appRoutes: RouteRecordRaw[] = [];
|
||||||
|
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
if ((route.name as string)?.startsWith("_")) {
|
if ((route.name as string)?.startsWith(TOP_ROUTE_PREF)) {
|
||||||
if (route.name === APP_ROUTE_NAME) {
|
if (route.name === APP_ROUTE_NAME) {
|
||||||
route.children = appRoutes;
|
route.children = appRoutes;
|
||||||
}
|
}
|
||||||
route.path = route.path.replace("_", "");
|
route.path = route.path.replace(TOP_ROUTE_PREF, "");
|
||||||
topRoutes.push(route);
|
topRoutes.push(route);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,13 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
interface PageTag {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
path: string;
|
|
||||||
closable?: boolean;
|
|
||||||
closible?: boolean;
|
|
||||||
actived?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAppStore = defineStore({
|
export const useAppStore = defineStore({
|
||||||
id: "app",
|
id: "app",
|
||||||
state: () => ({
|
state: (): AppStore => ({
|
||||||
/**
|
|
||||||
* 是否为暗模式
|
|
||||||
*/
|
|
||||||
isDarkMode: false,
|
isDarkMode: false,
|
||||||
/**
|
|
||||||
* 站点标题
|
|
||||||
*/
|
|
||||||
title: import.meta.env.VITE_TITLE,
|
title: import.meta.env.VITE_TITLE,
|
||||||
/**
|
|
||||||
* 站点副标题
|
|
||||||
*/
|
|
||||||
subtitle: import.meta.env.VITE_SUBTITLE,
|
subtitle: import.meta.env.VITE_SUBTITLE,
|
||||||
/**
|
|
||||||
* 页面是否加载中,用于路由首次加载
|
|
||||||
*/
|
|
||||||
pageLoding: false,
|
pageLoding: false,
|
||||||
pageTags: [
|
pageTags: [],
|
||||||
{
|
|
||||||
id: "/",
|
|
||||||
title: "首页",
|
|
||||||
path: "/",
|
|
||||||
closable: false,
|
|
||||||
closible: false,
|
|
||||||
actived: false,
|
|
||||||
},
|
|
||||||
] as PageTag[],
|
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,6 +16,7 @@ export const useAppStore = defineStore({
|
||||||
toggleDark() {
|
toggleDark() {
|
||||||
this.isDarkMode ? this.setLight() : this.setDark();
|
this.isDarkMode ? this.setLight() : this.setDark();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换为亮模式
|
* 切换为亮模式
|
||||||
*/
|
*/
|
||||||
|
|
@ -54,6 +25,7 @@ export const useAppStore = defineStore({
|
||||||
document.body.classList.remove("dark");
|
document.body.classList.remove("dark");
|
||||||
this.isDarkMode = false;
|
this.isDarkMode = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换为暗模式
|
* 切换为暗模式
|
||||||
*/
|
*/
|
||||||
|
|
@ -62,12 +34,14 @@ export const useAppStore = defineStore({
|
||||||
document.body.classList.add("dark");
|
document.body.classList.add("dark");
|
||||||
this.isDarkMode = true;
|
this.isDarkMode = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置页面加载loading
|
* 设置页面加载loading
|
||||||
*/
|
*/
|
||||||
setPageLoading(loading: boolean) {
|
setPageLoading(loading: boolean) {
|
||||||
this.pageLoding = loading;
|
this.pageLoding = loading;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加页面标签
|
* 添加页面标签
|
||||||
* @param tag 标签
|
* @param tag 标签
|
||||||
|
|
@ -83,14 +57,13 @@ export const useAppStore = defineStore({
|
||||||
actived: false,
|
actived: false,
|
||||||
...tag,
|
...tag,
|
||||||
});
|
});
|
||||||
console.log(this.pageTags);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除页面标签
|
* 移除页面标签
|
||||||
* @param tag 标签
|
* @param tag 标签
|
||||||
*/
|
*/
|
||||||
delPageTag(tag: PageTag) {
|
delPageTag(tag: PageTag) {
|
||||||
console.log("del page tag");
|
|
||||||
const index = this.pageTags.findIndex((i) => i.id === tag.id);
|
const index = this.pageTags.findIndex((i) => i.id === tag.id);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.pageTags.splice(index, 1);
|
this.pageTags.splice(index, 1);
|
||||||
|
|
@ -99,3 +72,35 @@ export const useAppStore = defineStore({
|
||||||
},
|
},
|
||||||
persist: !import.meta.env.DEV,
|
persist: !import.meta.env.DEV,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
interface AppStore {
|
||||||
|
/**
|
||||||
|
* 是否为暗模式
|
||||||
|
*/
|
||||||
|
isDarkMode: boolean;
|
||||||
|
/**
|
||||||
|
* 站点标题
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
/**
|
||||||
|
* 站点副标题
|
||||||
|
*/
|
||||||
|
subtitle: string;
|
||||||
|
/**
|
||||||
|
* 页面是否加载中,用于路由首次加载
|
||||||
|
*/
|
||||||
|
pageLoding: boolean;
|
||||||
|
/**
|
||||||
|
* 标签数组
|
||||||
|
*/
|
||||||
|
pageTags: PageTag[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageTag {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
path: string;
|
||||||
|
closable?: boolean;
|
||||||
|
closible?: boolean;
|
||||||
|
actived?: boolean;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { MenuItem } from "@/router";
|
||||||
|
|
||||||
|
export const useMenuStore = defineStore({
|
||||||
|
id: "menu",
|
||||||
|
state: (): MenuStore => {
|
||||||
|
return {
|
||||||
|
menus: [],
|
||||||
|
home: "/",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
/**
|
||||||
|
* 设置菜单
|
||||||
|
*/
|
||||||
|
setMenus(menus: MenuItem[]) {
|
||||||
|
this.menus = menus;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface MenuStore {
|
||||||
|
/**
|
||||||
|
* 路由列表
|
||||||
|
*/
|
||||||
|
menus: MenuItem[];
|
||||||
|
/**
|
||||||
|
* 首页路径
|
||||||
|
*/
|
||||||
|
home: string;
|
||||||
|
}
|
||||||
|
|
@ -2,4 +2,5 @@ import { createPinia } from "pinia";
|
||||||
import persistedstatePlugin from "pinia-plugin-persistedstate";
|
import persistedstatePlugin from "pinia-plugin-persistedstate";
|
||||||
|
|
||||||
export const store = createPinia();
|
export const store = createPinia();
|
||||||
|
|
||||||
store.use(persistedstatePlugin);
|
store.use(persistedstatePlugin);
|
||||||
|
|
@ -2,31 +2,13 @@ import { defineStore } from "pinia";
|
||||||
|
|
||||||
export const useUserStore = defineStore({
|
export const useUserStore = defineStore({
|
||||||
id: "user",
|
id: "user",
|
||||||
state: () => {
|
state: (): UserStore => {
|
||||||
return {
|
return {
|
||||||
/**
|
|
||||||
* 用户ID
|
|
||||||
*/
|
|
||||||
id: 0,
|
id: 0,
|
||||||
/**
|
|
||||||
* 登录用户名
|
|
||||||
*/
|
|
||||||
username: "juetan",
|
username: "juetan",
|
||||||
/**
|
|
||||||
* 用户昵称
|
|
||||||
*/
|
|
||||||
nickname: "绝弹",
|
nickname: "绝弹",
|
||||||
/** `
|
|
||||||
* 用户头像地址
|
|
||||||
*/
|
|
||||||
avatar: "https://github.com/juetan.png",
|
avatar: "https://github.com/juetan.png",
|
||||||
/**
|
|
||||||
* JWT令牌
|
|
||||||
*/
|
|
||||||
accessToken: "",
|
accessToken: "",
|
||||||
/**
|
|
||||||
* 刷新令牌
|
|
||||||
*/
|
|
||||||
refreshToken: undefined,
|
refreshToken: undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -38,7 +20,11 @@ export const useUserStore = defineStore({
|
||||||
this.accessToken = token;
|
this.accessToken = token;
|
||||||
},
|
},
|
||||||
|
|
||||||
setAccessToken(token: string) {
|
/**
|
||||||
|
* 设置访问令牌
|
||||||
|
* @param token 令牌
|
||||||
|
*/
|
||||||
|
setAccessToken(token?: string) {
|
||||||
this.accessToken = token;
|
this.accessToken = token;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -52,13 +38,41 @@ export const useUserStore = defineStore({
|
||||||
/**
|
/**
|
||||||
* 设置用户信息
|
* 设置用户信息
|
||||||
*/
|
*/
|
||||||
setUser(user: any) {
|
setUser(user: Partial<UserStore>) {
|
||||||
this.id = user.id;
|
const { id, username, nickname, avatar, accessToken } = user;
|
||||||
this.username = user.username;
|
id && (this.id = id);
|
||||||
this.nickname = user.nickname;
|
username && (this.username = username);
|
||||||
this.avatar = user.avatar;
|
nickname && (this.nickname = nickname);
|
||||||
this.accessToken = user.token;
|
avatar && (this.avatar = avatar);
|
||||||
|
accessToken && (this.accessToken = accessToken);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
persist: true,
|
persist: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export interface UserStore {
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
id: number;
|
||||||
|
/**
|
||||||
|
* 登录用户名
|
||||||
|
*/
|
||||||
|
username: string;
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
*/
|
||||||
|
nickname: string;
|
||||||
|
/**
|
||||||
|
* 用户头像地址
|
||||||
|
*/
|
||||||
|
avatar?: string;
|
||||||
|
/**
|
||||||
|
* JWT令牌
|
||||||
|
*/
|
||||||
|
accessToken?: string;
|
||||||
|
/**
|
||||||
|
* 刷新令牌
|
||||||
|
*/
|
||||||
|
refreshToken?: string;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'vue-router';
|
import "vue-router";
|
||||||
|
|
||||||
declare module 'vue-router' {
|
declare module "vue-router" {
|
||||||
interface RouteRecordRaw {
|
interface RouteRecordSingleView {
|
||||||
parentMeta: {
|
parentMeta: {
|
||||||
/**
|
/**
|
||||||
* 页面标题
|
* 页面标题
|
||||||
|
|
@ -34,7 +34,7 @@ declare module 'vue-router' {
|
||||||
/**
|
/**
|
||||||
* 是否在菜单导航中隐藏
|
* 是否在菜单导航中隐藏
|
||||||
*/
|
*/
|
||||||
hidden?: boolean;
|
hide?: boolean;
|
||||||
/**
|
/**
|
||||||
* 所需权限
|
* 所需权限
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue