feat: 新增权限管理&表格样式调整

This commit is contained in:
RubyLiu 2024-01-25 18:24:03 +08:00 committed by 刘瑞斌
parent 7ab276d3b9
commit 802efe9900
26 changed files with 439 additions and 104 deletions

View File

@ -59,7 +59,9 @@
:style="{ width: props.width }" :style="{ width: props.width }"
> >
<ms-button :disabled="!canSave" @click="handleReset">{{ t('system.userGroup.reset') }}</ms-button> <ms-button :disabled="!canSave" @click="handleReset">{{ t('system.userGroup.reset') }}</ms-button>
<a-button :disabled="!canSave" type="primary" @click="handleSave">{{ t('system.userGroup.save') }}</a-button> <a-button v-permission="props.savePermission || []" :disabled="!canSave" type="primary" @click="handleSave">{{
t('system.userGroup.save')
}}</a-button>
</div> </div>
</template> </template>
@ -101,6 +103,7 @@
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
current: CurrentUserGroupItem; current: CurrentUserGroupItem;
savePermission?: string[];
width?: string; width?: string;
showBottom?: boolean; showBottom?: boolean;
scroll?: { scroll?: {

View File

@ -8,7 +8,7 @@
@search="searchData" @search="searchData"
/> />
</div> </div>
<div v-if="showSystem" class="mt-2"> <div v-if="showSystem" v-permission="['SYSTEM_USER_ROLE:READ']" class="mt-2">
<CreateUserGroupPopup <CreateUserGroupPopup
:list="systemUserGroupList" :list="systemUserGroupList"
:visible="systemUserGroupVisible" :visible="systemUserGroupVisible"
@ -36,7 +36,11 @@
{{ t('system.userGroup.systemUserGroup') }} {{ t('system.userGroup.systemUserGroup') }}
</div> </div>
</div> </div>
<MsMoreAction :list="createSystemUGActionItem" @select="handleCreateUG(AuthScopeEnum.SYSTEM)"> <MsMoreAction
v-permission="['SYSTEM_USER_ROLE:READ+ADD']"
:list="createSystemUGActionItem"
@select="handleCreateUG(AuthScopeEnum.SYSTEM)"
>
<icon-plus-circle-fill class="cursor-pointer text-[rgb(var(--primary-7))]" size="20" /> <icon-plus-circle-fill class="cursor-pointer text-[rgb(var(--primary-7))]" size="20" />
</MsMoreAction> </MsMoreAction>
</div> </div>
@ -67,6 +71,7 @@
<div v-if="element.id === currentId && !element.internal" class="flex flex-row items-center gap-[8px]"> <div v-if="element.id === currentId && !element.internal" class="flex flex-row items-center gap-[8px]">
<MsMoreAction <MsMoreAction
v-if="element.type === systemType" v-if="element.type === systemType"
v-permission="['SYSTEM_USER_ROLE:READ+UPDATE']"
:list="addMemberActionItem" :list="addMemberActionItem"
@select="handleAddMember" @select="handleAddMember"
> >
@ -75,7 +80,7 @@
</div> </div>
</MsMoreAction> </MsMoreAction>
<MsMoreAction <MsMoreAction
:list="moreAction" :list="systemMoreAction"
@select="(value) => handleMoreAction(value, element.id, AuthScopeEnum.SYSTEM)" @select="(value) => handleMoreAction(value, element.id, AuthScopeEnum.SYSTEM)"
> >
<div class="icon-button"> <div class="icon-button">
@ -90,7 +95,7 @@
</div> </div>
</Transition> </Transition>
</div> </div>
<div v-if="showOrg" class="mt-2"> <div v-if="showOrg" v-permission="['ORGANIZATION_USER_ROLE:READ']" class="mt-2">
<CreateUserGroupPopup <CreateUserGroupPopup
:list="orgUserGroupList" :list="orgUserGroupList"
:visible="orgUserGroupVisible" :visible="orgUserGroupVisible"
@ -118,7 +123,11 @@
{{ t('system.userGroup.orgUserGroup') }} {{ t('system.userGroup.orgUserGroup') }}
</div> </div>
</div> </div>
<MsMoreAction :list="createOrgUGActionItem" @select="orgUserGroupVisible = true"> <MsMoreAction
v-permission="['ORGANIZATION_USER_ROLE:READ+ADD']"
:list="createOrgUGActionItem"
@select="orgUserGroupVisible = true"
>
<icon-plus-circle-fill class="cursor-pointer text-[rgb(var(--primary-7))]" size="20" /> <icon-plus-circle-fill class="cursor-pointer text-[rgb(var(--primary-7))]" size="20" />
</MsMoreAction> </MsMoreAction>
</div> </div>
@ -149,6 +158,7 @@
<div v-if="element.id === currentId && !element.internal" class="flex flex-row items-center gap-[8px]"> <div v-if="element.id === currentId && !element.internal" class="flex flex-row items-center gap-[8px]">
<MsMoreAction <MsMoreAction
v-if="element.type === systemType" v-if="element.type === systemType"
v-permission="['ORGANIZATION_USER_ROLE:READ+UPDATE']"
:list="addMemberActionItem" :list="addMemberActionItem"
@select="handleAddMember" @select="handleAddMember"
> >
@ -157,7 +167,7 @@
</div> </div>
</MsMoreAction> </MsMoreAction>
<MsMoreAction <MsMoreAction
:list="moreAction" :list="orgMoreAction"
@select="(value) => handleMoreAction(value, element.id, AuthScopeEnum.ORGANIZATION)" @select="(value) => handleMoreAction(value, element.id, AuthScopeEnum.ORGANIZATION)"
> >
<div class="icon-button"> <div class="icon-button">
@ -172,7 +182,7 @@
</div> </div>
</Transition> </Transition>
</div> </div>
<div v-if="showProject" class="mt-2"> <div v-if="showProject" v-permission="['PROJECT_GROUP:READ']" class="mt-2">
<CreateUserGroupPopup <CreateUserGroupPopup
:list="projectUserGroupList" :list="projectUserGroupList"
:visible="projectUserGroupVisible" :visible="projectUserGroupVisible"
@ -200,7 +210,11 @@
{{ t('system.userGroup.projectUserGroup') }} {{ t('system.userGroup.projectUserGroup') }}
</div> </div>
</div> </div>
<MsMoreAction :list="createProjectUGActionItem" @select="projectUserGroupVisible = true"> <MsMoreAction
v-permission="['PROJECT_GROUP:READ+ADD']"
:list="createProjectUGActionItem"
@select="projectUserGroupVisible = true"
>
<icon-plus-circle-fill class="cursor-pointer text-[rgb(var(--primary-7))]" size="20" /> <icon-plus-circle-fill class="cursor-pointer text-[rgb(var(--primary-7))]" size="20" />
</MsMoreAction> </MsMoreAction>
</div> </div>
@ -231,6 +245,7 @@
<div v-if="element.id === currentId && !element.internal" class="flex flex-row items-center gap-[8px]"> <div v-if="element.id === currentId && !element.internal" class="flex flex-row items-center gap-[8px]">
<MsMoreAction <MsMoreAction
v-if="element.type === systemType" v-if="element.type === systemType"
v-permission="['PROJECT_GROUP:READ+UPDATE']"
:list="addMemberActionItem" :list="addMemberActionItem"
@select="handleAddMember" @select="handleAddMember"
> >
@ -239,7 +254,7 @@
</div> </div>
</MsMoreAction> </MsMoreAction>
<MsMoreAction <MsMoreAction
:list="moreAction" :list="projectMoreAction"
@select="(value) => handleMoreAction(value, element.id, AuthScopeEnum.PROJECT)" @select="(value) => handleMoreAction(value, element.id, AuthScopeEnum.PROJECT)"
> >
<div class="icon-button"> <div class="icon-button">
@ -278,6 +293,7 @@
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { CurrentUserGroupItem, PopVisible, PopVisibleItem, UserGroupItem } from '@/models/setting/usergroup'; import { CurrentUserGroupItem, PopVisible, PopVisibleItem, UserGroupItem } from '@/models/setting/usergroup';
import { AuthScopeEnum } from '@/enums/commonEnum'; import { AuthScopeEnum } from '@/enums/commonEnum';
@ -362,6 +378,57 @@
eventTag: 'delete', eventTag: 'delete',
}, },
]; ];
const systemMoreAction: ActionsItem[] = [
{
label: 'system.userGroup.rename',
danger: false,
eventTag: 'rename',
permission: ['SYSTEM_USER_ROLE:READ+UPDATE'],
},
{
isDivider: true,
},
{
label: 'system.userGroup.delete',
danger: true,
eventTag: 'delete',
permission: ['SYSTEM_USER_ROLE:READ+DELETE'],
},
];
const orgMoreAction: ActionsItem[] = [
{
label: 'system.userGroup.rename',
danger: false,
eventTag: 'rename',
permission: ['ORGANIZATION_USER_ROLE:READ+UPDATE'],
},
{
isDivider: true,
},
{
label: 'system.userGroup.delete',
danger: true,
eventTag: 'delete',
permission: ['ORGANIZATION_USER_ROLE:READ+UPDATE'],
},
];
const projectMoreAction: ActionsItem[] = [
{
label: 'system.userGroup.rename',
danger: false,
eventTag: 'rename',
permission: ['PROJECT_GROUP:READ+UPDATE'],
},
{
isDivider: true,
},
{
label: 'system.userGroup.delete',
danger: true,
eventTag: 'delete',
permission: ['PROJECT_GROUP:READ+UPDATE'],
},
];
// //
const handleListItemClick = (element: UserGroupItem) => { const handleListItemClick = (element: UserGroupItem) => {
@ -375,11 +442,11 @@
const initData = async (id?: string, isSelect = true) => { const initData = async (id?: string, isSelect = true) => {
try { try {
let res: UserGroupItem[] = []; let res: UserGroupItem[] = [];
if (systemType === AuthScopeEnum.SYSTEM) { if (systemType === AuthScopeEnum.SYSTEM && hasAnyPermission(['SYSTEM_USER_ROLE:READ'])) {
res = await getUserGroupList(); res = await getUserGroupList();
} else if (systemType === AuthScopeEnum.ORGANIZATION) { } else if (systemType === AuthScopeEnum.ORGANIZATION && hasAnyPermission(['ORGANIZATION_USER_ROLE:READ'])) {
res = await getOrgUserGroupList(appStore.currentOrgId); res = await getOrgUserGroupList(appStore.currentOrgId);
} else if (systemType === AuthScopeEnum.PROJECT) { } else if (systemType === AuthScopeEnum.PROJECT && hasAnyPermission(['PROJECT_GROUP:READ'])) {
res = await getProjectUserGroupList(appStore.currentProjectId); res = await getProjectUserGroupList(appStore.currentProjectId);
} }
if (res.length > 0) { if (res.length > 0) {

View File

@ -1,7 +1,6 @@
<template> <template>
<MsBaseTable class="mt-[16px]" v-bind="propsRes" v-on="propsEvent"> <MsBaseTable class="mt-[16px]" v-bind="propsRes" v-on="propsEvent">
<template #quickCreate> <template v-if="hasAnyPermission(props.updatePermission || [])" #quickCreate>
<!-- <a-button type="primary" @click="handleAddUser">{{ t('system.userGroup.quickAddUser') }}</a-button> -->
<MsConfirmUserSelector :ok-loading="okLoading" v-bind="userSelectorProps" @confirm="handleAddMember" /> <MsConfirmUserSelector :ok-loading="okLoading" v-bind="userSelectorProps" @confirm="handleAddMember" />
</template> </template>
<template #action="{ record }"> <template #action="{ record }">
@ -33,6 +32,7 @@
} from '@/api/modules/setting/usergroup'; } from '@/api/modules/setting/usergroup';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { hasAnyPermission } from '@/utils/permission';
import { CurrentUserGroupItem, UserTableItem } from '@/models/setting/usergroup'; import { CurrentUserGroupItem, UserTableItem } from '@/models/setting/usergroup';
import { AuthScopeEnum } from '@/enums/commonEnum'; import { AuthScopeEnum } from '@/enums/commonEnum';
@ -48,6 +48,9 @@
const props = defineProps<{ const props = defineProps<{
keyword: string; keyword: string;
current: CurrentUserGroupItem; current: CurrentUserGroupItem;
deletePermission?: string[];
readPermission?: string[];
updatePermission?: string[];
}>(); }>();
const userSelectorProps = computed(() => { const userSelectorProps = computed(() => {
@ -112,26 +115,37 @@
heightUsed: 288, heightUsed: 288,
}); });
const handlePermission = (permission: string[], cb: () => void) => {
if (!hasAnyPermission(permission)) {
Message.error(t('common.noPermission'));
return false;
}
cb();
};
const fetchData = async () => { const fetchData = async () => {
setKeyword(props.keyword); handlePermission(props.readPermission || [], async () => {
await loadList(); setKeyword(props.keyword);
await loadList();
});
}; };
const handleRemove = async (record: UserTableItem) => { const handleRemove = async (record: UserTableItem) => {
try { handlePermission(props.deletePermission || [], async () => {
if (systemType === AuthScopeEnum.SYSTEM) { try {
await deleteUserFromUserGroup(record.id); if (systemType === AuthScopeEnum.SYSTEM) {
} else if (systemType === AuthScopeEnum.ORGANIZATION) { await deleteUserFromUserGroup(record.id);
await deleteOrgUserFromUserGroup({ } else if (systemType === AuthScopeEnum.ORGANIZATION) {
organizationId: currentOrgId.value, await deleteOrgUserFromUserGroup({
userRoleId: props.current.id, organizationId: currentOrgId.value,
userIds: [record.id], userRoleId: props.current.id,
}); userIds: [record.id],
});
}
await fetchData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} }
await fetchData(); });
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}; };
/** /**

View File

@ -59,10 +59,22 @@
<a-button :disabled="props.okLoading" @click="handleCancel"> <a-button :disabled="props.okLoading" @click="handleCancel">
{{ t(props.cancelText || 'ms.drawer.cancel') }} {{ t(props.cancelText || 'ms.drawer.cancel') }}
</a-button> </a-button>
<a-button v-if="showContinue" type="secondary" :loading="props.okLoading" @click="handleContinue"> <a-button
v-if="showContinue"
v-permission="props.okPermission || []"
type="secondary"
:loading="props.okLoading"
@click="handleContinue"
>
{{ t(props.saveContinueText || 'ms.drawer.saveContinue') }} {{ t(props.saveContinueText || 'ms.drawer.saveContinue') }}
</a-button> </a-button>
<a-button type="primary" :disabled="okDisabled" :loading="props.okLoading" @click="handleOk"> <a-button
v-permission="props.okPermission || []"
type="primary"
:disabled="okDisabled"
:loading="props.okLoading"
@click="handleOk"
>
{{ t(props.okText || 'ms.drawer.ok') }} {{ t(props.okText || 'ms.drawer.ok') }}
</a-button> </a-button>
</div> </div>
@ -93,6 +105,7 @@
showSkeleton?: boolean; // showSkeleton?: boolean; //
okLoading?: boolean; okLoading?: boolean;
okDisabled?: boolean; okDisabled?: boolean;
okPermission?: string[]; //
okText?: string; okText?: string;
cancelText?: string; cancelText?: string;
saveContinueText?: string; saveContinueText?: string;
@ -110,6 +123,7 @@
showContinue: false, showContinue: false,
popupContainer: 'body', popupContainer: 'body',
disabledWidthDrag: false, disabledWidthDrag: false,
okPermission: () => [], //
}); });
const emit = defineEmits(['update:visible', 'confirm', 'cancel', 'continue']); const emit = defineEmits(['update:visible', 'confirm', 'cancel', 'continue']);

View File

@ -12,6 +12,7 @@
<a-doption <a-doption
v-else v-else
:key="item.label" :key="item.label"
v-permission="item.permission || []"
:class="item.danger ? 'error-6' : ''" :class="item.danger ? 'error-6' : ''"
:disabled="item.disabled" :disabled="item.disabled"
:value="item.eventTag" :value="item.eventTag"

View File

@ -5,6 +5,7 @@ export interface ActionsItem {
danger?: boolean; // 是否危险操作true 的话会显示红色按钮 danger?: boolean; // 是否危险操作true 的话会显示红色按钮
disabled?: boolean; // 是否禁用 disabled?: boolean; // 是否禁用
icon?: string; // 按钮图标 icon?: string; // 按钮图标
permission?: string[]; // 权限标识
} }
export type SelectedValue = string | number | Record<string, any> | undefined; export type SelectedValue = string | number | Record<string, any> | undefined;

View File

@ -75,7 +75,6 @@
@show-setting="handleShowSetting" @show-setting="handleShowSetting"
@init-data="handleInitColumn" @init-data="handleInitColumn"
/> />
<slot v-else-if="item.filterConfig" :name="item.filterConfig.filterSlotName"> <slot v-else-if="item.filterConfig" :name="item.filterConfig.filterSlotName">
<DefaultFilter <DefaultFilter
class="ml-[4px]" class="ml-[4px]"
@ -541,9 +540,6 @@
} }
} }
} }
:deep(.arco-table-th-title) {
width: 100%;
}
.setting-icon { .setting-icon {
margin-left: 16px; margin-left: 16px;
color: var(--color-text-4); color: var(--color-text-4);

View File

@ -103,6 +103,7 @@ export interface MsTableProps<T> {
emptyDataShowLine?: boolean; // 空数据是否显示 "-" emptyDataShowLine?: boolean; // 空数据是否显示 "-"
showJumpMethod?: boolean; // 是否展示跳转方法 showJumpMethod?: boolean; // 是否展示跳转方法
isSimpleSetting?: boolean; // 是否是简单的设置 isSimpleSetting?: boolean; // 是否是简单的设置
filterIconAlignLeft?: boolean; // 筛选图标是否靠左
[key: string]: any; [key: string]: any;
} }

View File

@ -80,6 +80,7 @@ export default function useTableProps<T>(
showJumpMethod: false, // 是否显示跳转方法 showJumpMethod: false, // 是否显示跳转方法
showFooterActionWrap: false, // 是否显示底部操作区域 showFooterActionWrap: false, // 是否显示底部操作区域
isSimpleSetting: false, // 是否是简易column设置 isSimpleSetting: false, // 是否是简易column设置
filterIconAlignLeft: true, // 筛选图标是否靠左
...props, ...props,
}; };

View File

@ -6,6 +6,7 @@
:min="0" :min="0"
hide-button hide-button
size="small" size="small"
:disabled="props.disabled"
@press-enter="handleEnter(false)" @press-enter="handleEnter(false)"
@blur="handleEnter(true)" @blur="handleEnter(true)"
> >
@ -14,6 +15,7 @@
v-model:model-value="current.type" v-model:model-value="current.type"
size="small" size="small"
class="max-w-[64px]" class="max-w-[64px]"
:disabled="props.disabled"
:options="option" :options="option"
:trigger-props="{ autoFitPopupMinWidth: true }" :trigger-props="{ autoFitPopupMinWidth: true }"
@change="handleEnter(false)" @change="handleEnter(false)"
@ -28,7 +30,7 @@
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ modelValue?: string; defaultValue?: string }>(); const props = defineProps<{ modelValue?: string; defaultValue?: string; disabled?: boolean }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: string): void; (e: 'update:modelValue', value: string): void;
(e: 'change', value: string): void; (e: 'change', value: string): void;

View File

@ -101,4 +101,5 @@ export default {
'common.noProject': 'No project, please contact the administrator', 'common.noProject': 'No project, please contact the administrator',
'common.noResource': 'No resource, please contact the administrator', 'common.noResource': 'No resource, please contact the administrator',
'common.noSelectProject': 'No optional items available', 'common.noSelectProject': 'No optional items available',
'common.noPermission': 'No permission',
}; };

View File

@ -104,4 +104,5 @@ export default {
'common.noProject': '暂无项目权限,请联系管理员', 'common.noProject': '暂无项目权限,请联系管理员',
'common.noResource': '暂无资源权限,请联系管理员', 'common.noResource': '暂无资源权限,请联系管理员',
'common.noSelectProject': '无可选项目', 'common.noSelectProject': '无可选项目',
'common.noPermission': '无权限',
}; };

View File

@ -44,7 +44,15 @@ const ProjectManagement: AppRouteRecordRaw = {
component: () => import('@/views/project-management/projectAndPermission/menuManagement/menuManagement.vue'), component: () => import('@/views/project-management/projectAndPermission/menuManagement/menuManagement.vue'),
meta: { meta: {
locale: 'project.permission.menuManagement', locale: 'project.permission.menuManagement',
roles: ['PROJECT_APPLICATION_WORKSTATION:READ'], roles: [
'PROJECT_APPLICATION_WORKSTATION:READ',
'PROJECT_APPLICATION_TEST_PLAN:READ',
'PROJECT_APPLICATION_BUG:READ',
'PROJECT_APPLICATION_CASE:READ',
'PROJECT_APPLICATION_API:READ',
'PROJECT_APPLICATION_UI:READ',
'PROJECT_APPLICATION_PERFORMANCE_TEST:READ',
],
}, },
}, },
// 项目版本 // 项目版本
@ -250,7 +258,7 @@ const ProjectManagement: AppRouteRecordRaw = {
import('@/views/project-management/projectAndPermission/menuManagement/components/falseAlermRule.vue'), import('@/views/project-management/projectAndPermission/menuManagement/components/falseAlermRule.vue'),
meta: { meta: {
locale: 'project.menu.API_ERROR_REPORT_RULE', locale: 'project.menu.API_ERROR_REPORT_RULE',
roles: ['*'], roles: ['PROJECT_APPLICATION_API:READ'],
breadcrumbs: [ breadcrumbs: [
{ {
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT, name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT,

View File

@ -44,7 +44,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/system/usergroup/systemUserGroup.vue'), component: () => import('@/views/setting/system/usergroup/systemUserGroup.vue'),
meta: { meta: {
locale: 'menu.settings.system.usergroup', locale: 'menu.settings.system.usergroup',
roles: ['*'], roles: ['SYSTEM_USER_ROLE:READ'],
isTopMenu: true, isTopMenu: true,
}, },
}, },
@ -158,7 +158,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/organization/usergroup/orgUserGroup.vue'), component: () => import('@/views/setting/organization/usergroup/orgUserGroup.vue'),
meta: { meta: {
locale: 'menu.settings.organization.userGroup', locale: 'menu.settings.organization.userGroup',
roles: ['*'], roles: ['ORGANIZATION_USER_ROLE:READ'],
isTopMenu: true, isTopMenu: true,
}, },
}, },
@ -168,7 +168,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/organization/project/orgProject.vue'), component: () => import('@/views/setting/organization/project/orgProject.vue'),
meta: { meta: {
locale: 'menu.settings.organization.project', locale: 'menu.settings.organization.project',
roles: ['*'], roles: ['ORGANIZATION_PROJECT:READ'],
isTopMenu: true, isTopMenu: true,
}, },
}, },

View File

@ -24,11 +24,13 @@
import MsMenuPanel from '@/components/pure/ms-menu-panel/index.vue'; import MsMenuPanel from '@/components/pure/ms-menu-panel/index.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import usePermission from '@/hooks/usePermission';
import useLicenseStore from '@/store/modules/setting/license'; import useLicenseStore from '@/store/modules/setting/license';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum'; import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
const { t } = useI18n(); const { t } = useI18n();
const permission = usePermission();
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
@ -47,7 +49,7 @@
} }
return []; return [];
} }
const menuList = ref([ const sourceMenuList = [
{ {
key: 'project', key: 'project',
title: t('project.permission.project'), title: t('project.permission.project'),
@ -85,7 +87,18 @@
level: 2, level: 2,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_USER_GROUP, name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_USER_GROUP,
}, },
]); ];
const menuList = computed(() => {
const routerList = router.getRoutes();
return sourceMenuList.filter((item) => {
if (item.name) {
const routerItem = routerList.find((rou) => rou.name === item.name);
if (!routerItem) return false;
return permission.accessRouter(routerItem);
}
return true;
});
});
const currentKey = ref<string>(''); const currentKey = ref<string>('');

View File

@ -10,6 +10,7 @@
:width="680" :width="680"
:ok-loading="okLoading" :ok-loading="okLoading"
:ok-disabled="okDisabled" :ok-disabled="okDisabled"
:ok-permission="['PROJECT_APPLICATION_CASE:READ+UPDATE']"
@cancel="handleCancel(false)" @cancel="handleCancel(false)"
@confirm="handleConfirm" @confirm="handleConfirm"
> >

View File

@ -6,7 +6,9 @@
</div> </div>
</div> </div>
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<a-button type="primary" @click="showAddRule(undefined)">{{ t('project.menu.addFalseAlertRules') }}</a-button> <a-button v-permission="['PROJECT_APPLICATION_API:READ+ADD']" type="primary" @click="showAddRule(undefined)">{{
t('project.menu.addFalseAlertRules')
}}</a-button>
<a-input-search <a-input-search
v-model="keyword" v-model="keyword"
:placeholder="t('project.menu.nameSearch')" :placeholder="t('project.menu.nameSearch')"
@ -24,18 +26,36 @@
> >
<template #operation="{ record }"> <template #operation="{ record }">
<template v-if="!record.enable"> <template v-if="!record.enable">
<MsButton class="!mr-0" @click="handleEnableOrDisableProject(record.id)">{{ t('common.enable') }}</MsButton> <div class="flex flex-row">
<a-divider direction="vertical" /> <span v-permission="['PROJECT_APPLICATION_API:READ+UPDATE']" class="flex flex-row">
<MsButton class="!mr-0" @click="handleDelete(record.id)">{{ t('common.delete') }}</MsButton> <MsButton class="!mr-0" @click="handleEnableOrDisableProject(record.id)">{{
t('common.enable')
}}</MsButton>
<a-divider direction="vertical" />
</span>
<span>
<MsButton
v-permission="['PROJECT_APPLICATION_API:READ+DELETE']"
class="!mr-0"
@click="handleDelete(record.id)"
>{{ t('common.delete') }}</MsButton
>
</span>
</div>
</template> </template>
<template v-else> <template v-else>
<MsButton class="!mr-0" @click="showAddRule(record)">{{ t('common.edit') }}</MsButton> <span v-permission="['PROJECT_APPLICATION_API:READ+UPDATE']" class="flex flex-row">
<a-divider direction="vertical" /> <MsButton class="!mr-0" @click="showAddRule(record)">{{ t('common.edit') }}</MsButton>
<MsButton class="!mr-0" @click="handleEnableOrDisableProject(record.id, false)">{{ <a-divider direction="vertical" />
t('common.disable') </span>
}}</MsButton> <span v-permission="['PROJECT_APPLICATION_API:READ+UPDATE']" class="flex flex-row">
<a-divider direction="vertical" /> <MsButton class="!mr-0" @click="handleEnableOrDisableProject(record.id, false)">{{
t('common.disable')
}}</MsButton>
<a-divider direction="vertical" />
</span>
<MsTableMoreAction <MsTableMoreAction
v-permission="['PROJECT_APPLICATION_API:READ+DELETE']"
class="!mr-0" class="!mr-0"
:list="tableActions" :list="tableActions"
@select="handleMoreAction($event, record)" @select="handleMoreAction($event, record)"
@ -70,7 +90,7 @@
</MsDrawer> </MsDrawer>
</template> </template>
<script lang="ts" setup> <script async lang="ts" setup>
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { Message, TableData } from '@arco-design/web-vue'; import { Message, TableData } from '@arco-design/web-vue';
@ -94,7 +114,7 @@
} from '@/api/modules/project-management/menuManagement'; } from '@/api/modules/project-management/menuManagement';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { useAppStore } from '@/store'; import { useAppStore, useTableStore } from '@/store';
import { FakeTableListItem } from '@/models/projectManagement/menuManagement'; import { FakeTableListItem } from '@/models/projectManagement/menuManagement';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum'; import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
@ -111,6 +131,7 @@
const batchFormRef = ref(); const batchFormRef = ref();
const ruleFormMode = ref<UserModalMode>('create'); const ruleFormMode = ref<UserModalMode>('create');
const currentList = ref<FakeTableListItem[]>([]); const currentList = ref<FakeTableListItem[]>([]);
const tableStore = useTableStore();
const headerOptions = computed(() => [ const headerOptions = computed(() => [
{ label: 'Response Headers', value: 'headers' }, { label: 'Response Headers', value: 'headers' },
{ label: 'Response Data', value: 'data' }, { label: 'Response Data', value: 'data' },
@ -131,15 +152,18 @@
{ {
label: 'common.enable', label: 'common.enable',
eventTag: 'batchEnable', eventTag: 'batchEnable',
permission: ['PROJECT_APPLICATION_API:READ+UPDATE'],
}, },
{ {
label: 'common.disable', label: 'common.disable',
eventTag: 'batchDisable', eventTag: 'batchDisable',
permission: ['PROJECT_APPLICATION_API:READ+UPDATE'],
}, },
{ {
label: 'common.delete', label: 'common.delete',
eventTag: 'batchDelete', eventTag: 'batchDelete',
danger: true, danger: true,
permission: ['PROJECT_APPLICATION_API:READ+UPDATE'],
}, },
], ],
}; };
@ -196,17 +220,16 @@
width: 169, width: 169,
}, },
]; ];
await tableStore.initColumn(TableKeyEnum.PROJECT_MANAGEMENT_MENU_FALSE_ALERT, rulesColumn, 'drawer');
const { propsRes, propsEvent, loadList, setKeyword, setLoadListParams, resetSelector } = useTable( const { propsRes, propsEvent, loadList, setKeyword, setLoadListParams, resetSelector } = useTable(
postFakeTableList, postFakeTableList,
{ {
scroll: { x: 1200 }, scroll: { x: 1200 },
columns: rulesColumn,
tableKey: TableKeyEnum.PROJECT_MANAGEMENT_MENU_FALSE_ALERT, tableKey: TableKeyEnum.PROJECT_MANAGEMENT_MENU_FALSE_ALERT,
selectable: true, selectable: true,
noDisable: false, noDisable: false,
size: 'default', size: 'default',
debug: true, showSetting: true,
}, },
(record: TableData) => { (record: TableData) => {
record.typeList = record.type ? record.type.split(',') : []; record.typeList = record.type ? record.type.split(',') : [];

View File

@ -10,6 +10,7 @@
:width="680" :width="680"
:ok-loading="okLoading" :ok-loading="okLoading"
:ok-disabled="okDisabled" :ok-disabled="okDisabled"
:ok-permission="['PROJECT_APPLICATION_CASE:READ+UPDATE']"
@cancel="handleCancel(false)" @cancel="handleCancel(false)"
@confirm="handleConfirm" @confirm="handleConfirm"
> >

View File

@ -33,6 +33,7 @@
<!-- 测试计划 报告保留时间范围 --> <!-- 测试计划 报告保留时间范围 -->
<MsTimeSelectorVue <MsTimeSelectorVue
v-model="allValueMap['TEST_PLAN_CLEAN_REPORT']" v-model="allValueMap['TEST_PLAN_CLEAN_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_TEST_PLAN:READ+UPDATE'])"
@change="(v: string) => handleMenuStatusChange('TEST_PLAN_CLEAN_REPORT',v,MenuEnum.testPlan)" @change="(v: string) => handleMenuStatusChange('TEST_PLAN_CLEAN_REPORT',v,MenuEnum.testPlan)"
/> />
</div> </div>
@ -40,15 +41,19 @@
<!-- 测试计划 报告链接有效期 --> <!-- 测试计划 报告链接有效期 -->
<MsTimeSelectorVue <MsTimeSelectorVue
v-model="allValueMap['TEST_PLAN_SHARE_REPORT']" v-model="allValueMap['TEST_PLAN_SHARE_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_TEST_PLAN:READ+UPDATE'])"
@change="(v: string) => handleMenuStatusChange('TEST_PLAN_SHARE_REPORT',v,MenuEnum.testPlan)" @change="(v: string) => handleMenuStatusChange('TEST_PLAN_SHARE_REPORT',v,MenuEnum.testPlan)"
/> />
</div> </div>
<template v-if="record.type === 'BUG_SYNC'"> <template v-if="record.type === 'BUG_SYNC'">
<!-- 同步缺陷 --> <!-- 同步缺陷 -->
<span>{{ t('project.menu.row2') }}</span> <span>{{ t('project.menu.row2') }}</span>
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="showDefectDrawer">{{ <div
t('project.menu.BUG_SYNC') v-permission="['PROJECT_APPLICATION_BUG:READ+UPDATE']"
}}</div> class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]"
@click="showDefectDrawer"
>{{ t('project.menu.BUG_SYNC') }}</div
>
</template> </template>
<div v-if="record.type === 'CASE_PUBLIC'"> <div v-if="record.type === 'CASE_PUBLIC'">
<!-- 用例 公共用例库 --> <!-- 用例 公共用例库 -->
@ -57,9 +62,12 @@
<div v-if="record.type === 'CASE_RELATED'" class="flex flex-row"> <div v-if="record.type === 'CASE_RELATED'" class="flex flex-row">
<!-- 用例 关联需求 --> <!-- 用例 关联需求 -->
<div>{{ t('project.menu.row4') }}</div> <div>{{ t('project.menu.row4') }}</div>
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="showRelatedCaseDrawer">{{ <div
t('project.menu.CASE_RELATED') v-permission="['PROJECT_APPLICATION_CASE:READ+UPDATE']"
}}</div> class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]"
@click="showRelatedCaseDrawer"
>{{ t('project.menu.CASE_RELATED') }}</div
>
</div> </div>
<div v-if="record.type === 'CASE_RE_REVIEW'"> <div v-if="record.type === 'CASE_RE_REVIEW'">
<!-- 用例 重新提审 --> <!-- 用例 重新提审 -->
@ -72,6 +80,7 @@
<div v-if="record.type === 'API_CLEAN_REPORT'"> <div v-if="record.type === 'API_CLEAN_REPORT'">
<MsTimeSelectorVue <MsTimeSelectorVue
v-model="allValueMap['API_CLEAN_REPORT']" v-model="allValueMap['API_CLEAN_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_API:READ+UPDATE'])"
@change="(v: string) => handleMenuStatusChange('API_CLEAN_REPORT',v,MenuEnum.apiTest)" @change="(v: string) => handleMenuStatusChange('API_CLEAN_REPORT',v,MenuEnum.apiTest)"
/> />
</div> </div>
@ -79,6 +88,7 @@
<!--接口测试 报告链接有效期 --> <!--接口测试 报告链接有效期 -->
<MsTimeSelectorVue <MsTimeSelectorVue
v-model="allValueMap['API_SHARE_REPORT']" v-model="allValueMap['API_SHARE_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_API:READ+UPDATE'])"
@change="(v: string) => handleMenuStatusChange('API_SHARE_REPORT',v,MenuEnum.apiTest)" @change="(v: string) => handleMenuStatusChange('API_SHARE_REPORT',v,MenuEnum.apiTest)"
/> />
</div> </div>
@ -89,6 +99,7 @@
:field-names="{ label: 'name', value: 'id' }" :field-names="{ label: 'name', value: 'id' }"
:options="apiPoolOption" :options="apiPoolOption"
class="w-[120px]" class="w-[120px]"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_API:READ+UPDATE'])"
@change="(v: SelectValue) => handleMenuStatusChange('API_RESOURCE_POOL_ID',v as string,MenuEnum.apiTest)" @change="(v: SelectValue) => handleMenuStatusChange('API_RESOURCE_POOL_ID',v as string,MenuEnum.apiTest)"
/> />
<a-tooltip <a-tooltip
@ -110,6 +121,7 @@
v-model="allValueMap['API_SCRIPT_REVIEWER_ID']" v-model="allValueMap['API_SCRIPT_REVIEWER_ID']"
:field-names="{ label: 'name', value: 'id' }" :field-names="{ label: 'name', value: 'id' }"
:options="apiAuditorOption" :options="apiAuditorOption"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_API:READ+UPDATE'])"
class="w-[120px]" class="w-[120px]"
@change="(v: SelectValue) => handleMenuStatusChange('API_SCRIPT_REVIEWER_ID',v as string,MenuEnum.apiTest)" @change="(v: SelectValue) => handleMenuStatusChange('API_SCRIPT_REVIEWER_ID',v as string,MenuEnum.apiTest)"
/> />
@ -133,9 +145,12 @@
</template> </template>
</a-input-number> </a-input-number>
</div> </div>
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="pushFar">{{ <div
t('project.menu.API_ERROR_REPORT_RULE') v-permission="['PROJECT_APPLICATION_API:READ+UPDATE']"
}}</div> class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]"
@click="pushFar"
>{{ t('project.menu.API_ERROR_REPORT_RULE') }}</div
>
<a-tooltip :content="t('project.menu.API_ERROR_REPORT_RULE_TIP')" position="right"> <a-tooltip :content="t('project.menu.API_ERROR_REPORT_RULE_TIP')" position="right">
<div> <div>
<MsIcon class="ml-[4px] text-[rgb(var(--primary-5))]" type="icon-icon-maybe_outlined" /> <MsIcon class="ml-[4px] text-[rgb(var(--primary-5))]" type="icon-icon-maybe_outlined" />
@ -147,6 +162,7 @@
<!--UI 报告保留时间范围 --> <!--UI 报告保留时间范围 -->
<MsTimeSelectorVue <MsTimeSelectorVue
v-model="allValueMap['UI_CLEAN_REPORT']" v-model="allValueMap['UI_CLEAN_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_UI:READ+UPDATE'])"
@change="(v: string) => handleMenuStatusChange('UI_CLEAN_REPORT',v,MenuEnum.uiTest)" @change="(v: string) => handleMenuStatusChange('UI_CLEAN_REPORT',v,MenuEnum.uiTest)"
/> />
</div> </div>
@ -154,6 +170,7 @@
<!--UI 报告链接有效期 --> <!--UI 报告链接有效期 -->
<MsTimeSelectorVue <MsTimeSelectorVue
v-model="allValueMap['UI_SHARE_REPORT']" v-model="allValueMap['UI_SHARE_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_UI:READ+UPDATE'])"
@change="(v: string) => handleMenuStatusChange('UI_SHARE_REPORT',v,MenuEnum.uiTest)" @change="(v: string) => handleMenuStatusChange('UI_SHARE_REPORT',v,MenuEnum.uiTest)"
/> />
</div> </div>
@ -164,6 +181,7 @@
:field-names="{ label: 'name', value: 'id' }" :field-names="{ label: 'name', value: 'id' }"
:options="uiPoolOption" :options="uiPoolOption"
class="w-[120px]" class="w-[120px]"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_UI:READ+UPDATE'])"
@change="(v: SelectValue) => handleMenuStatusChange('UI_RESOURCE_POOL_ID',v as string,MenuEnum.uiTest)" @change="(v: SelectValue) => handleMenuStatusChange('UI_RESOURCE_POOL_ID',v as string,MenuEnum.uiTest)"
/> />
<a-tooltip <a-tooltip
@ -183,6 +201,7 @@
<!--性能测试 报告保留时间范围 --> <!--性能测试 报告保留时间范围 -->
<MsTimeSelectorVue <MsTimeSelectorVue
v-model="allValueMap['PERFORMANCE_TEST_CLEAN_REPORT']" v-model="allValueMap['PERFORMANCE_TEST_CLEAN_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_PERFORMANCE_TEST:READ+UPDATE'])"
@change="(v: string) => handleMenuStatusChange('PERFORMANCE_TEST_CLEAN_REPORT',v,MenuEnum.loadTest)" @change="(v: string) => handleMenuStatusChange('PERFORMANCE_TEST_CLEAN_REPORT',v,MenuEnum.loadTest)"
/> />
</div> </div>
@ -190,6 +209,7 @@
<!--性能测试 报告链接有效期 --> <!--性能测试 报告链接有效期 -->
<MsTimeSelectorVue <MsTimeSelectorVue
v-model="allValueMap['PERFORMANCE_TEST_SHARE_REPORT']" v-model="allValueMap['PERFORMANCE_TEST_SHARE_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_PERFORMANCE_TEST:READ+UPDATE'])"
@change="(v: string) => handleMenuStatusChange('PERFORMANCE_TEST_SHARE_REPORT',v,MenuEnum.loadTest)" @change="(v: string) => handleMenuStatusChange('PERFORMANCE_TEST_SHARE_REPORT',v,MenuEnum.loadTest)"
/> />
</div> </div>
@ -200,6 +220,7 @@
:field-names="{ label: 'name', value: 'id' }" :field-names="{ label: 'name', value: 'id' }"
:options="performanceAuditorOption" :options="performanceAuditorOption"
class="w-[120px]" class="w-[120px]"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_PERFORMANCE_TEST:READ+UPDATE'])"
@change="(v: SelectValue) => handleMenuStatusChange('PERFORMANCE_TEST_SCRIPT_REVIEWER_ID',v as string,MenuEnum.loadTest)" @change="(v: SelectValue) => handleMenuStatusChange('PERFORMANCE_TEST_SCRIPT_REVIEWER_ID',v as string,MenuEnum.loadTest)"
/> />
<a-tooltip :content="t('project.menu.PERFORMANCE_TEST_SCRIPT_REVIEWER_TIP')" position="right"> <a-tooltip :content="t('project.menu.PERFORMANCE_TEST_SCRIPT_REVIEWER_TIP')" position="right">
@ -210,8 +231,12 @@
</div> </div>
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<!-- 同步缺陷状态 --> <!-- 用例 同步缺陷状态 -->
<a-tooltip v-if="record.type === 'BUG_SYNC' && !allValueMap['BUG_SYNC_SYNC_ENABLE']" position="tr"> <a-tooltip
v-if="record.type === 'BUG_SYNC' && !allValueMap['BUG_SYNC_SYNC_ENABLE']"
v-permission="['PROJECT_APPLICATION_BUG:READ+UPDATE']"
position="tr"
>
<template #content> <template #content>
<span> <span>
{{ t('project.menu.notConfig') }} {{ t('project.menu.notConfig') }}
@ -224,6 +249,7 @@
<a-switch <a-switch
checked-value="true" checked-value="true"
unchecked-value="false" unchecked-value="false"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_BUG:READ+UPDATE'])"
:value="allValueMap['BUG_SYNC_SYNC_ENABLE']" :value="allValueMap['BUG_SYNC_SYNC_ENABLE']"
size="small" size="small"
type="line" type="line"
@ -234,12 +260,17 @@
v-if="record.type === 'BUG_SYNC' && allValueMap['BUG_SYNC_SYNC_ENABLE']" v-if="record.type === 'BUG_SYNC' && allValueMap['BUG_SYNC_SYNC_ENABLE']"
checked-value="true" checked-value="true"
unchecked-value="false" unchecked-value="false"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_BUG:READ+UPDATE'])"
size="small" size="small"
type="line" type="line"
@change="(v: boolean | string| number) => handleMenuStatusChange('BUG_SYNC_SYNC_ENABLE',v as boolean, MenuEnum.bugManagement)" @change="(v: boolean | string| number) => handleMenuStatusChange('BUG_SYNC_SYNC_ENABLE',v as boolean, MenuEnum.bugManagement)"
/> />
<!-- 关联需求状态 --> <!-- 功能测试 同步缺陷 -->
<a-tooltip v-if="record.type === 'CASE_RELATED' && !allValueMap['CASE_RELATED_CASE_ENABLE']" position="tr"> <a-tooltip
v-if="record.type === 'CASE_RELATED' && !allValueMap['CASE_RELATED_CASE_ENABLE']"
v-permission="['PROJECT_APPLICATION_BUG:READ+UPDATE']"
position="tr"
>
<template #content> <template #content>
<span> <span>
{{ t('project.menu.notConfig') }} {{ t('project.menu.notConfig') }}
@ -252,6 +283,7 @@
<a-switch <a-switch
checked-value="true" checked-value="true"
unchecked-value="false" unchecked-value="false"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_CASE:READ+UPDATE'])"
:value="allValueMap['CASE_RELATED_CASE_ENABLE']" :value="allValueMap['CASE_RELATED_CASE_ENABLE']"
size="small" size="small"
type="line" type="line"
@ -260,6 +292,7 @@
</a-tooltip> </a-tooltip>
<a-switch <a-switch
v-if="record.type === 'CASE_RELATED' && allValueMap['CASE_RELATED_CASE_ENABLE']" v-if="record.type === 'CASE_RELATED' && allValueMap['CASE_RELATED_CASE_ENABLE']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_BUG:READ+UPDATE'])"
checked-value="true" checked-value="true"
unchecked-value="false" unchecked-value="false"
size="small" size="small"
@ -271,6 +304,7 @@
<a-switch <a-switch
v-if="record.type === 'WORKSTATION_SYNC_RULE'" v-if="record.type === 'WORKSTATION_SYNC_RULE'"
v-model="allValueMap['WORKSTATION_SYNC_RULE']" v-model="allValueMap['WORKSTATION_SYNC_RULE']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_API:READ+UPDATE'])"
checked-value="true" checked-value="true"
unchecked-value="false" unchecked-value="false"
size="small" size="small"
@ -281,6 +315,7 @@
<a-switch <a-switch
v-if="record.type === 'CASE_PUBLIC'" v-if="record.type === 'CASE_PUBLIC'"
v-model="allValueMap['CASE_PUBLIC']" v-model="allValueMap['CASE_PUBLIC']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_CASE:READ+UPDATE'])"
checked-value="true" checked-value="true"
unchecked-value="false" unchecked-value="false"
size="small" size="small"
@ -291,6 +326,7 @@
<a-switch <a-switch
v-if="record.type === 'CASE_RE_REVIEW'" v-if="record.type === 'CASE_RE_REVIEW'"
v-model="allValueMap['CASE_RE_REVIEW']" v-model="allValueMap['CASE_RE_REVIEW']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_CASE:READ+UPDATE'])"
checked-value="true" checked-value="true"
unchecked-value="false" unchecked-value="false"
size="small" size="small"
@ -301,6 +337,7 @@
<a-switch <a-switch
v-if="record.type === 'API_URL_REPEATABLE'" v-if="record.type === 'API_URL_REPEATABLE'"
v-model="allValueMap['API_URL_REPEATABLE']" v-model="allValueMap['API_URL_REPEATABLE']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_API:READ+UPDATE'])"
checked-value="true" checked-value="true"
unchecked-value="false" unchecked-value="false"
size="small" size="small"
@ -311,6 +348,7 @@
<a-switch <a-switch
v-if="record.type === 'API_SYNC_CASE'" v-if="record.type === 'API_SYNC_CASE'"
v-model="allValueMap['API_SYNC_CASE']" v-model="allValueMap['API_SYNC_CASE']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_API:READ+UPDATE'])"
checked-value="true" checked-value="true"
unchecked-value="false" unchecked-value="false"
size="small" size="small"
@ -321,6 +359,7 @@
<a-switch <a-switch
v-if="record.type === 'PERFORMANCE_TEST_SCRIPT_REVIEWER'" v-if="record.type === 'PERFORMANCE_TEST_SCRIPT_REVIEWER'"
v-model="allValueMap['PERFORMANCE_TEST_SCRIPT_REVIEWER_ENABLE']" v-model="allValueMap['PERFORMANCE_TEST_SCRIPT_REVIEWER_ENABLE']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_PERFORMANCE_TEST:READ+UPDATE'])"
checked-value="true" checked-value="true"
unchecked-value="false" unchecked-value="false"
size="small" size="small"
@ -356,6 +395,7 @@
} from '@/api/modules/project-management/menuManagement'; } from '@/api/modules/project-management/menuManagement';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { hasAnyPermission } from '@/utils/permission';
import { MenuTableConfigItem, PoolOption, SelectValue } from '@/models/projectManagement/menuManagement'; import { MenuTableConfigItem, PoolOption, SelectValue } from '@/models/projectManagement/menuManagement';
import { MenuEnum } from '@/enums/commonEnum'; import { MenuEnum } from '@/enums/commonEnum';
@ -562,6 +602,52 @@
const getMenuConfig = async (type: string) => { const getMenuConfig = async (type: string) => {
try { try {
let hasAuth = false;
switch (type) {
case MenuEnum.workstation:
if (hasAnyPermission(['PROJECT_APPLICATION_WORKSTATION:READ'])) {
hasAuth = true;
}
break;
case MenuEnum.apiTest:
if (hasAnyPermission(['PROJECT_APPLICATION_API:READ'])) {
hasAuth = true;
}
break;
case MenuEnum.bugManagement:
if (hasAnyPermission(['PROJECT_APPLICATION_BUG:READ'])) {
hasAuth = true;
}
break;
case MenuEnum.caseManagement:
if (hasAnyPermission(['PROJECT_APPLICATION_CASE:READ'])) {
hasAuth = true;
}
break;
case 'loadTest':
if (hasAnyPermission(['PROJECT_APPLICATION_PERFORMANCE_TEST:READ'])) {
hasAuth = true;
}
break;
case MenuEnum.testPlan:
if (hasAnyPermission(['PROJECT_APPLICATION_TEST_PLAN:READ'])) {
hasAuth = true;
}
break;
case MenuEnum.uiTest:
if (hasAnyPermission(['PROJECT_APPLICATION_UI:READ'])) {
hasAuth = true;
}
break;
default:
break;
}
if (!hasAuth) {
Message.error(t('common.noPermission'));
return;
}
const resultObj = await getConfigByMenuItem({ const resultObj = await getConfigByMenuItem({
projectId: currentProjectId.value, projectId: currentProjectId.value,
type, type,
@ -584,14 +670,11 @@
getMenuConfig(record.module); getMenuConfig(record.module);
if (record.module === MenuEnum.apiTest && !apiPoolOption.value.length) { if (record.module === MenuEnum.apiTest && !apiPoolOption.value.length) {
apiPoolOption.value = await getPoolOptions(currentProjectId.value, record.module); apiPoolOption.value = await getPoolOptions(currentProjectId.value, record.module);
} } else if (record.module === MenuEnum.uiTest && !uiPoolOption.value.length) {
if (record.module === MenuEnum.uiTest && !uiPoolOption.value.length) {
uiPoolOption.value = await getPoolOptions(currentProjectId.value, record.module); uiPoolOption.value = await getPoolOptions(currentProjectId.value, record.module);
} } else if (record.module === MenuEnum.apiTest && !apiAuditorOption.value.length) {
if (record.module === MenuEnum.apiTest && !apiAuditorOption.value.length) {
apiAuditorOption.value = await getAuditorOptions(currentProjectId.value, record.module); apiAuditorOption.value = await getAuditorOptions(currentProjectId.value, record.module);
} } else if (record.module === MenuEnum.loadTest && !performanceAuditorOption.value.length) {
if (record.module === MenuEnum.loadTest && !performanceAuditorOption.value.length) {
performanceAuditorOption.value = await getAuditorOptions(currentProjectId.value, record.module); performanceAuditorOption.value = await getAuditorOptions(currentProjectId.value, record.module);
} }
} catch (e) { } catch (e) {
@ -620,6 +703,53 @@
const handleMenuStatusChange = async (type: string, typeValue: string | boolean, suffix: string) => { const handleMenuStatusChange = async (type: string, typeValue: string | boolean, suffix: string) => {
try { try {
let hasAuth = false;
switch (suffix) {
case MenuEnum.workstation:
if (hasAnyPermission(['PROJECT_APPLICATION_WORKSTATION:READ+UPDATE'])) {
hasAuth = true;
}
break;
case MenuEnum.apiTest:
if (hasAnyPermission(['PROJECT_APPLICATION_API:READ+UPDATE'])) {
hasAuth = true;
}
break;
case MenuEnum.bugManagement:
if (hasAnyPermission(['PROJECT_APPLICATION_BUG:READ+UPDATE'])) {
hasAuth = true;
}
break;
case MenuEnum.caseManagement:
if (hasAnyPermission(['PROJECT_APPLICATION_CASE:READ+UPDATE'])) {
hasAuth = true;
}
break;
case MenuEnum.loadTest:
if (hasAnyPermission(['PROJECT_APPLICATION_PERFORMANCE_TEST:READ+UPDATE'])) {
hasAuth = true;
}
break;
case MenuEnum.testPlan:
if (hasAnyPermission(['PROJECT_APPLICATION_TEST_PLAN:READ+UPDATE'])) {
hasAuth = true;
}
break;
case MenuEnum.uiTest:
if (hasAnyPermission(['PROJECT_APPLICATION_UI:READ+UPDATE'])) {
hasAuth = true;
}
break;
default:
// eslint-disable-next-line no-console
console.log('no ');
break;
}
if (!hasAuth) {
Message.error(t('common.noPermission'));
return;
}
await postUpdateMenu( await postUpdateMenu(
{ {
projectId: currentProjectId.value, projectId: currentProjectId.value,

View File

@ -1,6 +1,8 @@
<template> <template>
<div class="flex flex-row items-center justify-between"> <div class="flex flex-row items-center justify-between">
<a-button type="primary" @click="addUserGroup">{{ t('project.userGroup.add') }}</a-button> <a-button v-permission="['PROJECT_GROUP:READ+ADD']" type="primary" @click="addUserGroup">{{
t('project.userGroup.add')
}}</a-button>
<a-input-search <a-input-search
v-model="keyword" v-model="keyword"
:placeholder="t('project.userGroup.searchUser')" :placeholder="t('project.userGroup.searchUser')"
@ -61,9 +63,14 @@
<ms-button class="btn" :disabled="!canSave" @click="handleAuthReset">{{ <ms-button class="btn" :disabled="!canSave" @click="handleAuthReset">{{
t('system.userGroup.reset') t('system.userGroup.reset')
}}</ms-button> }}</ms-button>
<a-button class="btn" :disabled="!canSave" type="primary" @click="handleAuthSave">{{ <a-button
t('common.save') v-permission="['PROJECT_GROUP:READ+UPDATE']"
}}</a-button> class="btn"
:disabled="!canSave"
type="primary"
@click="handleAuthSave"
>{{ t('common.save') }}</a-button
>
</div> </div>
</template> </template>
</MsDrawer> </MsDrawer>

View File

@ -1,6 +1,6 @@
<template> <template>
<MsCard simple> <MsCard simple>
<div class="mb-4 flex items-center justify-between"> <div class="mb-[16px] flex items-center justify-between">
<a-button type="primary" @click="showAddProject">{{ t('system.organization.createProject') }}</a-button> <a-button type="primary" @click="showAddProject">{{ t('system.organization.createProject') }}</a-button>
<a-input-search <a-input-search
v-model="keyword" v-model="keyword"
@ -31,17 +31,35 @@
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<template v-if="record.deleted"> <template v-if="record.deleted">
<MsButton @click="handleRevokeDelete(record)">{{ t('common.revokeDelete') }}</MsButton> <MsButton v-permission="['ORGANIZATION_PROJECT:READ+RECOVER']" @click="handleRevokeDelete(record)">{{
t('common.revokeDelete')
}}</MsButton>
</template> </template>
<template v-else-if="!record.enable"> <template v-else-if="!record.enable">
<MsButton @click="handleEnableOrDisableProject(record)">{{ t('common.enable') }}</MsButton> <MsButton v-permission="['ORGANIZATION_PROJECT:READ+UPDATE']" @click="handleEnableOrDisableProject(record)">{{
<MsButton @click="handleDelete(record)">{{ t('common.delete') }}</MsButton> t('common.enable')
}}</MsButton>
<MsButton v-permission="['ORGANIZATION_PROJECT:READ+DELETE']" @click="handleDelete(record)">{{
t('common.delete')
}}</MsButton>
</template> </template>
<template v-else> <template v-else>
<MsButton @click="showAddProjectModal(record)">{{ t('common.edit') }}</MsButton> <MsButton v-permission="['ORGANIZATION_PROJECT:READ+UPDATE']" @click="showAddProjectModal(record)">{{
<MsButton @click="showAddUserModal(record)">{{ t('system.organization.addMember') }}</MsButton> t('common.edit')
<MsButton @click="handleEnableOrDisableProject(record, false)">{{ t('common.end') }}</MsButton> }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleMoreAction($event, record)"></MsTableMoreAction> <MsButton v-permission="['ORGANIZATION_PROJECT:READ+UPDATE']" @click="showAddUserModal(record)">{{
t('system.organization.addMember')
}}</MsButton>
<MsButton
v-permission="['ORGANIZATION_PROJECT:READ+UPDATE']"
@click="handleEnableOrDisableProject(record, false)"
>{{ t('common.end') }}</MsButton
>
<MsTableMoreAction
v-permission="['ORGANIZATION_PROJECT:READ+DELETE']"
:list="tableActions"
@select="handleMoreAction($event, record)"
></MsTableMoreAction>
</template> </template>
</template> </template>
</MsBaseTable> </MsBaseTable>
@ -177,6 +195,10 @@
dataIndex: 'createTime', dataIndex: 'createTime',
width: 180, width: 180,
showDrag: true, showDrag: true,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
}, },
{ {
title: 'system.organization.operation', title: 'system.organization.operation',
@ -205,9 +227,8 @@
tableKey: TableKeyEnum.ORGANIZATION_PROJECT, tableKey: TableKeyEnum.ORGANIZATION_PROJECT,
selectable: false, selectable: false,
noDisable: false, noDisable: false,
size: 'default',
showSetting: true, showSetting: true,
heightUsed: 286, heightUsed: 300,
}, },
undefined, undefined,
(record) => handleNameChange(record) (record) => handleNameChange(record)

View File

@ -36,11 +36,15 @@
ref="userRef" ref="userRef"
:keyword="currentKeyword" :keyword="currentKeyword"
:current="currentUserGroupItem" :current="currentUserGroupItem"
:delete-permission="['ORGANIZATION_USER_ROLE:READ+DELETE']"
:read-permission="['ORGANIZATION_USER_ROLE:READ']"
:update-permission="['ORGANIZATION_USER_ROLE:READ+UPDATE']"
/> />
<AuthTable <AuthTable
v-if="currentTable === 'auth' && couldShowAuth" v-if="currentTable === 'auth' && couldShowAuth"
:current="currentUserGroupItem" :current="currentUserGroupItem"
:width="bottomWidth" :width="bottomWidth"
:save-permission="['ORGANIZATION_GROUP:READ+UPDATE']"
/> />
</div> </div>
</div> </div>

View File

@ -3,8 +3,15 @@
<template #revokeDelete="{ record }"> <template #revokeDelete="{ record }">
<a-tooltip background-color="#FFFFFF"> <a-tooltip background-color="#FFFFFF">
<template #content> <template #content>
<span class="text-[var(--color-text-1)]">{{ t('system.organization.revokeDeleteToolTip') }}</span> <span>
<MsButton class="ml-[8px]" @click="handleRevokeDelete(record)">{{ t('common.revokeDelete') }}</MsButton> <span class="text-[var(--color-text-1)]">{{ t('system.organization.revokeDeleteToolTip') }}</span>
<MsButton
v-permission="['SYSTEM_ORGANIZATION_PROJECT:READ+RECOVER']"
class="ml-[8px]"
@click="handleRevokeDelete(record)"
>{{ t('common.revokeDelete') }}</MsButton
>
</span>
</template> </template>
<MsIcon v-if="record.deleted" type="icon-icon_alarm_clock" class="ml-[4px] text-[rgb(var(--danger-6))]" /> <MsIcon v-if="record.deleted" type="icon-icon_alarm_clock" class="ml-[4px] text-[rgb(var(--danger-6))]" />
</a-tooltip> </a-tooltip>
@ -93,6 +100,7 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { useTableStore } from '@/store'; import { useTableStore } from '@/store';
import { hasAnyPermission } from '@/utils/permission';
import { CreateOrUpdateSystemOrgParams, OrgProjectTableItem } from '@/models/setting/system/orgAndProject'; import { CreateOrUpdateSystemOrgParams, OrgProjectTableItem } from '@/models/setting/system/orgAndProject';
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum'; import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
@ -122,7 +130,7 @@
dataIndex: 'name', dataIndex: 'name',
width: 300, width: 300,
revokeDeletedSlot: 'revokeDelete', revokeDeletedSlot: 'revokeDelete',
editType: ColumnEditTypeEnum.INPUT, editType: hasAnyPermission(['SYSTEM_ORGANIZATIN_PROJECT:READ+UPDATE']) ? ColumnEditTypeEnum.INPUT : undefined,
showTooltip: true, showTooltip: true,
}, },
{ {

View File

@ -3,8 +3,15 @@
<template #revokeDelete="{ record }"> <template #revokeDelete="{ record }">
<a-tooltip background-color="#FFFFFF"> <a-tooltip background-color="#FFFFFF">
<template #content> <template #content>
<span class="text-[var(--color-text-1)]">{{ t('system.project.revokeDeleteToolTip') }}</span> <span>
<MsButton class="ml-[8px]" @click="handleRevokeDelete(record)">{{ t('common.revokeDelete') }}</MsButton> <span class="text-[var(--color-text-1)]">{{ t('system.project.revokeDeleteToolTip') }}</span>
<MsButton
v-permission="['SYSTEM_ORGANIZATION_PROJECT:READ+RECOVER']"
class="ml-[8px]"
@click="handleRevokeDelete(record)"
>{{ t('common.revokeDelete') }}</MsButton
>
</span>
</template> </template>
<MsIcon v-if="record.deleted" type="icon-icon_alarm_clock" class="ml-[4px] text-[rgb(var(--danger-6))]" /> <MsIcon v-if="record.deleted" type="icon-icon_alarm_clock" class="ml-[4px] text-[rgb(var(--danger-6))]" />
</a-tooltip> </a-tooltip>
@ -91,6 +98,7 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { useTableStore } from '@/store'; import { useTableStore } from '@/store';
import { hasAnyPermission } from '@/utils/permission';
import { UserItem } from '@/models/setting/log'; import { UserItem } from '@/models/setting/log';
import { CreateOrUpdateSystemProjectParams, OrgProjectTableItem } from '@/models/setting/system/orgAndProject'; import { CreateOrUpdateSystemProjectParams, OrgProjectTableItem } from '@/models/setting/system/orgAndProject';
@ -120,7 +128,7 @@
title: 'system.organization.name', title: 'system.organization.name',
dataIndex: 'name', dataIndex: 'name',
revokeDeletedSlot: 'revokeDelete', revokeDeletedSlot: 'revokeDelete',
editType: ColumnEditTypeEnum.INPUT, editType: hasAnyPermission(['SYSTEM_ORGANIZATIN_PROJECT:READ+UPDATE']) ? ColumnEditTypeEnum.INPUT : undefined,
showTooltip: true, showTooltip: true,
}, },
{ {

View File

@ -2,11 +2,16 @@
<MsCard simple> <MsCard simple>
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<div> <div>
<a-button type="primary" @click="handleAddOrganization">{{ <a-button
currentTable === 'organization' v-permission="['SYSTEM_ORGANIZATION_PROJECT:READ+ADD']"
? t('system.organization.createOrganization') type="primary"
: t('system.organization.createProject') @click="handleAddOrganization"
}}</a-button> >{{
currentTable === 'organization'
? t('system.organization.createOrganization')
: t('system.organization.createProject')
}}</a-button
>
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<a-input-search <a-input-search

View File

@ -36,11 +36,15 @@
ref="userRef" ref="userRef"
:keyword="currentKeyword" :keyword="currentKeyword"
:current="currentUserGroupItem" :current="currentUserGroupItem"
:delete-permission="['SYSTEM_USER_ROLE:READ+DELETE']"
:read-permission="['SYSTEM_USER_ROLE:READ']"
:update-permission="['SYSTEM_USER_ROLE:READ+UPDATE']"
/> />
<AuthTable <AuthTable
v-if="currentTable === 'auth' && couldShowAuth" v-if="currentTable === 'auth' && couldShowAuth"
:current="currentUserGroupItem" :current="currentUserGroupItem"
:width="bottomWidth" :width="bottomWidth"
:save-permission="['PROJECT_GROUP:READ+UPDATE']"
/> />
</div> </div>
</div> </div>