feat(功能用例): 用例导入联调&bug修改&部分权限&非路由xpack入口控制

This commit is contained in:
xinxin.wu 2024-01-25 11:26:52 +08:00 committed by 刘瑞斌
parent 9117526096
commit 4305aa9add
48 changed files with 468 additions and 194 deletions

View File

@ -53,8 +53,10 @@
onBeforeMount(async () => { onBeforeMount(async () => {
try { try {
appStore.initSystemVersion(); // appStore.initSystemVersion(); //
appStore.initPageConfig(); //
licenseStore.getValidateLicense(); // license licenseStore.getValidateLicense(); // license
if (licenseStore.hasLicense()) {
appStore.initPageConfig(); //
}
// url url // url url
const isInitUrl = getLocalStorage('isInitUrl'); // url const isInitUrl = getLocalStorage('isInitUrl'); // url
if (isInitUrl === 'true') return; if (isInitUrl === 'true') return;

View File

@ -55,6 +55,7 @@ import {
GetSearchCustomFieldsUrl, GetSearchCustomFieldsUrl,
getTransferTreeUrl, getTransferTreeUrl,
GetTrashCaseModuleTreeUrl, GetTrashCaseModuleTreeUrl,
importExcelCaseUrl,
MoveCaseModuleTreeUrl, MoveCaseModuleTreeUrl,
PreviewEditorImageUrl, PreviewEditorImageUrl,
PreviewFileUrl, PreviewFileUrl,
@ -395,5 +396,9 @@ export function editorUploadFile(data: { fileList: File[] }) {
export function editorPreviewImages(data: PreviewImages) { export function editorPreviewImages(data: PreviewImages) {
return MSR.post({ url: PreviewEditorImageUrl, data }); return MSR.post({ url: PreviewEditorImageUrl, data });
} }
// 导入excel
export function importExcelCase(data: { request: ImportExcelType; fileList: File[] }) {
return MSR.uploadFile({ url: importExcelCaseUrl }, { request: data.request, fileList: data.fileList }, '');
}
export default {}; export default {};

View File

@ -141,3 +141,5 @@ export const EditorUploadFileUrl = '/attachment/upload/temp/file';
export const PreviewEditorImageUrl = '/attachment/download/file'; export const PreviewEditorImageUrl = '/attachment/download/file';
// 导入excel文件检查 // 导入excel文件检查
export const exportExcelCheckUrl = '/functional/case/pre-check/excel'; export const exportExcelCheckUrl = '/functional/case/pre-check/excel';
// 导入excel文件
export const importExcelCaseUrl = '/functional/case/import/excel';

View File

@ -10,7 +10,7 @@
<template #content> <template #content>
<a-upload <a-upload
ref="uploadRef" ref="uploadRef"
v-model:file-list="fileList" v-model:file-list="innerFileList"
:auto-upload="false" :auto-upload="false"
:show-file-list="false" :show-file-list="false"
:before-upload="beforeUpload" :before-upload="beforeUpload"
@ -38,7 +38,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { useVModel } from '@vueuse/core';
import type { MsFileItem } from '@/components/pure/ms-upload/types'; import type { MsFileItem } from '@/components/pure/ms-upload/types';
@ -46,19 +46,26 @@
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{
fileList: MsFileItem[];
}>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'upload', file: File): void; (e: 'upload', file: File): void;
(e: 'change', _fileList: MsFileItem[], fileItem: MsFileItem): void; (e: 'change', _fileList: MsFileItem[], fileItem: MsFileItem): void;
(e: 'linkFile'): void; (e: 'linkFile'): void;
(e: 'update:fileList', fileList: MsFileItem[]): void;
}>(); }>();
const fileList = ref<MsFileItem[]>([]); // const innerFileList = ref<MsFileItem[]>([]);
const innerFileList = useVModel(props, 'fileList', emit);
function beforeUpload(file: File) { function beforeUpload(file: File) {
emit('upload', file); emit('upload', file);
} }
function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) { function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) {
innerFileList.value = _fileList;
emit('change', _fileList, fileItem); emit('change', _fileList, fileItem);
} }

View File

@ -55,9 +55,13 @@
<a-radio value="commonScript">{{ t('project.commonScript.commonScript') }}</a-radio> <a-radio value="commonScript">{{ t('project.commonScript.commonScript') }}</a-radio>
<a-radio value="executionResult">{{ t('project.commonScript.executionResult') }}</a-radio> <a-radio value="executionResult">{{ t('project.commonScript.executionResult') }}</a-radio>
</a-radio-group> </a-radio-group>
<a-button type="outline" :loading="loading" @click="testScript">{{ <a-button
t('project.commonScript.scriptTest') v-permission="['PROJECT_CUSTOM_FUNCTION:READ+EXECUTE']"
}}</a-button> type="outline"
:loading="loading"
@click="testScript"
>{{ t('project.commonScript.scriptTest') }}</a-button
>
</div> </div>
<ScriptDefined <ScriptDefined
v-model:language="form.type" v-model:language="form.type"

View File

@ -13,6 +13,7 @@
import useUser from '@/hooks/useUser'; import useUser from '@/hooks/useUser';
import { BOTTOM_MENU_LIST } from '@/router/constants'; import { BOTTOM_MENU_LIST } from '@/router/constants';
import { useAppStore, useUserStore } from '@/store'; import { useAppStore, useUserStore } from '@/store';
import useLicenseStore from '@/store/modules/setting/license';
import { openWindow, regexUrl } from '@/utils'; import { openWindow, regexUrl } from '@/utils';
import { listenerRouteChange } from '@/utils/route-listener'; import { listenerRouteChange } from '@/utils/route-listener';
@ -138,16 +139,6 @@
} }
} }
onBeforeMount(async () => {
try {
const res = await getOrgOptions();
originOrgList.value = res || [];
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
});
const isActiveSwitchOrg = ref(false); const isActiveSwitchOrg = ref(false);
const personalMenus = [ const personalMenus = [
{ {
@ -181,6 +172,21 @@
}, },
]; ];
const licenseStore = useLicenseStore();
onBeforeMount(async () => {
if (!licenseStore.hasLicense()) {
personalMenus.splice(1, 1);
return;
}
try {
const res = await getOrgOptions();
originOrgList.value = res || [];
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
});
watch( watch(
() => personalMenusVisible.value, () => personalMenusVisible.value,
(val) => { (val) => {

View File

@ -1,16 +1,18 @@
<template> <template>
<MsPopconfirm <span>
type="error" <MsPopconfirm
:title="props.title" type="error"
:sub-title-tip="props.subTitleTip" :title="props.title"
:loading="props.loading" :sub-title-tip="props.subTitleTip"
:visible="currentVisible" :loading="props.loading"
:ok-text="props.okText" :visible="currentVisible"
@confirm="handleOk" :ok-text="props.okText"
@cancel="handleCancel" @confirm="handleOk"
> @cancel="handleCancel"
<MsButton @click="showPopover">{{ t(props.removeText) }}</MsButton> >
</MsPopconfirm> <MsButton @click="showPopover">{{ t(props.removeText) }}</MsButton>
</MsPopconfirm>
</span>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -22,7 +22,7 @@ const CaseManagement: AppRouteRecordRaw = {
component: () => import('@/views/case-management/caseManagementFeature/index.vue'), component: () => import('@/views/case-management/caseManagementFeature/index.vue'),
meta: { meta: {
locale: 'menu.caseManagement.featureCase', locale: 'menu.caseManagement.featureCase',
roles: ['*'], roles: ['FUNCTIONAL_CASE:READ'],
isTopMenu: true, isTopMenu: true,
}, },
}, },

View File

@ -23,7 +23,7 @@ const ProjectManagement: AppRouteRecordRaw = {
redirect: '/project-management/permission/basicInfo', redirect: '/project-management/permission/basicInfo',
meta: { meta: {
locale: 'menu.projectManagement.projectPermission', locale: 'menu.projectManagement.projectPermission',
roles: ['*'], roles: ['PROJECT_USER:READ'],
isTopMenu: true, isTopMenu: true,
}, },
children: [ children: [
@ -34,7 +34,7 @@ const ProjectManagement: AppRouteRecordRaw = {
component: () => import('@/views/project-management/projectAndPermission/basicInfos/index.vue'), component: () => import('@/views/project-management/projectAndPermission/basicInfos/index.vue'),
meta: { meta: {
locale: 'project.permission.basicInfo', locale: 'project.permission.basicInfo',
roles: ['*'], roles: ['SYSTEM_PARAMETER_SETTING_BASE:READ'],
}, },
}, },
// 菜单管理 // 菜单管理
@ -86,7 +86,7 @@ const ProjectManagement: AppRouteRecordRaw = {
component: () => import('@/views/project-management/template/index.vue'), component: () => import('@/views/project-management/template/index.vue'),
meta: { meta: {
locale: 'menu.projectManagement.templateManager', locale: 'menu.projectManagement.templateManager',
roles: ['*'], roles: ['PROJECT_TEMPLATE:READ'],
isTopMenu: true, isTopMenu: true,
}, },
}, },
@ -227,7 +227,7 @@ const ProjectManagement: AppRouteRecordRaw = {
component: () => import('@/views/project-management/commonScript/index.vue'), component: () => import('@/views/project-management/commonScript/index.vue'),
meta: { meta: {
locale: 'menu.projectManagement.commonScript', locale: 'menu.projectManagement.commonScript',
roles: ['*'], roles: ['PROJECT_CUSTOM_FUNCTION:READ'],
isTopMenu: true, isTopMenu: true,
}, },
}, },

View File

@ -1,8 +1,12 @@
// import useLicenseStore from '@/store/modules/setting/license';
import { SettingRouteEnum } from '@/enums/routeEnum'; import { SettingRouteEnum } from '@/enums/routeEnum';
import { DEFAULT_LAYOUT } from '../base'; import { DEFAULT_LAYOUT } from '../base';
import type { AppRouteRecordRaw } from '../types'; import type { AppRouteRecordRaw } from '../types';
// const licenseStore = useLicenseStore();
const Setting: AppRouteRecordRaw = { const Setting: AppRouteRecordRaw = {
path: '/setting', path: '/setting',
name: SettingRouteEnum.SETTING, name: SettingRouteEnum.SETTING,
@ -101,7 +105,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/system/authorizedManagement/index.vue'), component: () => import('@/views/setting/system/authorizedManagement/index.vue'),
meta: { meta: {
locale: 'menu.settings.system.authorizedManagement', locale: 'menu.settings.system.authorizedManagement',
roles: ['*'], roles: ['SYSTEM_AUTH:READ'],
isTopMenu: true, isTopMenu: true,
}, },
}, },
@ -121,7 +125,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/system/pluginManager/index.vue'), component: () => import('@/views/setting/system/pluginManager/index.vue'),
meta: { meta: {
locale: 'menu.settings.system.pluginManager', locale: 'menu.settings.system.pluginManager',
roles: ['*'], roles: ['SYSTEM_PLUGIN:READ'],
isTopMenu: true, isTopMenu: true,
}, },
}, },
@ -144,7 +148,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/organization/member/index.vue'), component: () => import('@/views/setting/organization/member/index.vue'),
meta: { meta: {
locale: 'menu.settings.organization.member', locale: 'menu.settings.organization.member',
roles: ['*'], roles: ['ORGANIZATION_MEMBER:READ'],
isTopMenu: true, isTopMenu: true,
}, },
}, },
@ -174,7 +178,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/organization/serviceIntegration/index.vue'), component: () => import('@/views/setting/organization/serviceIntegration/index.vue'),
meta: { meta: {
locale: 'menu.settings.organization.serviceIntegration', locale: 'menu.settings.organization.serviceIntegration',
roles: ['*'], roles: ['SYSTEM_SERVICE_INTEGRATION:READ'],
isTopMenu: true, isTopMenu: true,
}, },
}, },
@ -184,7 +188,7 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/organization/template/index.vue'), component: () => import('@/views/setting/organization/template/index.vue'),
meta: { meta: {
locale: 'menu.settings.organization.template', locale: 'menu.settings.organization.template',
roles: ['*'], roles: ['ORGANIZATION_TEMPLATE:READ'],
isTopMenu: true, isTopMenu: true,
}, },
}, },

View File

@ -22,7 +22,7 @@ const TestPlan: AppRouteRecordRaw = {
component: () => import('@/views/test-plan/testPlan/index.vue'), component: () => import('@/views/test-plan/testPlan/index.vue'),
meta: { meta: {
locale: 'menu.testPlan', locale: 'menu.testPlan',
roles: ['*'], roles: ['PROJECT_TEST_PLAN:READ'],
isTopMenu: true, isTopMenu: true,
}, },
}, },

View File

@ -9,7 +9,6 @@ const useFeatureCaseStore = defineStore('featureCase', {
persist: true, persist: true,
state: (): { state: (): {
moduleId: string[]; // 当前选中模块 moduleId: string[]; // 当前选中模块
allModuleId: string[]; // 所有模块
caseTree: ModuleTreeNode[]; // 用例树 caseTree: ModuleTreeNode[]; // 用例树
modulesCount: Record<string, any>; // 用例树模块数量 modulesCount: Record<string, any>; // 用例树模块数量
recycleModulesCount: Record<string, any>; // 回收站模块数量 recycleModulesCount: Record<string, any>; // 回收站模块数量
@ -17,7 +16,6 @@ const useFeatureCaseStore = defineStore('featureCase', {
tabSettingList: TabItemType[]; // 详情tab tabSettingList: TabItemType[]; // 详情tab
} => ({ } => ({
moduleId: [], moduleId: [],
allModuleId: [],
caseTree: [], caseTree: [],
modulesCount: {}, modulesCount: {},
recycleModulesCount: {}, recycleModulesCount: {},
@ -26,10 +24,11 @@ const useFeatureCaseStore = defineStore('featureCase', {
}), }),
actions: { actions: {
// 设置选择moduleId // 设置选择moduleId
setModuleId(currentModuleId: string[], offspringIds: string[]) { setModuleId(currentModuleId: string[]) {
this.moduleId = currentModuleId; if (['all', 'recycle'].includes(currentModuleId[0])) {
if (offspringIds.length > 0) { this.moduleId = ['root'];
this.allModuleId = offspringIds; } else {
this.moduleId = currentModuleId;
} }
}, },
// 设置用例树 // 设置用例树

View File

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

View File

@ -136,7 +136,7 @@
<span class="w-[calc(100%-36%)]"> <span class="w-[calc(100%-36%)]">
<a-tree-select <a-tree-select
v-model="detailInfo.moduleId" v-model="detailInfo.moduleId"
:data="featureCaseStore.caseTree" :data="caseTree"
class="w-full" class="w-full"
:allow-search="true" :allow-search="true"
:field-names="{ :field-names="{
@ -179,6 +179,7 @@
</div> </div>
<inputComment <inputComment
v-model:content="content" v-model:content="content"
v-permission="['FUNCTIONAL_CASE:READ+COMMENT']"
:is-active="isActive" :is-active="isActive"
is-show-avatar is-show-avatar
is-use-bottom is-use-bottom
@ -223,15 +224,17 @@
deleteCaseRequest, deleteCaseRequest,
followerCaseRequest, followerCaseRequest,
getCaseDetail, getCaseDetail,
getCaseModuleTree,
} from '@/api/modules/case-management/featureCase'; } from '@/api/modules/case-management/featureCase';
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 } from '@/store';
import useFeatureCaseStore from '@/store/modules/case/featureCase'; import useFeatureCaseStore from '@/store/modules/case/featureCase';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import { characterLimit, findNodeByKey } from '@/utils'; import { characterLimit } from '@/utils';
import type { CustomAttributes, DetailCase, TabItemType } from '@/models/caseManagement/featureCase'; import type { CustomAttributes, DetailCase, TabItemType } from '@/models/caseManagement/featureCase';
import { ModuleTreeNode } from '@/models/projectManagement/file';
import { CaseManagementRouteEnum } from '@/enums/routeEnum'; import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import { getCaseLevels } from './utils'; import { getCaseLevels } from './utils';
@ -310,20 +313,26 @@
reviewStatus: 'UN_REVIEWED', reviewStatus: 'UN_REVIEWED',
}; };
const caseTree = ref<ModuleTreeNode[]>([]);
async function getCaseTree() {
try {
caseTree.value = await getCaseModuleTree({ projectId: currentProjectId.value });
} catch (error) {
console.log(error);
}
}
const detailInfo = ref<DetailCase>({ ...initDetail }); const detailInfo = ref<DetailCase>({ ...initDetail });
const customFields = ref<CustomAttributes[]>([]); const customFields = ref<CustomAttributes[]>([]);
const caseLevels = ref<CaseLevel>('P0'); const caseLevels = ref<CaseLevel>('P0');
function loadedCase(detail: DetailCase) { function loadedCase(detail: DetailCase) {
getCaseTree();
detailInfo.value = { ...detail }; detailInfo.value = { ...detail };
customFields.value = detailInfo.value.customFields; customFields.value = detailInfo.value.customFields;
caseLevels.value = getCaseLevels(customFields.value) as CaseLevel; caseLevels.value = getCaseLevels(customFields.value) as CaseLevel;
} }
const moduleName = computed(() => {
return findNodeByKey<Record<string, any>>(featureCaseStore.caseTree, detailInfo.value?.moduleId as string, 'id')
?.name;
});
const editLoading = ref<boolean>(false); const editLoading = ref<boolean>(false);
function updateSuccess() { function updateSuccess() {

View File

@ -82,26 +82,32 @@
</template> --> </template> -->
<!-- 渲染自定义字段结束 --> <!-- 渲染自定义字段结束 -->
<template #operation="{ record }"> <template #operation="{ record }">
<MsButton @click="operateCase(record, 'edit')">{{ t('common.edit') }}</MsButton> <MsButton v-permission="['FUNCTIONAL_CASE:READ+UPDATE']" @click="operateCase(record, 'edit')">{{
<a-divider direction="vertical" :margin="8"></a-divider> t('common.edit')
<MsButton @click="operateCase(record, 'copy')">{{ t('caseManagement.featureCase.copy') }}</MsButton> }}</MsButton>
<a-divider direction="vertical" :margin="8"></a-divider> <a-divider v-permission="['FUNCTIONAL_CASE:READ+UPDATE']" direction="vertical" :margin="8"></a-divider>
<MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect($event, record)" /> <MsButton v-permission="['FUNCTIONAL_CASE:READ+ADD']" @click="operateCase(record, 'copy')">{{
t('caseManagement.featureCase.copy')
}}</MsButton>
<a-divider v-permission="['FUNCTIONAL_CASE:READ+ADD']" direction="vertical" :margin="8"></a-divider>
<span v-permission="['FUNCTIONAL_CASE:READ+DELETE']">
<MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect($event, record)" />
</span>
</template> </template>
<template v-if="(keyword || '').trim() === ''" #empty> <template v-if="(keyword || '').trim() === ''" #empty>
<div class="flex items-center justify-center p-[8px] text-[var(--color-text-4)]"> <div class="flex w-full items-center justify-center p-[8px] text-[var(--color-text-4)]">
{{ t('caseManagement.caseReview.tableNoData') }} {{ t('caseManagement.caseReview.tableNoData') }}
<MsButton class="ml-[8px]" @click="createCase"> <MsButton v-permission="['FUNCTIONAL_CASE:READ+ADD']" class="ml-[8px]" @click="createCase">
{{ t('caseManagement.featureCase.creatingCase') }} {{ t('caseManagement.featureCase.creatingCase') }}
</MsButton> </MsButton>
{{ t('caseManagement.featureCase.or') }} {{ t('caseManagement.featureCase.or') }}
<MsButton class="ml-[8px]" @click="emit('import', 'Excel')"> <MsButton v-permission="['FUNCTIONAL_CASE:READ+IMPORT']" class="ml-[8px]" @click="emit('import', 'Excel')">
{{ t('caseManagement.featureCase.importExcel') }} {{ t('caseManagement.featureCase.importExcel') }}
</MsButton> </MsButton>
<MsButton class="ml-[4px]" @click="emit('import', 'Xmind')"> <!-- <MsButton class="ml-[4px]" @click="emit('import', 'Xmind')">
{{ t('caseManagement.featureCase.importXmind') }} {{ t('caseManagement.featureCase.importXmind') }}
</MsButton> </MsButton> -->
</div> </div>
</template> </template>
</ms-base-table> </ms-base-table>
@ -1098,7 +1104,7 @@
initData(); initData();
}; };
onMounted(() => { onBeforeMount(() => {
if (route.query.id) { if (route.query.id) {
showCaseDetail(route.query.id as string, 0); showCaseDetail(route.query.id as string, 0);
} }
@ -1116,10 +1122,12 @@
watch( watch(
() => props.activeFolder, () => props.activeFolder,
() => { (val) => {
keyword.value = ''; keyword.value = '';
initData(); if (props.activeFolder !== 'recycle' && val) {
resetSelector(); initData();
resetSelector();
}
} }
); );
</script> </script>

View File

@ -77,7 +77,12 @@
:upload-image="handleUploadImage" :upload-image="handleUploadImage"
/> />
</a-form-item> </a-form-item>
<AddAttachment @change="handleChange" @link-file="associatedFile" @upload="beforeUpload" /> <AddAttachment
v-model:file-list="fileList"
@change="handleChange"
@link-file="associatedFile"
@upload="beforeUpload"
/>
</a-form> </a-form>
<!-- 文件列表开始 --> <!-- 文件列表开始 -->
<div class="w-[90%]"> <div class="w-[90%]">
@ -260,6 +265,7 @@
getAssociatedFileListUrl, getAssociatedFileListUrl,
getCaseDefaultFields, getCaseDefaultFields,
getCaseDetail, getCaseDetail,
getCaseModuleTree,
previewFile, previewFile,
transferFileRequest, transferFileRequest,
updateFile, updateFile,
@ -311,14 +317,6 @@
const featureCaseStore = useFeatureCaseStore(); const featureCaseStore = useFeatureCaseStore();
const modelId = computed(() => featureCaseStore.moduleId[0]); const modelId = computed(() => featureCaseStore.moduleId[0]);
const caseTree = computed(() => {
return mapTree<ModuleTreeNode>(featureCaseStore.caseTree, (e) => {
return {
...e,
draggable: false,
};
});
});
const initForm: DetailCase = { const initForm: DetailCase = {
id: '', id: '',
@ -560,7 +558,6 @@
async function getCaseInfo() { async function getCaseInfo() {
try { try {
isLoading.value = true; isLoading.value = true;
// await getAllCaseFields();
const detailResult: DetailCase = await getCaseDetail(props.caseId); const detailResult: DetailCase = await getCaseDetail(props.caseId);
const fileIds = (detailResult.attachments || []).map((item: any) => item.id); const fileIds = (detailResult.attachments || []).map((item: any) => item.id);
if (fileIds.length) { if (fileIds.length) {
@ -574,12 +571,23 @@
} }
} }
watchEffect(() => { const caseTree = ref<ModuleTreeNode[]>([]);
async function initSelectTree() {
try {
caseTree.value = await getCaseModuleTree({ projectId: currentProjectId.value });
} catch (error) {
console.log(error);
}
}
onMounted(() => {
if (route.params.mode === 'edit' || route.params.mode === 'copy') { if (route.params.mode === 'edit' || route.params.mode === 'copy') {
getCaseInfo(); getCaseInfo();
} else { } else {
initDefaultFields(); initDefaultFields();
} }
initSelectTree();
}); });
// //
function getFilesParams() { function getFilesParams() {
@ -619,7 +627,7 @@
if (val) { if (val) {
params.value.request = { ...form.value }; params.value.request = { ...form.value };
emit('update:formModeValue', params.value); emit('update:formModeValue', params.value);
featureCaseStore.setModuleId([form.value.moduleId], []); featureCaseStore.setModuleId([form.value.moduleId]);
} }
} }
}, },

View File

@ -96,6 +96,8 @@
import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase'; import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase';
import { ModuleTreeNode } from '@/models/projectManagement/file'; import { ModuleTreeNode } from '@/models/projectManagement/file';
const featureCaseStore = useFeatureCaseStore();
const { t } = useI18n(); const { t } = useI18n();
const { openModal } = useModal(); const { openModal } = useModal();
const appStore = useAppStore(); const appStore = useAppStore();
@ -141,6 +143,7 @@
() => props.selectedKeys, () => props.selectedKeys,
(val) => { (val) => {
selectedNodeKeys.value = val || []; selectedNodeKeys.value = val || [];
featureCaseStore.setModuleId(val as string[]);
} }
); );
@ -150,7 +153,6 @@
emits('update:selectedKeys', val); emits('update:selectedKeys', val);
} }
); );
const featureCaseStore = useFeatureCaseStore();
/** /**
* 初始化模块树 * 初始化模块树
* @param isSetDefaultKey 是否设置第一个节点为选中节点 * @param isSetDefaultKey 是否设置第一个节点为选中节点
@ -169,6 +171,7 @@
}; };
}); });
featureCaseStore.setModulesTree(caseTree.value); featureCaseStore.setModulesTree(caseTree.value);
featureCaseStore.setModuleId(['all']);
if (isSetDefaultKey) { if (isSetDefaultKey) {
selectedNodeKeys.value = [caseTree.value[0].id]; selectedNodeKeys.value = [caseTree.value[0].id];
} }

View File

@ -43,11 +43,12 @@
:auto-upload="false" :auto-upload="false"
:disabled="confirmLoading" :disabled="confirmLoading"
></MsUpload> ></MsUpload>
<a-form-item field="post" :label="t('caseManagement.featureCase.selectVersion')"> <!-- 版本暂时不上 -->
<!-- <a-form-item field="post" :label="t('caseManagement.featureCase.selectVersion')">
<a-select class="max-w-[240px]" :placeholder="t('caseManagement.featureCase.defaultSelectNewVersion')"> <a-select class="max-w-[240px]" :placeholder="t('caseManagement.featureCase.defaultSelectNewVersion')">
<a-option v-for="item of versionOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option> <a-option v-for="item of versionOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select> </a-select>
</a-form-item> </a-form-item> -->
</div> </div>
<template #footer> <template #footer>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
@ -69,9 +70,14 @@
></a-checkbox> ></a-checkbox>
<div> <div>
<a-button type="secondary" @click="handleCancel">{{ t('system.plugin.pluginCancel') }}</a-button> <a-button type="secondary" @click="handleCancel">{{ t('system.plugin.pluginCancel') }}</a-button>
<a-button class="ml-3" type="primary" :disabled="isDisabled" :loading="confirmLoading" @click="saveConfirm">{{ <a-button
t('caseManagement.featureCase.checkTemplate') class="ml-3"
}}</a-button> type="primary"
:disabled="isDisabled"
:loading="props.confirmLoading"
@click="saveConfirm"
>{{ t('caseManagement.featureCase.checkTemplate') }}</a-button
>
</div> </div>
</div> </div>
</template> </template>
@ -95,6 +101,7 @@
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
validateType: 'Excel' | 'Xmind'; validateType: 'Excel' | 'Xmind';
confirmLoading: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@ -112,19 +119,17 @@
set: (val) => emit('update:visible', val), set: (val) => emit('update:visible', val),
}); });
const confirmLoading = ref<boolean>(false);
const handleCancel = () => { const handleCancel = () => {
fileList.value = []; fileList.value = [];
emit('close'); emit('close');
}; };
const versionOptions = ref([ // const versionOptions = ref([
{ // {
id: '1001', // id: '1001',
name: 'V1.0', // name: 'V1.0',
}, // },
]); // ]);
const isRecover = ref<boolean>(false); const isRecover = ref<boolean>(false);

View File

@ -119,19 +119,18 @@
import { ValidateInfo } from '@/models/caseManagement/featureCase'; import { ValidateInfo } from '@/models/caseManagement/featureCase';
import type { FileItem } from '@arco-design/web-vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
validateType: 'Excel' | 'Xmind'; validateType: 'Excel' | 'Xmind';
validateInfo: ValidateInfo; validateInfo: ValidateInfo;
importLoading: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:visible', val: boolean): void; (e: 'update:visible', val: boolean): void;
(e: 'save', files: FileItem[]): void;
(e: 'close'): void; (e: 'close'): void;
(e: 'save'): void;
}>(); }>();
const validateResultModal = computed({ const validateResultModal = computed({
@ -175,7 +174,9 @@
} }
// //
function confirmImport() {} function confirmImport() {
emit('save');
}
watchEffect(() => { watchEffect(() => {
validateResultInfo.value = { ...props.validateInfo }; validateResultInfo.value = { ...props.validateInfo };

View File

@ -124,10 +124,15 @@
</div> </div>
</template> --> </template> -->
<template #operation="{ record }"> <template #operation="{ record }">
<MsButton @click="recoverCase(record.id)">{{ t('caseManagement.featureCase.batchRecover') }}</MsButton> <MsButton v-permission="['FUNCTIONAL_CASE:READ+DELETE']" @click="recoverCase(record.id)">{{
<MsButton class="!mr-0" @click="handleBatchCleanOut(record)">{{ t('caseManagement.featureCase.batchRecover')
t('caseManagement.featureCase.batchCleanOut')
}}</MsButton> }}</MsButton>
<MsButton
v-permission="['FUNCTIONAL_CASE:READ+DELETE']"
class="!mr-0"
@click="handleBatchCleanOut(record)"
>{{ t('caseManagement.featureCase.batchCleanOut') }}</MsButton
>
</template> </template>
</ms-base-table> </ms-base-table>
</div> </div>
@ -810,10 +815,6 @@
} }
} }
function getCaseLevel(record: CaseManagementTable): CaseLevel {
const caseLevelItem = record.customFields.find((it: any) => it.fieldName === '用例等级');
return caseLevelItem?.options.find((it: any) => it.value === caseLevelItem.defaultValue).text;
}
onMounted(() => { onMounted(() => {
getDefaultFields(); getDefaultFields();
initFilter(); initFilter();

View File

@ -53,9 +53,11 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import useFeatureCaseStore from '@/store/modules/case/featureCase'; import useFeatureCaseStore from '@/store/modules/case/featureCase';
import useLicenseStore from '@/store/modules/setting/license';
import type { TabItemType } from '@/models/caseManagement/featureCase'; import type { TabItemType } from '@/models/caseManagement/featureCase';
const licenseStore = useLicenseStore();
const { t } = useI18n(); const { t } = useI18n();
const featureCaseStore = useFeatureCaseStore(); const featureCaseStore = useFeatureCaseStore();
@ -98,6 +100,19 @@
let buggerTab: TabItemType[] = []; let buggerTab: TabItemType[] = [];
let testPlanTab: TabItemType[] = []; let testPlanTab: TabItemType[] = [];
function getTabList() {
if (licenseStore.hasLicense()) {
return [
{
key: 'changeHistory',
title: 'caseManagement.featureCase.changeHistory',
enable: true,
},
];
}
return [];
}
const tabDefaultSettingList = ref<TabItemType[]>([ const tabDefaultSettingList = ref<TabItemType[]>([
{ {
key: 'case', key: 'case',
@ -119,11 +134,7 @@
title: 'caseManagement.featureCase.comments', title: 'caseManagement.featureCase.comments',
enable: true, enable: true,
}, },
{ ...getTabList(),
key: 'changeHistory',
title: 'caseManagement.featureCase.changeHistory',
enable: true,
},
]); ]);
async function getTabModule() { async function getTabModule() {
buggerTab = []; buggerTab = [];

View File

@ -49,7 +49,7 @@
<MsButton @click="cancelLink(record.id)">{{ t('caseManagement.featureCase.cancelLink') }}</MsButton> <MsButton @click="cancelLink(record.id)">{{ t('caseManagement.featureCase.cancelLink') }}</MsButton>
</template> </template>
<template v-if="(keyword || '').trim() === ''" #empty> <template v-if="(keyword || '').trim() === ''" #empty>
<div class="flex items-center justify-center"> <div class="flex w-full items-center justify-center">
{{ t('caseManagement.caseReview.tableNoData') }} {{ t('caseManagement.caseReview.tableNoData') }}
<MsButton class="ml-[8px]" @click="linkDefect"> <MsButton class="ml-[8px]" @click="linkDefect">
{{ t('caseManagement.featureCase.linkDefect') }} {{ t('caseManagement.featureCase.linkDefect') }}
@ -77,7 +77,7 @@
<MsButton @click="cancelLink(record.id)">{{ t('caseManagement.featureCase.cancelLink') }}</MsButton> <MsButton @click="cancelLink(record.id)">{{ t('caseManagement.featureCase.cancelLink') }}</MsButton>
</template> </template>
<template v-if="(keyword || '').trim() === ''" #empty> <template v-if="(keyword || '').trim() === ''" #empty>
<div class="flex items-center justify-center"> <div class="flex w-full items-center justify-center">
{{ t('caseManagement.caseReview.tableNoData') }} {{ t('caseManagement.caseReview.tableNoData') }}
<MsButton class="ml-[8px]" @click="createDefect"> <MsButton class="ml-[8px]" @click="createDefect">
{{ t('caseManagement.featureCase.createDefect') }} {{ t('caseManagement.featureCase.createDefect') }}

View File

@ -26,6 +26,22 @@
<template #operation="{ record }"> <template #operation="{ record }">
<MsButton @click="cancelLink(record)">{{ t('caseManagement.featureCase.cancelLink') }}</MsButton> <MsButton @click="cancelLink(record)">{{ t('caseManagement.featureCase.cancelLink') }}</MsButton>
</template> </template>
<template v-if="(keyword || '').trim() === ''" #empty>
<div class="flex w-full items-center justify-center">
{{ t('caseManagement.caseReview.tableNoData') }}
<a-dropdown @select="handleSelect">
<MsButton class="ml-[8px]">
{{ t('caseManagement.featureCase.linkCase') }}
</MsButton>
<template #content>
<a-doption v-for="item of caseTypeOptions" :key="item.value" :value="item.value">{{
t(item.label)
}}</a-doption>
</template>
</a-dropdown>
</div>
</template>
</ms-base-table> </ms-base-table>
<MsCaseAssociate <MsCaseAssociate
v-model:visible="innerVisible" v-model:visible="innerVisible"

View File

@ -15,7 +15,7 @@
}}</MsButton> }}</MsButton>
</template> </template>
<template v-if="(props.funParams.keyword || '').trim() === ''" #empty> <template v-if="(props.funParams.keyword || '').trim() === ''" #empty>
<div class="flex items-center justify-center"> <div class="flex w-full w-full items-center justify-center">
{{ t('caseManagement.caseReview.tableNoData') }} {{ t('caseManagement.caseReview.tableNoData') }}
<MsButton class="ml-[8px]" @click="emit('create')"> <MsButton class="ml-[8px]" @click="emit('create')">
{{ t('caseManagement.featureCase.addDemand') }} {{ t('caseManagement.featureCase.addDemand') }}

View File

@ -84,7 +84,15 @@
</div> </div>
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent"> <ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
<template #caseLevel="{ record }"> <template #caseLevel="{ record }">
<caseLevel :case-level="getCaseLevel(record)" /> <caseLevel :case-level="getCaseLevels(record.customFields)" />
</template>
<template v-if="(keyword || '').trim() === ''" #empty>
<div class="flex w-full items-center justify-center p-[8px] text-[var(--color-text-4)]">
{{ t('caseManagement.caseReview.tableNoData') }}
<MsButton v-permission="['FUNCTIONAL_CASE:READ+ADD']" class="ml-[8px]" @click="createCase">
{{ t('caseManagement.featureCase.creatingCase') }}
</MsButton>
</div>
</template> </template>
</ms-base-table> </ms-base-table>
<div class="footer"> <div class="footer">
@ -114,8 +122,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type'; import { MsTableColumn } from '@/components/pure/ms-table/type';
@ -139,6 +149,9 @@
import type { CaseManagementTable, CaseModuleQueryParams } from '@/models/caseManagement/featureCase'; import type { CaseManagementTable, CaseModuleQueryParams } from '@/models/caseManagement/featureCase';
import type { TableQueryParams } from '@/models/common'; import type { TableQueryParams } from '@/models/common';
import { ModuleTreeNode } from '@/models/projectManagement/file'; import { ModuleTreeNode } from '@/models/projectManagement/file';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import { getCaseLevels } from '../../utils';
const appStore = useAppStore(); const appStore = useAppStore();
const currentProjectId = computed(() => appStore.currentProjectId); const currentProjectId = computed(() => appStore.currentProjectId);
@ -451,6 +464,13 @@
} }
); );
const router = useRouter();
function createCase() {
router.push({
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE_DETAIL,
});
}
onMounted(() => { onMounted(() => {
resetSelector(); resetSelector();
}); });

View File

@ -39,7 +39,7 @@
/> />
</template> </template>
<template v-if="(keyword || '').trim() === ''" #empty> <template v-if="(keyword || '').trim() === ''" #empty>
<div class="flex items-center justify-center"> <div class="flex w-full items-center justify-center">
{{ t('caseManagement.caseReview.tableNoData') }} {{ t('caseManagement.caseReview.tableNoData') }}
<MsButton class="ml-[8px]" @click="addCase"> <MsButton class="ml-[8px]" @click="addCase">
{{ {{

View File

@ -9,7 +9,13 @@
asterisk-position="end" asterisk-position="end"
> >
<span class="absolute right-[6px] top-0"> <span class="absolute right-[6px] top-0">
<a-button v-if="props.allowEdit" type="text" class="px-0" @click="prepositionEdit"> <a-button
v-if="props.allowEdit"
v-permission="['FUNCTIONAL_CASE:READ+UPDATE']"
type="text"
class="px-0"
@click="prepositionEdit"
>
<MsIcon type="icon-icon_edit_outlined" class="mr-1 font-[16px] text-[rgb(var(--primary-5))]" />{{ <MsIcon type="icon-icon_edit_outlined" class="mr-1 font-[16px] text-[rgb(var(--primary-5))]" />{{
t('caseManagement.featureCase.contentEdit') t('caseManagement.featureCase.contentEdit')
}}</a-button }}</a-button
@ -99,7 +105,7 @@
<div v-if="props.allowEdit" class="flex flex-col"> <div v-if="props.allowEdit" class="flex flex-col">
<div class="mb-1"> <div class="mb-1">
<a-dropdown position="tr" trigger="hover"> <a-dropdown position="tr" trigger="hover">
<a-button type="outline"> <a-button v-permission="['FUNCTIONAL_CASE:READ+UPDATE']" type="outline">
<template #icon> <icon-plus class="text-[14px]" /> </template <template #icon> <icon-plus class="text-[14px]" /> </template
>{{ t('system.orgTemplate.addAttachment') }}</a-button >{{ t('system.orgTemplate.addAttachment') }}</a-button
> >
@ -261,7 +267,7 @@
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement'; import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { downloadByteFile, getGenerateId } from '@/utils'; import { downloadByteFile, encrypted, getGenerateId, sleep } from '@/utils';
import { scrollIntoView } from '@/utils/dom'; import { scrollIntoView } from '@/utils/dom';
import type { AssociatedList, DetailCase, StepList } from '@/models/caseManagement/featureCase'; import type { AssociatedList, DetailCase, StepList } from '@/models/caseManagement/featureCase';
@ -479,10 +485,6 @@
} }
const fileListRef = ref<InstanceType<typeof MsFileList>>(); const fileListRef = ref<InstanceType<typeof MsFileList>>();
async function startUpload() {
await fileListRef.value?.startUpload();
emit('updateSuccess');
}
function beforeUpload(file: File) { function beforeUpload(file: File) {
const _maxSize = 50 * 1024 * 1024; const _maxSize = 50 * 1024 * 1024;
@ -600,16 +602,18 @@
} }
); );
async function startUpload() {
await sleep(300);
await fileListRef.value?.startUpload();
emit('updateSuccess');
}
// //
watch( watch(
() => fileList.value, () => fileList.value,
(val) => { async (val) => {
if (val) { const isNewFiles = val.filter((item) => item.status === 'init').length;
if (val.filter((item) => item.status === 'init').length) { if (val && isNewFiles) {
setTimeout(() => { startUpload();
startUpload();
}, 30);
}
} }
} }
); );

View File

@ -1,15 +1,15 @@
<template> <template>
<div class="rounded-2xl bg-white"> <div class="rounded-2xl bg-white">
<div class="p-[24px] pb-[16px]"> <div class="p-[24px] pb-[16px]">
<a-button type="primary" @click="caseDetail"> <a-button v-permission="['FUNCTIONAL_CASE:READ+ADD']" type="primary" @click="caseDetail">
{{ t('caseManagement.featureCase.creatingCase') }} {{ t('caseManagement.featureCase.creatingCase') }}
</a-button> </a-button>
<a-button class="mx-3" type="outline" @click="importCase('Excel')"> <a-button v-permission="['FUNCTIONAL_CASE:READ+IMPORT']" class="mx-3" type="outline" @click="importCase('Excel')">
{{ t('caseManagement.featureCase.importExcel') }} {{ t('caseManagement.featureCase.importExcel') }}
</a-button> </a-button>
<a-button type="outline" @click="importCase('Xmind')"> <!-- <a-button type="outline" @click="importCase('Xmind')">
{{ t('caseManagement.featureCase.importXmind') }} {{ t('caseManagement.featureCase.importXmind') }}
</a-button> </a-button> -->
</div> </div>
<a-divider class="!my-0" /> <a-divider class="!my-0" />
<div class="pageWrap"> <div class="pageWrap">
@ -98,6 +98,7 @@
<ExportExcelModal <ExportExcelModal
v-model:visible="showExcelModal" v-model:visible="showExcelModal"
:validate-type="validateType" :validate-type="validateType"
:confirm-loading="validateLoading"
@save="validateTemplate" @save="validateTemplate"
@close="showExcelModal = false" @close="showExcelModal = false"
/> />
@ -112,7 +113,9 @@
v-model:visible="validateResultModal" v-model:visible="validateResultModal"
:validate-type="validateType" :validate-type="validateType"
:validate-info="validateInfo" :validate-info="validateInfo"
:import-loading="importLoading"
@close="closeHandler" @close="closeHandler"
@save="conFirmImport"
/> />
</template> </template>
@ -133,7 +136,7 @@
import ValidateModal from './components/export/validateModal.vue'; import ValidateModal from './components/export/validateModal.vue';
import ValidateResult from './components/export/validateResult.vue'; import ValidateResult from './components/export/validateResult.vue';
import { createCaseModuleTree, importExcelChecked } from '@/api/modules/case-management/featureCase'; import { createCaseModuleTree, importExcelCase, importExcelChecked } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import useFeatureCaseStore from '@/store/modules/case/featureCase'; import useFeatureCaseStore from '@/store/modules/case/featureCase';
@ -193,7 +196,7 @@
[activeFolder.value] = keys; [activeFolder.value] = keys;
activeCaseType.value = 'module'; activeCaseType.value = 'module';
offspringIds.value = [..._offspringIds]; offspringIds.value = [..._offspringIds];
featureCaseStore.setModuleId(keys, offspringIds.value); featureCaseStore.setModuleId(keys);
} }
const confirmLoading = ref(false); const confirmLoading = ref(false);
@ -322,8 +325,15 @@
}, 100); }, 100);
} }
const fileList = ref<FileItem[]>([]);
const isCover = ref<boolean>(false);
const validateLoading = ref<boolean>(false);
// //
async function validateTemplate(files: FileItem[], cover: boolean) { async function validateTemplate(files: FileItem[], cover: boolean) {
fileList.value = files;
isCover.value = cover;
validateLoading.value = true;
try { try {
start(); start();
validateModal.value = true; validateModal.value = true;
@ -339,6 +349,8 @@
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally {
validateLoading.value = false;
} }
} }
@ -353,6 +365,29 @@
function closeHandler() { function closeHandler() {
showExcelModal.value = false; showExcelModal.value = false;
validateResultModal.value = false; validateResultModal.value = false;
caseTreeRef.value.initModules();
}
const importLoading = ref<boolean>(false);
//
async function conFirmImport() {
importLoading.value = true;
try {
const params = {
projectId: appStore.currentProjectId,
versionId: '',
cover: isCover.value,
};
await importExcelCase({ request: params, fileList: fileList.value.map((item: any) => item.file) });
Message.success(t('caseManagement.featureCase.importSuccess'));
validateResultModal.value = false;
showExcelModal.value = false;
caseTreeRef.value.initModules();
} catch (error) {
console.log(error);
} finally {
importLoading.value = false;
}
} }
onMounted(() => { onMounted(() => {

View File

@ -3,6 +3,7 @@ export default {
'caseManagement.featureCase.importCase': 'Import Case', 'caseManagement.featureCase.importCase': 'Import Case',
'caseManagement.featureCase.importExcel': 'Import Excel', 'caseManagement.featureCase.importExcel': 'Import Excel',
'caseManagement.featureCase.importXmind': 'Import Xmind', 'caseManagement.featureCase.importXmind': 'Import Xmind',
'caseManagement.featureCase.importSuccess': 'Import successfully',
'caseManagement.featureCase.publicCase': 'Public of Cases', 'caseManagement.featureCase.publicCase': 'Public of Cases',
'caseManagement.featureCase.allCase': 'All of Cases', 'caseManagement.featureCase.allCase': 'All of Cases',
'caseManagement.featureCase.searchTip': 'Please enter a group name', 'caseManagement.featureCase.searchTip': 'Please enter a group name',

View File

@ -3,6 +3,7 @@ export default {
'caseManagement.featureCase.importCase': '导入用例', 'caseManagement.featureCase.importCase': '导入用例',
'caseManagement.featureCase.importExcel': 'Excel导入', 'caseManagement.featureCase.importExcel': 'Excel导入',
'caseManagement.featureCase.importXmind': 'Xmind导入', 'caseManagement.featureCase.importXmind': 'Xmind导入',
'caseManagement.featureCase.importSuccess': '导入成功',
'caseManagement.featureCase.publicCase': '公共用例库', 'caseManagement.featureCase.publicCase': '公共用例库',
'caseManagement.featureCase.allCase': '全部用例', 'caseManagement.featureCase.allCase': '全部用例',
'caseManagement.featureCase.searchTip': '请输入分组名称', 'caseManagement.featureCase.searchTip': '请输入分组名称',

View File

@ -13,7 +13,7 @@
<a-form-item class="login-form-item" field="radio" hide-label> <a-form-item class="login-form-item" field="radio" hide-label>
<a-radio-group v-model="userInfo.authenticate" type="button"> <a-radio-group v-model="userInfo.authenticate" type="button">
<a-radio value="LOCAL">普通登陆</a-radio> <a-radio value="LOCAL">普通登陆</a-radio>
<a-radio value="LDAP">LDAP</a-radio> <a-radio v-xpack value="LDAP">LDAP</a-radio>
<a-radio value="OAuth2">OAuth2 测试</a-radio> <a-radio value="OAuth2">OAuth2 测试</a-radio>
<a-radio value="OIDC 90">OIDC 90</a-radio> <a-radio value="OIDC 90">OIDC 90</a-radio>
</a-radio-group> </a-radio-group>

View File

@ -1,7 +1,9 @@
<template> <template>
<MsCard simple> <MsCard simple>
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<a-button type="outline" @click="addCommonScript"> {{ t('project.commonScript.addPublicScript') }} </a-button> <a-button v-permission="['PROJECT_CUSTOM_FUNCTION:READ+ADD']" type="outline" @click="addCommonScript">
{{ t('project.commonScript.addPublicScript') }}
</a-button>
<a-input-search <a-input-search
v-model:model-value="keyword" v-model:model-value="keyword"
:placeholder="t('project.commonScript.searchByNameAndId')" :placeholder="t('project.commonScript.searchByNameAndId')"
@ -43,11 +45,12 @@
<MsTag v-else>{{ t('project.commonScript.draft') }}</MsTag> <MsTag v-else>{{ t('project.commonScript.draft') }}</MsTag>
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<MsButton status="primary" @click="editHandler(record)"> <MsButton v-permission="['PROJECT_CUSTOM_FUNCTION:READ+UPDATE']" status="primary" @click="editHandler(record)">
{{ t('common.edit') }} {{ t('common.edit') }}
</MsButton> </MsButton>
<MsTableMoreAction <MsTableMoreAction
v-if="!record.internal" v-if="!record.internal"
v-permission="['PROJECT_CUSTOM_FUNCTION:READ+DELETE']"
:list="actions" :list="actions"
@select="(item:ActionsItem) => handleMoreActionSelect(item,record)" @select="(item:ActionsItem) => handleMoreActionSelect(item,record)"
/></template> /></template>

View File

@ -4,9 +4,13 @@
</div> </div>
<div class="wrapper mb-6 flex justify-between"> <div class="wrapper mb-6 flex justify-between">
<span class="font-medium text-[var(--color-text-000)]">{{ t('project.basicInfo.basicInfo') }}</span> <span class="font-medium text-[var(--color-text-000)]">{{ t('project.basicInfo.basicInfo') }}</span>
<a-button v-if="!projectDetail?.deleted" type="outline" @click="editHandler">{{ <a-button
t('project.basicInfo.edit') v-if="!projectDetail?.deleted"
}}</a-button> v-permission="['SYSTEM_PARAMETER_SETTING_BASE:READ+UPDATE']"
type="outline"
@click="editHandler"
>{{ t('project.basicInfo.edit') }}</a-button
>
</div> </div>
<div class="project-info mb-6 h-[112px] bg-white p-1"> <div class="project-info mb-6 h-[112px] bg-white p-1">
<div class="inner-wrapper rounded-md p-4"> <div class="inner-wrapper rounded-md p-4">

View File

@ -24,6 +24,7 @@
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 useLicenseStore from '@/store/modules/setting/license';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum'; import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
@ -31,6 +32,21 @@
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const licenseStore = useLicenseStore();
function getProjectVersion() {
if (licenseStore.hasLicense()) {
return [
{
key: 'projectVersion',
title: t('project.permission.projectVersion'),
level: 2,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_VERSION,
},
];
}
return [];
}
const menuList = ref([ const menuList = ref([
{ {
key: 'project', key: 'project',
@ -50,12 +66,7 @@
level: 2, level: 2,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT, name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT,
}, },
{ ...getProjectVersion(),
key: 'projectVersion',
title: t('project.permission.projectVersion'),
level: 2,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_VERSION,
},
{ {
key: 'memberPermission', key: 'memberPermission',
title: t('project.permission.memberPermission'), title: t('project.permission.memberPermission'),

View File

@ -1,7 +1,9 @@
<template> <template>
<div class="mb-4 grid grid-cols-4 gap-2"> <div class="mb-4 grid grid-cols-4 gap-2">
<div class="col-span-2" <div class="col-span-2"
><a-button class="mr-3" type="primary" @click="addMember">{{ t('project.member.addMember') }}</a-button></div ><a-button v-permission="['PROJECT_USER:READ+ADD']" class="mr-3" type="primary" @click="addMember">{{
t('project.member.addMember')
}}</a-button></div
> >
<div> <div>
<a-select v-model="roleIds" @change="changeSelect"> <a-select v-model="roleIds" @change="changeSelect">
@ -60,6 +62,7 @@
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<MsRemoveButton <MsRemoveButton
v-permission="['PROJECT_USER:READ+DELETE']"
position="br" position="br"
:title="t('project.member.deleteMemberTip', { name: characterLimit(record.name) })" :title="t('project.member.deleteMemberTip', { name: characterLimit(record.name) })"
:sub-title-tip="t('project.member.subTitle')" :sub-title-tip="t('project.member.subTitle')"
@ -180,10 +183,12 @@
{ {
label: 'project.member.batchActionAddUserGroup', label: 'project.member.batchActionAddUserGroup',
eventTag: 'batchAddUserGroup', eventTag: 'batchAddUserGroup',
permission: ['PROJECT_USER:READ+ADD'],
}, },
{ {
label: 'project.member.batchActionRemove', label: 'project.member.batchActionRemove',
eventTag: 'batchActionRemove', eventTag: 'batchActionRemove',
permission: ['PROJECT_USER:READ+ADD'],
}, },
], ],
}; };

View File

@ -2,7 +2,13 @@
<MsCard has-breadcrumb simple> <MsCard has-breadcrumb simple>
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<span v-if="isEnableOrdTemplate" class="font-medium">{{ t('system.orgTemplate.templateList') }}</span> <span v-if="isEnableOrdTemplate" class="font-medium">{{ t('system.orgTemplate.templateList') }}</span>
<a-button v-else type="primary" :disabled="false" @click="createTemplate"> <a-button
v-else
v-permission="['PROJECT_TEMPLATE:READ+ADD']"
type="primary"
:disabled="false"
@click="createTemplate"
>
{{ t('system.orgTemplate.createTemplate') }} {{ t('system.orgTemplate.createTemplate') }}
</a-button> </a-button>
<a-input-search <a-input-search
@ -36,12 +42,22 @@
</div> </div>
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<div class="flex flex-row flex-nowrap"> <div class="flex flex-row flex-nowrap items-center">
<MsButton @click="editTemplate(record.id)">{{ t('system.orgTemplate.edit') }}</MsButton> <MsButton v-permission="['PROJECT_TEMPLATE:READ+UPDATE']" @click="editTemplate(record.id)">{{
<MsButton class="!mr-0" @click="copyTemplate(record.id)">{{ t('system.orgTemplate.copy') }}</MsButton> t('system.orgTemplate.edit')
<a-divider v-if="!record.internal" direction="vertical" /> }}</MsButton>
<MsButton v-permission="['PROJECT_TEMPLATE:READ+ADD']" class="!mr-0" @click="copyTemplate(record.id)">{{
t('system.orgTemplate.copy')
}}</MsButton>
<a-divider
v-if="!record.internal"
v-permission="['PROJECT_TEMPLATE:READ+ADD']"
class="h-[16px]"
direction="vertical"
/>
<MsTableMoreAction <MsTableMoreAction
v-if="!record.internal" v-if="!record.internal"
v-permission="['PROJECT_TEMPLATE:READ+DELETE']"
:list="moreActions" :list="moreActions"
@select="(item) => handleMoreActionSelect(item, record)" @select="(item) => handleMoreActionSelect(item, record)"
/> />

View File

@ -2,9 +2,13 @@
<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 class="mr-3" type="primary" @click="addOrEditMember('add')">{{ <a-button
t('organization.member.addMember') v-permission="['ORGANIZATION_MEMBER:READ+ADD']"
}}</a-button> class="mr-3"
type="primary"
@click="addOrEditMember('add')"
>{{ t('organization.member.addMember') }}</a-button
>
</div> </div>
<a-input-search <a-input-search
v-model="keyword" v-model="keyword"
@ -78,8 +82,11 @@
</div> </div>
</template> </template>
<template #action="{ record }"> <template #action="{ record }">
<MsButton @click="addOrEditMember('edit', record)">{{ t('organization.member.edit') }}</MsButton> <MsButton v-permission="['ORGANIZATION_MEMBER:READ+UPDATE']" @click="addOrEditMember('edit', record)">{{
t('organization.member.edit')
}}</MsButton>
<MsRemoveButton <MsRemoveButton
v-permission="['ORGANIZATION_MEMBER:READ+DELETE']"
position="br" position="br"
:title="t('organization.member.deleteMemberTip', { name: characterLimit(record.name) })" :title="t('organization.member.deleteMemberTip', { name: characterLimit(record.name) })"
:sub-title-tip="t('organization.member.subTitle')" :sub-title-tip="t('organization.member.subTitle')"
@ -213,10 +220,12 @@
{ {
label: 'organization.member.batchActionAddProject', label: 'organization.member.batchActionAddProject',
eventTag: 'batchAddProject', eventTag: 'batchAddProject',
permission: ['ORGANIZATION_MEMBER:READ+ADD'],
}, },
{ {
label: 'organization.member.batchActionAddUserGroup', label: 'organization.member.batchActionAddUserGroup',
eventTag: 'batchAddUserGroup', eventTag: 'batchAddUserGroup',
permission: ['ORGANIZATION_MEMBER:READ+ADD'],
}, },
], ],
}; };

View File

@ -67,11 +67,28 @@
@click="getValidateHandler(item)" @click="getValidateHandler(item)"
>{{ t('organization.service.testLink') }}</a-button >{{ t('organization.service.testLink') }}</a-button
> >
<a-button type="outline" class="arco-btn-outline--secondary" size="mini" @click="editHandler(item)">{{
t('organization.service.edit')
}}</a-button>
<a-button <a-button
v-if="item.config" v-if="item.config"
v-permission="['SYSTEM_SERVICE_INTEGRATION:READ+UPDATE']"
type="outline"
class="arco-btn-outline--secondary"
size="mini"
@click="editHandler(item)"
>{{ t('organization.service.edit') }}</a-button
>
<a-button
v-else
v-permission="['SYSTEM_SERVICE_INTEGRATION:READ+ADD']"
type="outline"
class="arco-btn-outline--secondary"
size="mini"
@click="editHandler(item)"
>{{ t('common.add') }}</a-button
>
<a-button
v-if="item.config"
v-permission="['SYSTEM_SERVICE_INTEGRATION:READ+RESET']"
type="outline" type="outline"
class="arco-btn-outline--secondary" class="arco-btn-outline--secondary"
size="mini" size="mini"

View File

@ -15,7 +15,7 @@
<span @click="fieldSetting">{{ t('system.orgTemplate.fieldSetting') }}</span> <span @click="fieldSetting">{{ t('system.orgTemplate.fieldSetting') }}</span>
<a-divider direction="vertical" /> <a-divider direction="vertical" />
</span> </span>
<span class="operation hover:text-[rgb(var(--primary-5))]"> <span v-permission="['ORGANIZATION_TEMPLATE:READ']" class="operation hover:text-[rgb(var(--primary-5))]">
<span @click="templateManagement">{{ t('system.orgTemplate.TemplateManagement') }}</span> <span @click="templateManagement">{{ t('system.orgTemplate.TemplateManagement') }}</span>
<a-divider v-if="isEnableProject || props.cardItem.key === 'BUG'" direction="vertical" /> <a-divider v-if="isEnableProject || props.cardItem.key === 'BUG'" direction="vertical" />
</span> </span>
@ -23,7 +23,11 @@
<span @click="workflowSetup">{{ t('system.orgTemplate.workflowSetup') }}</span> <span @click="workflowSetup">{{ t('system.orgTemplate.workflowSetup') }}</span>
<a-divider v-if="isEnableProject && props.cardItem.key === 'BUG'" direction="vertical" /> <a-divider v-if="isEnableProject && props.cardItem.key === 'BUG'" direction="vertical" />
</span> </span>
<span v-if="isEnableProject" class="rounded p-[2px] hover:bg-[rgb(var(--primary-9))]"> <span
v-if="isEnableProject"
v-permission="['ORGANIZATION_TEMPLATE:READ+ENABLE']"
class="rounded p-[2px] hover:bg-[rgb(var(--primary-9))]"
>
<MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect" <MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect"
/></span> /></span>
</div> </div>

View File

@ -5,7 +5,13 @@
}}</a-alert> }}</a-alert>
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<span v-if="isEnableOrdTemplate" class="font-medium">{{ t('system.orgTemplate.templateList') }}</span> <span v-if="isEnableOrdTemplate" class="font-medium">{{ t('system.orgTemplate.templateList') }}</span>
<a-button v-else type="primary" :disabled="false" @click="createTemplate"> <a-button
v-else
v-permission="['ORGANIZATION_TEMPLATE:READ+ADD']"
type="primary"
:disabled="false"
@click="createTemplate"
>
{{ t('system.orgTemplate.createTemplate') }} {{ t('system.orgTemplate.createTemplate') }}
</a-button> </a-button>
<a-input-search <a-input-search
@ -28,12 +34,22 @@
{{ record.enableThirdPart ? t('system.orgTemplate.yes') : t('system.orgTemplate.no') }} {{ record.enableThirdPart ? t('system.orgTemplate.yes') : t('system.orgTemplate.no') }}
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<div class="flex flex-row flex-nowrap"> <div class="flex flex-row flex-nowrap items-center">
<MsButton @click="editTemplate(record.id)">{{ t('system.orgTemplate.edit') }}</MsButton> <MsButton v-permission="['ORGANIZATION_TEMPLATE:READ+UPDATE']" @click="editTemplate(record.id)">{{
<MsButton class="!mr-0" @click="copyTemplate(record.id)">{{ t('system.orgTemplate.copy') }}</MsButton> t('system.orgTemplate.edit')
<a-divider v-if="!record.internal" class="h-[12px]" direction="vertical" /> }}</MsButton>
<MsButton v-permission="['ORGANIZATION_TEMPLATE:READ+ADD']" class="!mr-0" @click="copyTemplate(record.id)">{{
t('system.orgTemplate.copy')
}}</MsButton>
<a-divider
v-if="!record.internal"
v-permission="['ORGANIZATION_TEMPLATE:READ+ADD']"
class="h-[12px]"
direction="vertical"
/>
<MsTableMoreAction <MsTableMoreAction
v-if="!record.internal" v-if="!record.internal"
v-permission="['ORGANIZATION_TEMPLATE:READ+DELETE']"
:list="moreActions" :list="moreActions"
@select="(item) => handleMoreActionSelect(item, record)" @select="(item) => handleMoreActionSelect(item, record)"
/> />

View File

@ -58,7 +58,7 @@
> >
</li> </li>
<li> <li>
<MsButton class="font-medium" @click="authChecking">{{ <MsButton v-permission="['SYSTEM_AUTH:READ+UPDATE']" class="font-medium" @click="authChecking">{{
t('system.authorized.authorityChecking') t('system.authorized.authorityChecking')
}}</MsButton> }}</MsButton>
</li> </li>

View File

@ -207,6 +207,7 @@
import { getBaseInfo, getEmailInfo, saveBaseInfo, saveEmailInfo, testEmail } from '@/api/modules/setting/config'; import { getBaseInfo, getEmailInfo, saveBaseInfo, saveEmailInfo, testEmail } from '@/api/modules/setting/config';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useLicenseStore from '@/store/modules/setting/license';
import { desensitize } from '@/utils'; import { desensitize } from '@/utils';
import { validateEmail } from '@/utils/validate'; import { validateEmail } from '@/utils/validate';
@ -241,22 +242,33 @@
/** /**
* 初始化基础信息 * 初始化基础信息
*/ */
const licenseStore = useLicenseStore();
async function initBaseInfo() { async function initBaseInfo() {
try { try {
baseloading.value = true; baseloading.value = true;
const res = await getBaseInfo(); const res = await getBaseInfo();
baseInfo.value = { ...res }; baseInfo.value = { ...res };
baseInfoForm.value = { ...res }; baseInfoForm.value = { ...res };
baseInfoDescs.value = [ if (licenseStore.hasLicense()) {
{ baseInfoDescs.value = [
label: t('system.config.pageUrl'), {
value: res.url, label: t('system.config.pageUrl'),
}, value: res.url,
{ },
label: t('system.config.prometheus'), {
value: res.prometheusHost, label: t('system.config.prometheus'),
}, value: res.prometheusHost,
]; },
];
} else {
baseInfoDescs.value = [
{
label: t('system.config.pageUrl'),
value: res.url,
},
];
}
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {

View File

@ -20,6 +20,7 @@
import pageConfig from './components/pageConfig.vue'; import pageConfig from './components/pageConfig.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useLicenseStore from '@/store/modules/setting/license';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -29,12 +30,12 @@
const isInitAuthConfig = ref(activeTab.value === 'authConfig'); const isInitAuthConfig = ref(activeTab.value === 'authConfig');
const isInitMemoryCleanup = ref(activeTab.value === 'memoryCleanup'); const isInitMemoryCleanup = ref(activeTab.value === 'memoryCleanup');
const authConfigRef = ref<AuthConfigInstance | null>(); const authConfigRef = ref<AuthConfigInstance | null>();
const tabList = [ const tabList = ref([
{ key: 'baseConfig', title: t('system.config.baseConfig') }, { key: 'baseConfig', title: t('system.config.baseConfig') },
{ key: 'pageConfig', title: t('system.config.pageConfig') }, { key: 'pageConfig', title: t('system.config.pageConfig') },
{ key: 'authConfig', title: t('system.config.authConfig') }, { key: 'authConfig', title: t('system.config.authConfig') },
{ key: 'memoryCleanup', title: t('system.config.memoryCleanup') }, { key: 'memoryCleanup', title: t('system.config.memoryCleanup') },
]; ]);
watch( watch(
() => activeTab.value, () => activeTab.value,
@ -51,11 +52,15 @@
immediate: true, immediate: true,
} }
); );
const licenseStore = useLicenseStore();
onMounted(() => { onMounted(() => {
if (route.query.tab === 'authConfig' && route.query.id) { if (route.query.tab === 'authConfig' && route.query.id) {
authConfigRef.value?.openAuthDetail(route.query.id as string); authConfigRef.value?.openAuthDetail(route.query.id as string);
} }
if (!licenseStore.hasLicense()) {
const excludes = ['baseConfig', 'memoryCleanup'];
tabList.value = tabList.value.filter((item: any) => excludes.includes(item.key));
}
}); });
</script> </script>

View File

@ -3,7 +3,9 @@
<div> <div>
<a-row class="grid-demo mb-4" :gutter="10"> <a-row class="grid-demo mb-4" :gutter="10">
<a-col :span="5"> <a-col :span="5">
<a-button class="mr-3" type="primary" @click="uploadPlugin">{{ t('system.plugin.uploadPlugin') }}</a-button> <a-button v-permission="['SYSTEM_PLUGIN:READ+ADD']" class="mr-3" type="primary" @click="uploadPlugin">{{
t('system.plugin.uploadPlugin')
}}</a-button>
</a-col> </a-col>
<a-col :span="5" :offset="9"> <a-col :span="5" :offset="9">
<a-select v-model="searchKeys.scene" @change="searchHandler"> <a-select v-model="searchKeys.scene" @change="searchHandler">
@ -108,12 +110,18 @@
</template> </template>
<template #cell="{ record }"> <template #cell="{ record }">
<div class="flex"> <div class="flex">
<MsButton @click="update(record)">{{ t('system.plugin.edit') }}</MsButton> <MsButton v-permission="['SYSTEM_PLUGIN:READ+UPDATE']" @click="update(record)">{{
t('system.plugin.edit')
}}</MsButton>
<MsButton v-if="record.enable" @click="disableHandler(record)">{{ <MsButton v-if="record.enable" @click="disableHandler(record)">{{
t('system.plugin.tableDisable') t('system.plugin.tableDisable')
}}</MsButton> }}</MsButton>
<MsButton v-else @click="enableHandler(record)">{{ t('system.plugin.tableEnable') }}</MsButton> <MsButton v-else @click="enableHandler(record)">{{ t('system.plugin.tableEnable') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction> <MsTableMoreAction
v-permission="['SYSTEM_PLUGIN:READ+DELETE']"
:list="tableActions"
@select="handleSelect($event, record)"
></MsTableMoreAction>
</div> </div>
</template> </template>
</a-table-column> </a-table-column>

View File

@ -136,7 +136,7 @@
<a-form-item v-if="isShowTypeItem" :label="t('system.resourcePool.type')" field="type" class="form-item"> <a-form-item v-if="isShowTypeItem" :label="t('system.resourcePool.type')" field="type" class="form-item">
<a-radio-group v-model:model-value="form.type" type="button" @change="changeResourceType"> <a-radio-group v-model:model-value="form.type" type="button" @change="changeResourceType">
<a-radio value="Node">Node</a-radio> <a-radio value="Node">Node</a-radio>
<a-radio value="Kubernetes">Kubernetes</a-radio> <a-radio v-xpack value="Kubernetes">Kubernetes</a-radio>
</a-radio-group> </a-radio-group>
</a-form-item> </a-form-item>
<template v-if="isShowNodeResources"> <template v-if="isShowNodeResources">
@ -356,6 +356,7 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useVisit from '@/hooks/useVisit'; import useVisit from '@/hooks/useVisit';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import useLicenseStore from '@/store/modules/setting/license';
import { downloadStringFile, sleep } from '@/utils'; import { downloadStringFile, sleep } from '@/utils';
import { scrollIntoView } from '@/utils/dom'; import { scrollIntoView } from '@/utils/dom';
@ -363,6 +364,8 @@
import { getYaml, job, YamlType } from './template'; import { getYaml, job, YamlType } from './template';
const licenseStore = useLicenseStore();
const isXpack = computed(() => licenseStore.hasLicense());
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
@ -399,7 +402,7 @@
const form = ref({ ...defaultForm }); const form = ref({ ...defaultForm });
const formRef = ref<FormInstance | null>(null); const formRef = ref<FormInstance | null>(null);
const orgOptions = ref<SelectOptionData>([]); const orgOptions = ref<SelectOptionData>([]);
const useList = [ const useList = ref([
{ {
label: 'system.resourcePool.usePerformance', label: 'system.resourcePool.usePerformance',
value: 'performance', value: 'performance',
@ -412,11 +415,14 @@
label: 'system.resourcePool.useUI', label: 'system.resourcePool.useUI',
value: 'UI', value: 'UI',
}, },
]; ]);
const defaultGrid = 'http://selenium-hub:4444'; const defaultGrid = 'http://selenium-hub:4444';
onBeforeMount(async () => { onBeforeMount(async () => {
orgOptions.value = await getSystemOrgOption(); orgOptions.value = await getSystemOrgOption();
if (!isXpack.value) {
useList.value = useList.value.filter((item) => item.value === 'API');
}
}); });
async function initPoolInfo() { async function initPoolInfo() {

View File

@ -1,7 +1,7 @@
<template> <template>
<MsCard :loading="loading" simple> <MsCard :loading="loading" simple>
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<a-button type="primary" @click="addPool"> <a-button v-xpack type="primary" @click="addPool">
{{ t('system.resourcePool.createPool') }} {{ t('system.resourcePool.createPool') }}
</a-button> </a-button>
<a-input-search <a-input-search
@ -19,10 +19,10 @@
</template> </template>
<template #action="{ record }"> <template #action="{ record }">
<MsButton @click="editPool(record)">{{ t('system.resourcePool.editPool') }}</MsButton> <MsButton @click="editPool(record)">{{ t('system.resourcePool.editPool') }}</MsButton>
<MsButton v-if="record.enable" @click="disabledPool(record)"> <MsButton v-if="record.enable" v-xpack @click="disabledPool(record)">
{{ t('system.resourcePool.tableDisable') }} {{ t('system.resourcePool.tableDisable') }}
</MsButton> </MsButton>
<MsButton v-else @click="enablePool(record)">{{ t('system.resourcePool.tableEnable') }}</MsButton> <MsButton v-else v-xpack @click="enablePool(record)">{{ t('system.resourcePool.tableEnable') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction> <MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
</template> </template>
</ms-base-table> </ms-base-table>
@ -83,7 +83,6 @@
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const columns: MsTableColumn = [ const columns: MsTableColumn = [
{ {
title: 'system.resourcePool.tableColumnName', title: 'system.resourcePool.tableColumnName',

View File

@ -66,9 +66,13 @@
<template #operation="{ record }"> <template #operation="{ record }">
<MsButton>{{ t('testPlan.testPlanIndex.execution') }}</MsButton> <MsButton>{{ t('testPlan.testPlanIndex.execution') }}</MsButton>
<a-divider direction="vertical" :margin="8"></a-divider> <a-divider direction="vertical" :margin="8"></a-divider>
<MsButton>{{ t('testPlan.testPlanIndex.copy') }}</MsButton> <MsButton v-permission="['PROJECT_TEST_PLAN:READ+ADD']">{{ t('testPlan.testPlanIndex.copy') }}</MsButton>
<a-divider direction="vertical" :margin="8"></a-divider> <a-divider direction="vertical" :margin="8"></a-divider>
<MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect($event, record)" /> <MsTableMoreAction
v-permission="['PROJECT_TEST_PLAN:READ+DELETE']"
:list="moreActions"
@select="handleMoreActionSelect($event, record)"
/>
</template> </template>
</ms-base-table> </ms-base-table>
</template> </template>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="rounded-2xl bg-white"> <div class="rounded-2xl bg-white">
<div class="p-[24px] pb-[16px]"> <div class="p-[24px] pb-[16px]">
<a-button type="primary"> <a-button v-permission="['PROJECT_TEST_PLAN:READ+ADD']" type="primary">
{{ t('testPlan.testPlanIndex.createTestPlan') }} {{ t('testPlan.testPlanIndex.createTestPlan') }}
</a-button> </a-button>
</div> </div>