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 }"
>
<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>
</template>
@ -101,6 +103,7 @@
const props = withDefaults(
defineProps<{
current: CurrentUserGroupItem;
savePermission?: string[];
width?: string;
showBottom?: boolean;
scroll?: {

View File

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

View File

@ -1,7 +1,6 @@
<template>
<MsBaseTable class="mt-[16px]" v-bind="propsRes" v-on="propsEvent">
<template #quickCreate>
<!-- <a-button type="primary" @click="handleAddUser">{{ t('system.userGroup.quickAddUser') }}</a-button> -->
<template v-if="hasAnyPermission(props.updatePermission || [])" #quickCreate>
<MsConfirmUserSelector :ok-loading="okLoading" v-bind="userSelectorProps" @confirm="handleAddMember" />
</template>
<template #action="{ record }">
@ -33,6 +32,7 @@
} from '@/api/modules/setting/usergroup';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { hasAnyPermission } from '@/utils/permission';
import { CurrentUserGroupItem, UserTableItem } from '@/models/setting/usergroup';
import { AuthScopeEnum } from '@/enums/commonEnum';
@ -48,6 +48,9 @@
const props = defineProps<{
keyword: string;
current: CurrentUserGroupItem;
deletePermission?: string[];
readPermission?: string[];
updatePermission?: string[];
}>();
const userSelectorProps = computed(() => {
@ -112,26 +115,37 @@
heightUsed: 288,
});
const handlePermission = (permission: string[], cb: () => void) => {
if (!hasAnyPermission(permission)) {
Message.error(t('common.noPermission'));
return false;
}
cb();
};
const fetchData = async () => {
setKeyword(props.keyword);
await loadList();
handlePermission(props.readPermission || [], async () => {
setKeyword(props.keyword);
await loadList();
});
};
const handleRemove = async (record: UserTableItem) => {
try {
if (systemType === AuthScopeEnum.SYSTEM) {
await deleteUserFromUserGroup(record.id);
} else if (systemType === AuthScopeEnum.ORGANIZATION) {
await deleteOrgUserFromUserGroup({
organizationId: currentOrgId.value,
userRoleId: props.current.id,
userIds: [record.id],
});
handlePermission(props.deletePermission || [], async () => {
try {
if (systemType === AuthScopeEnum.SYSTEM) {
await deleteUserFromUserGroup(record.id);
} else if (systemType === AuthScopeEnum.ORGANIZATION) {
await deleteOrgUserFromUserGroup({
organizationId: currentOrgId.value,
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">
{{ t(props.cancelText || 'ms.drawer.cancel') }}
</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') }}
</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') }}
</a-button>
</div>
@ -93,6 +105,7 @@
showSkeleton?: boolean; //
okLoading?: boolean;
okDisabled?: boolean;
okPermission?: string[]; //
okText?: string;
cancelText?: string;
saveContinueText?: string;
@ -110,6 +123,7 @@
showContinue: false,
popupContainer: 'body',
disabledWidthDrag: false,
okPermission: () => [], //
});
const emit = defineEmits(['update:visible', 'confirm', 'cancel', 'continue']);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@
:min="0"
hide-button
size="small"
:disabled="props.disabled"
@press-enter="handleEnter(false)"
@blur="handleEnter(true)"
>
@ -14,6 +15,7 @@
v-model:model-value="current.type"
size="small"
class="max-w-[64px]"
:disabled="props.disabled"
:options="option"
:trigger-props="{ autoFitPopupMinWidth: true }"
@change="handleEnter(false)"
@ -28,7 +30,7 @@
const { t } = useI18n();
const props = defineProps<{ modelValue?: string; defaultValue?: string }>();
const props = defineProps<{ modelValue?: string; defaultValue?: string; disabled?: boolean }>();
const emit = defineEmits<{
(e: 'update:modelValue', 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.noResource': 'No resource, please contact the administrator',
'common.noSelectProject': 'No optional items available',
'common.noPermission': 'No permission',
};

View File

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

View File

@ -44,7 +44,15 @@ const ProjectManagement: AppRouteRecordRaw = {
component: () => import('@/views/project-management/projectAndPermission/menuManagement/menuManagement.vue'),
meta: {
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'),
meta: {
locale: 'project.menu.API_ERROR_REPORT_RULE',
roles: ['*'],
roles: ['PROJECT_APPLICATION_API:READ'],
breadcrumbs: [
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT,

View File

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

View File

@ -24,11 +24,13 @@
import MsMenuPanel from '@/components/pure/ms-menu-panel/index.vue';
import { useI18n } from '@/hooks/useI18n';
import usePermission from '@/hooks/usePermission';
import useLicenseStore from '@/store/modules/setting/license';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
const { t } = useI18n();
const permission = usePermission();
const router = useRouter();
const route = useRoute();
@ -47,7 +49,7 @@
}
return [];
}
const menuList = ref([
const sourceMenuList = [
{
key: 'project',
title: t('project.permission.project'),
@ -85,7 +87,18 @@
level: 2,
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>('');

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
<template>
<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
v-model="keyword"
:placeholder="t('project.userGroup.searchUser')"
@ -61,9 +63,14 @@
<ms-button class="btn" :disabled="!canSave" @click="handleAuthReset">{{
t('system.userGroup.reset')
}}</ms-button>
<a-button class="btn" :disabled="!canSave" type="primary" @click="handleAuthSave">{{
t('common.save')
}}</a-button>
<a-button
v-permission="['PROJECT_GROUP:READ+UPDATE']"
class="btn"
:disabled="!canSave"
type="primary"
@click="handleAuthSave"
>{{ t('common.save') }}</a-button
>
</div>
</template>
</MsDrawer>

View File

@ -1,6 +1,6 @@
<template>
<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-input-search
v-model="keyword"
@ -31,17 +31,35 @@
</template>
<template #operation="{ record }">
<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 v-else-if="!record.enable">
<MsButton @click="handleEnableOrDisableProject(record)">{{ t('common.enable') }}</MsButton>
<MsButton @click="handleDelete(record)">{{ t('common.delete') }}</MsButton>
<MsButton v-permission="['ORGANIZATION_PROJECT:READ+UPDATE']" @click="handleEnableOrDisableProject(record)">{{
t('common.enable')
}}</MsButton>
<MsButton v-permission="['ORGANIZATION_PROJECT:READ+DELETE']" @click="handleDelete(record)">{{
t('common.delete')
}}</MsButton>
</template>
<template v-else>
<MsButton @click="showAddProjectModal(record)">{{ t('common.edit') }}</MsButton>
<MsButton @click="showAddUserModal(record)">{{ t('system.organization.addMember') }}</MsButton>
<MsButton @click="handleEnableOrDisableProject(record, false)">{{ t('common.end') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleMoreAction($event, record)"></MsTableMoreAction>
<MsButton v-permission="['ORGANIZATION_PROJECT:READ+UPDATE']" @click="showAddProjectModal(record)">{{
t('common.edit')
}}</MsButton>
<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>
</MsBaseTable>
@ -177,6 +195,10 @@
dataIndex: 'createTime',
width: 180,
showDrag: true,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
},
{
title: 'system.organization.operation',
@ -205,9 +227,8 @@
tableKey: TableKeyEnum.ORGANIZATION_PROJECT,
selectable: false,
noDisable: false,
size: 'default',
showSetting: true,
heightUsed: 286,
heightUsed: 300,
},
undefined,
(record) => handleNameChange(record)

View File

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

View File

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

View File

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

View File

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

View File

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