feat: 针对白板用户的权限优化

This commit is contained in:
rubyliu 2024-01-27 15:41:34 +08:00 committed by Craftsman
parent b63cb426a5
commit efbafded61
18 changed files with 139 additions and 33 deletions

View File

@ -1,3 +1,3 @@
export const ProjectListUrl = '/project/list/options'; // 项目列表
export const ProjectSwitchUrl = '/project/switch'; // 切换项目
export const projectModuleInfoUrl = '/project/get/'; // 获取项目模块信息
export const projectModuleInfoUrl = '/system/get/'; // 获取项目模块信息

View File

@ -153,8 +153,12 @@
async function initProjects() {
try {
const res = await getProjectList(appStore.getCurrentOrgId);
projectList.value = res;
if (appStore.currentOrgId) {
const res = await getProjectList(appStore.getCurrentOrgId);
projectList.value = res;
} else {
projectList.value = [];
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);

View File

@ -1,7 +1,7 @@
import { RouteLocationNormalized, RouteRecordRaw } from 'vue-router';
import { includes } from 'lodash-es';
import { hasAnyPermission, hasFirstMenuPermission } from '@/utils/permission';
import { hasAnyPermission, topLevelMenuHasPermission } from '@/utils/permission';
const firstLevelMenu = ['workstation', 'testPlan', 'bugManagement', 'caseManagement', 'apiTest', 'uiTest', 'loadTest'];
@ -18,8 +18,8 @@ export default function usePermission() {
*/
accessRouter(route: RouteLocationNormalized | RouteRecordRaw) {
if (includes(firstLevelMenu, route.name)) {
// 一级菜单
return hasFirstMenuPermission(route.name as string);
// 一级菜单: 创建项目时 被勾选的模块
return topLevelMenuHasPermission(route);
}
return (
route.meta?.requiresAuth === false ||

View File

@ -2,6 +2,7 @@
export const WHITE_LIST = [
{ name: 'notFound', path: '/notFound', children: [] },
{ name: 'invite', path: '/invite', children: [] },
{ name: 'index', path: '/index', children: [] },
];
// 左侧菜单底部对齐的菜单数组数组项为一级路由的name
@ -19,9 +20,12 @@ export const REDIRECT_ROUTE_NAME = 'Redirect';
export const DEFAULT_ROUTE_NAME = 'workbench';
// 无资源/权限路由
export const NO_RESOURCE_ROUTE_NAME = 'noResource';
export const NO_RESOURCE_ROUTE_NAME = 'no-resource';
// 无项目路由
export const NO_PROJECT_ROUTE_NAME = 'noProject';
export const NO_PROJECT_ROUTE_NAME = 'no-project';
// 白板用户首页
export const WHITEBOARD_INDEX = 'index';
export const WHITE_LIST_NAME = WHITE_LIST.map((el) => el.name);

View File

@ -3,7 +3,7 @@ import { createRouter, createWebHashHistory } from 'vue-router';
import 'nprogress/nprogress.css';
import createRouteGuard from './guard';
import appRoutes from './routes';
import { INVITE_ROUTE, NO_PROJECT, NO_RESOURCE, NOT_FOUND_ROUTE, REDIRECT_MAIN } from './routes/base';
import { INDEX_ROUTE, INVITE_ROUTE, NO_PROJECT, NO_RESOURCE, NOT_FOUND_ROUTE, REDIRECT_MAIN } from './routes/base';
import NProgress from 'nprogress'; // progress bar
NProgress.configure({ showSpinner: false }); // NProgress Configuration
@ -29,6 +29,7 @@ const router = createRouter({
INVITE_ROUTE,
NO_PROJECT,
NO_RESOURCE,
INDEX_ROUTE,
],
scrollBehavior() {
return { top: 0 };

View File

@ -5,6 +5,17 @@ import type { RouteRecordRaw } from 'vue-router';
export const DEFAULT_LAYOUT = () => import('@/layout/default-layout.vue');
export const PAGE_LAYOUT = () => import('@/layout/page-layout.vue');
export const INDEX_ROUTE: RouteRecordRaw = {
path: '/index',
name: 'metersphereIndex',
component: DEFAULT_LAYOUT,
meta: {
hideInMenu: true,
roles: ['*'],
requiresAuth: true,
},
};
export const REDIRECT_MAIN: RouteRecordRaw = {
path: '/redirect',
name: 'redirectWrapper',

View File

@ -13,6 +13,14 @@ const ApiTest: AppRouteRecordRaw = {
icon: 'icon-icon_api-test-filled',
order: 4,
hideChildrenInMenu: true,
roles: [
'PROJECT_API_DEBUG:READ',
'PROJECT_API_DEFINITION:READ',
'PROJECT_API_DEFINITION_CASE:READ',
'PROJECT_API_DEFINITION_MOCK:READ',
'PROJECT_API_SCENARIO:READ',
'PROJECT_API_REPORT:READ',
],
},
children: [
{
@ -21,7 +29,7 @@ const ApiTest: AppRouteRecordRaw = {
component: () => import('@/views/api-test/debug/index.vue'),
meta: {
locale: 'menu.apiTest.debug',
roles: ['*'],
roles: ['PROJECT_API_DEBUG:READ'],
isTopMenu: true,
},
},

View File

@ -12,6 +12,7 @@ const BugManagement: AppRouteRecordRaw = {
locale: 'menu.bugManagement',
icon: 'icon-icon_defect',
order: 2,
roles: ['PROJECT_BUG:READ+ADD'],
hideChildrenInMenu: true,
},
children: [

View File

@ -13,6 +13,7 @@ const CaseManagement: AppRouteRecordRaw = {
icon: 'icon-icon_functional_testing',
order: 3,
hideChildrenInMenu: true,
roles: ['FUNCTIONAL_CASE:READ', 'CASE_REVIEW:READ'],
},
children: [
// 功能用例
@ -33,7 +34,7 @@ const CaseManagement: AppRouteRecordRaw = {
component: () => import('@/views/case-management/caseManagementFeature/components/caseDetail.vue'),
meta: {
locale: 'menu.caseManagement.featureCaseDetail',
roles: ['*'],
roles: ['FUNCTIONAL_CASE:READ+EDIT'],
breadcrumbs: [
{
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE,
@ -54,7 +55,7 @@ const CaseManagement: AppRouteRecordRaw = {
component: () => import('@/views/case-management/caseManagementFeature/components/createSuccess.vue'),
meta: {
locale: 'menu.caseManagement.featureCaseCreateSuccess',
roles: ['*'],
roles: ['FUNCTIONAL_CASE:READ+EDIT'],
},
},
// 功能用例回收站
@ -64,7 +65,7 @@ const CaseManagement: AppRouteRecordRaw = {
component: () => import('@/views/case-management/caseManagementFeature/components/recycleCaseTable.vue'),
meta: {
locale: 'menu.caseManagement.featureCaseRecycle',
roles: ['*'],
roles: ['FUNCTIONAL_CASE:READ'],
breadcrumbs: [
{
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE,
@ -84,7 +85,7 @@ const CaseManagement: AppRouteRecordRaw = {
component: () => import('@/views/case-management/caseReview/index.vue'),
meta: {
locale: 'menu.caseManagement.caseManagementReview',
roles: ['*'],
roles: ['CASE_REVIEW:READ'],
isTopMenu: true,
},
},
@ -95,7 +96,7 @@ const CaseManagement: AppRouteRecordRaw = {
component: () => import('@/views/case-management/caseReview/create.vue'),
meta: {
locale: 'menu.caseManagement.caseManagementReviewCreate',
roles: ['*'],
roles: ['CASE_REVIEW:READ+ADD'],
breadcrumbs: [
{
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW,
@ -117,7 +118,7 @@ const CaseManagement: AppRouteRecordRaw = {
component: () => import('@/views/case-management/caseReview/detail.vue'),
meta: {
locale: 'menu.caseManagement.caseManagementReviewDetail',
roles: ['*'],
roles: ['CASE_REVIEW:READ'],
breadcrumbs: [
{
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW,
@ -137,7 +138,7 @@ const CaseManagement: AppRouteRecordRaw = {
component: () => import('@/views/case-management/caseReview/caseDetail.vue'),
meta: {
locale: 'menu.caseManagement.caseManagementCaseDetail',
roles: ['*'],
roles: ['CASE_REVIEW:READ'],
breadcrumbs: [
{
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW,

View File

@ -13,6 +13,7 @@ const PerformanceTest: AppRouteRecordRaw = {
icon: 'icon-icon_performance-test-filled',
order: 6,
hideChildrenInMenu: true,
roles: ['LOAD_TEST:READ'],
},
children: [
{
@ -20,7 +21,7 @@ const PerformanceTest: AppRouteRecordRaw = {
name: 'performanceTestIndex',
component: () => import('@/views/performance-test/index.vue'),
meta: {
roles: ['*'],
roles: ['LOAD_TEST:READ'],
},
},
],

View File

@ -13,6 +13,15 @@ const ProjectManagement: AppRouteRecordRaw = {
icon: 'icon-icon_project-settings-filled',
order: 7,
hideChildrenInMenu: true,
roles: [
'PROJECT_USER:READ',
'PROJECT_TEMPLATE:READ',
'PROJECT_FILE_MANAGEMENT:READ',
'PROJECT_MESSAGE:READ',
'PROJECT_CUSTOM_FUNCTION:READ',
'PROJECT_LOG:READ',
'PROJECT_ENVIRONMENT:READ',
],
},
children: [
// 项目与权限
@ -105,7 +114,7 @@ const ProjectManagement: AppRouteRecordRaw = {
component: () => import('@/views/project-management/template/components/projectFieldSetting.vue'),
meta: {
locale: 'menu.settings.organization.templateFieldSetting',
roles: ['*'],
roles: ['PROJECT_TEMPLATE:READ'],
breadcrumbs: [
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE,
@ -126,7 +135,7 @@ const ProjectManagement: AppRouteRecordRaw = {
component: () => import('@/views/project-management/template/components/templateManagement.vue'),
meta: {
locale: 'menu.settings.organization.templateManagement',
roles: ['*'],
roles: ['PROJECT_TEMPLATE:READ'],
breadcrumbs: [
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE,
@ -147,7 +156,7 @@ const ProjectManagement: AppRouteRecordRaw = {
component: () => import('@/views/project-management/template/components/proTemplateDetail.vue'),
meta: {
locale: 'menu.settings.organization.templateManagementDetail',
roles: ['*'],
roles: ['PROJECT_TEMPLATE:READ+ADD'],
breadcrumbs: [
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE,
@ -207,6 +216,7 @@ const ProjectManagement: AppRouteRecordRaw = {
isTopMenu: true,
},
},
// 消息管理编辑
{
path: 'messageManagementEdit',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_MESSAGE_MANAGEMENT_EDIT,
@ -246,7 +256,7 @@ const ProjectManagement: AppRouteRecordRaw = {
component: () => import('@/views/project-management/log/index.vue'),
meta: {
locale: 'menu.projectManagement.log',
roles: ['*'],
roles: ['PROJECT_LOG:READ'],
isTopMenu: true,
},
},

View File

@ -15,6 +15,20 @@ const Setting: AppRouteRecordRaw = {
locale: 'menu.settings',
icon: 'icon-a-icon_system_settings',
order: 8,
roles: [
'SYSTEM_USER:READ',
'SYSTEM_USER_ROLE:READ',
'SYSTEM_ORGANIZATION_PROJECT:READ',
'SYSTEM_PARAMETER_SETTING_BASE:READ',
'SYSTEM_TEST_RESOURCE_POOL:READ',
'SYSTEM_AUTH:READ',
'SYSTEM_PLUGIN:READ',
'ORGANIZATION_MEMBER:READ',
'ORGANIZATION_USER_ROLE:READ',
'ORGANIZATION_PROJECT:READ',
'SYSTEM_SERVICE_INTEGRATION:READ',
'ORGANIZATION_TEMPLATE:READ',
],
},
children: [
{
@ -24,7 +38,15 @@ const Setting: AppRouteRecordRaw = {
component: null,
meta: {
locale: 'menu.settings.system',
roles: ['*'],
roles: [
'SYSTEM_USER:READ',
'SYSTEM_USER_ROLE:READ',
'SYSTEM_ORGANIZATION_PROJECT:READ',
'SYSTEM_PARAMETER_SETTING_BASE:READ',
'SYSTEM_TEST_RESOURCE_POOL:READ',
'SYSTEM_AUTH:READ',
'SYSTEM_PLUGIN:READ',
],
hideChildrenInMenu: true,
},
children: [
@ -115,7 +137,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/system/log/index.vue'),
meta: {
locale: 'menu.settings.system.log',
roles: ['*'],
roles: ['SYSTEM_LOG:READ'],
isTopMenu: true,
},
},
@ -138,7 +160,13 @@ const Setting: AppRouteRecordRaw = {
component: null,
meta: {
locale: 'menu.settings.organization',
roles: ['*'],
roles: [
'ORGANIZATION_MEMBER:READ',
'ORGANIZATION_USER_ROLE:READ',
'ORGANIZATION_PROJECT:READ',
'SYSTEM_SERVICE_INTEGRATION:READ',
'ORGANIZATION_TEMPLATE:READ',
],
hideChildrenInMenu: true,
},
children: [

View File

@ -13,6 +13,7 @@ const TestPlan: AppRouteRecordRaw = {
icon: 'icon-icon_test-tracking_filled',
order: 1,
hideChildrenInMenu: true,
roles: ['TEST_PLAN:READ'],
},
children: [
// 测试计划
@ -22,7 +23,7 @@ const TestPlan: AppRouteRecordRaw = {
component: () => import('@/views/test-plan/testPlan/index.vue'),
meta: {
locale: 'menu.testPlan',
roles: ['PROJECT_TEST_PLAN:READ'],
roles: ['TEST_PLAN:READ'],
isTopMenu: true,
},
},

View File

@ -13,6 +13,7 @@ const UiTest: AppRouteRecordRaw = {
icon: 'icon-icon_ui-test-filled',
order: 5,
hideChildrenInMenu: true,
roles: ['UI_INDEX:READ'],
},
children: [
{
@ -20,7 +21,7 @@ const UiTest: AppRouteRecordRaw = {
name: 'uiTestIndex',
component: () => import('@/views/ui-test/index.vue'),
meta: {
roles: ['*'],
roles: ['UI_INDEX:READ'],
},
},
],

View File

@ -13,6 +13,7 @@ const Workbench: AppRouteRecordRaw = {
icon: 'icon-icon_pc_filled',
order: 0,
hideChildrenInMenu: true,
roles: ['WORKSTATION_INDEX:READ'],
},
children: [
{
@ -20,7 +21,7 @@ const Workbench: AppRouteRecordRaw = {
name: 'workbenchIndex',
component: () => import('@/views/workbench/index.vue'),
meta: {
roles: ['*'],
roles: ['WORKSTATION_INDEX:READ'],
},
},
],

View File

@ -1,6 +1,22 @@
import { RouteLocationNormalized, RouteRecordNormalized, RouteRecordRaw, useRouter } from 'vue-router';
import { includes } from 'lodash-es';
import { INDEX_ROUTE } from '@/router/routes/base';
import { useAppStore, useUserStore } from '@/store';
import { SystemScopeType, UserRole, UserRolePermissions } from '@/store/modules/user/types';
const firstLevelMenuNames = [
'workstation',
'testPlan',
'bugManagement',
'caseManagement',
'apiTest',
'uiTest',
'loadTest',
'setting',
'projectManagement',
];
export function hasPermission(permission: string, typeList: string[]) {
const userStore = useUserStore();
if (userStore.isAdmin) {
@ -8,6 +24,10 @@ export function hasPermission(permission: string, typeList: string[]) {
}
const { projectPermissions, orgPermissions, systemPermissions } = userStore.currentRole;
if (projectPermissions.length === 0 && orgPermissions.length === 0 && systemPermissions.length === 0) {
return false;
}
if (typeList.includes('PROJECT') && projectPermissions.includes(permission)) {
return true;
}
@ -58,13 +78,24 @@ export function composePermissions(userRolePermissions: UserRolePermissions[], t
}
// 判断当前一级菜单是否有权限
export function hasFirstMenuPermission(menuName: string) {
export function topLevelMenuHasPermission(route: RouteLocationNormalized | RouteRecordRaw) {
const userStore = useUserStore();
const appStore = useAppStore();
if (userStore.isAdmin || menuName === 'setting' || menuName === 'projectManagement') {
if (userStore.isAdmin) {
// 如果是超级管理员,或者是系统设置菜单,或者是项目菜单,都有权限
return true;
}
const { currentMenuConfig } = appStore;
return currentMenuConfig.includes(menuName);
return currentMenuConfig.includes(route.name as string) && hasAnyPermission(route.meta?.roles || []);
}
// 有权限的第一个路由名如果没有找到则返回IndexRoute
export function getFirstRouteNameByPermission(routerList: RouteRecordNormalized[]) {
const currentRoute = routerList
.filter((item) => includes(firstLevelMenuNames, item.name))
.sort((a, b) => {
return (a.meta.order || 0) - (b.meta.order || 0);
})
.find((item) => hasAnyPermission(item.meta.roles || []));
return currentRoute?.name || INDEX_ROUTE.name;
}

View File

@ -32,6 +32,7 @@
import { getProjectList, switchProject } from '@/api/modules/project-management/project';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore, useUserStore } from '@/store';
import { getFirstRouteNameByPermission } from '@/utils/permission';
import { SelectValue } from '@/models/projectManagement/menuManagement';
import type { ProjectListItem } from '@/models/setting/project';
@ -68,7 +69,7 @@
console.log(error);
} finally {
router.replace({
path: route.path,
name: getFirstRouteNameByPermission(router.getRoutes()),
query: {
...route.query,
organizationId: appStore.currentOrgId,

View File

@ -63,6 +63,7 @@
import { useAppStore, useUserStore } from '@/store';
import { encrypted } from '@/utils';
import { setLoginExpires } from '@/utils/auth';
import { getFirstRouteNameByPermission } from '@/utils/permission';
import type { LoginData } from '@/models/user';
import { WorkbenchRouteEnum } from '@/enums/routeEnum';
@ -125,9 +126,10 @@
loginConfig.value.username = rememberPassword ? username : '';
loginConfig.value.password = rememberPassword ? password : '';
const { redirect, ...othersQuery } = router.currentRoute.value.query;
const currentRouteName = getFirstRouteNameByPermission(router.getRoutes());
setLoginExpires();
router.push({
name: (redirect as string) || WorkbenchRouteEnum.WORKBENCH,
name: (redirect as string) || currentRouteName,
query: {
...othersQuery,
organizationId: appStore.currentOrgId,