feat(功能用例): 联调公共关联用例抽屉接口

This commit is contained in:
xinxin.wu 2024-01-03 10:51:23 +08:00 committed by Craftsman
parent f650d0c78d
commit 58c27af024
13 changed files with 407 additions and 271 deletions

View File

@ -1,6 +1,7 @@
import MSR from '@/api/http/index';
import {
AddDemandUrl,
AddDependOnRelationUrl,
BatchAssociationDemandUrl,
BatchCopyCaseUrl,
BatchDeleteCaseUrl,
@ -21,12 +22,17 @@ import {
DownloadFileUrl,
FollowerCaseUrl,
GetAssociatedFilePageUrl,
GetAssociationPublicCaseModuleCountUrl,
GetAssociationPublicCasePageUrl,
GetAssociationPublicModuleTreeUrl,
GetCaseListUrl,
GetCaseModulesCountUrl,
GetCaseModuleTreeUrl,
GetCommentListUrl,
GetDefaultTemplateFieldsUrl,
GetDemandListUrl,
GetDependOnPageUrl,
GetDependOnRelationUrl,
GetDetailCaseReviewUrl,
GetFileIsUpdateUrl,
GetRecycleCaseListUrl,
@ -67,8 +73,8 @@ import type {
import type { CommonList, MoveModules, TableQueryParams } from '@/models/common';
import type { UserListItem } from '@/models/setting/user';
// 获取模块树
export function getCaseModuleTree(projectId: string) {
return MSR.get<ModulesTreeType[]>({ url: `${GetCaseModuleTreeUrl}/${projectId}` });
export function getCaseModuleTree(params: TableQueryParams) {
return MSR.get<ModulesTreeType[]>({ url: `${GetCaseModuleTreeUrl}/${params.projectId}` });
}
// 创建模块树
@ -157,7 +163,7 @@ export function getRecycleModulesCounts(data: CaseModuleQueryParams) {
return MSR.post({ url: GetRecycleCaseModulesCountUrl, data });
}
// 获取全部用例模块数量
export function getCaseModulesCounts(data: CaseModuleQueryParams) {
export function getCaseModulesCounts(data: TableQueryParams) {
return MSR.post({ url: GetCaseModulesCountUrl, data });
}
// 批量恢复回收站用例表
@ -276,4 +282,31 @@ export function getReviewerList(projectId: string, keyword: string) {
return MSR.get<UserListItem[]>({ url: `${GetReviewerListUrl}/${projectId}`, params: { keyword } });
}
// 用例接口用例分页列表
export function getPublicLinkCaseList(data: TableQueryParams) {
return MSR.post<CommonList<CaseManagementTable>>({ url: GetAssociationPublicCasePageUrl, data });
}
// 获取用例详情接口用例模块数量
export function getPublicLinkCaseModulesCounts(data: TableQueryParams) {
return MSR.post({ url: GetAssociationPublicCaseModuleCountUrl, data });
}
// 获取关联用例接口模块树
export function getPublicLinkModuleTree(data: TableQueryParams) {
return MSR.post<ModulesTreeType[]>({ url: `${GetAssociationPublicModuleTreeUrl}`, data });
}
// 获取前后置用例
export function getDependOnCase(data: TableQueryParams) {
return MSR.post<CommonList<CaseManagementTable>>({ url: `${GetDependOnPageUrl}`, data });
}
// 用例管理-功能用例-用例详情-前后置关系
export function getPrepositionRelation(data: TableQueryParams) {
return MSR.post<CommonList<CaseManagementTable>>({ url: `${GetDependOnRelationUrl}`, data });
}
// 添加前后置关系
export function addPrepositionRelation(data: TableQueryParams) {
return MSR.post<ModulesTreeType[]>({ url: `${AddDependOnRelationUrl}`, data });
}
export default {};

View File

@ -102,5 +102,15 @@ export const DeleteCommentItemUrl = '/functional/case/comment/delete';
export const GetDetailCaseReviewUrl = '/functional/case/review/page';
// 获取有权限的评审人
export const GetReviewerListUrl = '/case/review/user-option';
export default {};
// 获取用例详情弹窗关联用例接口用例
export const GetAssociationPublicCasePageUrl = '/functional/case/test/associate/case/page';
// 获取接口测试接口模块数量
export const GetAssociationPublicCaseModuleCountUrl = '/functional/case/test/associate/case/module/count';
// 获取用例详情接口模块树
export const GetAssociationPublicModuleTreeUrl = '/functional/case/test/associate/case/module/tree';
// 获取前后置用例列表
export const GetDependOnPageUrl = '/functional/case/relationship/page';
// 用例管理-功能用例-用例详情-前后置关系
export const GetDependOnRelationUrl = '/functional/case/relationship/relate/page';
// 添加前后置关系
export const AddDependOnRelationUrl = '/functional/case/relationship/add';

View File

@ -5,6 +5,7 @@
:width="1200"
:footer="false"
no-content-padding
unmount-on-close
>
<template #headerLeft>
<div class="float-left">
@ -24,7 +25,7 @@
<div class="w-[292px] border-r border-[var(--color-text-n8)] p-[16px]">
<div class="flex items-center justify-between">
<MsProjectSelect v-model:project="innerProject" class="mb-[16px]" />
<a-select v-if="caseType === 'API_CASE'" v-model="protocolType" class="mb-[16px] ml-2 max-w-[90px]">
<a-select v-if="caseType === 'API'" v-model="protocolType" class="mb-[16px] ml-2 max-w-[90px]">
<a-option v-for="item of protocolOptions" :key="item" :value="item">{{ item }}</a-option>
</a-select>
</div>
@ -38,7 +39,7 @@
<div :class="getFolderClass('all')" @click="setActiveFolder('all')">
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
<div class="folder-name">{{ t('caseManagement.featureCase.allCase') }}</div>
<div class="folder-count">({{ props.modulesCount['all'] }})</div>
<div class="folder-count">({{ modulesCount['all'] }})</div>
</div>
</div>
<a-divider class="my-[8px]" />
@ -47,7 +48,7 @@
v-model:selected-keys="selectedModuleKeys"
:data="folderTree"
:keyword="moduleKeyword"
:empty-text="t('caseManagement.caseReview.noReviews')"
:empty-text="t('caseManagement.featureCase.caseEmptyRecycle')"
:virtual-list-props="virtualListProps"
:field-names="{
title: 'name',
@ -146,7 +147,7 @@
import useAppStore from '@/store/modules/app';
import { mapTree } from '@/utils';
import type { CaseManagementTable, CaseModuleQueryParams } from '@/models/caseManagement/featureCase';
import type { CaseManagementTable } from '@/models/caseManagement/featureCase';
import type { CommonList, TableQueryParams } from '@/models/common';
import { ModuleTreeNode } from '@/models/projectManagement/file';
@ -157,11 +158,13 @@
const props = defineProps<{
visible: boolean;
project: string;
getModulesFunc: (projectId: string) => Promise<ModuleTreeNode[]>; //
getTableFunc: (params: TableQueryParams) => Promise<CommonList<CaseManagementTable>>; //
projectId: string; // id
caseId?: string; // id
getModulesFunc: (params: TableQueryParams) => Promise<ModuleTreeNode[]>; //
modulesParams?: Record<string, any>; //
getTableFunc: (params: TableQueryParams) => Promise<CommonList<CaseManagementTable>>; //
tableParams?: TableQueryParams; //
modulesCount: Record<string, number>; //
modulesCount: Record<string, any>; //
okButtonDisabled?: boolean; //
currentSelectCase: string | number | Record<string, any> | undefined; //
moduleOptions?: { label: string; value: string }[]; //
@ -173,7 +176,7 @@
(e: 'update:visible', val: boolean): void;
(e: 'update:project', val: string): void;
(e: 'update:currentSelectCase', val: string | number | Record<string, any> | undefined): void;
(e: 'init', val: CaseModuleQueryParams): void; //
(e: 'init', val: TableQueryParams): void; //
(e: 'close'): void;
(e: 'save', params: TableQueryParams): void; // table
}>();
@ -184,7 +187,7 @@
};
});
const activeFolder = ref('all');
const activeFolder = ref('');
const activeFolderName = ref(t('ms.case.associate.allCase'));
const filterRowCount = ref(0);
@ -205,11 +208,21 @@
}
const innerVisible = ref(props.visible);
const innerProject = ref(props.project);
const innerProject = ref(props.projectId);
const protocolType = ref('HTTP'); //
const protocolOptions = ref(['HTTP']);
//
const caseType = computed({
get() {
return props.currentSelectCase;
},
set(val) {
emit('update:currentSelectCase', val);
},
});
/**
* 初始化模块树
* @param isSetDefaultKey 是否设置第一个节点为选中节点
@ -217,7 +230,18 @@
async function initModules(isSetDefaultKey = false) {
try {
moduleLoading.value = true;
const res = await props.getModulesFunc(innerProject.value);
let params = {
projectId: innerProject.value,
sourceType: props.moduleOptions && props.moduleOptions.length ? caseType.value : undefined,
sourceId: props.moduleOptions && props.moduleOptions.length ? props.caseId : undefined,
};
if (props.modulesParams) {
params = {
...params,
...props.modulesParams,
};
}
const res = await props.getModulesFunc(params);
folderTree.value = mapTree<ModuleTreeNode>(res, (e) => {
return {
...e,
@ -260,16 +284,6 @@
});
}
//
const caseType = computed({
get() {
return props.currentSelectCase;
},
set(val) {
emit('update:currentSelectCase', val);
},
});
const keyword = ref('');
const version = ref('');
const versionOptions = ref([
@ -339,13 +353,16 @@
},
];
const getTableList = computed(() => props.getTableFunc);
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
props.getTableFunc,
getTableList.value,
{
columns,
showSetting: false,
selectable: true,
showSelectAll: true,
heightUsed: 310,
},
(record) => {
return {
@ -366,17 +383,21 @@
});
function getLoadListParams() {
if (activeFolder.value === 'all') {
if (activeFolder.value === 'all' || !activeFolder.value) {
searchParams.value.moduleIds = [];
} else {
searchParams.value.moduleIds = [activeFolder.value, ...offspringIds.value];
}
if (props.moduleOptions && props.moduleOptions.length) {
searchParams.value.sourceType = caseType.value;
searchParams.value.sourceId = props.caseId;
}
setLoadListParams({
...searchParams.value,
...props.tableParams,
keyword: keyword.value,
projectId: innerProject.value,
excludeIds: [...props.associatedIds],
excludeIds: [...props.associatedIds], // id
});
}
@ -484,6 +505,7 @@
projectId: innerProject.value,
current: propsRes.value.msPagination?.current,
pageSize: propsRes.value.msPagination?.pageSize,
sourceId: props.caseId,
combine: combine.value,
});
}
@ -505,8 +527,11 @@
moduleIds,
versionId,
refId: '',
sourceType: caseType.value,
projectId: innerProject.value,
sourceId: props.caseId,
};
emit('save', params);
}
@ -530,6 +555,7 @@
(val) => {
innerVisible.value = val;
if (val) {
resetSelector();
searchCase();
initFilter();
}
@ -539,8 +565,9 @@
watch(
() => innerVisible.value,
(val) => {
if (!val) {
emit('update:visible', false);
emit('update:visible', val);
if (val) {
initModules(true);
}
}
);
@ -550,6 +577,7 @@
() => caseType.value,
(val) => {
if (val) {
emit('update:currentSelectCase', val);
initModules(true);
searchCase();
}
@ -557,7 +585,7 @@
);
watch(
() => props.project,
() => props.projectId,
(val) => {
if (val) {
innerProject.value = val;
@ -569,7 +597,6 @@
() => innerProject.value,
(val) => {
emit('update:project', val);
resetSelector();
initModules(true);
searchCase();
}
@ -597,10 +624,6 @@
}
);
onBeforeMount(() => {
innerProject.value = appStore.currentProjectId;
});
defineExpose({
initModules,
});

View File

@ -117,9 +117,9 @@
@update-success="updateSuccess"
/>
<TabDemand v-else-if="activeTab === 'requirement'" :case-id="props.detailId" />
<TabCaseTable v-else-if="activeTab === 'case'" />
<TabCaseTable v-else-if="activeTab === 'case'" :case-id="props.detailId" />
<TabDefect v-else-if="activeTab === 'bug'" />
<TabDependency v-else-if="activeTab === 'dependency'" />
<TabDependency v-else-if="activeTab === 'dependency'" :case-id="props.detailId" />
<TabCaseReview v-else-if="activeTab === 'caseReview'" :case-id="props.detailId" />
<TabTestPlan v-else-if="activeTab === 'testPlan'" />
<TabComment v-else-if="activeTab === 'comments'" :case-id="props.detailId" />
@ -248,6 +248,7 @@
const userId = computed(() => userStore.userInfo.id);
const appStore = useAppStore();
provide('caseId', props.detailId);
const currentProjectId = computed(() => appStore.currentProjectId);

View File

@ -188,6 +188,7 @@
:page-change="propsEvent.pageChange"
:pagination="propsRes.msPagination!"
/>
<AddDemandModal v-model:visible="showDemandModel" :case-id="caseId" :form="modelForm" />
</template>
<script setup lang="ts">
@ -220,6 +221,7 @@
import CaseDetailDrawer from './caseDetailDrawer.vue';
import FeatureCaseTree from './caseTree.vue';
import ExportExcelDrawer from './exportExcelDrawer.vue';
import AddDemandModal from './tabContent/tabDemand/addDemandModal.vue';
import TableFormChange from './tableFormChange.vue';
import {
@ -243,6 +245,7 @@
CaseManagementTable,
CaseModuleQueryParams,
CustomAttributes,
DemandItem,
} from '@/models/caseManagement/featureCase';
import type { TableQueryParams } from '@/models/common';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
@ -924,8 +927,25 @@
});
}
const showDemandModel = ref<boolean>(false);
const caseId = ref('');
const modelForm = ref<DemandItem>({
id: '',
caseId: '', // ID
demandId: '', // ID
demandName: '', //
demandUrl: '', //
demandPlatform: '', //
createTime: '',
updateTime: '',
createUser: '',
updateUser: '',
children: [], //
});
//
function addDemand() {}
function addDemand() {
showDemandModel.value = true;
}
//
function handleAssociatedDemand() {}
@ -991,7 +1011,7 @@
}
//
const customFieldsColumns = ref<Record<string, any>[]>([]);
let customFieldsColumns: Record<string, any>[] = [];
let fullColumns: MsTableColumn = []; //
const tableRef = ref<InstanceType<typeof MsBaseTable> | null>(null);
@ -999,7 +1019,7 @@
async function getDefaultFields() {
const result = await getCaseDefaultFields(currentProjectId.value);
initDefaultFields.value = result.customFields;
customFieldsColumns.value = initDefaultFields.value.map((item: any) => {
customFieldsColumns = initDefaultFields.value.map((item: any) => {
return {
title: item.fieldName,
slotName: item.fieldId as string,
@ -1012,7 +1032,7 @@
fullColumns = [
...columns.slice(0, columns.length - 1),
...customFieldsColumns.value,
...customFieldsColumns,
...columns.slice(columns.length - 1, columns.length),
];
tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_TABLE, fullColumns, 'drawer');

View File

@ -230,8 +230,8 @@
import MsFileList from '@/components/pure/ms-upload/fileList.vue';
import MsUpload from '@/components/pure/ms-upload/index.vue';
import type { MsFileItem } from '@/components/pure/ms-upload/types';
import AddAttachment from '@/components/business/ms-add-attachment/index.vue';
import LinkFileDrawer from '@/components/business/ms-link-file/associatedFileDrawer.vue';
import AddAttachment from './addAttachment.vue';
import AddStep from './addStep.vue';
import TransferModal from './tabContent/transferModal.vue';

View File

@ -158,7 +158,7 @@
async function initModules(isSetDefaultKey = false) {
try {
loading.value = true;
const res = await getCaseModuleTree(currentProjectId.value);
const res = await getCaseModuleTree({ projectId: currentProjectId.value });
caseTree.value = mapTree<ModuleTreeNode>(res, (e) => {
return {
...e,

View File

@ -96,8 +96,8 @@
],
});
const buggerTab: TabItemType[] = [];
const testPlanTab: TabItemType[] = [];
let buggerTab: TabItemType[] = [];
let testPlanTab: TabItemType[] = [];
const tabDefaultSettingList = ref<TabItemType[]>([
{
key: 'case',
@ -126,6 +126,8 @@
},
]);
async function getTabModule() {
buggerTab = [];
testPlanTab = [];
const result = await postTabletList({ projectId: currentProjectId.value });
const enableModuleArr = result.filter((item: any) => item.module === 'testPlan' || item.module === 'bugManagement');
enableModuleArr.forEach((item) => {

View File

@ -27,18 +27,21 @@
</ms-base-table>
<MsCaseAssociate
v-model:visible="innerVisible"
v-model:project="innerProject"
v-model:project-id="innerProject"
v-model:currentSelectCase="currentSelectCase"
:ok-button-disabled="associateForm.reviewers.length === 0"
:get-modules-func="getCaseModuleTree"
:get-table-func="getCaseList"
:get-modules-func="getPublicLinkModuleTree"
:modules-params="modulesTreeParams"
:get-table-func="getPublicLinkCaseList"
:table-params="getTableParams"
:modules-count="modulesCount"
:module-options="caseTypeOptions"
:confirm-loading="confirmLoading"
:case-id="props.caseId"
:associated-ids="associatedIds"
@close="emit('close')"
@init="getModuleCount"
@save="saveHandler"
@init="getModuleCount"
>
</MsCaseAssociate>
</div>
@ -55,16 +58,15 @@
import { getAssociatedIds } from '@/api/modules/case-management/caseReview';
import {
getCaseList,
getCaseModulesCounts,
getCaseModuleTree,
getPublicLinkCaseList,
getPublicLinkCaseModulesCounts,
getPublicLinkModuleTree,
getRecycleListRequest,
} from '@/api/modules/case-management/featureCase';
import { postTabletList } from '@/api/modules/project-management/menuManagement';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import type { CaseModuleQueryParams } from '@/models/caseManagement/featureCase';
import type { TableQueryParams } from '@/models/common';
import { TableKeyEnum } from '@/enums/tableEnum';
@ -74,6 +76,10 @@
const currentProjectId = computed(() => appStore.currentProjectId);
const props = defineProps<{
caseId: string; // id
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(e: 'update:project', val: string): void;
@ -152,7 +158,7 @@
});
const innerVisible = ref(false);
const innerProject = ref('');
const innerProject = ref(currentProjectId.value);
const associateForm = ref({
reviewers: [],
@ -168,11 +174,30 @@
// }
}
const currentSelectCase = ref<string | number | Record<string, any> | undefined>('');
const currentSelectCase = ref<string>('');
const countParams = ref<TableQueryParams>({});
const modulesTreeParams = ref<TableQueryParams>({});
const getTableParams = ref<TableQueryParams>({});
function getParams() {
switch (currentSelectCase.value) {
case 'API':
modulesTreeParams.value = { protocol: 'HTTP' };
countParams.value = { sourceId: props.caseId, protocol: 'HTTP' };
getTableParams.value = { sourceId: props.caseId, protocol: 'HTTP' };
break;
default:
break;
}
}
function handleSelect(value: string | number | Record<string, any> | undefined) {
currentSelectCase.value = value;
currentSelectCase.value = value as string;
innerVisible.value = true;
getLinkedIds();
// getParams();
}
function cancelLink(record: any) {}
@ -181,9 +206,9 @@
const modulesCount = ref<Record<string, any>>({});
async function getModuleCount(params: CaseModuleQueryParams) {
async function getModuleCount(params: TableQueryParams) {
try {
modulesCount.value = await getCaseModulesCounts(params);
modulesCount.value = await getPublicLinkCaseModulesCounts(params);
} catch (error) {
console.log(error);
}
@ -191,40 +216,44 @@
const confirmLoading = ref<boolean>(false);
function saveHandler(params: TableQueryParams) {}
function saveHandler(params: TableQueryParams) {
console.log(params);
}
const moduleMaps: Record<string, { label: string; value: string }[]> = {
apiTest: [
{
value: 'API_CASE',
value: 'API',
label: t('caseManagement.featureCase.apiCase'),
},
{
value: 'SCENE_CASE',
value: 'SCENARIO',
label: t('caseManagement.featureCase.sceneCase'),
},
],
uiTest: [
{
value: 'UI_CASE',
value: 'UI',
label: t('caseManagement.featureCase.uiCase'),
},
],
loadTest: [
{
value: 'LOAD_CASE',
value: 'PERFORMANCE',
label: t('caseManagement.featureCase.propertyCase'),
},
],
};
onBeforeMount(async () => {
onMounted(async () => {
const result = await postTabletList({ projectId: currentProjectId.value });
const caseArr = result.filter((item) => Object.keys(moduleMaps).includes(item.module));
caseArr.forEach((item: any) => {
const currentModule = moduleMaps[item.module];
caseTypeOptions.value.push(...currentModule);
});
currentSelectCase.value = caseTypeOptions.value[0].value;
getParams();
});
</script>

View File

@ -12,14 +12,15 @@
<div :class="getFolderClass('all')" @click="setActiveFolder('all')">
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
<div class="folder-name">{{ t('caseManagement.featureCase.allCase') }}</div>
<div class="folder-count">({{ allFileCount }})</div>
<div class="folder-count">({{ modulesCount['all'] }})</div>
</div>
</div>
<a-divider class="my-[8px]" />
<a-spin class="w-full" :loading="moduleLoading">
<MsTree
v-model:selected-keys="selectedModuleKeys"
:data="folderTree"
v-model:focus-node-key="focusNodeKey"
:selected-keys="selectedNodeKeys"
:data="caseTree"
:keyword="moduleKeyword"
:empty-text="t('caseManagement.featureCase.caseEmptyRecycle')"
:virtual-list-props="virtualListProps"
@ -31,11 +32,15 @@
}"
block-node
title-tooltip-position="left"
@select="folderNodeSelect"
@select="caseNodeSelect"
>
<template #title="nodeData">
<div class="inline-flex w-full">
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
<div
class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]"
@click="setFocusKey(nodeData)"
>{{ nodeData.name }}</div
>
<div class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</div>
</div>
</template>
@ -46,7 +51,7 @@
<div class="mb-[16px] flex items-center justify-between">
<div class="flex items-center">
<div class="mr-[4px] text-[var(--color-text-1)]">{{ activeFolderName }}</div>
<div class="text-[var(--color-text-4)]">({{ activeFolderName }})</div>
<div class="text-[var(--color-text-4)]">({{ modulesCount[activeFolder] }})</div>
</div>
<div class="flex items-center gap-[8px]">
<a-select
@ -72,7 +77,7 @@
</div>
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
<template #caseLevel="{ record }">
<caseLevel :case-level="record.caseLevel" />
<caseLevel :case-level="(getCaseLevel(record) as CaseLevel)" />
</template>
</ms-base-table>
<div class="footer">
@ -84,7 +89,12 @@
<a-button type="secondary" :disabled="loading" class="mr-[12px]" @click="cancel">{{
t('common.cancel')
}}</a-button>
<a-button type="primary" :loading="loading" @click="handleConfirm">
<a-button
type="primary"
:loading="loading"
:disabled="propsRes.selectedKeys.size === 0"
@click="handleConfirm"
>
{{ t('common.add') }}
</a-button>
</slot>
@ -104,29 +114,36 @@
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
import MsTree from '@/components/business/ms-tree/index.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import { getModules } from '@/api/modules/project-management/fileManagement';
import {
addPrepositionRelation,
getCaseModulesCounts,
getCaseModuleTree,
getPrepositionRelation,
} from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { mapTree } from '@/utils';
import type { CaseManagementTable, CaseModuleQueryParams } from '@/models/caseManagement/featureCase';
import type { TableQueryParams } from '@/models/common';
import { ModuleTreeNode } from '@/models/projectManagement/file';
const appStore = useAppStore();
const currentProjectId = computed(() => appStore.currentProjectId);
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
showType: 'preposition' | 'postPosition';
caseId: string;
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(e: 'update:project', val: string): void;
(e: 'init', val: string[]): void;
(e: 'folderNodeSelect', ids: (string | number)[], springIds: string[]): void;
(e: 'success', val: string[]): void;
(e: 'close'): void;
}>();
@ -140,13 +157,11 @@
},
});
const selectedModuleKeys = ref<string[]>([]);
const title = computed(() => {
return props.showType === 'preposition' ? '添加前置用例' : '添加后置用例';
});
const folderTree = ref<ModuleTreeNode[]>([]);
const caseTree = ref<ModuleTreeNode[]>([]);
const moduleLoading = ref(false);
const moduleKeyword = ref('');
@ -159,20 +174,30 @@
const activeFolder = ref('all');
const activeFolderName = ref(t('ms.case.associate.allCase'));
const offspringIds = ref<string[]>([]);
const modulesCount = ref<Record<string, any>>({});
//
const selectedNodeKeys = computed({
get: () => [activeFolder.value],
set: (val) => val,
});
/**
* 处理文件夹树节点选中事件
*/
function folderNodeSelect(_selectedKeys: (string | number)[], node: MsTreeNodeData) {
selectedModuleKeys.value = _selectedKeys as string[];
const focusNodeKey = ref<string>('');
function caseNodeSelect(_selectedKeys: (string | number)[], node: MsTreeNodeData) {
[activeFolder.value] = _selectedKeys as string[];
activeFolder.value = node.id;
activeFolderName.value = node.name;
const offspringIds: string[] = [];
offspringIds.value = [];
mapTree(node.children || [], (e) => {
offspringIds.push(e.id);
offspringIds.value.push(e.id);
return e;
});
emit('folderNodeSelect', _selectedKeys, offspringIds);
focusNodeKey.value = '';
}
const allFileCount = ref(0);
@ -183,8 +208,6 @@
function setActiveFolder(id: string) {
activeFolder.value = id;
activeFolderName.value = t('ms.case.associate.allCase');
selectedModuleKeys.value = [];
emit('folderNodeSelect', [id], []);
}
const keyword = ref('');
const version = ref('');
@ -212,7 +235,7 @@
sortable: {
sortDirections: ['ascend', 'descend'],
},
width: 90,
width: 200,
},
{
title: 'caseManagement.featureCase.tableColumnName',
@ -221,7 +244,7 @@
sortDirections: ['ascend', 'descend'],
},
showTooltip: true,
width: 200,
width: 300,
},
{
title: 'ms.case.associate.caseLevel',
@ -241,85 +264,8 @@
},
];
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
() =>
Promise.resolve({
list: [
{
id: 'ded3d43',
name: '测试评审1',
creator: '张三',
reviewer: '李四',
module: '模块1',
caseLevel: 0, //
caseCount: 100,
passCount: 0,
failCount: 10,
reviewCount: 20,
reviewingCount: 25,
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'g545hj4',
name: '测试评审2',
creator: '张三',
reviewer: '李四',
module: '模块1',
caseLevel: 1, //
caseCount: 105,
passCount: 50,
failCount: 10,
reviewCount: 20,
reviewingCount: 25,
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'hj65b54',
name: '测试评审3',
creator: '张三',
reviewer: '李四',
module: '模块1',
caseLevel: 2, //
caseCount: 125,
passCount: 70,
failCount: 10,
reviewCount: 20,
reviewingCount: 25,
passRate: '80%',
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'wefwefw',
name: '测试评审4',
creator: '张三',
reviewer: '李四',
module: '模块1',
caseLevel: 3, //
caseCount: 130,
passCount: 70,
failCount: 10,
reviewCount: 0,
reviewingCount: 50,
passRate: '80%',
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
],
current: 1,
pageSize: 10,
total: 2,
}),
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
getPrepositionRelation,
{
columns,
scroll: {
@ -329,29 +275,29 @@
selectable: true,
showSelectAll: true,
},
(item) => {
(record) => {
return {
...item,
tags: item.tags?.map((e: string) => ({ id: e, name: e })) || [],
...record,
tags: (JSON.parse(record.tags) || []).map((item: string, i: number) => {
return {
id: `${record.id}-${i}`,
name: item,
};
}),
};
}
);
const loading = ref(false);
//
function getCaseLevel(record: CaseManagementTable) {
const caseLevelRes = record.customFields.find((item: any) => item.name === '用例等级');
if (caseLevelRes) {
return JSON.parse(caseLevelRes.value).replaceAll('P', '') * 1;
}
return 0;
}
async function handleConfirm() {
try {
loading.value = true;
Message.success(t('ms.case.associate.associateSuccess'));
innerVisible.value = false;
emit('success', Array.from(propsRes.value.selectedKeys));
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
}
const loading = ref(false);
function cancel() {
innerVisible.value = false;
@ -361,26 +307,24 @@
* 初始化模块树
* @param isSetDefaultKey 是否设置第一个节点为选中节点
*/
const selectedKeysNode = ref<(string | number)[]>([]);
async function initModules(isSetDefaultKey = false) {
try {
moduleLoading.value = true;
const res = await getModules(appStore.currentProjectId);
folderTree.value = res;
if (isSetDefaultKey) {
selectedModuleKeys.value = [folderTree.value[0].id];
activeFolderName.value = folderTree.value[0].name;
const offspringIds: string[] = [];
mapTree(folderTree.value[0].children || [], (e) => {
offspringIds.push(e.id);
return e;
const res = await getCaseModuleTree({ projectId: appStore.currentProjectId });
caseTree.value = mapTree<ModuleTreeNode>(res, (e) => {
return {
...e,
hideMoreAction: e.id === 'root',
draggable: false,
disabled: false,
count: modulesCount.value?.[e.id] || 0,
};
});
emit('folderNodeSelect', selectedModuleKeys.value, offspringIds);
if (isSetDefaultKey) {
selectedKeysNode.value = [caseTree.value[0].id];
}
emit(
'init',
folderTree.value.map((e) => e.name)
);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@ -388,17 +332,115 @@
moduleLoading.value = false;
}
}
/**
* @param 获取回收站模块
*/
function searchCase() {
setLoadListParams({
version: version.value,
// count
const emitTableParams: CaseModuleQueryParams = {
keyword: keyword.value,
});
loadList();
moduleIds: [],
projectId: currentProjectId.value,
current: propsRes.value.msPagination?.current,
pageSize: propsRes.value.msPagination?.pageSize,
};
async function getModulesCount() {
try {
modulesCount.value = await getCaseModulesCounts(emitTableParams);
} catch (error) {
console.log(error);
}
}
onBeforeMount(() => {
const searchParams = ref<TableQueryParams>({
projectId: currentProjectId.value,
moduleIds: [],
});
//
function getLoadListParams() {
if (activeFolder.value === 'all') {
searchParams.value.moduleIds = [];
} else {
searchParams.value.moduleIds = [activeFolder.value, ...offspringIds.value];
}
setLoadListParams({
...searchParams.value,
keyword: keyword.value,
id: props.caseId,
type: props.showType === 'preposition' ? 'PRE' : 'POST',
});
}
const setFocusKey = (node: MsTreeNodeData) => {
focusNodeKey.value = node.id || '';
};
function searchCase() {
getLoadListParams();
loadList();
getModulesCount();
}
async function handleConfirm() {
loading.value = true;
try {
const { excludeKeys, selectedKeys, selectorStatus } = propsRes.value;
const { versionId, moduleIds } = searchParams.value;
const params = {
id: props.caseId,
excludeIds: [...excludeKeys],
selectIds: selectorStatus === 'all' ? [] : [...selectedKeys],
selectAll: selectorStatus === 'all',
moduleIds,
versionId,
refId: '',
type: props.showType === 'preposition' ? 'PRE' : 'POST',
projectId: currentProjectId.value,
};
await addPrepositionRelation(params);
Message.success(t('common.addSuccess'));
innerVisible.value = false;
resetSelector();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
}
watch(
() => innerVisible.value,
(val) => {
if (val) {
searchCase();
}
}
);
watch(
() => modulesCount.value,
(obj) => {
caseTree.value = mapTree<ModuleTreeNode>(caseTree.value, (node) => {
return {
...node,
count: obj?.[node.id] || 0,
};
});
}
);
watch(
() => activeFolder.value,
() => {
searchCase();
}
);
onMounted(() => {
resetSelector();
});
defineExpose({

View File

@ -10,7 +10,7 @@
</a-button>
</div>
<div>
<a-radio-group v-model:model-value="showType" type="button" class="file-show-type ml-[4px]">
<a-radio-group v-model="showType" type="button" class="file-show-type ml-[4px]">
<a-radio value="preposition" class="show-type-icon p-[2px]">{{
t('caseManagement.featureCase.preCase')
}}</a-radio>
@ -26,7 +26,7 @@
></a-input-search>
</div>
</div>
<ms-base-table v-if="showType === 'preposition'" ref="tableRef" v-bind="prePropsRes" v-on="preTableEvent">
<ms-base-table ref="tableRef" v-bind="propsRes" v-on="propsEvent">
<template #operation="{ record }">
<MsButton @click="cancelDependency(record)">{{ t('caseManagement.featureCase.cancelDependency') }}</MsButton>
</template>
@ -39,20 +39,7 @@
</div>
</template>
</ms-base-table>
<ms-base-table v-else v-bind="postPropsRes" v-on="postTableEvent">
<template #operation="{ record }">
<MsButton @click="cancelDependency(record)">{{ t('caseManagement.featureCase.cancelDependency') }}</MsButton>
</template>
<template v-if="(keyword || '').trim() === ''" #empty>
<div class="flex items-center justify-center">
{{ t('caseManagement.caseReview.tableNoData') }}
<MsButton class="ml-[8px]" @click="addCase('postPosition')">
{{ t('caseManagement.featureCase.addPostCase') }}
</MsButton>
</div>
</template>
</ms-base-table>
<PreAndPostCaseDrawer ref="drawerRef" v-model:visible="showDrawer" :show-type="showType" />
<PreAndPostCaseDrawer ref="drawerRef" v-model:visible="showDrawer" :show-type="showType" :case-id="props.caseId" />
</div>
</template>
@ -65,16 +52,21 @@
import useTable from '@/components/pure/ms-table/useTable';
import PreAndPostCaseDrawer from './preAndPostCaseDrawer.vue';
import { getRecycleListRequest } from '@/api/modules/case-management/featureCase';
import { getDependOnCase } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { TableKeyEnum } from '@/enums/tableEnum';
const appStore = useAppStore();
export type types = 'preposition' | 'postPosition';
const currentProjectId = computed(() => appStore.currentProjectId);
const showType = ref<types>('preposition');
const { t } = useI18n();
const keyword = ref<string>('');
const props = defineProps<{
caseId: string;
}>();
const columns: MsTableColumn = [
{
@ -84,7 +76,6 @@
showInTable: true,
showTooltip: true,
ellipsis: true,
showDrag: false,
},
{
title: 'caseManagement.featureCase.tableColumnName',
@ -94,7 +85,6 @@
showTooltip: true,
width: 300,
ellipsis: true,
showDrag: false,
},
{
title: 'caseManagement.featureCase.tableColumnVersion',
@ -104,7 +94,6 @@
showTooltip: true,
width: 300,
ellipsis: true,
showDrag: false,
},
{
title: 'caseManagement.featureCase.tableColumnCreateUser',
@ -114,7 +103,6 @@
showTooltip: true,
width: 300,
ellipsis: true,
showDrag: false,
},
{
title: 'caseManagement.featureCase.tableColumnActions',
@ -123,47 +111,29 @@
fixed: 'right',
width: 140,
showInTable: true,
showDrag: false,
},
];
const {
propsRes: prePropsRes,
propsEvent: preTableEvent,
loadList: loadPreList,
setLoadListParams: setPreListParams,
} = useTable(getRecycleListRequest, {
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(getDependOnCase, {
columns,
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEPENDENCY_PRE_CASE,
scroll: { x: '100%' },
heightUsed: 340,
enableDrag: true,
});
const {
propsRes: postPropsRes,
propsEvent: postTableEvent,
loadList: loadPostList,
setLoadListParams: setPostListParams,
} = useTable(getRecycleListRequest, {
columns,
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEPENDENCY_POST_CASE,
scroll: { x: '100%' },
heightUsed: 340,
selectable: false,
noDisable: true,
showSetting: false,
enableDrag: true,
});
//
function cancelDependency(record: any) {}
function getFetch() {
if (showType.value === 'preposition') {
setPreListParams({ keyword: keyword.value });
loadPreList();
} else {
setPostListParams({ keyword: keyword.value });
loadPostList();
}
function getParams() {
setLoadListParams({
projectId: currentProjectId.value,
keyword: keyword.value,
type: showType.value === 'preposition' ? 'PRE' : 'POST',
id: props.caseId,
});
}
const showDrawer = ref<boolean>(false);
@ -176,12 +146,16 @@
watch(
() => showType.value,
(val) => {
if (val) {
getFetch();
}
() => {
getParams();
loadList();
}
);
onBeforeMount(() => {
getParams();
loadList();
});
</script>
<style scoped></style>

View File

@ -1,16 +1,18 @@
<template>
<MsCaseAssociate
v-model:visible="innerVisible"
v-model:project="innerProject"
v-model:project-id="innerProject"
v-model:currentSelectCase="currentSelectCase"
:ok-button-disabled="associateForm.reviewers.length === 0"
:get-modules-func="getCaseModuleTree"
:modules-count="modulesCount"
:modules-params="modulesTreeParams"
:get-table-func="getCaseList"
:associated-ids="associatedIds"
:modules-count="modulesCount"
:confirm-loading="confirmLoading"
@init="getModuleCount"
:associated-ids="associatedIds"
@close="emit('close')"
@save="saveHandler"
@init="getModuleCount"
>
<template #footerLeft>
<a-form ref="associateFormRef" :model="associateForm">
@ -119,7 +121,7 @@
}
);
const innerProject = ref('');
const innerProject = ref(appStore.currentProjectId);
watch(
() => props.project,
@ -166,8 +168,8 @@
const currentSelectCase = ref<string | number | Record<string, any> | undefined>('');
const modulesCount = ref<Record<string, any>>({});
async function getModuleCount(params: CaseModuleQueryParams) {
const modulesTreeParams = ref<TableQueryParams>({});
async function getModuleCount(params: TableQueryParams) {
try {
modulesCount.value = await getCaseModulesCounts(params);
} catch (error) {