diff --git a/frontend/src/api/requrls/project-management/project.ts b/frontend/src/api/requrls/project-management/project.ts index 9ea894dff9..2e80b59585 100644 --- a/frontend/src/api/requrls/project-management/project.ts +++ b/frontend/src/api/requrls/project-management/project.ts @@ -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/'; // 获取项目模块信息 diff --git a/frontend/src/components/pure/navbar/index.vue b/frontend/src/components/pure/navbar/index.vue index 7a51d1684e..11c0dcdf49 100644 --- a/frontend/src/components/pure/navbar/index.vue +++ b/frontend/src/components/pure/navbar/index.vue @@ -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); diff --git a/frontend/src/hooks/usePermission.ts b/frontend/src/hooks/usePermission.ts index 29cadbd4ac..1ad5488b57 100644 --- a/frontend/src/hooks/usePermission.ts +++ b/frontend/src/hooks/usePermission.ts @@ -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 || diff --git a/frontend/src/router/constants.ts b/frontend/src/router/constants.ts index 39e0d723f9..1b16c9130c 100644 --- a/frontend/src/router/constants.ts +++ b/frontend/src/router/constants.ts @@ -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); diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index ec83b730d2..1e9313644a 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -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 }; diff --git a/frontend/src/router/routes/base.ts b/frontend/src/router/routes/base.ts index 56757c28a9..4002dc33ee 100644 --- a/frontend/src/router/routes/base.ts +++ b/frontend/src/router/routes/base.ts @@ -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', diff --git a/frontend/src/router/routes/modules/apiTest.ts b/frontend/src/router/routes/modules/apiTest.ts index 6e043e92bf..55ec51805c 100644 --- a/frontend/src/router/routes/modules/apiTest.ts +++ b/frontend/src/router/routes/modules/apiTest.ts @@ -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, }, }, diff --git a/frontend/src/router/routes/modules/bugManagement.ts b/frontend/src/router/routes/modules/bugManagement.ts index 83d73b6249..728900b7b5 100644 --- a/frontend/src/router/routes/modules/bugManagement.ts +++ b/frontend/src/router/routes/modules/bugManagement.ts @@ -12,6 +12,7 @@ const BugManagement: AppRouteRecordRaw = { locale: 'menu.bugManagement', icon: 'icon-icon_defect', order: 2, + roles: ['PROJECT_BUG:READ+ADD'], hideChildrenInMenu: true, }, children: [ diff --git a/frontend/src/router/routes/modules/caseManagement.ts b/frontend/src/router/routes/modules/caseManagement.ts index 7a208c3d50..634b3b4cb6 100644 --- a/frontend/src/router/routes/modules/caseManagement.ts +++ b/frontend/src/router/routes/modules/caseManagement.ts @@ -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, diff --git a/frontend/src/router/routes/modules/performanceTest.ts b/frontend/src/router/routes/modules/performanceTest.ts index 21c0dadb25..0068ec3092 100644 --- a/frontend/src/router/routes/modules/performanceTest.ts +++ b/frontend/src/router/routes/modules/performanceTest.ts @@ -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'], }, }, ], diff --git a/frontend/src/router/routes/modules/projectManagement.ts b/frontend/src/router/routes/modules/projectManagement.ts index 3c5b5ddd2e..66d0532665 100644 --- a/frontend/src/router/routes/modules/projectManagement.ts +++ b/frontend/src/router/routes/modules/projectManagement.ts @@ -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, }, }, diff --git a/frontend/src/router/routes/modules/setting.ts b/frontend/src/router/routes/modules/setting.ts index dcd4e418a0..73ec6002a9 100644 --- a/frontend/src/router/routes/modules/setting.ts +++ b/frontend/src/router/routes/modules/setting.ts @@ -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: [ diff --git a/frontend/src/router/routes/modules/testPlan.ts b/frontend/src/router/routes/modules/testPlan.ts index fac4c8689a..d6cf21ec20 100644 --- a/frontend/src/router/routes/modules/testPlan.ts +++ b/frontend/src/router/routes/modules/testPlan.ts @@ -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, }, }, diff --git a/frontend/src/router/routes/modules/uiTest.ts b/frontend/src/router/routes/modules/uiTest.ts index 220fc7f772..f90713ce01 100644 --- a/frontend/src/router/routes/modules/uiTest.ts +++ b/frontend/src/router/routes/modules/uiTest.ts @@ -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'], }, }, ], diff --git a/frontend/src/router/routes/modules/workbench.ts b/frontend/src/router/routes/modules/workbench.ts index d6f352d22c..2421a50cf4 100644 --- a/frontend/src/router/routes/modules/workbench.ts +++ b/frontend/src/router/routes/modules/workbench.ts @@ -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'], }, }, ], diff --git a/frontend/src/utils/permission.ts b/frontend/src/utils/permission.ts index d1b59f9711..d05725c6ec 100644 --- a/frontend/src/utils/permission.ts +++ b/frontend/src/utils/permission.ts @@ -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; } diff --git a/frontend/src/views/base/no-project/index.vue b/frontend/src/views/base/no-project/index.vue index 00283f848f..6cc953a24e 100644 --- a/frontend/src/views/base/no-project/index.vue +++ b/frontend/src/views/base/no-project/index.vue @@ -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, diff --git a/frontend/src/views/login/components/login-form.vue b/frontend/src/views/login/components/login-form.vue index b3bb59cef1..fbffdacb8f 100644 --- a/frontend/src/views/login/components/login-form.vue +++ b/frontend/src/views/login/components/login-form.vue @@ -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,