feat: add some test case

This commit is contained in:
vben
2024-06-02 15:04:37 +08:00
parent fc423c3657
commit b200ae9997
40 changed files with 1469 additions and 452 deletions

View File

@@ -22,6 +22,7 @@
"typecheck": "vue-tsc --noEmit --skipLibCheck"
},
"dependencies": {
"@vben-core/helpers": "workspace:*",
"@vben-core/preferences": "workspace:*",
"@vben-core/stores": "workspace:*",
"@vben/common-ui": "workspace:*",
@@ -30,6 +31,7 @@
"@vben/icons": "workspace:*",
"@vben/layouts": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/request": "workspace:*",
"@vben/styles": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",

View File

@@ -0,0 +1,139 @@
import { generatorMenus, generatorRoutes } from '@vben-core/helpers';
import { preferences } from '@vben-core/preferences';
import { useAccessStore } from '@vben-core/stores';
import type { RouteLocationNormalized, Router } from 'vue-router';
import { LOGIN_PATH } from '@vben/constants';
import { $t } from '@vben/locales';
import { startProgress, stopProgress } from '@vben/utils';
import { useTitle } from '@vueuse/core';
import { dynamicRoutes } from '@/router/routes';
/**
* 通用守卫配置
* @param router
*/
function configCommonGuard(router: Router) {
// 记录已经加载的页面
const loadedPaths = new Set<string>();
router.beforeEach(async (to) => {
// 页面加载进度条
if (preferences.transition.progress) {
startProgress();
}
to.meta.loaded = loadedPaths.has(to.path);
return true;
});
router.afterEach((to) => {
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
loadedPaths.add(to.path);
// 关闭页面加载进度条
if (preferences.transition.progress) {
stopProgress();
}
// 动态修改标题
if (preferences.app.dynamicTitle) {
const { title } = to.meta;
useTitle(`${$t(title)} - ${preferences.app.name}`);
}
});
}
// 不需要权限的页面白名单
const WHITE_ROUTE_NAMES = new Set<string>([]);
/**
* 跳转登录页面
* @param to
*/
function loginPageMeta(to: RouteLocationNormalized) {
return {
path: LOGIN_PATH,
// 如不需要,直接删除 query
query: { redirect: encodeURIComponent(to.fullPath) },
// 携带当前跳转的页面,登录后重新跳转该页面
replace: true,
};
}
/**
* 权限访问守卫配置
* @param router
*/
function configAccessGuard(router: Router) {
router.beforeEach(async (to, from) => {
const accessStore = useAccessStore();
const accessToken = accessStore.getAccessToken;
// accessToken 检查
if (!accessToken) {
if (to.path === '/') {
return loginPageMeta(to);
}
// 明确声明忽略权限访问权限,则可以访问
if (to.meta.ignoreAccess) {
return true;
}
// 白名单路由列表检查
// TODO: 不是很需要,通过 ignoreAccess 也可以做到,考虑删除
if (WHITE_ROUTE_NAMES.has(to.name as string)) {
return true;
}
// 没有访问权限,跳转登录页面
if (to.fullPath !== LOGIN_PATH) {
return loginPageMeta(to);
}
return to;
}
const accessRoutes = accessStore.getAccessRoutes;
// 是否已经生成过动态路由
if (accessRoutes && accessRoutes.length > 0) {
return true;
}
// 生成路由表
// 当前登录用户拥有的角色标识列表
const userRoles = accessStore.getUserRoles;
const routes = await generatorRoutes(dynamicRoutes, userRoles);
// 动态添加到router实例内
routes.forEach((route) => router.addRoute(route));
const menus = await generatorMenus(routes, router);
// 保存菜单信息和路由信息
accessStore.setAccessMenus(menus);
accessStore.setAccessRoutes(routes);
const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath);
return {
path: redirect,
replace: true,
};
});
}
export { configAccessGuard };
/**
* 项目守卫配置
* @param router
*/
function createRouterGuard(router: Router) {
/** 通用 */
configCommonGuard(router);
/** 权限访问 */
configAccessGuard(router);
}
export { createRouterGuard };

View File

@@ -1,184 +0,0 @@
import type { ExRouteRecordRaw, MenuRecordRaw } from '@vben/types';
import { useAccessStore } from '@vben-core/stores';
import type { RouteRecordRaw, Router } from 'vue-router';
import { LOGIN_PATH } from '@vben/constants';
import { filterTree, mapTree, traverseTreeValues } from '@vben/utils';
import { dynamicRoutes } from '@/router/routes';
// 不需要权限的页面白名单
const WHITE_ROUTE_NAMES = new Set<string>([]);
/**
* 权限访问守卫配置
* @param router
*/
function configAccessGuard(router: Router) {
router.beforeEach(async (to, from) => {
const accessStore = useAccessStore();
const accessToken = accessStore.getAccessToken;
// accessToken 检查
if (!accessToken) {
// 明确声明忽略权限访问权限,则可以访问
if (to.meta.ignoreAccess) {
return true;
}
// 白名单路由列表检查
// TODO: 不是很需要,通过 ignoreAccess 也可以做到,考虑删除
if (WHITE_ROUTE_NAMES.has(to.name as string)) {
return true;
}
// 没有访问权限,跳转登录页面
if (to.fullPath !== LOGIN_PATH) {
return {
path: LOGIN_PATH,
// 如不需要,直接删除 query
query: { redirect: encodeURIComponent(to.fullPath) },
// 携带当前跳转的页面,登录后重新跳转该页面
replace: true,
};
}
return to;
}
const accessRoutes = accessStore.getAccessRoutes;
// 是否已经生成过动态路由
if (accessRoutes && accessRoutes.length > 0) {
return true;
}
// 生成路由表
// 当前登录用户拥有的角色标识列表
const userRoles = accessStore.getUserRoles;
const routes = await generatorRoutes(userRoles);
// 动态添加到router实例内
routes.forEach((route) => router.addRoute(route));
const menus = await generatorMenus(routes, router);
// 保存菜单信息和路由信息
accessStore.setAccessMenus(menus);
accessStore.setAccessRoutes(routes);
const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath);
return {
path: redirect,
replace: true,
};
});
}
/**
* 动态生成路由
*/
async function generatorRoutes(roles: string[]): Promise<RouteRecordRaw[]> {
// 根据角色标识过滤路由表,判断当前用户是否拥有指定权限
return filterTree(dynamicRoutes, (route) => {
return hasVisible(route) && hasAuthority(route, roles);
});
}
/**
* 根据 routes 生成菜单列表
* @param routes
*/
async function generatorMenus(
routes: RouteRecordRaw[],
router: Router,
): Promise<MenuRecordRaw[]> {
// 获取所有router最终的path及name
const finalRoutes = traverseTreeValues(
router.getRoutes(),
({ name, path }) => {
return {
name,
path,
};
},
);
const menus = mapTree<ExRouteRecordRaw, MenuRecordRaw>(routes, (route) => {
// 路由表的路径写法有多种这里从router获取到最终的path并赋值
const matchRoute = finalRoutes.find(
(finalRoute) => finalRoute.name === route.name,
);
// 转换为菜单结构
const path = matchRoute?.path ?? route.path;
const { meta, name: routeName, redirect, children } = route;
const {
badge,
badgeType,
badgeVariants,
hideChildrenInMenu = false,
icon,
orderNo,
target,
title = '',
} = meta || {};
const name = (title || routeName || '') as string;
// 隐藏子菜单
const resultChildren = hideChildrenInMenu
? []
: (children as MenuRecordRaw[]);
// 将菜单的所有父级和父级菜单记录到菜单项内
if (resultChildren && resultChildren.length > 0) {
resultChildren.forEach((child) => {
child.parents = [...(route.parents || []), path];
child.parent = path;
});
}
// 隐藏子菜单
const resultPath = hideChildrenInMenu ? redirect : target || path;
return {
badge,
badgeType,
badgeVariants,
icon,
name,
orderNo,
parent: route.parent,
parents: route.parents,
path: resultPath,
children: resultChildren,
};
});
return menus;
}
/**
* 判断路由是否有权限访问
* @param route
* @param access
*/
function hasAuthority(route: RouteRecordRaw, access: string[]) {
const authority = route.meta?.authority;
if (!authority) {
return true;
}
const authSet = new Set(authority);
return access.some((value) => {
return authSet.has(value);
});
}
/**
* 判断路由是否需要在菜单中显示
* @param route
*/
function hasVisible(route: RouteRecordRaw) {
return !route.meta?.hideInMenu;
}
export { configAccessGuard };

View File

@@ -1,55 +0,0 @@
import { preferences } from '@vben-core/preferences';
import type { Router } from 'vue-router';
import { $t } from '@vben/locales';
import { startProgress, stopProgress } from '@vben/utils';
import { useTitle } from '@vueuse/core';
import { configAccessGuard } from './access';
/**
* 通用守卫配置
* @param router
*/
function configCommonGuard(router: Router) {
// 记录已经加载的页面
const loadedPaths = new Set<string>();
router.beforeEach(async (to) => {
// 页面加载进度条
if (preferences.transition.progress) {
startProgress();
}
to.meta.loaded = loadedPaths.has(to.path);
return true;
});
router.afterEach((to) => {
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
loadedPaths.add(to.path);
// 关闭页面加载进度条
if (preferences.transition.progress) {
stopProgress();
}
// 动态修改标题
if (preferences.app.dynamicTitle) {
const { title } = to.meta;
useTitle(`${$t(title)} - ${preferences.app.name}`);
}
});
}
/**
* 项目守卫配置
* @param router
*/
function createRouteGuard(router: Router) {
/** 通用 */
configCommonGuard(router);
/** 权限访问 */
configAccessGuard(router);
}
export { createRouteGuard };

View File

@@ -3,7 +3,7 @@ import type { RouteRecordName, RouteRecordRaw } from 'vue-router';
import { traverseTreeValues } from '@vben/utils';
import { createRouter, createWebHashHistory } from 'vue-router';
import { createRouteGuard } from './guard';
import { createRouterGuard } from './guard';
import { staticRoutes } from './routes';
/**
@@ -54,6 +54,6 @@ function resetRoutes() {
});
}
// 创建路由守卫
createRouteGuard(router);
createRouterGuard(router);
export { resetRoutes, router };

View File

@@ -1,24 +1,17 @@
import { createPinia, setActivePinia } from 'pinia';
import {
// beforeEach,
describe,
// expect,
it,
} from 'vitest';
import { beforeEach, describe, expect, it } from 'vitest';
// import { useAccessStore } from '../modules/access';
import { useCounterStore } from './example';
describe('useCounterStore', () => {
it('app Name with test', () => {
beforeEach(() => {
setActivePinia(createPinia());
// let referenceStore = usePreferencesStore();
});
// beforeEach(() => {
// referenceStore = usePreferencesStore();
// });
it('count test', () => {
setActivePinia(createPinia());
const counterStore = useCounterStore();
// expect(referenceStore.appName).toBe('vben-admin');
// referenceStore.setAppName('vbenAdmin');
// expect(referenceStore.getAppName).toBe('vbenAdmin');
expect(counterStore.count).toBe(0);
});
});

View File

@@ -9,5 +9,6 @@ export const useCounterStore = defineStore('counter', {
getters: {
double: (state) => state.count * 2,
},
persist: [],
state: () => ({ count: 0 }),
});

View File

@@ -5,8 +5,8 @@ import { useAccessStore } from '@vben-core/stores';
import { getUserInfo, userLogin } from '@/services';
import { AuthenticationLogin } from '@vben/common-ui';
import { useRequest } from '@vben/hooks';
import { $t } from '@vben/locales';
import { useRequest } from '@vben/request';
import { notification } from 'ant-design-vue';
import { computed } from 'vue';
import { useRouter } from 'vue-router';
@@ -35,6 +35,7 @@ const { loading: userInfoLoading, runAsync: runGetUserInfo } = useRequest(
async function handleLogin(values: LoginAndRegisterParams) {
// 异步处理用户登录操作并获取 accessToken
// Asynchronously handle the user login operation and obtain the accessToken
const { accessToken } = await runUserLogin(values);
// 如果成功获取到 accessToken