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