feat(测试计划): 测试计划详情-接口用例和接口场景页面样式
This commit is contained in:
parent
0e57b01a8b
commit
86eee33ee9
|
@ -0,0 +1,86 @@
|
||||||
|
<template>
|
||||||
|
<div class="folder">
|
||||||
|
<div :class="getFolderClass()" @click="emit('setActiveFolder', 'all')">
|
||||||
|
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
||||||
|
<div class="folder-name">{{ props.folderName }}</div>
|
||||||
|
<div class="folder-count">({{ addCommasToNumber(props.allCount) }})</div>
|
||||||
|
</div>
|
||||||
|
<div class="ml-auto flex items-center">
|
||||||
|
<slot name="expandLeft"></slot>
|
||||||
|
<a-tooltip
|
||||||
|
v-if="typeof isExpandAll === 'boolean'"
|
||||||
|
:content="isExpandAll ? t('common.collapseAll') : t('common.expandAll')"
|
||||||
|
>
|
||||||
|
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="changeExpand">
|
||||||
|
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
|
||||||
|
</MsButton>
|
||||||
|
</a-tooltip>
|
||||||
|
<slot name="expandRight"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { addCommasToNumber } from '@/utils';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
activeFolder: string; // 选中的节点
|
||||||
|
folderName: string; // 名称
|
||||||
|
allCount: number; // 总数
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const isExpandAll = defineModel<boolean>('isExpandAll', {
|
||||||
|
required: false,
|
||||||
|
default: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'setActiveFolder', val: string): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
function getFolderClass() {
|
||||||
|
return props.activeFolder === 'all' ? 'folder-text folder-text--active' : 'folder-text';
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeExpand() {
|
||||||
|
isExpandAll.value = !isExpandAll.value;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.folder {
|
||||||
|
@apply flex cursor-pointer items-center justify-between;
|
||||||
|
|
||||||
|
padding: 8px 4px;
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(var(--primary-1));
|
||||||
|
}
|
||||||
|
.folder-text {
|
||||||
|
@apply flex cursor-pointer items-center;
|
||||||
|
.folder-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
color: var(--color-text-4);
|
||||||
|
}
|
||||||
|
.folder-name {
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
.folder-count {
|
||||||
|
margin-left: 4px;
|
||||||
|
color: var(--color-text-4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.folder-text--active {
|
||||||
|
.folder-icon,
|
||||||
|
.folder-name,
|
||||||
|
.folder-count {
|
||||||
|
color: rgb(var(--primary-5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -69,6 +69,8 @@ export enum TableKeyEnum {
|
||||||
TEST_PLAN_DETAIL_FEATURE_CASE_TABLE = 'testPlanDetailFeatureCaseTable',
|
TEST_PLAN_DETAIL_FEATURE_CASE_TABLE = 'testPlanDetailFeatureCaseTable',
|
||||||
TEST_PLAN_DETAIL_BUG_TABLE_CASE_COUNT = 'testPlanDetailBugCaseCount',
|
TEST_PLAN_DETAIL_BUG_TABLE_CASE_COUNT = 'testPlanDetailBugCaseCount',
|
||||||
TEST_PLAN_DETAIL_CASE_TABLE_BUG_COUNT = 'testPlanDetailCaseBugCount',
|
TEST_PLAN_DETAIL_CASE_TABLE_BUG_COUNT = 'testPlanDetailCaseBugCount',
|
||||||
|
TEST_PLAN_DETAIL_API_CASE = 'testPlanDetailApiCase',
|
||||||
|
TEST_PLAN_DETAIL_API_SCENARIO = 'testPlanDetailApiScenario',
|
||||||
TEST_PLAN_REPORT_TABLE = 'testPlanReportTable',
|
TEST_PLAN_REPORT_TABLE = 'testPlanReportTable',
|
||||||
TEST_PLAN_REPORT_DETAIL_BUG = 'testPlanReportDetailBug',
|
TEST_PLAN_REPORT_DETAIL_BUG = 'testPlanReportDetailBug',
|
||||||
TEST_PLAN_REPORT_DETAIL_FEATURE_CASE = 'testPlanReportDetailFeatureCase',
|
TEST_PLAN_REPORT_DETAIL_FEATURE_CASE = 'testPlanReportDetailFeatureCase',
|
||||||
|
|
|
@ -69,6 +69,8 @@ export interface TestPlanDetail extends AddTestPlanParams {
|
||||||
reReviewedCount: number;
|
reReviewedCount: number;
|
||||||
underReviewedCount: number;
|
underReviewedCount: number;
|
||||||
functionalCaseCount?: number;
|
functionalCaseCount?: number;
|
||||||
|
apiCaseCount?: number;
|
||||||
|
apiScenarioCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计划分页
|
// 计划分页
|
||||||
|
|
|
@ -386,7 +386,6 @@
|
||||||
},
|
},
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
width: 130,
|
width: 130,
|
||||||
ellipsis: true,
|
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
columnSelectorDisabled: true,
|
columnSelectorDisabled: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,13 +7,12 @@
|
||||||
class="mb-[8px]"
|
class="mb-[8px]"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
/>
|
/>
|
||||||
<div class="folder">
|
<MsFolderAll
|
||||||
<div :class="getFolderClass('all')" @click="setActiveFolder('all')">
|
:active-folder="activeFolder"
|
||||||
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
:folder-name="t('caseManagement.caseReview.allCases')"
|
||||||
<div class="folder-name">{{ t('caseManagement.caseReview.allCases') }}</div>
|
:all-count="allCount"
|
||||||
<div class="folder-count">({{ allCount }})</div>
|
@set-active-folder="setActiveFolder"
|
||||||
</div>
|
/>
|
||||||
</div>
|
|
||||||
<a-divider class="my-[8px]" />
|
<a-divider class="my-[8px]" />
|
||||||
<a-spin class="min-h-[200px] w-full" :loading="loading">
|
<a-spin class="min-h-[200px] w-full" :loading="loading">
|
||||||
<MsTree
|
<MsTree
|
||||||
|
@ -51,7 +50,7 @@
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsFolderAll from '@/components/business/ms-folder-all/index.vue';
|
||||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
|
|
||||||
|
@ -92,10 +91,6 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
function getFolderClass(id: string) {
|
|
||||||
return activeFolder.value === id ? 'folder-text folder-text--active' : 'folder-text';
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveFolder(id: string) {
|
function setActiveFolder(id: string) {
|
||||||
activeFolder.value = id;
|
activeFolder.value = id;
|
||||||
emit('folderNodeSelect', [id], []);
|
emit('folderNodeSelect', [id], []);
|
||||||
|
@ -166,36 +161,3 @@
|
||||||
initModules,
|
initModules,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.folder {
|
|
||||||
@apply flex cursor-pointer items-center justify-between;
|
|
||||||
|
|
||||||
padding: 8px 4px;
|
|
||||||
border-radius: var(--border-radius-small);
|
|
||||||
&:hover {
|
|
||||||
background-color: rgb(var(--primary-1));
|
|
||||||
}
|
|
||||||
.folder-text {
|
|
||||||
@apply flex cursor-pointer items-center;
|
|
||||||
.folder-icon {
|
|
||||||
margin-right: 4px;
|
|
||||||
color: var(--color-text-4);
|
|
||||||
}
|
|
||||||
.folder-name {
|
|
||||||
color: var(--color-text-1);
|
|
||||||
}
|
|
||||||
.folder-count {
|
|
||||||
margin-left: 4px;
|
|
||||||
color: var(--color-text-4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.folder-text--active {
|
|
||||||
.folder-icon,
|
|
||||||
.folder-name,
|
|
||||||
.folder-count {
|
|
||||||
color: rgb(var(--primary-5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -16,19 +16,15 @@
|
||||||
{{ t('common.newCreate') }}
|
{{ t('common.newCreate') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
<MsFolderAll
|
||||||
<div v-if="!props.isModal" class="folder">
|
v-if="!props.isModal"
|
||||||
<div :class="getFolderClass('all')" @click="setActiveFolder('all')">
|
v-model:isExpandAll="isExpandAll"
|
||||||
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
:active-folder="activeFolder"
|
||||||
<div class="folder-name">{{ t('caseManagement.caseReview.allReviews') }}</div>
|
:folder-name="t('caseManagement.caseReview.allReviews')"
|
||||||
<div class="folder-count">({{ allFileCount }})</div>
|
:all-count="allFileCount"
|
||||||
</div>
|
@set-active-folder="setActiveFolder"
|
||||||
<div class="ml-auto flex items-center">
|
>
|
||||||
<a-tooltip :content="isExpandAll ? t('common.collapseAll') : t('common.expandAll')">
|
<template #expandRight>
|
||||||
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="changeExpand">
|
|
||||||
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
|
|
||||||
</MsButton>
|
|
||||||
</a-tooltip>
|
|
||||||
<popConfirm
|
<popConfirm
|
||||||
v-if="hasAnyPermission(['CASE_REVIEW:READ+UPDATE'])"
|
v-if="hasAnyPermission(['CASE_REVIEW:READ+UPDATE'])"
|
||||||
mode="add"
|
mode="add"
|
||||||
|
@ -44,8 +40,8 @@
|
||||||
/>
|
/>
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</popConfirm>
|
</popConfirm>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</MsFolderAll>
|
||||||
<a-divider v-if="!props.isModal" class="my-[8px]" />
|
<a-divider v-if="!props.isModal" class="my-[8px]" />
|
||||||
<a-spin class="min-h-[400px] w-full" :loading="loading">
|
<a-spin class="min-h-[400px] w-full" :loading="loading">
|
||||||
<MsTree
|
<MsTree
|
||||||
|
@ -119,6 +115,7 @@
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
|
import MsFolderAll from '@/components/business/ms-folder-all/index.vue';
|
||||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
import popConfirm from './popConfirm.vue';
|
import popConfirm from './popConfirm.vue';
|
||||||
|
@ -173,14 +170,6 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
function changeExpand() {
|
|
||||||
isExpandAll.value = !isExpandAll.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFolderClass(id: string) {
|
|
||||||
return activeFolder.value === id ? 'folder-text folder-text--active' : 'folder-text';
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveFolder(id: string) {
|
function setActiveFolder(id: string) {
|
||||||
activeFolder.value = id;
|
activeFolder.value = id;
|
||||||
if (id === 'all') {
|
if (id === 'all') {
|
||||||
|
@ -385,36 +374,3 @@
|
||||||
initModules,
|
initModules,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.folder {
|
|
||||||
@apply flex cursor-pointer items-center justify-between;
|
|
||||||
|
|
||||||
padding: 8px 4px;
|
|
||||||
border-radius: var(--border-radius-small);
|
|
||||||
&:hover {
|
|
||||||
background-color: rgb(var(--primary-1));
|
|
||||||
}
|
|
||||||
.folder-text {
|
|
||||||
@apply flex cursor-pointer items-center;
|
|
||||||
.folder-icon {
|
|
||||||
margin-right: 4px;
|
|
||||||
color: var(--color-text-4);
|
|
||||||
}
|
|
||||||
.folder-name {
|
|
||||||
color: var(--color-text-1);
|
|
||||||
}
|
|
||||||
.folder-count {
|
|
||||||
margin-left: 4px;
|
|
||||||
color: var(--color-text-4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.folder-text--active {
|
|
||||||
.folder-icon,
|
|
||||||
.folder-name,
|
|
||||||
.folder-count {
|
|
||||||
color: rgb(var(--primary-5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
},
|
},
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
width: 100,
|
width: 100,
|
||||||
ellipsis: true,
|
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,528 @@
|
||||||
|
<template>
|
||||||
|
<div class="p-[16px]">
|
||||||
|
<MsAdvanceFilter
|
||||||
|
v-model:keyword="keyword"
|
||||||
|
:filter-config-list="[]"
|
||||||
|
:custom-fields-config-list="[]"
|
||||||
|
:row-count="0"
|
||||||
|
:count="props.modulesCount[props.activeModule] || 0"
|
||||||
|
:name="moduleNamePath"
|
||||||
|
:search-placeholder="t('common.searchByIdName')"
|
||||||
|
@keyword-search="loadCaseList"
|
||||||
|
@adv-search="loadCaseList"
|
||||||
|
@refresh="loadCaseList"
|
||||||
|
/>
|
||||||
|
<MsBaseTable
|
||||||
|
ref="tableRef"
|
||||||
|
class="mt-[16px]"
|
||||||
|
v-bind="propsRes"
|
||||||
|
:action-config="batchActions"
|
||||||
|
v-on="propsEvent"
|
||||||
|
@batch-action="handleTableBatch"
|
||||||
|
@drag-change="handleDragChange"
|
||||||
|
@selected-change="handleTableSelect"
|
||||||
|
@filter-change="getModuleCount"
|
||||||
|
>
|
||||||
|
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
|
||||||
|
<CaseLevel :case-level="filterContent.value" />
|
||||||
|
</template>
|
||||||
|
<template #caseLevel="{ record }">
|
||||||
|
<CaseLevel :case-level="record.caseLevel" />
|
||||||
|
</template>
|
||||||
|
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT]="{ filterContent }">
|
||||||
|
<ExecuteResult :execute-result="filterContent.key" />
|
||||||
|
</template>
|
||||||
|
<template #lastExecResult="{ record }">
|
||||||
|
<ExecuteResult :execute-result="record.lastExecResult" />
|
||||||
|
<MsIcon
|
||||||
|
v-show="record.lastExecResult !== LastExecuteResults.PENDING"
|
||||||
|
type="icon-icon_take-action_outlined"
|
||||||
|
class="ml-[8px] cursor-pointer text-[rgb(var(--primary-5))]"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #status="{ record }">
|
||||||
|
<apiStatus :status="record.status" />
|
||||||
|
</template>
|
||||||
|
<template v-if="props.canEdit" #operation="{ record }">
|
||||||
|
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+EXECUTE']" type="text" class="!mr-0">
|
||||||
|
{{ t('common.execute') }}
|
||||||
|
</MsButton>
|
||||||
|
<a-divider v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']" direction="vertical" :margin="8"></a-divider>
|
||||||
|
<MsPopconfirm
|
||||||
|
:title="t('testPlan.featureCase.disassociateTip', { name: characterLimit(record.name) })"
|
||||||
|
:sub-title-tip="t('testPlan.featureCase.disassociateTipContent')"
|
||||||
|
:ok-text="t('common.confirm')"
|
||||||
|
:loading="disassociateLoading"
|
||||||
|
type="error"
|
||||||
|
@confirm="(val, done) => handleDisassociateCase(record, done)"
|
||||||
|
>
|
||||||
|
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']" type="text" class="!mr-0">
|
||||||
|
{{ t('common.cancelLink') }}
|
||||||
|
</MsButton>
|
||||||
|
</MsPopconfirm>
|
||||||
|
<a-divider
|
||||||
|
v-if="props.repeatCase"
|
||||||
|
v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']"
|
||||||
|
direction="vertical"
|
||||||
|
:margin="8"
|
||||||
|
></a-divider>
|
||||||
|
<MsButton
|
||||||
|
v-if="props.repeatCase"
|
||||||
|
v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']"
|
||||||
|
type="text"
|
||||||
|
class="!mr-0"
|
||||||
|
@click="handleCopyCase(record)"
|
||||||
|
>
|
||||||
|
{{ t('common.copy') }}
|
||||||
|
</MsButton>
|
||||||
|
</template>
|
||||||
|
</MsBaseTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onBeforeMount, ref } from 'vue';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
|
||||||
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
|
import MsPopconfirm from '@/components/pure/ms-popconfirm/index.vue';
|
||||||
|
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||||
|
import type {
|
||||||
|
BatchActionParams,
|
||||||
|
BatchActionQueryParams,
|
||||||
|
MsTableColumn,
|
||||||
|
MsTableProps,
|
||||||
|
} 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 ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
||||||
|
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
associationCaseToPlan,
|
||||||
|
batchDisassociateCase,
|
||||||
|
disassociateCase,
|
||||||
|
getPlanDetailFeatureCaseList,
|
||||||
|
sortFeatureCase,
|
||||||
|
} from '@/api/modules/test-plan/testPlan';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useModal from '@/hooks/useModal';
|
||||||
|
import useTableStore from '@/hooks/useTableStore';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import { characterLimit } from '@/utils';
|
||||||
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
|
import { DragSortParams, ModuleTreeNode } from '@/models/common';
|
||||||
|
import type { PlanDetailFeatureCaseItem, PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
|
||||||
|
import { LastExecuteResults } from '@/enums/caseEnum';
|
||||||
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||||
|
|
||||||
|
import { casePriorityOptions } from '@/views/api-test/components/config';
|
||||||
|
import {
|
||||||
|
executionResultMap,
|
||||||
|
getCaseLevels,
|
||||||
|
getModules,
|
||||||
|
} from '@/views/case-management/caseManagementFeature/components/utils';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modulesCount: Record<string, number>; // 模块数量统计对象
|
||||||
|
moduleName: string;
|
||||||
|
activeModule: string;
|
||||||
|
offspringIds: string[];
|
||||||
|
planId: string;
|
||||||
|
moduleTree: ModuleTreeNode[];
|
||||||
|
repeatCase: boolean;
|
||||||
|
canEdit: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'getModuleCount', params: PlanDetailFeatureCaseListQueryParams): void;
|
||||||
|
(e: 'refresh'): void;
|
||||||
|
(e: 'initModules'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const tableStore = useTableStore();
|
||||||
|
const { openModal } = useModal();
|
||||||
|
|
||||||
|
const keyword = ref('');
|
||||||
|
const moduleNamePath = computed(() => {
|
||||||
|
return props.activeModule === 'all' ? t('apiTestManagement.allApi') : props.moduleName;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasOperationPermission = computed(
|
||||||
|
() => hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN:READ+ASSOCIATION']) && props.canEdit
|
||||||
|
);
|
||||||
|
const columns = computed<MsTableColumn>(() => [
|
||||||
|
{
|
||||||
|
title: 'ID',
|
||||||
|
dataIndex: 'num',
|
||||||
|
sortIndex: 1,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
fixed: 'left',
|
||||||
|
width: 100,
|
||||||
|
showTooltip: true,
|
||||||
|
columnSelectorDisabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'case.caseName',
|
||||||
|
dataIndex: 'name',
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
width: 150,
|
||||||
|
showTooltip: true,
|
||||||
|
columnSelectorDisabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'case.caseLevel',
|
||||||
|
dataIndex: 'caseLevel',
|
||||||
|
slotName: 'caseLevel',
|
||||||
|
filterConfig: {
|
||||||
|
options: casePriorityOptions,
|
||||||
|
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL,
|
||||||
|
},
|
||||||
|
width: 150,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.executionResult',
|
||||||
|
dataIndex: 'lastExecResult',
|
||||||
|
slotName: 'lastExecResult',
|
||||||
|
filterConfig: {
|
||||||
|
valueKey: 'key',
|
||||||
|
labelKey: 'statusText',
|
||||||
|
options: Object.values(executionResultMap),
|
||||||
|
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT,
|
||||||
|
},
|
||||||
|
width: 150,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.apiStatus',
|
||||||
|
dataIndex: 'status',
|
||||||
|
slotName: 'status',
|
||||||
|
width: 150,
|
||||||
|
showDrag: true,
|
||||||
|
showInTable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.path',
|
||||||
|
dataIndex: 'path',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 200,
|
||||||
|
showDrag: true,
|
||||||
|
showInTable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.belongModule',
|
||||||
|
dataIndex: 'moduleId',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 200,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.belongProject',
|
||||||
|
dataIndex: 'projectName',
|
||||||
|
showTooltip: true,
|
||||||
|
showDrag: true,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'report.detail.api.executeEnv',
|
||||||
|
dataIndex: 'executeEnv',
|
||||||
|
width: 150,
|
||||||
|
showInTable: false,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'case.tableColumnCreateUser',
|
||||||
|
dataIndex: 'createUserName',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 130,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'testPlan.featureCase.executor',
|
||||||
|
dataIndex: 'executeUserName',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 130,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: hasOperationPermission.value ? 'common.operation' : '',
|
||||||
|
slotName: 'operation',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
width: hasOperationPermission.value ? 200 : 50,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tableProps = ref<Partial<MsTableProps<PlanDetailFeatureCaseItem>>>({
|
||||||
|
scroll: { x: '100%' },
|
||||||
|
tableKey: TableKeyEnum.TEST_PLAN_DETAIL_API_CASE,
|
||||||
|
showSetting: true,
|
||||||
|
heightUsed: 460,
|
||||||
|
showSubdirectory: true,
|
||||||
|
draggable: { type: 'handle' },
|
||||||
|
draggableCondition: true,
|
||||||
|
selectable: hasOperationPermission.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||||
|
// TODO 联调
|
||||||
|
getPlanDetailFeatureCaseList,
|
||||||
|
tableProps.value,
|
||||||
|
(record) => {
|
||||||
|
return {
|
||||||
|
...record,
|
||||||
|
lastExecResult: record.lastExecResult ?? LastExecuteResults.PENDING,
|
||||||
|
caseLevel: getCaseLevels(record.customFields),
|
||||||
|
moduleId: getModules(record.moduleId, props.moduleTree),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.canEdit,
|
||||||
|
(val) => {
|
||||||
|
tableProps.value.draggableCondition = hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && val;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const tableRef = ref<InstanceType<typeof MsBaseTable>>();
|
||||||
|
watch(
|
||||||
|
() => hasOperationPermission.value,
|
||||||
|
() => {
|
||||||
|
tableRef.value?.initColumn(columns.value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const batchActions = {
|
||||||
|
baseAction: [
|
||||||
|
{
|
||||||
|
label: 'common.execute',
|
||||||
|
eventTag: 'execute',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+EXECUTE'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'testPlan.featureCase.changeExecutor',
|
||||||
|
eventTag: 'changeExecutor',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.move',
|
||||||
|
eventTag: 'move',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.cancelLink',
|
||||||
|
eventTag: 'disassociate',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+ASSOCIATION'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getModuleIds() {
|
||||||
|
let moduleIds: string[] = [];
|
||||||
|
if (props.activeModule !== 'all') {
|
||||||
|
moduleIds = [props.activeModule];
|
||||||
|
const getAllChildren = await tableStore.getSubShow(TableKeyEnum.TEST_PLAN_DETAIL_API_CASE);
|
||||||
|
if (getAllChildren) {
|
||||||
|
moduleIds = [props.activeModule, ...props.offspringIds];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return moduleIds;
|
||||||
|
}
|
||||||
|
async function getTableParams(isBatch: boolean) {
|
||||||
|
const selectModules = await getModuleIds();
|
||||||
|
const commonParams = {
|
||||||
|
testPlanId: props.planId,
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
moduleIds: selectModules,
|
||||||
|
};
|
||||||
|
if (isBatch) {
|
||||||
|
return {
|
||||||
|
condition: {
|
||||||
|
keyword: keyword.value,
|
||||||
|
filter: propsRes.value.filter,
|
||||||
|
},
|
||||||
|
...commonParams,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
keyword: keyword.value,
|
||||||
|
filter: propsRes.value.filter,
|
||||||
|
...commonParams,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCaseList() {
|
||||||
|
const tableParams = await getTableParams(false);
|
||||||
|
setLoadListParams(tableParams);
|
||||||
|
loadList();
|
||||||
|
emit('getModuleCount', {
|
||||||
|
...tableParams,
|
||||||
|
current: propsRes.value.msPagination?.current,
|
||||||
|
pageSize: propsRes.value.msPagination?.pageSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => props.activeModule,
|
||||||
|
() => {
|
||||||
|
loadCaseList();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
async function getModuleCount() {
|
||||||
|
const tableParams = await getTableParams(false);
|
||||||
|
emit('getModuleCount', {
|
||||||
|
...tableParams,
|
||||||
|
current: propsRes.value.msPagination?.current,
|
||||||
|
pageSize: propsRes.value.msPagination?.pageSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableSelected = ref<(string | number)[]>([]); // 表格选中的
|
||||||
|
const batchParams = ref<BatchActionQueryParams>({
|
||||||
|
selectIds: [],
|
||||||
|
selectAll: false,
|
||||||
|
excludeIds: [],
|
||||||
|
condition: {},
|
||||||
|
currentSelectCount: 0,
|
||||||
|
});
|
||||||
|
function handleTableSelect(arr: (string | number)[]) {
|
||||||
|
tableSelected.value = arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetCaseList() {
|
||||||
|
resetSelector();
|
||||||
|
getModuleCount();
|
||||||
|
loadList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拖拽排序
|
||||||
|
async function handleDragChange(params: DragSortParams) {
|
||||||
|
try {
|
||||||
|
// TODO 联调
|
||||||
|
await sortFeatureCase({ ...params, testPlanId: props.planId });
|
||||||
|
Message.success(t('caseManagement.featureCase.sortSuccess'));
|
||||||
|
loadCaseList();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制用例
|
||||||
|
async function handleCopyCase(record: PlanDetailFeatureCaseItem) {
|
||||||
|
try {
|
||||||
|
// TODO 联调
|
||||||
|
await associationCaseToPlan({
|
||||||
|
functionalSelectIds: [record.caseId],
|
||||||
|
testPlanId: props.planId,
|
||||||
|
});
|
||||||
|
Message.success(t('ms.case.associate.associateSuccess'));
|
||||||
|
resetCaseList();
|
||||||
|
emit('refresh');
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消关联
|
||||||
|
const disassociateLoading = ref(false);
|
||||||
|
async function handleDisassociateCase(record: PlanDetailFeatureCaseItem, done?: () => void) {
|
||||||
|
try {
|
||||||
|
disassociateLoading.value = true;
|
||||||
|
// TODO 联调
|
||||||
|
await disassociateCase({ testPlanId: props.planId, id: record.id });
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
Message.success(t('common.unLinkSuccess'));
|
||||||
|
resetCaseList();
|
||||||
|
emit('initModules');
|
||||||
|
emit('refresh');
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
disassociateLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量取消关联用例
|
||||||
|
function handleBatchDisassociateCase() {
|
||||||
|
openModal({
|
||||||
|
type: 'warning',
|
||||||
|
title: t('caseManagement.caseReview.disassociateConfirmTitle', {
|
||||||
|
count: batchParams.value.currentSelectCount || tableSelected.value.length,
|
||||||
|
}),
|
||||||
|
content: t('testPlan.featureCase.batchDisassociateTipContent'),
|
||||||
|
okText: t('common.cancelLink'),
|
||||||
|
cancelText: t('common.cancel'),
|
||||||
|
onBeforeOk: async () => {
|
||||||
|
try {
|
||||||
|
const tableParams = await getTableParams(true);
|
||||||
|
// TODO 联调
|
||||||
|
await batchDisassociateCase({
|
||||||
|
selectIds: tableSelected.value as string[],
|
||||||
|
selectAll: batchParams.value.selectAll,
|
||||||
|
excludeIds: batchParams.value?.excludeIds || [],
|
||||||
|
...tableParams,
|
||||||
|
});
|
||||||
|
Message.success(t('common.updateSuccess'));
|
||||||
|
resetCaseList();
|
||||||
|
emit('initModules');
|
||||||
|
emit('refresh');
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideCancel: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理表格选中后批量操作
|
||||||
|
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
|
||||||
|
tableSelected.value = params?.selectedIds || [];
|
||||||
|
batchParams.value = { ...params, selectIds: params?.selectedIds };
|
||||||
|
switch (event.eventTag) {
|
||||||
|
case 'execute':
|
||||||
|
break;
|
||||||
|
case 'disassociate':
|
||||||
|
handleBatchDisassociateCase();
|
||||||
|
break;
|
||||||
|
case 'changeExecutor':
|
||||||
|
break;
|
||||||
|
case 'move':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
loadCaseList();
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
resetSelector,
|
||||||
|
loadCaseList,
|
||||||
|
});
|
||||||
|
|
||||||
|
await tableStore.initColumn(TableKeyEnum.TEST_PLAN_DETAIL_API_CASE, columns.value, 'drawer', true);
|
||||||
|
</script>
|
|
@ -0,0 +1,151 @@
|
||||||
|
<template>
|
||||||
|
<div class="p-[16px]">
|
||||||
|
<a-input
|
||||||
|
v-model:model-value="moduleKeyword"
|
||||||
|
:placeholder="t('caseManagement.caseReview.folderSearchPlaceholder')"
|
||||||
|
allow-clear
|
||||||
|
class="mb-[8px]"
|
||||||
|
:max-length="255"
|
||||||
|
/>
|
||||||
|
<MsFolderAll
|
||||||
|
v-model:isExpandAll="isExpandAll"
|
||||||
|
:active-folder="activeFolder"
|
||||||
|
:folder-name="t('apiTestManagement.allApi')"
|
||||||
|
:all-count="allCount"
|
||||||
|
@set-active-folder="setActiveFolder"
|
||||||
|
/>
|
||||||
|
<a-divider class="my-[8px]" />
|
||||||
|
<a-spin class="min-h-[200px] w-full" :loading="loading">
|
||||||
|
<MsTree
|
||||||
|
:selected-keys="selectedKeys"
|
||||||
|
:data="folderTree"
|
||||||
|
:keyword="moduleKeyword"
|
||||||
|
:default-expand-all="isExpandAll"
|
||||||
|
:expand-all="isExpandAll"
|
||||||
|
:empty-text="t('common.noMatchData')"
|
||||||
|
:draggable="false"
|
||||||
|
:virtual-list-props="virtualListProps"
|
||||||
|
:field-names="{
|
||||||
|
title: 'name',
|
||||||
|
key: 'id',
|
||||||
|
children: 'children',
|
||||||
|
count: 'count',
|
||||||
|
}"
|
||||||
|
block-node
|
||||||
|
@select="folderNodeSelect"
|
||||||
|
>
|
||||||
|
<template #title="nodeData">
|
||||||
|
<div class="inline-flex w-full gap-[8px]">
|
||||||
|
<div class="one-line-text w-full text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
||||||
|
<div class="ms-tree-node-count ml-[4px] text-[var(--color-text-brand)]">{{ nodeData.count || 0 }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MsTree>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onBeforeMount, ref, watch } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
|
import MsFolderAll from '@/components/business/ms-folder-all/index.vue';
|
||||||
|
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||||
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
|
|
||||||
|
import { getFeatureCaseModule } from '@/api/modules/test-plan/testPlan';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { mapTree } from '@/utils';
|
||||||
|
|
||||||
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||||
|
selectedKeys: string[]; // 选中的节点 key
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'folderNodeSelect', ids: string[], _offspringIds: string[], nodeName?: string): void;
|
||||||
|
(e: 'init', params: ModuleTreeNode[]): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const virtualListProps = computed(() => {
|
||||||
|
return {
|
||||||
|
height: 'calc(100vh - 408px)',
|
||||||
|
threshold: 200,
|
||||||
|
fixedSize: true,
|
||||||
|
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeFolder = ref<string>('all');
|
||||||
|
const allCount = ref(0);
|
||||||
|
const isExpandAll = ref(false);
|
||||||
|
|
||||||
|
function setActiveFolder(id: string) {
|
||||||
|
activeFolder.value = id;
|
||||||
|
emit('folderNodeSelect', [id], []);
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleKeyword = ref('');
|
||||||
|
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const selectedKeys = useVModel(props, 'selectedKeys', emit);
|
||||||
|
|
||||||
|
// 初始化模块树
|
||||||
|
async function initModules() {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
// TODO 联调
|
||||||
|
const res = await getFeatureCaseModule(route.query.id as string);
|
||||||
|
folderTree.value = mapTree<ModuleTreeNode>(res, (node) => {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
count: props.modulesCount?.[node.id] || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
emit('init', folderTree.value);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文件夹树节点选中事件
|
||||||
|
function folderNodeSelect(_selectedKeys: (string | number)[], node: MsTreeNodeData) {
|
||||||
|
const offspringIds: string[] = [];
|
||||||
|
mapTree(node.children || [], (e) => {
|
||||||
|
offspringIds.push(e.id);
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
activeFolder.value = node.id;
|
||||||
|
emit('folderNodeSelect', _selectedKeys as string[], offspringIds, node.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化模块文件数量
|
||||||
|
watch(
|
||||||
|
() => props.modulesCount,
|
||||||
|
(obj) => {
|
||||||
|
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
count: obj?.[node.id] || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
allCount.value = obj?.all || 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
initModules,
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,100 @@
|
||||||
|
<template>
|
||||||
|
<MsSplitBox>
|
||||||
|
<template #first>
|
||||||
|
<CaseTree
|
||||||
|
ref="caseTreeRef"
|
||||||
|
:modules-count="modulesCount"
|
||||||
|
:selected-keys="selectedKeys"
|
||||||
|
@folder-node-select="handleFolderNodeSelect"
|
||||||
|
@init="initModuleTree"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #second>
|
||||||
|
<CaseTable
|
||||||
|
ref="caseTableRef"
|
||||||
|
:plan-id="planId"
|
||||||
|
:modules-count="modulesCount"
|
||||||
|
:module-name="moduleName"
|
||||||
|
:repeat-case="props.repeatCase"
|
||||||
|
:active-module="activeFolderId"
|
||||||
|
:offspring-ids="offspringIds"
|
||||||
|
:module-tree="moduleTree"
|
||||||
|
:can-edit="props.canEdit"
|
||||||
|
@get-module-count="getModuleCount"
|
||||||
|
@refresh="emit('refresh')"
|
||||||
|
@init-modules="initModules"
|
||||||
|
></CaseTable>
|
||||||
|
</template>
|
||||||
|
</MsSplitBox>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||||
|
import CaseTable from './components/caseTable.vue';
|
||||||
|
import CaseTree from './components/caseTree.vue';
|
||||||
|
|
||||||
|
import { getFeatureCaseModuleCount } from '@/api/modules/test-plan/testPlan';
|
||||||
|
|
||||||
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
import type { PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
repeatCase: boolean;
|
||||||
|
canEdit: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'refresh'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const planId = ref(route.query.id as string);
|
||||||
|
const modulesCount = ref<Record<string, any>>({});
|
||||||
|
async function getModuleCount(params: PlanDetailFeatureCaseListQueryParams) {
|
||||||
|
try {
|
||||||
|
// TODO 联调
|
||||||
|
modulesCount.value = await getFeatureCaseModuleCount(params);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const caseTableRef = ref<InstanceType<typeof CaseTable>>();
|
||||||
|
const activeFolderId = ref<string>('all');
|
||||||
|
const moduleName = ref<string>('');
|
||||||
|
const offspringIds = ref<string[]>([]);
|
||||||
|
const selectedKeys = computed({
|
||||||
|
get: () => [activeFolderId.value],
|
||||||
|
set: (val) => val,
|
||||||
|
});
|
||||||
|
function handleFolderNodeSelect(ids: string[], _offspringIds: string[], name?: string) {
|
||||||
|
[activeFolderId.value] = ids;
|
||||||
|
offspringIds.value = [..._offspringIds];
|
||||||
|
moduleName.value = name ?? '';
|
||||||
|
caseTableRef.value?.resetSelector();
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleTree = ref<ModuleTreeNode[]>([]);
|
||||||
|
function initModuleTree(tree: ModuleTreeNode[]) {
|
||||||
|
moduleTree.value = unref(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
const caseTreeRef = ref<InstanceType<typeof CaseTree>>();
|
||||||
|
function initModules() {
|
||||||
|
caseTreeRef.value?.initModules();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCaseTableList() {
|
||||||
|
initModules();
|
||||||
|
caseTableRef.value?.loadCaseList();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getCaseTableList,
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,521 @@
|
||||||
|
<template>
|
||||||
|
<div class="p-[16px]">
|
||||||
|
<MsAdvanceFilter
|
||||||
|
v-model:keyword="keyword"
|
||||||
|
:filter-config-list="[]"
|
||||||
|
:custom-fields-config-list="[]"
|
||||||
|
:row-count="0"
|
||||||
|
:count="props.modulesCount[props.activeModule] || 0"
|
||||||
|
:name="moduleNamePath"
|
||||||
|
:search-placeholder="t('common.searchByIdName')"
|
||||||
|
@keyword-search="loadCaseList"
|
||||||
|
@adv-search="loadCaseList"
|
||||||
|
@refresh="loadCaseList"
|
||||||
|
/>
|
||||||
|
<MsBaseTable
|
||||||
|
ref="tableRef"
|
||||||
|
class="mt-[16px]"
|
||||||
|
v-bind="propsRes"
|
||||||
|
:action-config="batchActions"
|
||||||
|
v-on="propsEvent"
|
||||||
|
@batch-action="handleTableBatch"
|
||||||
|
@drag-change="handleDragChange"
|
||||||
|
@selected-change="handleTableSelect"
|
||||||
|
@filter-change="getModuleCount"
|
||||||
|
>
|
||||||
|
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
|
||||||
|
<CaseLevel :case-level="filterContent.value" />
|
||||||
|
</template>
|
||||||
|
<template #caseLevel="{ record }">
|
||||||
|
<CaseLevel :case-level="record.caseLevel" />
|
||||||
|
</template>
|
||||||
|
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT]="{ filterContent }">
|
||||||
|
<ExecuteResult :execute-result="filterContent.key" />
|
||||||
|
</template>
|
||||||
|
<template #lastExecResult="{ record }">
|
||||||
|
<ExecuteResult :execute-result="record.lastExecResult" />
|
||||||
|
<MsIcon
|
||||||
|
v-show="record.lastExecResult !== LastExecuteResults.PENDING"
|
||||||
|
type="icon-icon_take-action_outlined"
|
||||||
|
class="ml-[8px] cursor-pointer text-[rgb(var(--primary-5))]"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #status="{ record }">
|
||||||
|
<apiStatus :status="record.status" />
|
||||||
|
</template>
|
||||||
|
<template v-if="props.canEdit" #operation="{ record }">
|
||||||
|
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+EXECUTE']" type="text" class="!mr-0">
|
||||||
|
{{ t('common.execute') }}
|
||||||
|
</MsButton>
|
||||||
|
<a-divider v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']" direction="vertical" :margin="8"></a-divider>
|
||||||
|
<MsPopconfirm
|
||||||
|
:title="t('testPlan.featureCase.disassociateTip', { name: characterLimit(record.name) })"
|
||||||
|
:sub-title-tip="t('testPlan.featureCase.disassociateTipContent')"
|
||||||
|
:ok-text="t('common.confirm')"
|
||||||
|
:loading="disassociateLoading"
|
||||||
|
type="error"
|
||||||
|
@confirm="(val, done) => handleDisassociateCase(record, done)"
|
||||||
|
>
|
||||||
|
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']" type="text" class="!mr-0">
|
||||||
|
{{ t('common.cancelLink') }}
|
||||||
|
</MsButton>
|
||||||
|
</MsPopconfirm>
|
||||||
|
<a-divider
|
||||||
|
v-if="props.repeatCase"
|
||||||
|
v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']"
|
||||||
|
direction="vertical"
|
||||||
|
:margin="8"
|
||||||
|
></a-divider>
|
||||||
|
<MsButton
|
||||||
|
v-if="props.repeatCase"
|
||||||
|
v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']"
|
||||||
|
type="text"
|
||||||
|
class="!mr-0"
|
||||||
|
@click="handleCopyCase(record)"
|
||||||
|
>
|
||||||
|
{{ t('common.copy') }}
|
||||||
|
</MsButton>
|
||||||
|
</template>
|
||||||
|
</MsBaseTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onBeforeMount, ref } from 'vue';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
|
||||||
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
|
import MsPopconfirm from '@/components/pure/ms-popconfirm/index.vue';
|
||||||
|
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||||
|
import type {
|
||||||
|
BatchActionParams,
|
||||||
|
BatchActionQueryParams,
|
||||||
|
MsTableColumn,
|
||||||
|
MsTableProps,
|
||||||
|
} 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 ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
||||||
|
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
associationCaseToPlan,
|
||||||
|
batchDisassociateCase,
|
||||||
|
disassociateCase,
|
||||||
|
getPlanDetailFeatureCaseList,
|
||||||
|
sortFeatureCase,
|
||||||
|
} from '@/api/modules/test-plan/testPlan';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useModal from '@/hooks/useModal';
|
||||||
|
import useTableStore from '@/hooks/useTableStore';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import { characterLimit } from '@/utils';
|
||||||
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
|
import { DragSortParams, ModuleTreeNode } from '@/models/common';
|
||||||
|
import type { PlanDetailFeatureCaseItem, PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
|
||||||
|
import { LastExecuteResults } from '@/enums/caseEnum';
|
||||||
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||||
|
|
||||||
|
import { casePriorityOptions } from '@/views/api-test/components/config';
|
||||||
|
import {
|
||||||
|
executionResultMap,
|
||||||
|
getCaseLevels,
|
||||||
|
getModules,
|
||||||
|
} from '@/views/case-management/caseManagementFeature/components/utils';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modulesCount: Record<string, number>; // 模块数量统计对象
|
||||||
|
moduleName: string;
|
||||||
|
activeModule: string;
|
||||||
|
offspringIds: string[];
|
||||||
|
planId: string;
|
||||||
|
moduleTree: ModuleTreeNode[];
|
||||||
|
repeatCase: boolean;
|
||||||
|
canEdit: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'getModuleCount', params: PlanDetailFeatureCaseListQueryParams): void;
|
||||||
|
(e: 'refresh'): void;
|
||||||
|
(e: 'initModules'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const tableStore = useTableStore();
|
||||||
|
const { openModal } = useModal();
|
||||||
|
|
||||||
|
const keyword = ref('');
|
||||||
|
const moduleNamePath = computed(() => {
|
||||||
|
return props.activeModule === 'all' ? t('apiScenario.allScenario') : props.moduleName;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasOperationPermission = computed(
|
||||||
|
() => hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN:READ+ASSOCIATION']) && props.canEdit
|
||||||
|
);
|
||||||
|
const columns = computed<MsTableColumn>(() => [
|
||||||
|
{
|
||||||
|
title: 'ID',
|
||||||
|
dataIndex: 'num',
|
||||||
|
sortIndex: 1,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
fixed: 'left',
|
||||||
|
width: 100,
|
||||||
|
showTooltip: true,
|
||||||
|
columnSelectorDisabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'case.caseName',
|
||||||
|
dataIndex: 'name',
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
width: 150,
|
||||||
|
showTooltip: true,
|
||||||
|
columnSelectorDisabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'case.caseLevel',
|
||||||
|
dataIndex: 'caseLevel',
|
||||||
|
slotName: 'caseLevel',
|
||||||
|
filterConfig: {
|
||||||
|
options: casePriorityOptions,
|
||||||
|
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL,
|
||||||
|
},
|
||||||
|
width: 150,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.executionResult',
|
||||||
|
dataIndex: 'lastExecResult',
|
||||||
|
slotName: 'lastExecResult',
|
||||||
|
filterConfig: {
|
||||||
|
valueKey: 'key',
|
||||||
|
labelKey: 'statusText',
|
||||||
|
options: Object.values(executionResultMap),
|
||||||
|
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT,
|
||||||
|
},
|
||||||
|
width: 150,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.apiStatus',
|
||||||
|
dataIndex: 'status',
|
||||||
|
slotName: 'status',
|
||||||
|
width: 150,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.belongModule',
|
||||||
|
dataIndex: 'moduleId',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 200,
|
||||||
|
showDrag: true,
|
||||||
|
showInTable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.belongProject',
|
||||||
|
dataIndex: 'projectName',
|
||||||
|
showTooltip: true,
|
||||||
|
showDrag: true,
|
||||||
|
width: 150,
|
||||||
|
showInTable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'report.detail.api.executeEnv',
|
||||||
|
dataIndex: 'executeEnv',
|
||||||
|
width: 150,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'case.tableColumnCreateUser',
|
||||||
|
dataIndex: 'createUserName',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 130,
|
||||||
|
showDrag: true,
|
||||||
|
showInTable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'testPlan.featureCase.executor',
|
||||||
|
dataIndex: 'executeUserName',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 130,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: hasOperationPermission.value ? 'common.operation' : '',
|
||||||
|
slotName: 'operation',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
width: hasOperationPermission.value ? 200 : 50,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tableProps = ref<Partial<MsTableProps<PlanDetailFeatureCaseItem>>>({
|
||||||
|
scroll: { x: '100%' },
|
||||||
|
tableKey: TableKeyEnum.TEST_PLAN_DETAIL_API_CASE,
|
||||||
|
showSetting: true,
|
||||||
|
heightUsed: 460,
|
||||||
|
showSubdirectory: true,
|
||||||
|
draggable: { type: 'handle' },
|
||||||
|
draggableCondition: true,
|
||||||
|
selectable: hasOperationPermission.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||||
|
// TODO 联调
|
||||||
|
getPlanDetailFeatureCaseList,
|
||||||
|
tableProps.value,
|
||||||
|
(record) => {
|
||||||
|
return {
|
||||||
|
...record,
|
||||||
|
lastExecResult: record.lastExecResult ?? LastExecuteResults.PENDING,
|
||||||
|
caseLevel: getCaseLevels(record.customFields),
|
||||||
|
moduleId: getModules(record.moduleId, props.moduleTree),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.canEdit,
|
||||||
|
(val) => {
|
||||||
|
tableProps.value.draggableCondition = hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && val;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const tableRef = ref<InstanceType<typeof MsBaseTable>>();
|
||||||
|
watch(
|
||||||
|
() => hasOperationPermission.value,
|
||||||
|
() => {
|
||||||
|
tableRef.value?.initColumn(columns.value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const batchActions = {
|
||||||
|
baseAction: [
|
||||||
|
{
|
||||||
|
label: 'common.execute',
|
||||||
|
eventTag: 'execute',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+EXECUTE'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'testPlan.featureCase.changeExecutor',
|
||||||
|
eventTag: 'changeExecutor',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.move',
|
||||||
|
eventTag: 'move',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.cancelLink',
|
||||||
|
eventTag: 'disassociate',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+ASSOCIATION'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getModuleIds() {
|
||||||
|
let moduleIds: string[] = [];
|
||||||
|
if (props.activeModule !== 'all') {
|
||||||
|
moduleIds = [props.activeModule];
|
||||||
|
const getAllChildren = await tableStore.getSubShow(TableKeyEnum.TEST_PLAN_DETAIL_API_CASE);
|
||||||
|
if (getAllChildren) {
|
||||||
|
moduleIds = [props.activeModule, ...props.offspringIds];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return moduleIds;
|
||||||
|
}
|
||||||
|
async function getTableParams(isBatch: boolean) {
|
||||||
|
const selectModules = await getModuleIds();
|
||||||
|
const commonParams = {
|
||||||
|
testPlanId: props.planId,
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
moduleIds: selectModules,
|
||||||
|
};
|
||||||
|
if (isBatch) {
|
||||||
|
return {
|
||||||
|
condition: {
|
||||||
|
keyword: keyword.value,
|
||||||
|
filter: propsRes.value.filter,
|
||||||
|
},
|
||||||
|
...commonParams,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
keyword: keyword.value,
|
||||||
|
filter: propsRes.value.filter,
|
||||||
|
...commonParams,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCaseList() {
|
||||||
|
const tableParams = await getTableParams(false);
|
||||||
|
setLoadListParams(tableParams);
|
||||||
|
loadList();
|
||||||
|
emit('getModuleCount', {
|
||||||
|
...tableParams,
|
||||||
|
current: propsRes.value.msPagination?.current,
|
||||||
|
pageSize: propsRes.value.msPagination?.pageSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => props.activeModule,
|
||||||
|
() => {
|
||||||
|
loadCaseList();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
async function getModuleCount() {
|
||||||
|
const tableParams = await getTableParams(false);
|
||||||
|
emit('getModuleCount', {
|
||||||
|
...tableParams,
|
||||||
|
current: propsRes.value.msPagination?.current,
|
||||||
|
pageSize: propsRes.value.msPagination?.pageSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableSelected = ref<(string | number)[]>([]); // 表格选中的
|
||||||
|
const batchParams = ref<BatchActionQueryParams>({
|
||||||
|
selectIds: [],
|
||||||
|
selectAll: false,
|
||||||
|
excludeIds: [],
|
||||||
|
condition: {},
|
||||||
|
currentSelectCount: 0,
|
||||||
|
});
|
||||||
|
function handleTableSelect(arr: (string | number)[]) {
|
||||||
|
tableSelected.value = arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetCaseList() {
|
||||||
|
resetSelector();
|
||||||
|
getModuleCount();
|
||||||
|
loadList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拖拽排序
|
||||||
|
async function handleDragChange(params: DragSortParams) {
|
||||||
|
try {
|
||||||
|
// TODO 联调
|
||||||
|
await sortFeatureCase({ ...params, testPlanId: props.planId });
|
||||||
|
Message.success(t('caseManagement.featureCase.sortSuccess'));
|
||||||
|
loadCaseList();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制用例
|
||||||
|
async function handleCopyCase(record: PlanDetailFeatureCaseItem) {
|
||||||
|
try {
|
||||||
|
// TODO 联调
|
||||||
|
await associationCaseToPlan({
|
||||||
|
functionalSelectIds: [record.caseId],
|
||||||
|
testPlanId: props.planId,
|
||||||
|
});
|
||||||
|
Message.success(t('ms.case.associate.associateSuccess'));
|
||||||
|
resetCaseList();
|
||||||
|
emit('refresh');
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消关联
|
||||||
|
const disassociateLoading = ref(false);
|
||||||
|
async function handleDisassociateCase(record: PlanDetailFeatureCaseItem, done?: () => void) {
|
||||||
|
try {
|
||||||
|
disassociateLoading.value = true;
|
||||||
|
// TODO 联调
|
||||||
|
await disassociateCase({ testPlanId: props.planId, id: record.id });
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
Message.success(t('common.unLinkSuccess'));
|
||||||
|
resetCaseList();
|
||||||
|
emit('initModules');
|
||||||
|
emit('refresh');
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
disassociateLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量取消关联用例
|
||||||
|
function handleBatchDisassociateCase() {
|
||||||
|
openModal({
|
||||||
|
type: 'warning',
|
||||||
|
title: t('caseManagement.caseReview.disassociateConfirmTitle', {
|
||||||
|
count: batchParams.value.currentSelectCount || tableSelected.value.length,
|
||||||
|
}),
|
||||||
|
content: t('testPlan.featureCase.batchDisassociateTipContent'),
|
||||||
|
okText: t('common.cancelLink'),
|
||||||
|
cancelText: t('common.cancel'),
|
||||||
|
onBeforeOk: async () => {
|
||||||
|
try {
|
||||||
|
const tableParams = await getTableParams(true);
|
||||||
|
// TODO 联调
|
||||||
|
await batchDisassociateCase({
|
||||||
|
selectIds: tableSelected.value as string[],
|
||||||
|
selectAll: batchParams.value.selectAll,
|
||||||
|
excludeIds: batchParams.value?.excludeIds || [],
|
||||||
|
...tableParams,
|
||||||
|
});
|
||||||
|
Message.success(t('common.updateSuccess'));
|
||||||
|
resetCaseList();
|
||||||
|
emit('initModules');
|
||||||
|
emit('refresh');
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideCancel: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理表格选中后批量操作
|
||||||
|
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
|
||||||
|
tableSelected.value = params?.selectedIds || [];
|
||||||
|
batchParams.value = { ...params, selectIds: params?.selectedIds };
|
||||||
|
switch (event.eventTag) {
|
||||||
|
case 'execute':
|
||||||
|
break;
|
||||||
|
case 'disassociate':
|
||||||
|
handleBatchDisassociateCase();
|
||||||
|
break;
|
||||||
|
case 'changeExecutor':
|
||||||
|
break;
|
||||||
|
case 'move':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
loadCaseList();
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
resetSelector,
|
||||||
|
loadCaseList,
|
||||||
|
});
|
||||||
|
|
||||||
|
await tableStore.initColumn(TableKeyEnum.TEST_PLAN_DETAIL_API_CASE, columns.value, 'drawer', true);
|
||||||
|
</script>
|
|
@ -0,0 +1,151 @@
|
||||||
|
<template>
|
||||||
|
<div class="p-[16px]">
|
||||||
|
<a-input
|
||||||
|
v-model:model-value="moduleKeyword"
|
||||||
|
:placeholder="t('caseManagement.caseReview.folderSearchPlaceholder')"
|
||||||
|
allow-clear
|
||||||
|
class="mb-[8px]"
|
||||||
|
:max-length="255"
|
||||||
|
/>
|
||||||
|
<MsFolderAll
|
||||||
|
v-model:isExpandAll="isExpandAll"
|
||||||
|
:active-folder="activeFolder"
|
||||||
|
:folder-name="t('apiScenario.allScenario')"
|
||||||
|
:all-count="allCount"
|
||||||
|
@set-active-folder="setActiveFolder"
|
||||||
|
/>
|
||||||
|
<a-divider class="my-[8px]" />
|
||||||
|
<a-spin class="min-h-[200px] w-full" :loading="loading">
|
||||||
|
<MsTree
|
||||||
|
:selected-keys="selectedKeys"
|
||||||
|
:data="folderTree"
|
||||||
|
:keyword="moduleKeyword"
|
||||||
|
:default-expand-all="isExpandAll"
|
||||||
|
:expand-all="isExpandAll"
|
||||||
|
:empty-text="t('common.noMatchData')"
|
||||||
|
:draggable="false"
|
||||||
|
:virtual-list-props="virtualListProps"
|
||||||
|
:field-names="{
|
||||||
|
title: 'name',
|
||||||
|
key: 'id',
|
||||||
|
children: 'children',
|
||||||
|
count: 'count',
|
||||||
|
}"
|
||||||
|
block-node
|
||||||
|
@select="folderNodeSelect"
|
||||||
|
>
|
||||||
|
<template #title="nodeData">
|
||||||
|
<div class="inline-flex w-full gap-[8px]">
|
||||||
|
<div class="one-line-text w-full text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
||||||
|
<div class="ms-tree-node-count ml-[4px] text-[var(--color-text-brand)]">{{ nodeData.count || 0 }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MsTree>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onBeforeMount, ref, watch } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
|
import MsFolderAll from '@/components/business/ms-folder-all/index.vue';
|
||||||
|
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||||
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
|
|
||||||
|
import { getFeatureCaseModule } from '@/api/modules/test-plan/testPlan';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { mapTree } from '@/utils';
|
||||||
|
|
||||||
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||||
|
selectedKeys: string[]; // 选中的节点 key
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'folderNodeSelect', ids: string[], _offspringIds: string[], nodeName?: string): void;
|
||||||
|
(e: 'init', params: ModuleTreeNode[]): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const virtualListProps = computed(() => {
|
||||||
|
return {
|
||||||
|
height: 'calc(100vh - 408px)',
|
||||||
|
threshold: 200,
|
||||||
|
fixedSize: true,
|
||||||
|
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeFolder = ref<string>('all');
|
||||||
|
const allCount = ref(0);
|
||||||
|
const isExpandAll = ref(false);
|
||||||
|
|
||||||
|
function setActiveFolder(id: string) {
|
||||||
|
activeFolder.value = id;
|
||||||
|
emit('folderNodeSelect', [id], []);
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleKeyword = ref('');
|
||||||
|
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const selectedKeys = useVModel(props, 'selectedKeys', emit);
|
||||||
|
|
||||||
|
// 初始化模块树
|
||||||
|
async function initModules() {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
// TODO 联调
|
||||||
|
const res = await getFeatureCaseModule(route.query.id as string);
|
||||||
|
folderTree.value = mapTree<ModuleTreeNode>(res, (node) => {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
count: props.modulesCount?.[node.id] || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
emit('init', folderTree.value);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文件夹树节点选中事件
|
||||||
|
function folderNodeSelect(_selectedKeys: (string | number)[], node: MsTreeNodeData) {
|
||||||
|
const offspringIds: string[] = [];
|
||||||
|
mapTree(node.children || [], (e) => {
|
||||||
|
offspringIds.push(e.id);
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
activeFolder.value = node.id;
|
||||||
|
emit('folderNodeSelect', _selectedKeys as string[], offspringIds, node.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化模块文件数量
|
||||||
|
watch(
|
||||||
|
() => props.modulesCount,
|
||||||
|
(obj) => {
|
||||||
|
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
count: obj?.[node.id] || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
allCount.value = obj?.all || 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
initModules,
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,100 @@
|
||||||
|
<template>
|
||||||
|
<MsSplitBox>
|
||||||
|
<template #first>
|
||||||
|
<CaseTree
|
||||||
|
ref="caseTreeRef"
|
||||||
|
:modules-count="modulesCount"
|
||||||
|
:selected-keys="selectedKeys"
|
||||||
|
@folder-node-select="handleFolderNodeSelect"
|
||||||
|
@init="initModuleTree"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #second>
|
||||||
|
<CaseTable
|
||||||
|
ref="caseTableRef"
|
||||||
|
:plan-id="planId"
|
||||||
|
:modules-count="modulesCount"
|
||||||
|
:module-name="moduleName"
|
||||||
|
:repeat-case="props.repeatCase"
|
||||||
|
:active-module="activeFolderId"
|
||||||
|
:offspring-ids="offspringIds"
|
||||||
|
:module-tree="moduleTree"
|
||||||
|
:can-edit="props.canEdit"
|
||||||
|
@get-module-count="getModuleCount"
|
||||||
|
@refresh="emit('refresh')"
|
||||||
|
@init-modules="initModules"
|
||||||
|
></CaseTable>
|
||||||
|
</template>
|
||||||
|
</MsSplitBox>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||||
|
import CaseTable from './components/scenarioTable.vue';
|
||||||
|
import CaseTree from './components/scenarioTree.vue';
|
||||||
|
|
||||||
|
import { getFeatureCaseModuleCount } from '@/api/modules/test-plan/testPlan';
|
||||||
|
|
||||||
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
import type { PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
repeatCase: boolean;
|
||||||
|
canEdit: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'refresh'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const planId = ref(route.query.id as string);
|
||||||
|
const modulesCount = ref<Record<string, any>>({});
|
||||||
|
async function getModuleCount(params: PlanDetailFeatureCaseListQueryParams) {
|
||||||
|
try {
|
||||||
|
// TODO 联调
|
||||||
|
modulesCount.value = await getFeatureCaseModuleCount(params);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const caseTableRef = ref<InstanceType<typeof CaseTable>>();
|
||||||
|
const activeFolderId = ref<string>('all');
|
||||||
|
const moduleName = ref<string>('');
|
||||||
|
const offspringIds = ref<string[]>([]);
|
||||||
|
const selectedKeys = computed({
|
||||||
|
get: () => [activeFolderId.value],
|
||||||
|
set: (val) => val,
|
||||||
|
});
|
||||||
|
function handleFolderNodeSelect(ids: string[], _offspringIds: string[], name?: string) {
|
||||||
|
[activeFolderId.value] = ids;
|
||||||
|
offspringIds.value = [..._offspringIds];
|
||||||
|
moduleName.value = name ?? '';
|
||||||
|
caseTableRef.value?.resetSelector();
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleTree = ref<ModuleTreeNode[]>([]);
|
||||||
|
function initModuleTree(tree: ModuleTreeNode[]) {
|
||||||
|
moduleTree.value = unref(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
const caseTreeRef = ref<InstanceType<typeof CaseTree>>();
|
||||||
|
function initModules() {
|
||||||
|
caseTreeRef.value?.initModules();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCaseTableList() {
|
||||||
|
initModules();
|
||||||
|
caseTableRef.value?.loadCaseList();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getCaseTableList,
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -268,7 +268,6 @@
|
||||||
},
|
},
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
width: 100,
|
width: 100,
|
||||||
ellipsis: true,
|
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
columnSelectorDisabled: true,
|
columnSelectorDisabled: true,
|
||||||
},
|
},
|
||||||
|
@ -310,7 +309,6 @@
|
||||||
{
|
{
|
||||||
title: 'common.belongModule',
|
title: 'common.belongModule',
|
||||||
dataIndex: 'moduleId',
|
dataIndex: 'moduleId',
|
||||||
ellipsis: true,
|
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
width: 200,
|
width: 200,
|
||||||
showDrag: true,
|
showDrag: true,
|
||||||
|
|
|
@ -7,20 +7,13 @@
|
||||||
class="mb-[8px]"
|
class="mb-[8px]"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
/>
|
/>
|
||||||
<div class="folder">
|
<MsFolderAll
|
||||||
<div :class="getFolderClass('all')" @click="setActiveFolder('all')">
|
v-model:isExpandAll="isExpandAll"
|
||||||
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
:active-folder="activeFolder"
|
||||||
<div class="folder-name">{{ t('caseManagement.caseReview.allCases') }}</div>
|
:folder-name="t('caseManagement.caseReview.allCases')"
|
||||||
<div class="folder-count">({{ allCount }})</div>
|
:all-count="allCount"
|
||||||
</div>
|
@set-active-folder="setActiveFolder"
|
||||||
<a-tooltip
|
/>
|
||||||
:content="isExpandAll ? t('testPlan.testPlanIndex.collapseAll') : t('testPlan.testPlanIndex.expandAll')"
|
|
||||||
>
|
|
||||||
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" position="top" @click="expandHandler">
|
|
||||||
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
|
|
||||||
</MsButton>
|
|
||||||
</a-tooltip>
|
|
||||||
</div>
|
|
||||||
<a-divider class="my-[8px]" />
|
<a-divider class="my-[8px]" />
|
||||||
<a-spin class="min-h-[200px] w-full" :loading="loading">
|
<a-spin class="min-h-[200px] w-full" :loading="loading">
|
||||||
<MsTree
|
<MsTree
|
||||||
|
@ -57,8 +50,7 @@
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsFolderAll from '@/components/business/ms-folder-all/index.vue';
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
|
||||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
|
|
||||||
|
@ -92,13 +84,6 @@
|
||||||
const activeFolder = ref<string>('all');
|
const activeFolder = ref<string>('all');
|
||||||
const allCount = ref(0);
|
const allCount = ref(0);
|
||||||
const isExpandAll = ref(false);
|
const isExpandAll = ref(false);
|
||||||
function expandHandler() {
|
|
||||||
isExpandAll.value = !isExpandAll.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFolderClass(id: string) {
|
|
||||||
return activeFolder.value === id ? 'folder-text folder-text--active' : 'folder-text';
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveFolder(id: string) {
|
function setActiveFolder(id: string) {
|
||||||
activeFolder.value = id;
|
activeFolder.value = id;
|
||||||
|
@ -170,36 +155,3 @@
|
||||||
initModules,
|
initModules,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.folder {
|
|
||||||
@apply flex cursor-pointer items-center justify-between;
|
|
||||||
|
|
||||||
padding: 8px 4px;
|
|
||||||
border-radius: var(--border-radius-small);
|
|
||||||
&:hover {
|
|
||||||
background-color: rgb(var(--primary-1));
|
|
||||||
}
|
|
||||||
.folder-text {
|
|
||||||
@apply flex cursor-pointer items-center;
|
|
||||||
.folder-icon {
|
|
||||||
margin-right: 4px;
|
|
||||||
color: var(--color-text-4);
|
|
||||||
}
|
|
||||||
.folder-name {
|
|
||||||
color: var(--color-text-1);
|
|
||||||
}
|
|
||||||
.folder-count {
|
|
||||||
margin-left: 4px;
|
|
||||||
color: var(--color-text-4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.folder-text--active {
|
|
||||||
.folder-icon,
|
|
||||||
.folder-name,
|
|
||||||
.folder-count {
|
|
||||||
color: rgb(var(--primary-5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -108,6 +108,20 @@
|
||||||
@refresh="initDetail"
|
@refresh="initDetail"
|
||||||
/>
|
/>
|
||||||
<BugManagement v-if="activeTab === 'defectList'" />
|
<BugManagement v-if="activeTab === 'defectList'" />
|
||||||
|
<ApiCase
|
||||||
|
v-if="activeTab === 'apiCase'"
|
||||||
|
ref="apiCaseRef"
|
||||||
|
:repeat-case="detail.repeatCase"
|
||||||
|
:can-edit="detail.status !== 'ARCHIVED'"
|
||||||
|
@refresh="initDetail"
|
||||||
|
/>
|
||||||
|
<ApiScenario
|
||||||
|
v-if="activeTab === 'apiScenario'"
|
||||||
|
ref="apiScenarioRef"
|
||||||
|
:repeat-case="detail.repeatCase"
|
||||||
|
:can-edit="detail.status !== 'ARCHIVED'"
|
||||||
|
@refresh="initDetail"
|
||||||
|
/>
|
||||||
</MsCard>
|
</MsCard>
|
||||||
<AssociateDrawer
|
<AssociateDrawer
|
||||||
v-model:visible="caseAssociateVisible"
|
v-model:visible="caseAssociateVisible"
|
||||||
|
@ -142,6 +156,8 @@
|
||||||
import ActionModal from '../components/actionModal.vue';
|
import ActionModal from '../components/actionModal.vue';
|
||||||
import AssociateDrawer from '../components/associateDrawer.vue';
|
import AssociateDrawer from '../components/associateDrawer.vue';
|
||||||
import StatusProgress from '../components/statusProgress.vue';
|
import StatusProgress from '../components/statusProgress.vue';
|
||||||
|
import ApiCase from './apiCase/index.vue';
|
||||||
|
import ApiScenario from './apiScenario/index.vue';
|
||||||
import BugManagement from './bugManagement/index.vue';
|
import BugManagement from './bugManagement/index.vue';
|
||||||
import FeatureCase from './featureCase/index.vue';
|
import FeatureCase from './featureCase/index.vue';
|
||||||
import CreateAndEditPlanDrawer from '@/views/test-plan/testPlan/createAndEditPlanDrawer.vue';
|
import CreateAndEditPlanDrawer from '@/views/test-plan/testPlan/createAndEditPlanDrawer.vue';
|
||||||
|
@ -234,16 +250,6 @@
|
||||||
return hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && detail.value.status !== 'ARCHIVED';
|
return hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && detail.value.status !== 'ARCHIVED';
|
||||||
});
|
});
|
||||||
|
|
||||||
function getTabBadge(tabKey: string) {
|
|
||||||
switch (tabKey) {
|
|
||||||
case 'featureCase':
|
|
||||||
const count = detail.value.functionalCaseCount ?? 0;
|
|
||||||
return `${count > 0 ? count : ''}`;
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function archiveHandler() {
|
function archiveHandler() {
|
||||||
openModal({
|
openModal({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
@ -306,7 +312,30 @@
|
||||||
value: 'defectList',
|
value: 'defectList',
|
||||||
label: t('caseManagement.featureCase.defectList'),
|
label: t('caseManagement.featureCase.defectList'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: 'apiCase',
|
||||||
|
label: t('testPlan.testPlanIndex.apiCase'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'apiScenario',
|
||||||
|
label: t('testPlan.testPlanIndex.apiScenarioCase'),
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
function getTabBadge(tabKey: string) {
|
||||||
|
switch (tabKey) {
|
||||||
|
case 'featureCase':
|
||||||
|
const count = detail.value.functionalCaseCount ?? 0;
|
||||||
|
return `${count > 0 ? count : ''}`;
|
||||||
|
case 'apiCase':
|
||||||
|
const apiCaseCount = detail.value?.apiCaseCount ?? 0;
|
||||||
|
return `${apiCaseCount > 0 ? apiCaseCount : ''}`;
|
||||||
|
case 'apiScenario':
|
||||||
|
const apiScenarioCount = detail.value?.apiScenarioCount ?? 0;
|
||||||
|
return `${apiScenarioCount > 0 ? apiScenarioCount : ''}`;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
const hasSelectedIds = ref<string[]>([]);
|
const hasSelectedIds = ref<string[]>([]);
|
||||||
const caseAssociateVisible = ref(false);
|
const caseAssociateVisible = ref(false);
|
||||||
// 关联用例
|
// 关联用例
|
||||||
|
@ -376,9 +405,23 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const featureCaseRef = ref<InstanceType<typeof FeatureCase>>();
|
const featureCaseRef = ref<InstanceType<typeof FeatureCase>>();
|
||||||
|
const apiCaseRef = ref<InstanceType<typeof ApiCase>>();
|
||||||
|
const apiScenarioRef = ref<InstanceType<typeof ApiScenario>>();
|
||||||
function handleSuccess() {
|
function handleSuccess() {
|
||||||
initDetail();
|
initDetail();
|
||||||
featureCaseRef.value?.getCaseTableList();
|
switch (activeTab.value) {
|
||||||
|
case 'featureCase':
|
||||||
|
featureCaseRef.value?.getCaseTableList();
|
||||||
|
return;
|
||||||
|
case 'apiCase':
|
||||||
|
apiCaseRef.value?.getCaseTableList();
|
||||||
|
return;
|
||||||
|
case 'apiScenario':
|
||||||
|
apiScenarioRef.value?.getCaseTableList();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
|
|
Loading…
Reference in New Issue