feat(系统管理): 权限&修改bug&补充xpack

This commit is contained in:
xinxin.wu 2024-01-25 20:08:04 +08:00 committed by Craftsman
parent 43f8bfd268
commit 379f46c795
40 changed files with 381 additions and 91 deletions

View File

@ -27,6 +27,7 @@ import {
DetailCaseUrl,
DownloadExcelTemplateUrl,
DownloadFileUrl,
dragSortUrl,
EditorUploadFileUrl,
exportExcelCheckUrl,
FollowerCaseUrl,
@ -83,6 +84,7 @@ import type {
DeleteCaseType,
DemandItem,
DetailCase,
DragCase,
ImportExcelType,
ModulesTreeType,
OperationFile,
@ -401,4 +403,9 @@ export function importExcelCase(data: { request: ImportExcelType; fileList: File
return MSR.uploadFile({ url: importExcelCaseUrl }, { request: data.request, fileList: data.fileList }, '');
}
// 拖拽排序
export function dragSort(data: DragCase) {
return MSR.post({ url: dragSortUrl, data });
}
export default {};

View File

@ -143,3 +143,5 @@ export const PreviewEditorImageUrl = '/attachment/download/file';
export const exportExcelCheckUrl = '/functional/case/pre-check/excel';
// 导入excel文件
export const importExcelCaseUrl = '/functional/case/import/excel';
// 用例拖拽排序
export const dragSortUrl = '/functional/case/edit/pos';

View File

@ -313,6 +313,7 @@
showTooltip: true,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
width: 200,
},
@ -321,6 +322,7 @@
dataIndex: 'name',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showTooltip: true,
width: 300,

View File

@ -188,6 +188,7 @@
showTooltip: true,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
width: 200,
},
@ -196,6 +197,7 @@
dataIndex: 'name',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showTooltip: true,
width: 300,
@ -206,6 +208,7 @@
slotName: 'method',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
width: 200,
},

View File

@ -145,6 +145,7 @@
dataIndex: 'createTime',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showInTable: true,
width: 300,

View File

@ -11,6 +11,7 @@
</div>
<a-tooltip :content="t('ms.personal.maxTip')" position="right" :disabled="apiKeyList.length < 5">
<a-button
v-permission="['SYSTEM_PERSONAL_API_KEY:READ+ADD']"
type="outline"
class="w-[60px]"
:disabled="apiKeyList.length >= 5"
@ -63,12 +64,18 @@
</div>
<div class="flex items-center justify-between px-[16px]">
<MsTableMoreAction :list="actions" trigger="click" @select="handleMoreActionSelect($event, item)">
<a-button size="mini" type="outline" class="arco-btn-outline--secondary">
<a-button
v-permission="['SYSTEM_PERSONAL_API_KEY:READ+UPDATE']"
size="mini"
type="outline"
class="arco-btn-outline--secondary"
>
{{ t('common.setting') }}
</a-button>
</MsTableMoreAction>
<a-switch
v-model:model-value="item.enable"
v-permission="['SYSTEM_PERSONAL_API_KEY:READ+UPDATE']"
size="small"
:before-change="() => handleBeforeEnableChange(item)"
type="line"
@ -77,7 +84,9 @@
</div>
<div v-if="apiKeyList.length === 0" class="col-span-2 flex w-full items-center justify-center p-[44px]">
{{ t('ms.personal.nodata') }}
<MsButton type="text" class="ml-[8px]" @click="newApiKey">{{ t('common.new') }}</MsButton>
<MsButton v-permission="['SYSTEM_PERSONAL_API_KEY:READ+ADD']" type="text" class="ml-[8px]" @click="newApiKey">{{
t('common.new')
}}</MsButton>
</div>
</a-spin>
</div>

View File

@ -87,6 +87,7 @@
name: 'apiKey',
title: t('ms.personal.apiKey'),
level: 2,
permission: ['SYSTEM_PERSONAL_API_KEY:READ'],
},
{
name: 'local',

View File

@ -21,8 +21,13 @@
import usePermission from '@/hooks/usePermission';
import appClientMenus from '@/router/app-menus';
import { useAppStore } from '@/store';
import useLicenseStore from '@/store/modules/setting/license';
import { listenerRouteChange } from '@/utils/route-listener';
import { RouteEnum } from '@/enums/routeEnum';
const licenseStore = useLicenseStore();
const copyRouters = cloneDeep(appClientMenus) as RouteRecordRaw[];
const permission = usePermission();
const appStore = useAppStore();
@ -79,9 +84,17 @@
(item) => name && item?.name && (name as string).includes(item.name as string)
);
}
appStore.setTopMenus(
currentParent?.children?.filter((item) => permission.accessRouter(item) && item.meta?.isTopMenu)
);
const filterMenuTopRouter = currentParent?.children
?.filter((item: any) => permission.accessRouter(item) && item.meta?.isTopMenu)
.filter((item: any) => {
if (item.name === RouteEnum.SETTING_SYSTEM_AUTHORIZED_MANAGEMENT) {
return licenseStore.hasLicense();
}
return true;
});
appStore.setTopMenus(filterMenuTopRouter);
setCurrentTopMenu(name as string);
return;
}

View File

@ -4,7 +4,7 @@
<div v-if="props.title" class="mb-2 font-medium">{{ props.title }}</div>
<div class="menu">
<div
v-for="(item, index) of props.menuList"
v-for="(item, index) of innerMenuList"
:key="item.name"
class="menu-item px-2"
:class="{
@ -26,6 +26,8 @@
</template>
<script setup lang="ts">
import { hasAnyPermission } from '@/utils/permission';
const props = defineProps<{
title?: string;
defaultKey?: string;
@ -33,6 +35,7 @@
title: string;
level: number;
name: string;
permission?: string[];
}[];
activeClass?: string;
}>();
@ -42,6 +45,10 @@
const currentKey = ref(props.defaultKey);
const innerMenuList = computed(() => {
return props.menuList.filter((item: any) => hasAnyPermission(item.permission));
});
watch(
() => props.defaultKey,
(val) => {

View File

@ -1,7 +1,7 @@
<template>
<MsCard class="mb-[16px]" :title="props.title" hide-back hide-footer auto-height no-content-padding no-bottom-radius>
<a-tabs v-model:active-key="innerTab" class="no-content">
<a-tab-pane v-for="item of tabList" :key="item.key" :title="item.title" />
<a-tab-pane v-for="item of permissionTabList" :key="item.key" :title="item.title" />
</a-tabs>
</MsCard>
</template>
@ -11,15 +11,21 @@
import MsCard from '@/components/pure/ms-card/index.vue';
import { hasAnyPermission } from '@/utils/permission';
const props = defineProps<{
activeTab: string;
title?: string;
tabList: { key: string; title: string }[];
tabList: { key: string; title: string; permission?: string[] }[];
}>();
const emit = defineEmits(['update:activeTab']);
const innerTab = ref(props.activeTab);
const permissionTabList = computed(() => {
return props.tabList.filter((item: any) => hasAnyPermission(item.permission));
});
watch(
() => props.activeTab,
(val) => {

View File

@ -324,3 +324,10 @@ export interface ValidateInfo {
successCount: number;
errorMessages: errorMessagesType[];
}
// 拖拽排序
export interface DragCase {
projectId: string;
targetId: string;
moveMode: 'BEFORE' | 'AFTER' | 'APPEND'[]; // 拖拽类型
moveId: string;
}

View File

@ -26,6 +26,7 @@ export interface SkipTitle {
name: string;
src: string;
active: boolean; // 是否激活
disabled: boolean;
}
export interface StepListType {

View File

@ -192,7 +192,7 @@ const ProjectManagement: AppRouteRecordRaw = {
component: () => import('@/views/project-management/fileManagement/index.vue'),
meta: {
locale: 'menu.projectManagement.fileManagement',
roles: ['*'],
roles: ['PROJECT_FILE_MANAGEMENT:READ'],
isTopMenu: true,
},
},
@ -203,7 +203,7 @@ const ProjectManagement: AppRouteRecordRaw = {
component: () => import('@/views/project-management/messageManagement/index.vue'),
meta: {
locale: 'menu.projectManagement.messageManagement',
roles: ['*'],
roles: ['PROJECT_MESSAGE:READ'],
isTopMenu: true,
},
},

View File

@ -34,7 +34,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/system/user/index.vue'),
meta: {
locale: 'menu.settings.system.user',
roles: ['*'],
roles: ['SYSTEM_USER:READ'],
isTopMenu: true,
},
},
@ -64,7 +64,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/system/config/index.vue'),
meta: {
locale: 'menu.settings.system.parameter',
roles: ['*'],
roles: ['SYSTEM_PARAMETER_SETTING_BASE:READ'],
isTopMenu: true,
},
},
@ -74,7 +74,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/system/resourcePool/index.vue'),
meta: {
locale: 'menu.settings.system.resourcePool',
roles: ['*'],
roles: ['SYSTEM_TEST_RESOURCE_POOL:READ'],
isTopMenu: true,
},
},
@ -84,7 +84,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/system/resourcePool/detail.vue'),
meta: {
locale: 'menu.settings.system.resourcePoolDetail',
roles: ['*'],
roles: ['SYSTEM_TEST_RESOURCE_POOL:READ'],
breadcrumbs: [
{
name: SettingRouteEnum.SETTING_SYSTEM_RESOURCE_POOL,

View File

@ -24,8 +24,7 @@ const useLicenseStore = defineStore('license', {
if (!result || !result.status || !result.license || !result.license.count) {
return;
}
// this.setLicenseStatus(result.status);
this.setLicenseStatus('fail');
this.setLicenseStatus(result.status);
} catch (error) {
console.log(error);
}

View File

@ -107,6 +107,7 @@
dataIndex: 'createUser',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showTooltip: true,
showDrag: true,
@ -116,6 +117,7 @@
dataIndex: 'updateUser',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showTooltip: true,
showInTable: true,
@ -125,6 +127,7 @@
dataIndex: 'updateTime',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showTooltip: true,
showInTable: true,

View File

@ -4,6 +4,7 @@
v-model:visible="showDrawerVisible"
:width="1200"
:footer="false"
:mask="false"
:title="t('caseManagement.featureCase.caseDetailTitle', { id: detailInfo?.id, name: detailInfo?.name })"
:detail-id="props.detailId"
:detail-index="props.detailIndex"
@ -11,7 +12,6 @@
:pagination="props.pagination"
:table-data="props.tableData"
:page-change="props.pageChange"
:mask-closable="false"
@loaded="loadedCase"
>
<template #titleLeft>
@ -534,6 +534,15 @@
const changeHandler = debounce(() => {
tabDetailRef.value.handleOK();
}, 300);
watch(
() => props.detailId,
(val) => {
if (val) {
updateSuccess();
}
}
);
</script>
<style scoped lang="less">

View File

@ -25,12 +25,17 @@
v-if="showType === 'list'"
v-bind="propsRes"
ref="tableRef"
filter-icon-align-left
class="mt-4"
:action-config="tableBatchActions"
@selected-change="handleTableSelect"
v-on="propsEvent"
@batch-action="handleTableBatch"
@change="changeHandler"
>
<template #num="{ record, rowIndex }">
<span class="flex w-full" @click="showCaseDetail(record.id, rowIndex)">{{ record.num }}</span>
</template>
<template #name="{ record, rowIndex }">
<a-button type="text" class="px-0" @click="showCaseDetail(record.id, rowIndex)">{{ record.name }}</a-button>
</template>
@ -78,8 +83,11 @@
</a-tooltip>
</template>
<!-- 渲染自定义字段开始TODO -->
<!-- <template v-for="item in customFieldsColumns" :key="item.slotName" #[item.slotName]="{ record }">
</template> -->
<template v-for="item in customFieldsColumns" :key="item.slotName" #[item.slotName]="{ record }">
<a-tooltip :content="getTableFields(record.customFields, item)" position="top" :mouse-enter-delay="100" mini>
<div>{{ getTableFields(record.customFields, item) }}</div>
</a-tooltip>
</template>
<!-- 渲染自定义字段结束 -->
<template #operation="{ record }">
<MsButton v-permission="['FUNCTIONAL_CASE:READ+UPDATE']" @click="operateCase(record, 'edit')">{{
@ -186,7 +194,7 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import { Message, TableChangeExtra, TableData } from '@arco-design/web-vue';
import { CustomTypeMaps, MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import { FilterFormItem, FilterResult, FilterType } from '@/components/pure/ms-advance-filter/type';
@ -213,6 +221,7 @@
batchDeleteCase,
batchMoveToModules,
deleteCaseRequest,
dragSort,
getCaseDefaultFields,
getCaseDetail,
getCaseList,
@ -230,12 +239,13 @@
CaseModuleQueryParams,
CustomAttributes,
DemandItem,
DragCase,
} from '@/models/caseManagement/featureCase';
import type { TableQueryParams } from '@/models/common';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
import { getCaseLevels, getReviewStatusClass, getStatusText } from './utils';
import { getCaseLevels, getReviewStatusClass, getStatusText, getTableFields } from './utils';
import { LabelValue } from '@arco-design/web-vue/es/tree-select/interface';
const { openModal } = useModal();
@ -332,16 +342,19 @@
const columns: MsTableColumn = [
{
title: 'caseManagement.featureCase.tableColumnID',
dataIndex: 'num',
width: 200,
showInTable: true,
sortable: {
'title': 'caseManagement.featureCase.tableColumnID',
'slotName': 'num',
'dataIndex': 'num',
'width': 200,
'showInTable': true,
'sortable': {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showTooltip: true,
ellipsis: true,
showDrag: false,
'filter-icon-align-left': true,
'showTooltip': true,
'ellipsis': true,
'showDrag': false,
},
{
title: 'caseManagement.featureCase.tableColumnName',
@ -353,6 +366,7 @@
editType: ColumnEditTypeEnum.INPUT,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
ellipsis: true,
showDrag: false,
@ -411,6 +425,7 @@
dataIndex: 'updateTime',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showInTable: true,
width: 200,
@ -972,17 +987,18 @@
async function getDefaultFields() {
const result = await getCaseDefaultFields(currentProjectId.value);
initDefaultFields.value = result.customFields;
customFieldsColumns = initDefaultFields.value.map((item: any) => {
return {
title: item.fieldName,
slotName: item.fieldId as string,
dataIndex: item.fieldId,
showTooltip: true,
showInTable: true,
showDrag: true,
width: 300,
};
});
customFieldsColumns = initDefaultFields.value
.filter((item: any) => !item.internal)
.map((item: any) => {
return {
title: item.fieldName,
slotName: item.fieldId as string,
dataIndex: item.fieldId,
showInTable: true,
showDrag: true,
width: 300,
};
});
fullColumns = [
...columns.slice(0, columns.length - 1),
@ -1072,7 +1088,6 @@
...detailResult,
moduleId: value,
customFields: getCustomMaps(detailResult),
tags: JSON.parse(detailResult.tags),
},
fileList: [],
};
@ -1104,6 +1119,36 @@
initData();
};
//
async function changeHandler(data: TableData[], extra: TableChangeExtra, currentData: TableData[]) {
if (extra && extra.dragTarget?.id) {
const params: DragCase = {
projectId: currentProjectId.value,
targetId: '',
moveMode: 'BEFORE',
moveId: extra.dragTarget.id as string,
};
const index = currentData.findIndex((item: any) => item.raw.id === extra.dragTarget?.id);
if (index > -1 && currentData[index + 1].raw) {
params.moveMode = 'AFTER';
params.targetId = currentData[index + 1].raw.id;
} else if (index > -1 && !currentData[index + 1].raw) {
if (index > -1 && currentData[index - 1].raw) {
params.moveMode = 'BEFORE';
params.targetId = currentData[index - 1].raw.id;
}
}
try {
await dragSort(params);
Message.success(t('caseManagement.featureCase.sortSuccess'));
initData();
} catch (error) {
console.log(error);
}
}
}
onBeforeMount(() => {
if (route.query.id) {
showCaseDetail(route.query.id as string, 0);

View File

@ -237,6 +237,7 @@
showInTable: true,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showTooltip: true,
ellipsis: true,
@ -251,6 +252,7 @@
width: 300,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
ellipsis: true,
showDrag: false,
@ -325,6 +327,7 @@
showInTable: true,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
width: 200,
showDrag: true,
@ -343,6 +346,7 @@
dataIndex: 'updateTime',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showInTable: true,
width: 200,

View File

@ -68,6 +68,7 @@
dataIndex: 'reviewName',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showTooltip: true,
width: 300,

View File

@ -255,6 +255,7 @@
showTooltip: true,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
width: 200,
},
@ -263,6 +264,7 @@
dataIndex: 'name',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showTooltip: true,
width: 300,

View File

@ -132,4 +132,31 @@ export function getCaseLevels(customFields: CustomAttributes[]): CaseLevel {
);
}
// 处理自定义字段
export function getTableFields(customFields: any, itemDataIndex: any) {
const multipleExcludes = ['MULTIPLE_SELECT', 'CHECKBOX', 'MULTIPLE_MEMBER'];
const selectExcludes = ['MEMBER', 'RADIO', 'SELECT'];
const currentColumnData = customFields.find((item: any) => itemDataIndex.dataIndex === item.fieldId);
// 处理多选项
if (multipleExcludes.includes(currentColumnData.type)) {
const selectValue = JSON.parse(currentColumnData.defaultValue);
return currentColumnData.options
.filter((item: any) => selectValue.includes(item.value))
.map((it: any) => it.text)
.join(',');
}
if (currentColumnData.type === 'MULTIPLE_INPUT') {
// 处理标签形式
return JSON.parse(currentColumnData.defaultValue).join('');
}
if (selectExcludes.includes(currentColumnData.type)) {
return currentColumnData.options
.filter((item: any) => currentColumnData.defaultValue === item.value)
.map((it: any) => it.text)
.join();
}
return currentColumnData.defaultValue;
}
export default {};

View File

@ -250,4 +250,5 @@ export default {
'caseManagement.featureCase.cancelDependencyContent': 'Cancel after impact test plan related statistics',
'caseManagement.featureCase.associatedSuccess': 'Associated with success',
'caseManagement.featureCase.defectSource': 'defect Source',
'caseManagement.featureCase.sortSuccess': 'Sort successfully',
};

View File

@ -245,4 +245,5 @@ export default {
'caseManagement.featureCase.cancelDependencyContent': '取消后,影响测试计划相关统计',
'caseManagement.featureCase.associatedSuccess': '关联成功',
'caseManagement.featureCase.defectSource': '缺陷来源',
'caseManagement.featureCase.sortSuccess': '排序成功',
};

View File

@ -93,14 +93,14 @@
const loginConfig = useStorage('login-config', {
rememberPassword: true,
username: 'admin',
password: 'Calong@2015',
username: '',
password: '',
});
const userInfo = reactive({
authenticate: 'LOCAL',
username: 'admin',
password: 'Calong@2015',
username: '',
password: '',
});
const handleSubmit = async ({

View File

@ -164,6 +164,7 @@
dataIndex: 'createTime',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showInTable: true,
width: 300,

View File

@ -27,6 +27,7 @@
<MsIcon type="icon-icon-maybe_outlined" class="mr-[8px] cursor-pointer hover:text-[rgb(var(--primary-5))]" />
</a-tooltip>
<MsButton
v-permission="['PROJECT_FILE_MANAGEMENT:READ+DOWNLOAD']"
type="icon"
status="secondary"
class="!rounded-[var(--border-radius-small)] !text-[var(--color-text-1)]"
@ -39,6 +40,7 @@
</MsButton>
<MsButton
v-if="detail?.storage === 'GIT'"
v-permission="['PROJECT_FILE_MANAGEMENT:READ+UPDATE']"
type="icon"
status="secondary"
class="!rounded-[var(--border-radius-small)] !text-[var(--color-text-1)]"
@ -132,7 +134,9 @@
:all-names="[]"
@update-desc-finish="detailDrawerRef?.initDetail"
>
<MsButton class="ml-[8px]"><MsIcon type="icon-icon_edit_outlined"></MsIcon></MsButton>
<MsButton v-permission="['PROJECT_FILE_MANAGEMENT:READ+UPDATE']" class="ml-[8px]"
><MsIcon type="icon-icon_edit_outlined"></MsIcon
></MsButton>
</popConfirm>
</template>
</div>
@ -167,7 +171,12 @@
v-on="caseTableEvent"
>
<template #action="{ record }">
<MsButton type="text" class="mr-[8px]" @click="updateCase(record)">
<MsButton
v-permission="['PROJECT_FILE_MANAGEMENT:READ+UPDATE']"
type="text"
class="mr-[8px]"
@click="updateCase(record)"
>
{{ t('project.fileManagement.updateCaseFile') }}
</MsButton>
</template>

View File

@ -1,7 +1,9 @@
<template>
<div class="flex h-[calc(100vh-88px)] flex-col overflow-hidden p-[24px]">
<div class="header">
<a-button type="primary" @click="handleAddClick">{{ t('project.fileManagement.addFile') }}</a-button>
<a-button v-permission="['PROJECT_FILE_MANAGEMENT:READ+ADD']" type="primary" @click="handleAddClick">{{
t('project.fileManagement.addFile')
}}</a-button>
<div class="header-right">
<a-select v-model="tableFileType" class="w-[240px]" :loading="fileTypeLoading" @change="searchList">
<template #prefix>
@ -562,15 +564,18 @@
{
label: 'project.fileManagement.download',
eventTag: 'download',
permission: ['PROJECT_FILE_MANAGEMENT:READ+DOWNLOAD'],
},
{
label: 'project.fileManagement.move',
eventTag: 'move',
permission: ['PROJECT_FILE_MANAGEMENT:READ+UPDATE'],
},
{
label: 'project.fileManagement.delete',
eventTag: 'delete',
danger: true,
permission: ['PROJECT_FILE_MANAGEMENT:READ+DELETE'],
},
],
};
@ -579,11 +584,13 @@
{
label: 'project.fileManagement.download',
eventTag: 'download',
permission: ['PROJECT_FILE_MANAGEMENT:READ+DOWNLOAD'],
},
{
label: 'project.fileManagement.delete',
eventTag: 'delete',
danger: true,
permission: ['PROJECT_FILE_MANAGEMENT:READ+DELETE'],
},
],
};

View File

@ -18,7 +18,7 @@
>
<template #footer>
<div class="mb-[6px] mt-[4px] p-[3px_8px]">
<MsButton type="text" @click="emit('createRobot')">
<MsButton v-permission="['PROJECT_MESSAGE:READ+ADD']" type="text" @click="emit('createRobot')">
<MsIcon type="icon-icon_add_outlined" class="mr-[8px] text-[rgb(var(--primary-6))]" size="14" />
{{ t('project.messageManagement.createBot') }}
</MsButton>
@ -86,6 +86,7 @@
<div v-if="!record.children && record.projectRobotConfigMap?.[dataIndex as string]" class="flex items-center">
<a-switch
v-model:model-value="record.projectRobotConfigMap[dataIndex as string].enable"
v-permission="['PROJECT_MESSAGE:READ+UPDATE']"
:before-change="(val) => handleChangeIntercept(!!val, record, dataIndex as string)"
size="small"
type="line"
@ -104,7 +105,13 @@
/>
</template>
</a-popover>
<MsButton type="button" @click="editRobot(record, dataIndex as string)">{{ t('common.setting') }}</MsButton>
<MsButton
v-permission="['PROJECT_MESSAGE:READ+UPDATE']"
v-xpack
type="button"
@click="editRobot(record, dataIndex as string)"
>{{ t('common.setting') }}</MsButton
>
</div>
<span v-else></span>
</template>

View File

@ -7,7 +7,7 @@
<span class="text-[14px]">{{ t('project.messageManagement.notRemind') }}</span>
</template>
</a-alert>
<a-button type="primary" class="mb-[16px]" @click="handleCreateClick">
<a-button v-permission="['PROJECT_MESSAGE:READ+ADD']" type="primary" class="mb-[16px]" @click="handleCreateClick">
{{ t('project.messageManagement.createBot') }}
</a-button>
<div
@ -52,6 +52,7 @@
<div class="flex items-center justify-between leading-[24px]">
<div v-if="!['IN_SITE', 'MAIL'].includes(robot.platform)">
<a-button
v-permission="['PROJECT_MESSAGE:READ+UPDATE']"
type="outline"
size="mini"
class="arco-btn-outline--secondary mr-[8px]"
@ -59,7 +60,13 @@
>
{{ t('common.edit') }}
</a-button>
<a-button type="outline" size="mini" class="arco-btn-outline--secondary" @click="delRobot(robot)">
<a-button
v-permission="['PROJECT_MESSAGE:READ+DELETE']"
type="outline"
size="mini"
class="arco-btn-outline--secondary"
@click="delRobot(robot)"
>
{{ t('common.delete') }}
</a-button>
</div>

View File

@ -25,16 +25,22 @@
<div class="flex items-center justify-between">
<span class="font-normal">{{ t(item.title) }}</span>
<span>
<a-button
v-for="links of item.skipTitle"
:key="links.name"
size="mini"
class="ml-3 px-0 text-sm"
type="text"
@click.stop="jumpHandler(links)"
<a-tooltip
:content="isHasSystemPermission ? '' : t('organization.service.noPermissionsTip')"
position="bottom"
>
{{ t(links.name) }}
</a-button>
<a-button
v-for="links of item.skipTitle"
:key="links.name"
size="mini"
class="ml-3 px-0 text-sm"
type="text"
:disabled="!links.disabled"
@click.stop="jumpHandler(links)"
>
{{ t(links.name) }}
</a-button>
</a-tooltip>
</span>
</div>
<div class="text-xs text-[var(--color-text-4)]">
@ -61,11 +67,20 @@
import ServiceList from './components/serviceList.vue';
import { useI18n } from '@/hooks/useI18n';
import useUserStore from '@/store/modules/user/index';
import { openWindow } from '@/utils/index';
import { hasAnyPermission } from '@/utils/permission';
import type { SkipTitle, StepListType } from '@/models/setting/serviceIntegration';
import { SettingRouteEnum } from '@/enums/routeEnum';
const userStore = useUserStore();
const isHasSystemPermission = computed(() => {
const { systemPermissions } = userStore.currentRole;
return hasAnyPermission(systemPermissions, ['SYSTEM']) as boolean;
});
const { t } = useI18n();
const router = useRouter();
const cardContent = ref<StepListType[]>([
@ -78,11 +93,13 @@
name: 'organization.service.developmentDoc',
src: 'https://github.com/metersphere/metersphere-platform-plugin/wiki/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97',
active: false,
disabled: false,
},
{
name: 'organization.service.downPlugin',
src: 'https://github.com/metersphere/metersphere-platform-plugin',
active: false,
disabled: false,
},
],
step: '@/assets/images/ms_plugindownload.jpg',
@ -97,6 +114,7 @@
name: 'organization.service.jumpPlugin',
src: '',
active: true,
disabled: isHasSystemPermission.value,
},
],
step: '@/assets/images/ms_configplugin.jpg',

View File

@ -50,4 +50,5 @@ export default {
'organization.service.closeSuccess': 'Disable successfully',
'organization.service.configSuccess': 'Configuration successfully',
'organization.service.updateSuccess': 'Update successfully',
'organization.service.noPermissionsTip': 'You do not have operation permission, please contact the administrator',
};

View File

@ -45,4 +45,5 @@ export default {
'organization.service.closeSuccess': '禁用成功',
'organization.service.configSuccess': '配置成功',
'organization.service.updateSuccess': '更新成功',
'organization.service.noPermissionsTip': '您没有操作权限,请联系管理员',
};

View File

@ -2,7 +2,7 @@
<div>
<MsCard :loading="loading" simple>
<div class="mb-4 flex items-center justify-between">
<a-button type="primary" @click="createAuth">
<a-button v-permission="['SYSTEM_PARAMETER_SETTING_AUTH:READ+ADD']" type="primary" @click="createAuth">
{{ t('system.config.auth.add') }}
</a-button>
</div>
@ -11,12 +11,24 @@
<a-button type="text" class="px-0" @click="openAuthDetail(record.id)">{{ record.name }}</a-button>
</template>
<template #action="{ record }">
<MsButton @click="editAuth(record)">{{ t('system.config.auth.edit') }}</MsButton>
<MsButton v-if="record.enable" @click="disabledAuth(record)">
<MsButton v-permission="['SYSTEM_PARAMETER_SETTING_AUTH:READ+UPDATE']" @click="editAuth(record)">{{
t('system.config.auth.edit')
}}</MsButton>
<MsButton
v-if="record.enable"
v-permission="['SYSTEM_PARAMETER_SETTING_AUTH:READ+UPDATE']"
@click="disabledAuth(record)"
>
{{ t('system.config.auth.disable') }}
</MsButton>
<MsButton v-else @click="enableAuth(record)">{{ t('system.config.auth.enable') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
<MsButton v-else v-permission="['SYSTEM_PARAMETER_SETTING_AUTH:READ+UPDATE']" @click="enableAuth(record)">{{
t('system.config.auth.enable')
}}</MsButton>
<MsTableMoreAction
v-permission="['SYSTEM_PARAMETER_SETTING_AUTH:READ+DELETE']"
:list="tableActions"
@select="handleSelect($event, record)"
></MsTableMoreAction>
</template>
</ms-base-table>
</MsCard>

View File

@ -3,7 +3,12 @@
<MsCard class="mb-[16px]" :loading="baseloading" simple auto-height>
<div class="mb-[16px] flex justify-between">
<div class="text-[var(--color-text-000)]">{{ t('system.config.baseInfo') }}</div>
<a-button type="outline" size="mini" @click="baseInfoDrawerVisible = true">
<a-button
v-permission="['SYSTEM_PARAMETER_SETTING_BASE:READ+UPDATE']"
type="outline"
size="mini"
@click="baseInfoDrawerVisible = true"
>
{{ t('system.config.update') }}
</a-button>
</div>
@ -12,7 +17,12 @@
<MsCard class="mb-[16px]" :loading="emailLoading" simple auto-height>
<div class="mb-[16px] flex justify-between">
<div class="text-[var(--color-text-000)]">{{ t('system.config.emailConfig') }}</div>
<a-button type="outline" size="mini" @click="emailConfigDrawerVisible = true">
<a-button
v-permission="['SYSTEM_PARAMETER_SETTING_BASE:READ+UPDATE']"
type="outline"
size="mini"
@click="emailConfigDrawerVisible = true"
>
{{ t('system.config.update') }}
</a-button>
</div>

View File

@ -39,7 +39,12 @@
<div class="config-content">
<div class="config-title !mb-[8px] flex items-center justify-between">
{{ t('system.config.page.pagePreview') }}
<MsButton class="!leading-none" @click="resetLoginPageConfig">{{ t('system.config.page.reset') }}</MsButton>
<MsButton
v-permission="['SYSTEM_PARAMETER_SETTING_DISPLAY:READ+UPDATE']"
class="!leading-none"
@click="resetLoginPageConfig"
>{{ t('system.config.page.reset') }}</MsButton
>
</div>
<!-- 登录页预览盒子 -->
<div :class="['config-preview', currentLocale === 'en-US' ? 'config-preview--en' : '']">
@ -90,7 +95,12 @@
size-unit="KB"
:auto-upload="false"
>
<a-button type="outline" class="arco-btn-outline--secondary" size="mini">
<a-button
v-permission="['SYSTEM_PARAMETER_SETTING_DISPLAY:READ+UPDATE']"
type="outline"
class="arco-btn-outline--secondary"
size="mini"
>
{{ t('system.config.page.replace') }}
</a-button>
</MsUpload>
@ -118,7 +128,12 @@
size-unit="KB"
:auto-upload="false"
>
<a-button type="outline" class="arco-btn-outline--secondary" size="mini">
<a-button
v-permission="['SYSTEM_PARAMETER_SETTING_DISPLAY:READ+UPDATE']"
type="outline"
class="arco-btn-outline--secondary"
size="mini"
>
{{ t('system.config.page.replace') }}
</a-button>
</MsUpload>
@ -148,7 +163,12 @@
size-unit="MB"
:auto-upload="false"
>
<a-button type="outline" class="arco-btn-outline--secondary" size="mini">
<a-button
v-permission="['SYSTEM_PARAMETER_SETTING_DISPLAY:READ+UPDATE']"
type="outline"
class="arco-btn-outline--secondary"
size="mini"
>
{{ t('system.config.page.replace') }}
</a-button>
</MsUpload>
@ -199,7 +219,12 @@
<div class="config-content border border-solid border-[var(--color-text-n8)] !bg-white">
<div class="config-title !mb-[8px] flex items-center justify-between">
{{ t('system.config.page.pagePreview') }}
<MsButton class="!leading-none" @click="resetPlatformConfig">{{ t('system.config.page.reset') }}</MsButton>
<MsButton
v-permission="['SYSTEM_PARAMETER_SETTING_DISPLAY:READ+UPDATE']"
class="!leading-none"
@click="resetPlatformConfig"
>{{ t('system.config.page.reset') }}</MsButton
>
</div>
<!-- 平台主页预览盒子 -->
<div :class="['config-preview', '!h-[290px]', currentLocale === 'en-US' ? '!h-[340px]' : '']">
@ -252,7 +277,12 @@
size-unit="MB"
:auto-upload="false"
>
<a-button type="outline" class="arco-btn-outline--secondary" size="mini">
<a-button
v-permission="['SYSTEM_PARAMETER_SETTING_DISPLAY:READ+UPDATE']"
type="outline"
class="arco-btn-outline--secondary"
size="mini"
>
{{ t('system.config.page.replace') }}
</a-button>
</MsUpload>
@ -299,8 +329,10 @@
class="fixed bottom-0 right-[16px] z-[999] flex justify-between bg-white p-[24px] shadow-[0_-1px_4px_rgba(2,2,2,0.1)]"
:style="{ width: `calc(100% - ${menuWidth + 16}px)` }"
>
<a-button type="secondary" @click="resetAll">{{ t('system.config.page.resetAll') }}</a-button>
<a-button type="primary" @click="beforeSave">
<a-button v-permission="['SYSTEM_PARAMETER_SETTING_DISPLAY:READ+UPDATE']" type="secondary" @click="resetAll">{{
t('system.config.page.resetAll')
}}</a-button>
<a-button v-permission="['SYSTEM_PARAMETER_SETTING_DISPLAY:READ+UPDATE']" type="primary" @click="beforeSave">
{{ t('system.config.page.save') }}
</a-button>
</div>

View File

@ -31,10 +31,10 @@
const isInitMemoryCleanup = ref(activeTab.value === 'memoryCleanup');
const authConfigRef = ref<AuthConfigInstance | null>();
const tabList = ref([
{ key: 'baseConfig', title: t('system.config.baseConfig') },
{ key: 'pageConfig', title: t('system.config.pageConfig') },
{ key: 'authConfig', title: t('system.config.authConfig') },
{ key: 'memoryCleanup', title: t('system.config.memoryCleanup') },
{ key: 'baseConfig', title: t('system.config.baseConfig'), permission: ['SYSTEM_PARAMETER_SETTING_BASE:READ'] },
{ key: 'pageConfig', title: t('system.config.pageConfig'), permission: ['SYSTEM_PARAMETER_SETTING_DISPLAY:READ'] },
{ key: 'authConfig', title: t('system.config.authConfig'), permission: ['SYSTEM_PARAMETER_SETTING_AUTH:READ'] },
{ key: 'memoryCleanup', title: t('system.config.memoryCleanup'), permission: [] },
]);
watch(

View File

@ -1,7 +1,7 @@
<template>
<MsCard :loading="loading" simple>
<div class="mb-4 flex items-center justify-between">
<a-button v-xpack type="primary" @click="addPool">
<a-button v-permission="['SYSTEM_TEST_RESOURCE_POOL:READ+ADD']" v-xpack type="primary" @click="addPool">
{{ t('system.resourcePool.createPool') }}
</a-button>
<a-input-search
@ -18,12 +18,25 @@
<a-button type="text" class="px-0" @click="showPoolDetail(record.id)">{{ record.name }}</a-button>
</template>
<template #action="{ record }">
<MsButton @click="editPool(record)">{{ t('system.resourcePool.editPool') }}</MsButton>
<MsButton v-if="record.enable" v-xpack @click="disabledPool(record)">
<MsButton v-permission="['SYSTEM_TEST_RESOURCE_POOL:READ+UPDATE']" @click="editPool(record)">{{
t('system.resourcePool.editPool')
}}</MsButton>
<MsButton
v-if="record.enable"
v-permission="['SYSTEM_TEST_RESOURCE_POOL:READ+UPDATE']"
v-xpack
@click="disabledPool(record)"
>
{{ t('system.resourcePool.tableDisable') }}
</MsButton>
<MsButton v-else v-xpack @click="enablePool(record)">{{ t('system.resourcePool.tableEnable') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
<MsButton v-else v-permission="['SYSTEM_TEST_RESOURCE_POOL:READ+UPDATE']" v-xpack @click="enablePool(record)">{{
t('system.resourcePool.tableEnable')
}}</MsButton>
<MsTableMoreAction
v-permission="['SYSTEM_TEST_RESOURCE_POOL:READ+DELETE']"
:list="tableActions"
@select="handleSelect($event, record)"
></MsTableMoreAction>
</template>
</ms-base-table>
</MsCard>
@ -40,7 +53,13 @@
show-description
>
<template #tbutton>
<a-button type="outline" size="mini" :disabled="drawerLoading" @click="editPool(activePool)">
<a-button
v-permission="['SYSTEM_TEST_RESOURCE_POOL:READ+UPDATE']"
type="outline"
size="mini"
:disabled="drawerLoading"
@click="editPool(activePool)"
>
{{ t('system.resourcePool.editPool') }}
</a-button>
</template>

View File

@ -2,13 +2,15 @@
<MsCard simple>
<div class="mb-4 flex items-center justify-between">
<div>
<a-button class="mr-3" type="primary" @click="showUserModal('create')">
<a-button v-permission="['SYSTEM_USER:READ+ADD']" class="mr-3" type="primary" @click="showUserModal('create')">
{{ t('system.user.createUser') }}
</a-button>
<a-button class="mr-3" type="outline" @click="showEmailInviteModal">
<a-button v-permission="['SYSTEM_USER_INVITE']" class="mr-3" type="outline" @click="showEmailInviteModal">
{{ t('system.user.emailInvite') }}
</a-button>
<a-button class="mr-3" type="outline" @click="showImportModal">{{ t('system.user.importUser') }}</a-button>
<a-button v-permission="['SYSTEM_USER:READ+IMPORT']" class="mr-3" type="outline" @click="showImportModal">{{
t('system.user.importUser')
}}</a-button>
</div>
<a-input-search
v-model:model-value="keyword"
@ -28,10 +30,14 @@
<template #action="{ record }">
<template v-if="!record.enable">
<MsButton @click="enableUser(record)">{{ t('system.user.enable') }}</MsButton>
<MsButton @click="deleteUser(record)">{{ t('system.user.delete') }}</MsButton>
<MsButton v-permission="['SYSTEM_USER:READ+DELETE']" @click="deleteUser(record)">{{
t('system.user.delete')
}}</MsButton>
</template>
<template v-else>
<MsButton @click="showUserModal('edit', record)">{{ t('system.user.editUser') }}</MsButton>
<MsButton v-permission="['SYSTEM_USER:READ+UPDATE']" @click="showUserModal('edit', record)">{{
t('system.user.editUser')
}}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
</template>
</template>
@ -470,28 +476,34 @@
{
label: 'system.user.batchActionAddProject',
eventTag: 'batchAddProject',
permission: ['SYSTEM_USER:READ+ADD'],
},
{
label: 'system.user.batchActionAddUserGroup',
eventTag: 'batchAddUserGroup',
permission: ['SYSTEM_USER:READ+ADD'],
},
{
label: 'system.user.batchActionAddOrganization',
eventTag: 'batchAddOrganization',
permission: ['SYSTEM_USER:READ+ADD'],
},
],
moreAction: [
{
label: 'system.user.resetPassword',
eventTag: 'resetPassword',
permission: ['SYSTEM_USER:READ+UPDATE'],
},
{
label: 'system.user.disable',
eventTag: 'disabled',
permission: ['SYSTEM_USER:READ+UPDATE'],
},
{
label: 'system.user.enable',
eventTag: 'enable',
permission: ['SYSTEM_USER:READ+UPDATE'],
},
{
isDivider: true,
@ -500,6 +512,7 @@
label: 'system.user.delete',
eventTag: 'delete',
danger: true,
permission: ['SYSTEM_USER:READ+DELETE'],
},
],
};

View File

@ -121,6 +121,7 @@
editType: ColumnEditTypeEnum.INPUT,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
ellipsis: true,
showDrag: false,
@ -186,6 +187,7 @@
showInTable: true,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
width: 200,
showDrag: true,