Browse Source

🎟 用户信息持久化

master
wangxiang 3 years ago
parent
commit
67ef656068
  1. 4
      kicc-ui/src/components/Menu/src/BasicMenu.vue
  2. 8
      kicc-ui/src/router/constant.ts
  3. 41
      kicc-ui/src/router/guard/index.ts
  4. 31
      kicc-ui/src/router/guard/paramMenuGuard.ts
  5. 59
      kicc-ui/src/router/guard/permissionGuard.ts
  6. 11
      kicc-ui/src/router/guard/stateGuard.ts
  7. 73
      kicc-ui/src/router/helper/menuHelper.ts
  8. 65
      kicc-ui/src/router/helper/routeHelper.ts
  9. 3
      kicc-ui/src/router/types.ts
  10. 2
      kicc-ui/src/store/modules/permission.ts
  11. 2
      kicc-ui/types/vue-router.d.ts

4
kicc-ui/src/components/Menu/src/BasicMenu.vue

@ -138,13 +138,13 @@ @@ -138,13 +138,13 @@
return;
}
const path = (route || unref(currentRoute)).path;
setOpenKeys(path);
await setOpenKeys(path);
if (unref(currentActiveMenu)) return;
if (props.isHorizontal && unref(getSplit)) {
const parentPath = await getCurrentParentPath(path);
menuState.selectedKeys = [parentPath];
} else {
const parentPaths = await getAllParentPath(props.items, path);
const parentPaths = getAllParentPath(props.items, path);
menuState.selectedKeys = parentPaths;
}
}

8
kicc-ui/src/router/constant.ts

@ -16,9 +16,5 @@ export const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exceptio @@ -16,9 +16,5 @@ export const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exceptio
/** 默认布局 */
export const LAYOUT = () => import('/@/layouts/default/index.vue');
/** 父布局 */
export const getParentLayout = (_name?: string) => {
return () => new Promise((resolve) => {
resolve({ name: PARENT_LAYOUT_NAME });
});
};
/** 定义一个空组件什么都没有只有一个组件名称充当父布局,不让没有组件的时候报找不到组件的错误 */
export const getParentLayout = (_name?: string) => () => new Promise((resolve) => resolve({ name: PARENT_LAYOUT_NAME }));

41
kicc-ui/src/router/guard/index.ts

@ -18,9 +18,8 @@ import { createPermissionGuard } from './permissionGuard'; @@ -18,9 +18,8 @@ import { createPermissionGuard } from './permissionGuard';
import { createStateGuard } from './stateGuard';
import nProgress from 'nprogress';
import projectSetting from '/@/settings/projectSetting';
import { createParamMenuGuard } from './paramMenuGuard';
// 不要改变创建的顺序
/** 不要改变创建的顺序 */
export function setupRouterGuard(router: Router) {
createPageGuard(router);
createPageLoadingGuard(router);
@ -29,16 +28,14 @@ export function setupRouterGuard(router: Router) { @@ -29,16 +28,14 @@ export function setupRouterGuard(router: Router) {
createMessageGuard(router);
createProgressGuard(router);
createPermissionGuard(router);
// 必须在 createPermissionGuard 之后(菜单已构建)
createParamMenuGuard(router);
createStateGuard(router);
}
/** 处理页面状态的钩子 */
/** 处理页面缓存 */
function createPageGuard(router: Router) {
const loadedPageMap = new Map<string, boolean>();
router.beforeEach(async (to) => {
// 页面已经加载完毕,再次打开会更快,不需要再做加载等处理
// 页面已经加载完毕,再次打开会更快,不需要再做加载等处理
to.meta.loaded = !!loadedPageMap.get(to.path);
// 通知路由更改
setRouteChange(to);
@ -49,28 +46,23 @@ function createPageGuard(router: Router) { @@ -49,28 +46,23 @@ function createPageGuard(router: Router) {
});
}
/** 用于处理页面加载状态 */
/** 处理页面正在加载中 */
function createPageLoadingGuard(router: Router) {
const userStore = useUserStoreWithOut();
const appStore = useAppStoreWithOut();
const { getOpenPageLoading } = useTransitionSetting();
router.beforeEach(async (to) => {
if (!userStore.getAccessToken) {
return true;
}
if (to.meta.loaded) {
return true;
}
if (!userStore.getAccessToken) return true;
if (to.meta.loaded) return true;
if (unref(getOpenPageLoading)) {
appStore.setPageLoadingAction(true);
await appStore.setPageLoadingAction(true);
return true;
}
return true;
});
router.afterEach(async () => {
if (unref(getOpenPageLoading)) {
// TODO 寻找更好的方法
// 定时器模拟加载时间,防止闪烁过快,
// 定时器模拟加载时间,防止闪烁过快 TODO 寻找更好的方法
setTimeout(() => {
appStore.setPageLoading(false);
}, 220);
@ -79,7 +71,7 @@ function createPageLoadingGuard(router: Router) { @@ -79,7 +71,7 @@ function createPageLoadingGuard(router: Router) {
});
}
/** 路由切换时关闭当前页面完成请求的接口 */
/** 处理路由切换时关闭当前页面正在发生请求的axios */
function createHttpGuard(router: Router) {
const { removeAllHttpPending } = projectSetting;
let axiosCanceler: Nullable<AxiosCanceler>;
@ -93,11 +85,9 @@ function createHttpGuard(router: Router) { @@ -93,11 +85,9 @@ function createHttpGuard(router: Router) {
});
}
/** 路由开关回到顶部 */
/** 处理路由切换时自动滚动到顶部 */
function createScrollGuard(router: Router) {
const isHash = (href: string) => {
return /^#/.test(href);
};
const isHash = (href: string) => /^#/.test(href);
const body = document.body;
router.afterEach(async (to) => {
// 滚动顶部
@ -106,7 +96,7 @@ function createScrollGuard(router: Router) { @@ -106,7 +96,7 @@ function createScrollGuard(router: Router) {
});
}
/** 用于在路由切换时关闭消息实例 */
/** 处理路由切换时关闭当前页面正在提示的消息实例 */
export function createMessageGuard(router: Router) {
const { closeMessageOnSwitch } = projectSetting;
router.beforeEach(async () => {
@ -116,18 +106,17 @@ export function createMessageGuard(router: Router) { @@ -116,18 +106,17 @@ export function createMessageGuard(router: Router) {
notification.destroy();
}
} catch (error) {
warn('message guard error:' + error);
warn('消息关闭错误:' + error);
}
return true;
});
}
/** 处理发生请求时显示请求进度条 */
export function createProgressGuard(router: Router) {
const { getOpenNProgress } = useTransitionSetting();
router.beforeEach(async (to) => {
if (to.meta.loaded) {
return true;
}
if (to.meta.loaded) return true;
unref(getOpenNProgress) && nProgress.start();
return true;
});

31
kicc-ui/src/router/guard/paramMenuGuard.ts

@ -1,31 +0,0 @@ @@ -1,31 +0,0 @@
/**
* @program: kicc-ui
* @description:
* ,
* @author: entfrm开发团队-
* @create: 2022/4/9
*/
import type { Router } from 'vue-router';
import { configureDynamicParamsMenu } from '../helper/menuHelper';
import { Menu } from '../types';
import { usePermissionStoreWithOut } from '/@/store/modules/permission';
export function createParamMenuGuard(router: Router) {
const permissionStore = usePermissionStoreWithOut();
router.beforeEach(async (to, _, next) => {
// 过滤无名路由
if (!to.name) {
next();
return;
}
// 检测是否菜单已建立
if (!permissionStore.getIsDynamicAddedRoute) {
next();
return;
}
const menus: Menu[] = permissionStore.getMenuList;
menus.forEach((item) => configureDynamicParamsMenu(item, to.params));
next();
});
}

59
kicc-ui/src/router/guard/permissionGuard.ts

@ -20,57 +20,36 @@ export function createPermissionGuard(router: Router) { @@ -20,57 +20,36 @@ export function createPermissionGuard(router: Router) {
const userStore = useUserStoreWithOut();
const permissionStore = usePermissionStoreWithOut();
router.beforeEach(async (to, from, next) => {
// 首次登录进来进行跳转页面
if (from.path === ROOT_HOME && to.path === PageEnum.BASE_HOME && userStore.getUserInfo.homePath !== PageEnum.BASE_HOME) {
next(userStore.getUserInfo.homePath);
return;
}
// 白名单可直接进入
if (whitePathList.includes(to.path as PageEnum)) {
next();
return;
}
// 用户个性化设置根路由调整地址(首页地址),如果能进去说明以及获取了token不需要进行权限校验
if (from.path === ROOT_HOME && to.path === PageEnum.BASE_HOME && userStore.getUserInfo.homePath !== PageEnum.BASE_HOME)
return next(userStore.getUserInfo.homePath!);
// 放过白名单路由
if (whitePathList.includes(to.path as PageEnum))
return next();
// 校验token权限信息
const token = userStore.getAccessToken;
// 令牌不存在
if (!token) {
// 您可以在未经许可的情况下访问,您需要将路由meta.ignoreAuth设置为true
if (to.meta.ignoreAuth) {
next();
return;
}
// 是否忽略权限验证,需要将路由ignoreAuth设置true
if (to.meta.ignoreAuth) return next();
// 重定向登录页面
const redirectData: { path: string; replace: boolean; query?: Recordable<string> } = {
path: LOGIN_PATH,
replace: true,
};
const redirectLogin: { path: string; replace: boolean; query?: Recordable<string>; } = { path: LOGIN_PATH, replace: true };
// 补充拦截路由信息
if (to.path) {
redirectData.query = {
...redirectData.query,
redirectLogin.query = {
...redirectLogin.query,
redirect: to.path,
};
}
next(redirectData);
return;
}
// 处理完登录后未找到页面直接跳转到404页面
if (from.path === LOGIN_PATH && to.name === PAGE_NOT_FOUND_NAME && to.fullPath !== (userStore.getUserInfo.homePath || PageEnum.BASE_HOME)) {
next(userStore.getUserInfo.homePath || PageEnum.BASE_HOME);
console.log({ from, to });
return;
}
if (permissionStore.getIsDynamicAddedRoute) {
next();
return;
return next(redirectLogin);
}
// 检测是否构建完路由与菜单
if (permissionStore.getIsDynamicAddedRoute) return next();
// 构建路由与菜单
const routes = await permissionStore.buildRoutesAction();
routes.forEach((route) => {
router.addRoute(route as unknown as RouteRecordRaw);
});
routes.forEach((route) => router.addRoute(route as unknown as RouteRecordRaw));
permissionStore.setDynamicAddedRoute(true);
// 动态添加路由后,此处应当重定向到fullPath,否则会加载404页面内容,添加query以免丢失
if (to.name === PAGE_NOT_FOUND_NAME) {
// 动态添加路由后,此处应当重定向到fullPath,否则会加载404页面内容
// fix: 添加query以免丢失
next({ path: to.fullPath, replace: true, query: to.query });
} else {
const redirectPath = (from.query.redirect || to.path) as string;

11
kicc-ui/src/router/guard/stateGuard.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
/**
* @program: kicc-ui
* @description:
*
* @description:
* ,
* @author: entfrm开发团队-
* @create: 2022/4/9
*/
@ -16,17 +16,18 @@ import { removeTabChangeListener } from '/@/logics/mitt/routeChange'; @@ -16,17 +16,18 @@ import { removeTabChangeListener } from '/@/logics/mitt/routeChange';
export function createStateGuard(router: Router) {
router.afterEach((to) => {
// 只需进入登录页面,清除认证信息即可
if (to.path === PageEnum.BASE_LOGIN) {
const tabStore = useMultipleTabStore();
const userStore = useUserStore();
const appStore = useAppStore();
const permissionStore = usePermissionStore();
// 只需进入登录页面,清除认证信息即可
if (to.path === PageEnum.BASE_LOGIN) {
appStore.resetAllState();
appStore.resetAllState().then(()=>{
permissionStore.resetState();
tabStore.resetState();
userStore.resetState();
removeTabChangeListener();
});
}
});
}

73
kicc-ui/src/router/helper/menuHelper.ts

@ -11,50 +11,32 @@ import type { Menu, AppRouteRecordRaw } from '/@/router/types'; @@ -11,50 +11,32 @@ import type { Menu, AppRouteRecordRaw } from '/@/router/types';
import { findPath, treeMap } from '/@/utils/helper/treeHelper';
import { cloneDeep } from 'lodash-es';
import { isUrl } from '/@/utils/is';
import { RouteParams } from 'vue-router';
import { toRaw } from 'vue';
import {usePermissionStore} from "/@/store/modules/permission";
/** 获取当前菜单的所有父菜单路径 */
export function getAllParentPath<T = Recordable>(treeData: T[], path: string) {
const menuList = findPath(treeData, (n) => n.path === path) as Menu[];
return (menuList || []).map((item) => item.path);
}
function joinParentPath(menus: Menu[], parentPath = '') {
/** 构建嵌套路由,具体详情参考:https://next.router.vuejs.org/guide/essentials/nested-routes.html */
function joinNestedRoute(menus: Menu[], parentPath = '') {
for (let index = 0; index < menus.length; index++) {
const menu = menus[index];
// https://next.router.vuejs.org/guide/essentials/nested-routes.html
// 请注意,以 开头的嵌套路径将被视为根路径
// 这允许您利用组件嵌套,而无需使用嵌套 URL
if (!(menu.path.startsWith('/') || isUrl(menu.path))) {
// 路径不以 开头,也不是 url,加入父路径
menu.path = `${parentPath}/${menu.path}`;
}
if (menu?.children?.length) {
joinParentPath(menu.children, menu.meta?.hidePathForChildren ? parentPath : menu.path);
}
// 请注意,以/开头的嵌套路径将被视为根路径与外嵌网页url不允许加入嵌套路由
if (!(menu.path.startsWith('/') || isUrl(menu.path))) menu.path = `${parentPath}/${menu.path}`;
// 递归处理子路由的拼接
if (menu?.children?.length) joinNestedRoute(menu.children, menu.meta?.hidePathForChildren ? parentPath : menu.path);
}
}
export function transformRouteToMenu(routeModList: AppRouteModule[], routerMapping = false) {
const cloneRouteModList = cloneDeep(routeModList);
const routeList: AppRouteRecordRaw[] = [];
cloneRouteModList.forEach((item) => {
if (routerMapping && item.meta.hideChildrenInMenu && typeof item.redirect === 'string') {
item.path = item.redirect;
}
if (item.meta?.single) {
const realItem = item?.children?.[0];
realItem && routeList.push(realItem);
} else {
routeList.push(item);
}
});
/** 将路由对象变成菜单对象 */
export function transformRouteToMenu(routeModList: AppRouteModule[]) {
const routeList: AppRouteRecordRaw[] = cloneDeep(routeModList);
// 提取树指定的树形结构
const list = treeMap(routeList, {
conversion: (node: AppRouteRecordRaw) => {
const { meta: { title, hideMenu = false } = {} } = node;
return {
...(node.meta || {}),
meta: node.meta,
@ -63,32 +45,10 @@ export function transformRouteToMenu(routeModList: AppRouteModule[], routerMappi @@ -63,32 +45,10 @@ export function transformRouteToMenu(routeModList: AppRouteModule[], routerMappi
path: node.path,
...(node.redirect ? { redirect: node.redirect } : {}),
};
},
});
joinParentPath(list);
return cloneDeep(list);
}
/** 带有给定参数的配置菜单 */
const menuParamRegex = /(?::)([\s\S]+?)((?=\/)|$)/g;
export function configureDynamicParamsMenu(menu: Menu, params: RouteParams) {
const { path, paramPath } = toRaw(menu);
let realPath = paramPath ? paramPath : path;
const matchArr = realPath.match(menuParamRegex);
matchArr?.forEach((it) => {
const realIt = it.substr(1);
if (params[realIt]) {
realPath = realPath.replace(`:${realIt}`, params[realIt] as string);
}
});
// 保存原始参数路径
if (!paramPath && matchArr && matchArr.length > 0) {
menu.paramPath = path;
}
menu.path = realPath;
// children
menu.children?.forEach((item) => configureDynamicParamsMenu(item, params));
joinNestedRoute(list);
return list;
}
/** 异步获取后台菜单数据 */
@ -97,20 +57,21 @@ export const getMenus = async (): Promise<Menu[]> => { @@ -97,20 +57,21 @@ export const getMenus = async (): Promise<Menu[]> => {
return permissionStore.getMenuList.filter((item) => !item.hideMenu);
};
/** 获取当前菜单父级菜单路径 */
export async function getCurrentParentPath(currentPath: string) {
const menus = await getMenus();
const allParentPath = await getAllParentPath(menus, currentPath);
const allParentPath = getAllParentPath(menus, currentPath);
return allParentPath?.[0];
}
/** 获取一级菜单删除子项 */
/** 获取一级菜单,删除子项 */
export async function getShallowMenus(): Promise<Menu[]> {
const menus = await getMenus();
const shallowMenuList = menus.map((item) => ({ ...item, children: undefined }));
return shallowMenuList;
}
/** 获取菜单的子 */
/** 获取一级菜单的子集菜单 */
export async function getChildrenMenus(parentPath: string) {
const menus = await getMenus();
const parent = menus.find((item) => item.path === parentPath);

65
kicc-ui/src/router/helper/routeHelper.ts

@ -6,42 +6,22 @@ @@ -6,42 +6,22 @@
* @create: 2022/4/9
*/
import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types';
import type { AppRouteModule } from '/@/router/types';
import { getParentLayout, LAYOUT } from '/@/router/constant';
import { cloneDeep } from 'lodash-es';
import { warn } from '/@/utils/log';
const IFRAME = () => import('/@/views/sys/iframe/FrameBlank.vue');
const LayoutMap = new Map<string, () => Promise<typeof import('*.vue')>>();
/** 布局组件Map */
const LayoutMap = new Map<string, () => Promise<typeof import('*.vue')>>();
LayoutMap.set('LAYOUT', LAYOUT);
LayoutMap.set('IFRAME', IFRAME);
/** 性能优化,避免多次加载views下的组件 */
let dynamicViewsModules: Record<string, () => Promise<Recordable>>;
function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}');
if (!routes) return;
routes.forEach((item) => {
if (!item.component && item.meta?.frameSrc) {
item.component = 'IFRAME';
}
const { component, name } = item;
const { children } = item;
if (component) {
const layoutFound = LayoutMap.get(component as string);
if (layoutFound) {
item.component = layoutFound;
} else {
item.component = dynamicImport(dynamicViewsModules, component as string);
}
} else if (name) {
item.component = getParentLayout();
}
children && asyncImportRoute(children);
});
}
function dynamicImport(dynamicViewsModules: Record<string, () => Promise<Recordable>>, component: string) {
/** 查找导入的views目录下的组件 */
function findImportComponent(dynamicViewsModules: Record<string, () => Promise<Recordable>>, component: string) {
const keys = Object.keys(dynamicViewsModules);
// 检测同一个目录下是否存在多个名字相同的组件
const matchKeys = keys.filter((key) => {
let k = key.replace('../../views', '');
const lastIndex = k.lastIndexOf('.');
@ -53,30 +33,29 @@ function dynamicImport(dynamicViewsModules: Record<string, () => Promise<Recorda @@ -53,30 +33,29 @@ function dynamicImport(dynamicViewsModules: Record<string, () => Promise<Recorda
return dynamicViewsModules[matchKey];
}
if (matchKeys?.length > 1) {
warn('Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure');
warn('请不要创建".vue"和".TSX"具有相同文件的文件名称在同一层次结构目录中在视图文件夹下,这将导致动态引入失败!');
return;
}
}
/** 将菜单对象变成路由对象 */
export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] {
routeList.forEach((route) => {
const component = route.component as string;
export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]) {
dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}');
routeList.forEach((item) => {
if (!item.component && item.meta?.frameSrc) {
item.component = 'IFRAME';
}
const { component, name, children } = item;
if (component) {
if (component.toUpperCase() === 'LAYOUT') {
route.component = LayoutMap.get(component.toUpperCase());
const layoutFound = LayoutMap.get(component as string);
if (layoutFound) {
item.component = layoutFound;
} else {
route.children = [cloneDeep(route)];
route.component = LAYOUT;
route.name = `${route.name}Parent`;
route.path = '';
const meta = route.meta || {};
meta.single = true;
meta.affix = false;
route.meta = meta;
item.component = findImportComponent(dynamicViewsModules, component as string);
}
} else if (name) {
item.component = getParentLayout();
}
route.children && asyncImportRoute(route.children);
children && transformObjToRoute(children);
});
return routeList as unknown as T[];
}

3
kicc-ui/src/router/types.ts

@ -30,9 +30,6 @@ export interface Menu { @@ -30,9 +30,6 @@ export interface Menu {
path: string;
// 路径包含参数,自动分配
paramPath?: string;
disabled?: boolean;
children?: Menu[];

2
kicc-ui/src/store/modules/permission.ts

@ -112,7 +112,7 @@ export const usePermissionStore = defineStore({ @@ -112,7 +112,7 @@ export const usePermissionStore = defineStore({
return (await getMenuList().then(data=> {
let routeList = <AppRouteRecordRaw[]>data;
// 将菜单对象变成路由对象
routeList = transformObjToRoute(routeList);
transformObjToRoute(routeList);
const menuList = transformRouteToMenu(routeList);
this.setMenuList(menuList);
// 过滤忽略路由配置项,只构建菜单不构建路由

2
kicc-ui/types/vue-router.d.ts vendored

@ -29,8 +29,6 @@ declare module 'vue-router' { @@ -29,8 +29,6 @@ declare module 'vue-router' {
hideChildrenInMenu?: boolean;
// 承载参数
carryParam?: boolean;
// 在内部用于标记单级菜单
single?: boolean;
// 当前活动菜单
currentActiveMenu?: string;
// 从不显示在选项卡中

Loading…
Cancel
Save