feat(接口测试): 统一替换接口测试所有的table表头筛选

This commit is contained in:
xinxin.wu 2024-05-16 18:31:33 +08:00 committed by 刘瑞斌
parent c191e40a14
commit 37deac293a
31 changed files with 642 additions and 1151 deletions

View File

@ -16,6 +16,7 @@ import {
DeleteTestPlanModuleUrl,
DisassociateCaseUrl,
followPlanUrl,
GetAssociatedBugUrl,
GetFeatureCaseModuleCountUrl,
GetFeatureCaseModuleUrl,
GetPlanDetailFeatureCaseListUrl,
@ -29,6 +30,8 @@ import {
planPassRateUrl,
RunFeatureCaseUrl,
SortFeatureCaseUrl,
TestPlanAssociateBugUrl,
TestPlanCancelBugUrl,
updateTestPlanModuleUrl,
UpdateTestPlanUrl,
} from '@/api/requrls/test-plan/testPlan';
@ -188,3 +191,15 @@ export function batchUpdateCaseExecutor(data: BatchUpdateCaseExecutorParams) {
export function runFeatureCase(data: RunFeatureCaseParams) {
return MSR.post({ url: RunFeatureCaseUrl, data });
}
// 测试计划-用例详情-缺陷列表
export function associatedBugPage(data: TableQueryParams) {
return MSR.post({ url: GetAssociatedBugUrl, data });
}
// 测试计划-用例详情-关联缺陷
export function associateBugToPlan(data: TableQueryParams) {
return MSR.post({ url: TestPlanAssociateBugUrl, data });
}
// 测试计划-用例详情-关联缺陷
export function testPlanCancelBug(id: string) {
return MSR.get({ url: `${TestPlanCancelBugUrl}/${id}` });
}

View File

@ -56,6 +56,12 @@ export const DisassociateCaseUrl = '/test-plan/functional/case/disassociate';
export const BatchDisassociateCaseUrl = '/test-plan/functional/case/batch/disassociate';
// 计划详情-功能用例-执行
export const RunFeatureCaseUrl = '/test-plan/functional/case/run';
// 测试计划-用例详情-缺陷列表
export const GetAssociatedBugUrl = '/test-plan/functional/case/has/associate/bug/page';
// 测试计划-用例详情-关联缺陷
export const TestPlanAssociateBugUrl = '/test-plan/functional/case/associate/bug';
// 测试计划-用例详情-取消关联缺陷
export const TestPlanCancelBugUrl = '/test-plan/functional/case/disassociate/bug';
// 计划详情-功能用例-批量执行
export const BatchRunCaseUrl = '/test-plan/functional/case/batch/run';
// 计划详情-功能用例-批量更新执行人

View File

@ -4,9 +4,9 @@
:type="lastExecuteResultMap[props.executeResult]?.icon || ''"
class="mr-1"
:size="16"
:style="{ color: lastExecuteResultMap[props.executeResult].color }"
:style="{ color: lastExecuteResultMap[props.executeResult]?.color }"
></MsIcon>
<span class="text-[14px]">{{ status?.text || '' }}</span>
<span class="text-[14px]">{{ lastExecuteResultMap[props.executeResult]?.statusText || '-' }}</span>
</div>
</template>
@ -24,46 +24,42 @@
}>();
const lastExecuteResultMap = {
UN_EXECUTED: {
label: 'UN_EXECUTED',
icon: StatusType.UN_EXECUTED,
statusText: 'caseManagement.featureCase.nonExecution',
PENDING: {
label: 'PENDING',
icon: StatusType.PENDING,
statusText: t('caseManagement.featureCase.nonExecution'),
color: 'var(--color-text-brand)',
},
PASSED: {
label: 'PASSED',
icon: StatusType.PASSED,
statusText: 'common.success',
SUCCESS: {
label: 'SUCCESS',
icon: StatusType.SUCCESS,
statusText: t('common.success'),
color: '',
},
SKIPPED: {
label: 'SKIPPED',
icon: StatusType.SKIPPED,
statusText: 'caseManagement.featureCase.skip',
color: 'rgb(var(--link-6))',
},
BLOCKED: {
label: 'BLOCKED',
icon: StatusType.BLOCKED,
statusText: 'caseManagement.featureCase.chokeUp',
statusText: t('caseManagement.featureCase.chokeUp'),
color: 'rgb(var(--warning-6))',
},
FAILED: {
label: 'FAILED',
icon: StatusType.FAILED,
statusText: 'caseManagement.featureCase.failure',
ERROR: {
label: 'ERROR',
icon: StatusType.ERROR,
statusText: t('caseManagement.featureCase.failure'),
color: '',
},
};
const status = computed(() => {
if (props.executeResult) {
const config = lastExecuteResultMap[props.executeResult];
return {
text: t(config?.statusText || ''),
};
}
});
// const status = computed(() => {
// if (props.executeResult) {
// const config = lastExecuteResultMap[props.executeResult];
// if (config) {
// return {
// text: t(config?.statusText || ''),
// };
// }
// }
// });
</script>
<style lang="less" scoped></style>

View File

@ -66,8 +66,8 @@
:sortable="item.sortable"
:filterable="item.filterable"
:cell-class="item.cellClass"
:header-cell-class="`${
item.headerCellClass || (item.filterConfig && hasSelectedFilter(item)) ? 'header-cell-filter' : ''
:header-cell-class="`${item.filterConfig && hasSelectedFilter(item) ? 'header-cell-filter' : ''} ${
item.headerCellClass
}`"
:body-cell-class="item.bodyCellClass"
:summary-cell-class="item.summaryCellClass"
@ -101,7 +101,7 @@
@init-data="handleInitColumn"
/>
<DefaultFilter
v-else-if="item.filterConfig"
v-else-if="(item.filterConfig && item.filterConfig.options?.length) || item?.filterConfig?.remoteMethod"
class="ml-[4px]"
:options="item.filterConfig.options"
:data-index="item.dataIndex"
@ -639,6 +639,10 @@
columnSelectorVisible.value = true;
};
const filterData = computed(() => {
return (attrs.filter || {}) as Record<string, any>;
});
const handleFilterConfirm = (
value: string[] | (string | number)[] | undefined,
dataIndex: string,
@ -652,10 +656,6 @@
batchLeft.value = getBatchLeft();
});
const filterData = computed(() => {
return (attrs.filter || {}) as Record<string, any>;
});
function hasSelectedFilter(item: MsTableColumnData) {
if (item.filterConfig && item.dataIndex) {
return (filterData.value[item.dataIndex] || []).length > 0;

View File

@ -160,7 +160,7 @@
const isNoFilter = computed(() => {
if (props.filter && JSON.stringify(props.filter) !== '{}') {
return !Object.keys(props.filter).some((key: any) => {
return props.filter[key].length > 0;
return (props.filter[key] || []).length > 0;
});
}
return true;

View File

@ -1,4 +1,4 @@
import { getProjectMemberOptions } from '@/api/modules/project-management/projectMember';
import { getProjectOptions } from '@/api/modules/project-management/projectMember';
import { getProjectList } from '@/api/modules/setting/member';
import { getOrgOptions, getSystemProjectList } from '@/api/modules/system';
@ -7,7 +7,7 @@ import { FilterRemoteMethodsEnum } from '@/enums/tableFilterEnum';
export function initRemoteOptionsFunc(remoteMethod: string, params: Record<string, any>) {
switch (remoteMethod) {
case FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER:
return getProjectMemberOptions(params.projectId, params.keyword);
return getProjectOptions(params.projectId, params.keyword);
case FilterRemoteMethodsEnum.SYSTEM_ORGANIZATION_LIST:
return getOrgOptions();
case FilterRemoteMethodsEnum.SYSTEM_PROJECT_LIST:

View File

@ -2,22 +2,21 @@
export enum StatusType {
UN_REVIEWED = 'icon-icon_block_filled', // 未评审
UNDER_REVIEWED = 'icon-icon_testing', // 评审中
SUCCESS = 'icon-icon_succeed_colorful', // 成功
PASS = 'icon-icon_succeed_colorful', // 已通过
UN_PASS = 'icon-icon_close_colorful', // 未通过
RE_REVIEWED = 'icon-icon_resubmit_filled', // 重新提审
UN_EXECUTED = 'icon-icon_block_filled', // 未执行
PASSED = 'icon-icon_succeed_colorful', // 已执行
FAILED = 'icon-icon_close_colorful', // 失败
ERROR = 'icon-icon_close_colorful', // 失败
BLOCKED = 'icon-icon_block_filled', // 阻塞
SKIPPED = 'icon-icon_skip_planarity', // 跳过
PENDING = 'icon-icon_block_filled', // 未执行
}
export enum LastExecuteResults {
UN_EXECUTED = 'UN_EXECUTED',
PASSED = 'PASSED',
SKIPPED = 'SKIPPED',
PENDING = 'PENDING',
SUCCESS = 'SUCCESS',
BLOCKED = 'BLOCKED',
FAILED = 'FAILED',
ERROR = 'ERROR',
}
export enum CaseLinkEnum {

View File

@ -18,7 +18,14 @@ export enum TriggerModeLabel {
BATCH = 'report.trigger.batch.execution', // 批量执行
API = 'report.trigger.interface', // 接口调用
}
export const ReportStatus = {
export enum TriggerModeLabelEnum {
MANUAL = 'MANUAL', // 手动执行
SCHEDULE = 'SCHEDULE', // 定时任务
BATCH = 'BATCH', // 批量执行
API = 'API', // 接口调用
}
export const ReportStatus: Record<ReportEnum, Record<string, { icon: string; label: string; color?: string }>> = {
[ReportEnum.API_REPORT]: {
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
@ -30,16 +37,16 @@ export const ReportStatus = {
},
FAKE_ERROR: {
icon: 'icon-icon_warning_colorful',
label: 'report.falseAlarm',
label: 'report.fake.error',
},
STOPPED: {
icon: 'icon-icon_block_filled',
label: 'report.stop',
label: 'report.stopped',
color: '!var(--color-text-input-border)',
},
RUNNING: {
icon: 'icon-icon_testing',
label: 'report.inExecution',
label: 'report.status.running',
color: '!text-[rgb(var(--link-6))]',
},
// RERUNNING: {
@ -49,7 +56,7 @@ export const ReportStatus = {
// },
PENDING: {
icon: 'icon-icon_wait',
label: 'report.queuing',
label: 'report.status.pending',
color: '!text-[rgb(var(--link-6))]',
},
},
@ -64,16 +71,16 @@ export const ReportStatus = {
},
FAKE_ERROR: {
icon: 'icon-icon_warning_colorful',
label: 'report.falseAlarm',
label: 'report.fake.error',
},
STOPPED: {
icon: 'icon-icon_block_filled',
label: 'report.stop',
label: 'report.stopped',
color: 'var(--color-text-input-border)',
},
RUNNING: {
icon: 'icon-icon_testing',
label: 'report.inExecution',
label: 'report.status.running',
color: '!text-[rgb(var(--link-6))]',
},
// RERUNNING: {
@ -83,7 +90,7 @@ export const ReportStatus = {
// },
PENDING: {
icon: 'icon-icon_wait',
label: 'report.queuing',
label: 'report.status.pending',
color: '!text-[rgb(var(--link-6))]',
},
},
@ -130,4 +137,5 @@ export const PlanReportStatus: Record<string, any> = {
},
},
};
export default {};

View File

@ -11,6 +11,13 @@ export enum FilterSlotNameEnum {
PROJECT_MANAGEMENT_COMMON_SCRIPT = 'PROJECT_MANAGEMENT_COMMON_SCRIPT', // 项目管理公共脚本脚本状态
GLOBAL_TASK_CENTER_API_CASE_STATUS = 'GLOBAL_TASK_CENTER_API_CASE_STATUS', // 任务中心执行状态
GLOBAL_TASK_CENTER_API_CASE_TRIGGED_MODE = 'GLOBAL_TASK_CENTER_API_CASE_TRIGGED_MODE', // 任务中心触发方式
API_TEST_CASE_API_STATUS = 'API_TEST_CASE_API_STATUS', // 接口测试-定义-用例列表-接口状态
API_TEST_CASE_API_LAST_EXECUTE_STATUS = 'API_TEST_CASE_API_LAST_EXECUTE_STATUS', // 接口测试-定义-用例列表-最后执行状态
API_TEST_CASE_API_REPORT_TRIGGED_METHOD = 'API_TEST_CASE_API_REPORT_TRIGGED_METHOD', // 接口测试-定义-用例列表-最后执行状态
API_TEST_CASE_API_REPORT_EXECUTE_RESULT = 'API_TEST_CASE_API_REPORT_EXECUTE_RESULT', // 接口测试-定义-用例变更历史-最后执行结果
GLOBAL_CHANGE_HISTORY_TYPE = 'GLOBAL_CHANGE_HISTORY_TYPE', // 变更历史类型
API_TEST_SCENARIO_EXECUTE_RESULT = 'API_TEST_SCENARIO_EXECUTE_RESULT', // 接口测试-场景-变更历史-执行结果
API_TEST_REPORT_TYPE = 'API_TEST_REPORT_TYPE', // 接口测试-报告-报告类型
}
export enum FilterRemoteMethodsEnum {

View File

@ -19,6 +19,7 @@
</div>
</div>
<ms-base-table
ref="apiTableRef"
v-bind="propsRes"
:action-config="batchActions"
:first-column-width="44"
@ -30,67 +31,11 @@
@drag-change="handleTableDragSort"
@module-change="loadApiList(false)"
>
<template v-if="props.protocol === 'HTTP'" #methodFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="methodFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<MsButton type="text" class="arco-btn-text--secondary ml-[10px]" @click="methodFilterVisible = true">
{{ t(columnConfig.title as string) }}
<icon-down :class="methodFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="methodFilters" direction="vertical">
<a-checkbox v-for="key of RequestMethods" :key="key" :value="key">
<apiMethodName :method="key" />
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetMethodFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
<template v-if="props.protocol === 'HTTP'" #[FilterSlotNameEnum.API_TEST_API_REQUEST_METHODS]="{ filterContent }">
<apiMethodName :method="filterContent.value" />
</template>
<template #statusFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="statusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<MsButton type="text" class="arco-btn-text--secondary ml-[10px]" @click="statusFilterVisible = true">
{{ t(columnConfig.title as string) }}
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical">
<a-checkbox v-for="val of Object.values(RequestDefinitionStatus)" :key="val" :value="val">
<apiStatus :status="val" />
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
<template #[FilterSlotNameEnum.API_TEST_API_REQUEST_API_STATUS]="{ filterContent }">
<apiStatus :status="filterContent.value" />
</template>
<template #num="{ record }">
<MsButton type="text" @click="openApiTab(record)">{{ record.num }}</MsButton>
@ -115,6 +60,11 @@
<template #caseTotal="{ record }">
{{ record.caseTotal }}
</template>
<template #createUserName="{ record }">
<a-tooltip :content="`${record.createUserName}`" position="tl">
<div class="one-line-text">{{ characterLimit(record.createUserName) }}</div>
</a-tooltip>
</template>
<template #status="{ record }">
<a-select
v-if="hasAnyPermission(['PROJECT_API_DEFINITION:READ+UPDATE'])"
@ -132,20 +82,6 @@
</a-select>
<apiStatus v-else :status="record.status" />
</template>
<template #createUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="createUserFilterVisible"
v-model:status-filters="createUserFilters"
:title="(columnConfig.title as string)"
:list="memberOptions"
label-key="label"
@search="loadApiList(false)"
>
<template #item="{ item }">
{{ item.label }}
</template>
</TableFilter>
</template>
<template #action="{ record }">
<MsButton
v-permission="['PROJECT_API_DEFINITION:READ+UPDATE']"
@ -342,7 +278,6 @@
import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue';
import apiStatus from '@/views/api-test/components/apiStatus.vue';
import moduleTree from '@/views/api-test/management/components/moduleTree.vue';
import TableFilter from '@/views/case-management/caseManagementFeature/components/tableFilter.vue';
import {
batchDeleteDefinition,
@ -364,6 +299,7 @@
import { DragSortParams } from '@/models/common';
import { RequestDefinitionStatus, RequestMethods } from '@/enums/apiEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
const props = defineProps<{
class?: string;
@ -404,6 +340,24 @@
'PROJECT_API_DEFINITION:READ+UPDATE',
])
);
const requestMethodsOptions = computed(() => {
return Object.values(RequestMethods).map((e) => {
return {
value: e,
key: e,
};
});
});
const requestApiStatus = computed(() => {
return Object.values(RequestDefinitionStatus).map((e) => {
return {
value: e,
key: e,
};
});
});
let columns: MsTableColumn = [
{
title: 'ID',
@ -433,15 +387,21 @@
title: 'apiTestManagement.apiType',
dataIndex: 'method',
slotName: 'method',
titleSlotName: 'methodFilter',
width: 140,
showDrag: true,
filterConfig: {
options: requestMethodsOptions.value,
filterSlotName: FilterSlotNameEnum.API_TEST_API_REQUEST_METHODS,
},
},
{
title: 'apiTestManagement.apiStatus',
dataIndex: 'status',
slotName: 'status',
titleSlotName: 'statusFilter',
filterConfig: {
options: requestApiStatus.value,
filterSlotName: FilterSlotNameEnum.API_TEST_API_REQUEST_API_STATUS,
},
width: 130,
showDrag: true,
},
@ -498,10 +458,16 @@
{
title: 'common.creator',
slotName: 'createUserName',
dataIndex: 'createUserName',
titleSlotName: 'createUserFilter',
dataIndex: 'createUser',
filterConfig: {
mode: 'remote',
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
placeholderText: t('caseManagement.featureCase.PleaseSelect'),
},
showInTable: true,
showTooltip: true,
width: 200,
showDrag: true,
},
@ -514,6 +480,21 @@
},
];
function initFilterColumn() {
columns = columns.map((item) => {
if (item.dataIndex === 'method') {
return {
...item,
filterConfig: {
...item.filterConfig,
options: props.protocol === 'HTTP' ? requestMethodsOptions.value : [],
},
};
}
return item;
});
}
await initFilterColumn();
await tableStore.initColumn(TableKeyEnum.API_TEST, columns, 'drawer', true);
if (props.readOnly) {
columns = columns.filter(
@ -581,13 +562,6 @@
},
];
const methodFilterVisible = ref(false);
const methodFilters = ref<string[]>([]);
const statusFilterVisible = ref(false);
const statusFilters = ref<string[]>([]);
const createUserFilterVisible = ref(false);
const createUserFilters = ref<string[]>([]);
async function getModuleIds() {
let moduleIds: string[] = [];
if (props.activeModule !== 'all') {
@ -607,21 +581,13 @@
projectId: appStore.currentProjectId,
moduleIds,
protocol: props.protocol,
filter: {
status: statusFilters.value,
method: methodFilters.value,
createUser: createUserFilters.value,
},
filter: propsRes.value.filter,
};
if (!hasRefreshTree && typeof refreshModuleTreeCount === 'function') {
refreshModuleTreeCount({
keyword: keyword.value,
filter: {
status: statusFilters.value,
method: methodFilters.value,
createUser: createUserFilters.value,
},
filter: propsRes.value.filter,
moduleIds: [],
protocol: props.protocol,
projectId: appStore.currentProjectId,
@ -657,14 +623,6 @@
}
);
function handleFilterHidden(val: boolean) {
if (!val) {
loadApiList(false);
methodFilterVisible.value = false;
statusFilterVisible.value = false;
}
}
async function handleMethodChange(record: ApiDefinitionDetail) {
try {
await updateDefinition({
@ -734,11 +692,7 @@
excludeIds: params?.excludeIds || [],
condition: {
keyword: keyword.value,
filter: {
status: statusFilters.value,
method: methodFilters.value,
createUser: createUserFilters.value,
},
filter: propsRes.value.filter,
},
projectId: appStore.currentProjectId,
moduleIds: await getModuleIds(),
@ -861,11 +815,7 @@
excludeIds: batchParams.value?.excludeIds || [],
condition: {
keyword: keyword.value,
filter: {
status: statusFilters.value,
method: methodFilters.value,
createUser: createUserFilters.value,
},
filter: propsRes.value.filter,
},
projectId: appStore.currentProjectId,
moduleIds: await getModuleIds(),
@ -906,11 +856,7 @@
excludeIds: batchParams.value?.excludeIds || [],
condition: {
keyword: keyword.value,
filter: {
status: statusFilters.value,
method: methodFilters.value,
createUser: createUserFilters.value,
},
filter: propsRes.value.filter,
},
projectId: appStore.currentProjectId,
moduleIds: await getModuleIds(),
@ -943,18 +889,6 @@
selectedModuleKeys.value = [];
}
function resetMethodFilter() {
methodFilters.value = [];
methodFilterVisible.value = false;
loadApiList(false);
}
function resetStatusFilter() {
statusFilters.value = [];
statusFilterVisible.value = false;
loadApiList(false);
}
/**
* 处理文件夹树节点选中事件
*/
@ -1013,6 +947,17 @@
console.log(error);
}
}
const apiTableRef = ref();
watch(
() => props.protocol,
(val) => {
if (val) {
initFilterColumn();
apiTableRef.value.initColumn(columns);
}
}
);
</script>
<style lang="less" scoped>

View File

@ -68,32 +68,9 @@
</a-select>
<span v-else class="text-[var(--color-text-2)]"> <caseLevel :case-level="record.priority" /></span>
</template>
<template #caseLevelFilter="{ columnConfig }">
<a-trigger v-model:popup-visible="caseFilterVisible" trigger="click" @popup-visible-change="handleFilterHidden">
<MsButton type="text" class="arco-btn-text--secondary ml-[10px]" @click="caseFilterVisible = true">
{{ t(columnConfig.title as string) }}
<icon-down :class="caseFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="caseFilters" direction="vertical" size="small">
<a-checkbox v-for="item of casePriorityOptions" :key="item.value" :value="item.value">
<caseLevel :case-level="item.label as CaseLevel" />
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetCaseFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
<!-- 用例等级 -->
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
<caseLevel :case-level="filterContent.value" />
</template>
<template #status="{ record }">
<a-select
@ -112,85 +89,16 @@
</a-select>
<apiStatus v-else :status="record.status" />
</template>
<template #statusFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="statusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<MsButton type="text" class="arco-btn-text--secondary ml-[10px]" @click="statusFilterVisible = true">
{{ t(columnConfig.title as string) }}
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
<a-checkbox v-for="val of Object.values(RequestCaseStatus)" :key="val" :value="val">
<apiStatus :status="val" />
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
<template #[FilterSlotNameEnum.API_TEST_CASE_API_STATUS]="{ filterContent }">
<apiStatus :status="filterContent.value" />
</template>
<template #createUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="createUserFilterVisible"
v-model:status-filters="createUserFilters"
:title="(columnConfig.title as string)"
:list="memberOptions"
label-key="label"
@search="loadCaseList"
>
<template #item="{ item }">
{{ item.label }}
</template>
</TableFilter>
<template #createName="{ record }">
<a-tooltip :content="`${record.createName}`" position="tl">
<div class="one-line-text">{{ characterLimit(record.createName) }}</div>
</a-tooltip>
</template>
<template #lastReportStatusFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="lastReportStatusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<MsButton
type="text"
class="arco-btn-text--secondary ml-[10px]"
@click="lastReportStatusFilterVisible = true"
>
{{ t(columnConfig.title as string) }}
<icon-down :class="lastReportStatusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="lastReportStatusFilters" direction="vertical" size="small">
<a-checkbox v-for="val of lastReportStatusList" :key="val" :value="val">
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="val" />
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetLastReportStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
<template #[FilterSlotNameEnum.API_TEST_CASE_API_LAST_EXECUTE_STATUS]="{ filterContent }">
<apiStatus :status="filterContent.value" />
</template>
<template #lastReportStatus="{ record }">
<ExecutionStatus
@ -391,7 +299,6 @@
import BatchRunModal from '@/views/api-test/components/batchRunModal.vue';
import caseAndScenarioReportDrawer from '@/views/api-test/components/caseAndScenarioReportDrawer.vue';
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
import TableFilter from '@/views/case-management/caseManagementFeature/components/tableFilter.vue';
import {
batchDeleteCase,
@ -416,6 +323,7 @@
import { RequestCaseStatus } from '@/enums/apiEnum';
import { ReportEnum, ReportStatus } from '@/enums/reportEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { casePriorityOptions, caseStatusOptions } from '@/views/api-test/components/config';
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
@ -448,6 +356,23 @@
'PROJECT_API_DEFINITION_CASE:READ+EXECUTE',
])
);
const requestCaseStatusOptions = computed(() => {
return Object.values(RequestCaseStatus).map((key) => {
return {
value: key,
label: key,
};
});
});
const lastReportStatusListOptions = computed(() => {
return Object.keys(ReportStatus[ReportEnum.API_REPORT]).map((key) => {
return {
value: key,
...Object.keys(ReportStatus[ReportEnum.API_REPORT][key]),
};
});
});
const columns: MsTableColumn = [
{
title: 'ID',
@ -479,10 +404,9 @@
title: 'case.caseLevel',
dataIndex: 'priority',
slotName: 'caseLevel',
titleSlotName: 'caseLevelFilter',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
filterConfig: {
options: casePriorityOptions,
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL,
},
width: 150,
showDrag: true,
@ -491,11 +415,14 @@
title: 'apiTestManagement.apiStatus',
dataIndex: 'status',
slotName: 'status',
titleSlotName: 'statusFilter',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
filterConfig: {
options: requestCaseStatusOptions.value,
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_STATUS,
},
width: 150,
showDrag: true,
},
@ -518,7 +445,10 @@
title: 'case.lastReportStatus',
dataIndex: 'lastReportStatus',
slotName: 'lastReportStatus',
titleSlotName: 'lastReportStatusFilter',
filterConfig: {
options: lastReportStatusListOptions.value,
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_LAST_EXECUTE_STATUS,
},
showInTable: false,
width: 150,
showDrag: true,
@ -563,8 +493,15 @@
{
title: 'case.tableColumnCreateUser',
slotName: 'createName',
dataIndex: 'createName',
titleSlotName: 'createUserFilter',
dataIndex: 'createUser',
filterConfig: {
mode: 'remote',
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
placeholderText: t('caseManagement.featureCase.PleaseSelect'),
},
showInTable: true,
showTooltip: true,
width: 180,
@ -636,18 +573,6 @@
},
];
const statusFilterVisible = ref(false);
const statusFilters = ref<string[]>([]);
const caseFilterVisible = ref(false);
const caseFilters = ref<string[]>([]);
const createUserFilterVisible = ref(false);
const createUserFilters = ref<string[]>([]);
const lastReportStatusFilterVisible = ref(false);
const lastReportStatusList = computed(() => {
return Object.keys(ReportStatus[ReportEnum.API_REPORT]);
});
const lastReportStatusFilters = ref<string[]>([]);
async function getModuleIds() {
let moduleIds: string[] = [];
if (props.activeModule !== 'all') {
@ -667,12 +592,6 @@
projectId: appStore.currentProjectId,
moduleIds: selectModules,
protocol: props.protocol,
filter: {
status: statusFilters.value,
priority: caseFilters.value,
lastReportStatus: lastReportStatusFilters.value,
createUser: createUserFilters.value,
},
};
setLoadListParams(params);
loadList();
@ -686,33 +605,6 @@
loadCaseList();
});
function handleFilterHidden(val: boolean) {
if (!val) {
caseFilterVisible.value = false;
statusFilterVisible.value = false;
lastReportStatusFilterVisible.value = false;
loadCaseList();
}
}
function resetCaseFilter() {
caseFilters.value = [];
caseFilterVisible.value = false;
loadCaseList();
}
function resetStatusFilter() {
statusFilterVisible.value = false;
statusFilters.value = [];
loadCaseList();
}
function resetLastReportStatusFilter() {
lastReportStatusFilterVisible.value = false;
lastReportStatusFilters.value = [];
loadCaseList();
}
watch(
() => props.activeModule,
() => {
@ -773,12 +665,7 @@
return {
condition: {
keyword: keyword.value,
filter: {
status: statusFilters.value,
priority: caseFilters.value,
lastReportStatus: lastReportStatusFilters.value,
createUser: createUserFilters.value,
},
filter: propsRes.value.filter,
},
projectId: appStore.currentProjectId,
protocol: props.protocol,

View File

@ -6,46 +6,13 @@
<span class="text-[14px]">{{ t('common.notRemind') }}</span>
</template>
</a-alert>
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
<template #typeFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="statusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<MsButton type="text" class="arco-btn-text--secondary" @click="statusFilterVisible = true">
{{ t(columnConfig.title as string) }}
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="typeFilter" direction="vertical" size="small">
<a-checkbox v-for="val of typeOptions" :key="val.value" :value="val.value">
<span>{{ t(val.label) }}</span>
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetTypeFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
</template>
</ms-base-table>
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent" @filter-change="filterChange"> </ms-base-table>
</div>
</template>
<script setup lang="ts">
import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
@ -58,10 +25,7 @@
import { hasAnyPermission } from '@/utils/permission';
import { TableKeyEnum } from '@/enums/tableEnum';
const typeFilter = ref<string[]>([]);
const statusFilterVisible = ref(false);
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
const props = defineProps<{
sourceId: string | number;
@ -74,15 +38,15 @@
const typeOptions = [
{
label: 'system.log.operateType.add',
label: t('system.log.operateType.add'),
value: 'ADD',
},
{
label: 'system.log.operateType.update',
label: t('system.log.operateType.update'),
value: 'UPDATE',
},
{
label: 'system.log.operateType.import',
label: t('system.log.operateType.import'),
value: 'IMPORT',
},
];
@ -97,7 +61,10 @@
title: 'apiTestManagement.type',
dataIndex: 'type',
slotName: 'type',
titleSlotName: 'typeFilter',
filterConfig: {
options: typeOptions,
filterSlotName: FilterSlotNameEnum.GLOBAL_CHANGE_HISTORY_TYPE,
},
width: 100,
},
{
@ -139,27 +106,18 @@
})
);
function loadHistory() {
function loadHistory(types?: string[]) {
setLoadListParams({
projectId: appStore.currentProjectId,
sourceId: props.sourceId,
modules: 'API_TEST_MANAGEMENT_CASE',
types: typeFilter.value,
types,
});
loadList();
}
function handleFilterHidden(val: boolean) {
if (!val) {
statusFilterVisible.value = false;
loadHistory();
}
}
function resetTypeFilter() {
typeFilter.value = [];
statusFilterVisible.value = false;
loadHistory();
function filterChange(dataIndex: string, value: string[] | (string | number)[] | undefined) {
loadHistory(value as string[]);
}
onBeforeMount(() => {

View File

@ -12,69 +12,8 @@
/>-->
</div>
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
<template #triggerModeFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="triggerModeFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<MsButton type="text" class="arco-btn-text--secondary" @click="triggerModeFilterVisible = true">
<div class="font-medium">
{{ t(columnConfig.title as string) }}
</div>
<icon-down :class="triggerModeFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="triggerModeListFilters" direction="vertical" size="small">
<a-checkbox v-for="(key, value) of TriggerModeLabel" :key="key" :value="value">
<div class="font-medium">{{ t(key) }}</div>
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetModeFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
</template>
<template #statusFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="statusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<MsButton type="text" class="arco-btn-text--secondary" @click="statusFilterVisible = true">
{{ t(columnConfig.title as string) }}
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
<a-checkbox v-for="val of statusList" :key="val" :value="val">
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="val" />
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
<template #[FilterSlotNameEnum.API_TEST_CASE_API_REPORT_EXECUTE_RESULT]="{ filterContent }">
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="filterContent.value" />
</template>
<template #triggerMode="{ record }">
<span>{{ t(TriggerModeLabel[record.triggerMode as keyof typeof TriggerModeLabel]) }}</span>
@ -130,13 +69,19 @@
import { ApiCaseExecuteHistoryItem } from '@/models/apiTest/management';
import { ReportEnum, ReportStatus, TriggerModeLabel } from '@/enums/reportEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
const triggerModeListFilters = ref<string[]>();
const triggerModeFilterVisible = ref(false);
const statusFilterVisible = ref(false);
const statusFilters = ref<string[]>();
import { triggerModeOptions } from '@/views/api-test/report/utils';
const appStore = useAppStore();
const { t } = useI18n();
const statusList = computed(() => {
return Object.keys(ReportStatus[ReportEnum.API_REPORT]);
return Object.keys(ReportStatus[ReportEnum.API_REPORT]).map((key) => {
return {
value: key,
label: t(ReportStatus[ReportEnum.API_REPORT][key].label),
};
});
});
const showResponse = ref(false);
@ -147,9 +92,6 @@
protocol: string;
}>();
const appStore = useAppStore();
const { t } = useI18n();
const keyword = ref('');
const columns: MsTableColumn = [
@ -164,7 +106,9 @@
title: 'apiTestManagement.executeMethod',
dataIndex: 'triggerMode',
slotName: 'triggerMode',
titleSlotName: 'triggerModeFilter',
filterConfig: {
options: triggerModeOptions,
},
showTooltip: true,
sortable: {
sortDirections: ['ascend', 'descend'],
@ -176,11 +120,14 @@
title: 'apiTestManagement.executeResult',
dataIndex: 'status',
slotName: 'status',
titleSlotName: 'statusFilter',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
filterConfig: {
options: statusList.value,
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_REPORT_EXECUTE_RESULT,
},
width: 150,
},
{
@ -228,34 +175,10 @@
projectId: appStore.currentProjectId,
keyword: keyword.value,
id: props.sourceId,
filter: {
triggerMode: triggerModeListFilters.value,
status: statusFilters.value,
},
});
loadList();
}
function handleFilterHidden(val: boolean) {
if (!val) {
loadExecuteList();
triggerModeFilterVisible.value = false;
statusFilterVisible.value = false;
}
}
function resetModeFilter() {
triggerModeListFilters.value = [];
triggerModeFilterVisible.value = false;
loadExecuteList();
}
function resetStatusFilter() {
statusFilters.value = [];
statusFilterVisible.value = false;
loadExecuteList();
}
const activeReportIndex = ref<number>(0);
const activeReportId = ref('');
async function showResult(record: ApiCaseExecuteHistoryItem, rowIndex: number) {

View File

@ -38,102 +38,6 @@
{{ record.integrated ? t('report.collection') : t('report.independent') }}
</MsTag>
</template>
<template #integratedFilter="{ columnConfig }">
<TableFilter
v-model:visible="reportTypeVisible"
v-model:status-filters="integratedFiltersMap[showType]"
:title="(columnConfig.title as string)"
:list="reportTypeList"
@search="initData()"
>
<template #item="{ item }">
<MsTag theme="light" :type="item.value === 'INTEGRATED' ? 'primary' : undefined">
{{ item.value === 'INTEGRATED' ? t('report.collection') : t('report.independent') }}
</MsTag>
</template>
</TableFilter>
</template>
<!-- 报告触发方式筛选 -->
<template #triggerModeFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="triggerModeFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<a-button
type="text"
class="arco-btn-text--secondary p-[8px_4px]"
@click.stop="triggerModeFilterVisible = true"
>
<div class="font-medium">
{{ t(columnConfig.title as string) }}
</div>
<icon-down :class="triggerModeFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group
v-model:model-value="triggerModeListFiltersMaps[showType]"
direction="vertical"
size="small"
>
<a-checkbox v-for="(key, value) of TriggerModeLabel" :key="key" :value="value">
<div class="font-medium">{{ t(key) }}</div>
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetTriggerModeFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
</template>
<!-- 报告结果筛选 -->
<template #statusFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="statusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<a-button type="text" class="arco-btn-text--secondary p-[8px_4px]" @click.stop="statusFilterVisible = true">
<div class="font-medium">
{{ t(columnConfig.title as string) }}
</div>
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
<template #content>
<div class="arco-table-filters-content">
<div class="flex items-center justify-center px-[6px] py-[2px]">
<a-checkbox-group
v-model:model-value="statusListFiltersMap[showType]"
direction="vertical"
size="small"
>
<a-checkbox v-for="key of statusFilters" :key="key" :value="key">
<ExecutionStatus :module-type="props.moduleType" :status="key" />
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
</template>
<template #status="{ record }">
<ExecutionStatus
:module-type="props.moduleType"
@ -141,6 +45,14 @@
:script-identifier="props.moduleType === ReportEnum.API_SCENARIO_REPORT ? record.scriptIdentifier : null"
/>
</template>
<template #[FilterSlotNameEnum.API_TEST_CASE_API_REPORT_EXECUTE_RESULT]="{ filterContent }">
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="filterContent.value" />
</template>
<template #[FilterSlotNameEnum.API_TEST_REPORT_TYPE]="{ filterContent }">
<MsTag theme="light" :type="filterContent.value ? 'primary' : undefined">
{{ filterContent.value ? t('report.collection') : t('report.independent') }}
</MsTag>
</template>
<template #triggerMode="{ record }">
<span>{{ t(TriggerModeLabel[record.triggerMode as keyof typeof TriggerModeLabel]) }}</span>
</template>
@ -192,7 +104,6 @@
import CaseReportDrawer from './caseReportDrawer.vue';
import ReportDetailDrawer from './reportDetailDrawer.vue';
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
import TableFilter from '@/views/case-management/caseManagementFeature/components/tableFilter.vue';
import {
getShareTime,
@ -211,6 +122,9 @@
import { BatchApiParams } from '@/models/common';
import { ReportEnum, ReportStatus, TriggerModeLabel } from '@/enums/reportEnum';
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { triggerModeOptions } from '@/views/api-test/report/utils';
const { openModal } = useModal();
@ -224,14 +138,19 @@
name: string;
}>();
const keyword = ref<string>('');
const statusFilterVisible = ref(false);
const triggerModeFilterVisible = ref(false);
const triggerModeListFilters = ref<string[]>([]);
type ReportShowType = 'All' | 'INDEPENDENT' | 'INTEGRATED';
const showType = ref<ReportShowType>('All');
const statusList = computed(() => {
return Object.keys(ReportStatus[ReportEnum.API_REPORT]).map((key) => {
return {
value: key,
label: t(ReportStatus[ReportEnum.API_REPORT][key].label),
};
});
});
const columns: MsTableColumn = [
{
title: 'report.name',
@ -253,7 +172,6 @@
title: 'report.type',
slotName: 'integrated',
dataIndex: 'integrated',
titleSlotName: 'integratedFilter',
width: 150,
showDrag: true,
},
@ -261,7 +179,10 @@
title: 'report.result',
dataIndex: 'status',
slotName: 'status',
titleSlotName: 'statusFilter',
filterConfig: {
options: statusList.value,
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_REPORT_EXECUTE_RESULT,
},
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
@ -279,9 +200,11 @@
sortDirections: ['ascend', 'descend'],
sorter: true,
},
filterConfig: {
options: triggerModeOptions,
},
width: 150,
showDrag: true,
titleSlotName: 'triggerModeFilter',
},
{
title: 'report.operator',
@ -323,7 +246,7 @@
return false;
}
};
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, resetFilterParams } = useTable(
reportList,
{
tableKey: TableKeyEnum.API_TEST_REPORT,
@ -342,72 +265,12 @@
}),
rename
);
//
const allListFilters = ref<string[]>([]);
const independentListFilters = ref<string[]>([]);
const integratedListFilters = ref<string[]>([]);
const statusListFiltersMap = ref<Record<string, string[]>>({
All: allListFilters.value,
INDEPENDENT: independentListFilters.value,
INTEGRATED: integratedListFilters.value,
});
const allTriggerModeFilters = ref<string[]>([]);
const independentTriggerModeFilters = ref<string[]>([]);
const integratedTriggerModeFilters = ref<string[]>([]);
const triggerModeListFiltersMaps = ref<Record<string, string[]>>({
All: allTriggerModeFilters.value,
INDEPENDENT: independentTriggerModeFilters.value,
INTEGRATED: integratedTriggerModeFilters.value,
});
//
const allIntegratedFilters = ref<string[]>([]);
const independentIntegratedFilters = ref<string[]>([]);
const integratedIntegratedFilters = ref<string[]>([]);
const reportTypeVisible = ref<boolean>(false);
const integratedFiltersMap = ref<Record<string, string[]>>({
All: allIntegratedFilters.value,
INDEPENDENT: independentIntegratedFilters.value,
INTEGRATED: integratedIntegratedFilters.value,
});
const reportTypeList = ref([
{
value: 'INDEPENDENT',
label: t('report.independent'),
},
{
value: 'INTEGRATED',
label: t('report.collection'),
},
]);
const integratedFilters = computed(() => {
if (showType.value === 'All') {
if (integratedFiltersMap.value[showType.value].length === 1) {
return integratedFiltersMap.value[showType.value].includes('INDEPENDENT') ? [false] : [true];
}
return undefined;
}
if (showType.value === 'INTEGRATED') {
return [true];
}
return [false];
});
function initData() {
setLoadListParams({
keyword: keyword.value,
projectId: appStore.currentProjectId,
moduleType: props.moduleType,
filter: {
status: statusListFiltersMap.value[showType.value],
integrated: integratedFilters.value,
triggerMode: triggerModeListFiltersMaps.value[showType.value],
},
});
loadList();
}
@ -435,11 +298,7 @@
...params,
selectIds: params?.selectedIds || [],
condition: {
filter: {
status: statusListFiltersMap.value[showType.value],
integrated: integratedFilters.value,
triggerMode: triggerModeListFilters.value,
},
filter: propsRes.value.filter,
keyword: keyword.value,
},
projectId: appStore.currentProjectId,
@ -503,32 +362,9 @@
initData();
});
const statusFilters = computed(() => {
return Object.keys(ReportStatus[props.moduleType]) || [];
});
function handleFilterHidden(val: boolean) {
if (!val) {
triggerModeFilterVisible.value = false;
statusFilterVisible.value = false;
initData();
}
}
function resetTriggerModeFilter() {
triggerModeFilterVisible.value = false;
triggerModeListFilters.value = [];
initData();
}
function resetStatusFilter() {
statusFilterVisible.value = false;
statusListFiltersMap.value[showType.value] = [];
initData();
}
function changeShowType(val: string | number | boolean) {
showType.value = val as ReportShowType;
resetFilterParams();
resetSelector();
initData();
}
@ -602,6 +438,7 @@
(val) => {
if (val) {
resetSelector();
resetFilterParams();
initData();
}
}

View File

@ -1,5 +1,9 @@
import type { ScenarioItemType } from '@/models/apiTest/report';
import { useI18n } from '@/hooks/useI18n';
import type { ScenarioItemType } from '@/models/apiTest/report';
import { TriggerModeLabelEnum } from '@/enums/reportEnum';
const { t } = useI18n();
export function addFoldField(node: ScenarioItemType) {
if (node.children && node.children.length > 0) {
node.fold = true;
@ -22,4 +26,23 @@ export function getIndicators(value: any) {
return value;
}
export const triggerModeOptions = [
{
value: TriggerModeLabelEnum.MANUAL,
label: t('report.trigger.manual'),
},
{
value: TriggerModeLabelEnum.SCHEDULE,
label: t('report.trigger.scheduled'),
},
{
value: TriggerModeLabelEnum.BATCH,
label: t('report.trigger.batch.execution'),
},
{
value: TriggerModeLabelEnum.API,
label: t('report.trigger.interface'),
},
];
export default {};

View File

@ -4,69 +4,8 @@
<template #num="{ record }">
<span type="text" class="px-0">{{ record.num }}</span>
</template>
<template #triggerModeFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="triggerModeFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<MsButton type="text" class="arco-btn-text--secondary p-[8px_4px]" @click="triggerModeFilterVisible = true">
<div class="font-medium">
{{ t(columnConfig.title as string) }}
</div>
<icon-down :class="triggerModeFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="triggerModeListFilters" direction="vertical" size="small">
<a-checkbox v-for="(key, value) of TriggerModeLabel" :key="key" :value="value">
<div class="font-medium">{{ t(key) }}</div>
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetTriggerModeFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
</template>
<template #statusFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="statusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<MsButton type="text" class="arco-btn-text--secondary ml-[10px]" @click="statusFilterVisible = true">
{{ t(columnConfig.title as string) }}
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
<a-checkbox v-for="val of Object.values(ExecuteStatusFilters)" :key="val" :value="val">
<executeStatus :status="val" />
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
<template #[FilterSlotNameEnum.API_TEST_SCENARIO_EXECUTE_RESULT]="{ filterContent }">
<executeStatus :status="filterContent.value" />
</template>
<template #triggerMode="{ record }">
<span>{{ t(TriggerModeLabel[record.triggerMode as keyof typeof TriggerModeLabel]) }}</span>
@ -124,11 +63,10 @@
import { ExecuteHistoryItem } from '@/models/apiTest/scenario';
import { ExecuteStatusFilters } from '@/enums/apiEnum';
import { TriggerModeLabel } from '@/enums/reportEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { triggerModeOptions } from '@/views/api-test/report/utils';
const triggerModeListFilters = ref<string[]>([]);
const triggerModeFilterVisible = ref(false);
const statusFilterVisible = ref(false);
const statusFilters = ref<string[]>([]);
const tableQueryParams = ref<any>();
const keyword = ref('');
@ -137,6 +75,16 @@
scenarioId?: string | number; // id
readOnly?: boolean;
}>();
const executeStatusFilters = computed(() => {
return Object.values(ExecuteStatusFilters).map((key) => {
return {
value: key,
label: key,
};
});
});
const columns: MsTableColumn = [
{
title: 'apiScenario.executeHistory.num',
@ -150,14 +98,19 @@
dataIndex: 'triggerMode',
slotName: 'triggerMode',
showTooltip: true,
titleSlotName: 'triggerModeFilter',
filterConfig: {
options: triggerModeOptions,
},
width: 100,
},
{
title: 'apiScenario.executeHistory.execution.status',
dataIndex: 'status',
slotName: 'status',
titleSlotName: 'statusFilter',
filterConfig: {
options: executeStatusFilters.value,
filterSlotName: FilterSlotNameEnum.API_TEST_SCENARIO_EXECUTE_RESULT,
},
width: 100,
},
{
@ -207,10 +160,6 @@
const params = {
keyword: keyword.value,
id: props.scenarioId,
filter: {
triggerMode: triggerModeListFilters.value,
status: statusFilters.value,
},
};
setLoadListParams(params);
loadList();
@ -221,26 +170,6 @@
};
}
function handleFilterHidden(val: boolean) {
if (!val) {
triggerModeFilterVisible.value = false;
statusFilterVisible.value = false;
loadExecuteHistoryList();
}
}
function resetTriggerModeFilter() {
triggerModeFilterVisible.value = false;
triggerModeListFilters.value = [];
loadExecuteHistoryList();
}
function resetStatusFilter() {
statusFilterVisible.value = false;
statusFilters.value = [];
loadExecuteHistoryList();
}
const showScenarioReportVisible = ref(false);
const reportId = ref('');
function showResult(record: ExecuteHistoryItem) {

View File

@ -31,68 +31,6 @@
@drag-change="changeHandler"
@module-change="loadScenarioList(false)"
>
<template #statusFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="statusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<MsButton type="text" class="arco-btn-text--secondary ml-[10px]" @click="statusFilterVisible = true">
{{ t(columnConfig.title as string) }}
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical">
<a-checkbox v-for="val of Object.values(ApiScenarioStatus)" :key="val" :value="val">
<apiStatus :status="val" />
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
</template>
<template #priorityFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="priorityFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<MsButton type="text" class="arco-btn-text--secondary ml-[10px]" @click="priorityFilterVisible = true">
{{ t(columnConfig.title as string) }}
<icon-down :class="priorityFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="priorityFilters" direction="vertical" size="small">
<a-checkbox v-for="item of casePriorityOptions" :key="item.value" :value="item.value">
<caseLevel :case-level="item.label as CaseLevel" />
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetPriorityFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
</template>
<template #num="{ record }">
<div>
<MsButton type="text" class="float-left" style="margin-right: 4px" @click="openScenarioTab(record)">
@ -133,23 +71,6 @@
</div>
</div>
</template>
<template #status="{ record }">
<a-select
v-if="hasAnyPermission(['PROJECT_API_SCENARIO:READ+UPDATE'])"
v-model:model-value="record.status"
class="param-input w-full"
size="mini"
@change="() => handleStatusChange(record)"
>
<template #label>
<apiStatus :status="record.status" />
</template>
<a-option v-for="item of Object.values(ApiScenarioStatus)" :key="item" :value="item">
<apiStatus :status="item" />
</a-option>
</a-select>
<apiStatus v-else :status="record.status" />
</template>
<template #priority="{ record }">
<a-select
v-if="hasAnyPermission(['PROJECT_API_SCENARIO:READ+UPDATE'])"
@ -169,44 +90,42 @@
</a-select>
<span v-else class="text-[var(--color-text-2)]"> <caseLevel :case-level="record.priority" /></span>
</template>
<!-- 报告结果筛选 -->
<template #lastReportStatusFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="lastReportStatusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
<caseLevel :case-level="filterContent.value" />
</template>
<template #[FilterSlotNameEnum.API_TEST_CASE_API_STATUS]="{ filterContent }">
<apiStatus :status="filterContent.value" />
</template>
<template #status="{ record }">
<a-select
v-if="hasAnyPermission(['PROJECT_API_SCENARIO:READ+UPDATE'])"
v-model:model-value="record.status"
class="param-input w-full"
size="mini"
@change="() => handleStatusChange(record)"
>
<a-button
type="text"
class="arco-btn-text--secondary p-[8px_4px] text-[14px]"
size="mini"
@click="lastReportStatusFilterVisible = true"
>
<div class="font-medium">
{{ t(columnConfig.title as string) }}
</div>
<icon-down :class="lastReportStatusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
<template #content>
<div class="arco-table-filters-content">
<div class="flex items-center justify-center px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="lastReportStatusListFilters" direction="vertical" size="small">
<a-checkbox v-for="key of lastReportStatusFilters" :key="key" :value="key">
<ExecutionStatus :module-type="ReportEnum.API_SCENARIO_REPORT" :status="key" />
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetLastReportStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
<template #label>
<apiStatus :status="record.status" />
</template>
</a-trigger>
<a-option v-for="item of Object.values(ApiScenarioStatus)" :key="item" :value="item">
<apiStatus :status="item" />
</a-option>
</a-select>
<apiStatus v-else :status="record.status" />
</template>
<template #createUserName="{ record }">
<a-tooltip :content="`${record.createName}`" position="tl">
<div class="one-line-text">{{ characterLimit(record.createUserName) }}</div>
</a-tooltip>
</template>
<template #updateUserName="{ record }">
<a-tooltip :content="`${record.createName}`" position="tl">
<div class="one-line-text">{{ characterLimit(record.updateUserName) }}</div>
</a-tooltip>
</template>
<!-- 报告结果筛选 -->
<template #[FilterSlotNameEnum.API_TEST_CASE_API_REPORT_EXECUTE_RESULT]="{ filterContent }">
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="filterContent.value" />
</template>
<template #lastReportStatus="{ record }">
<ExecutionStatus
@ -215,32 +134,6 @@
:script-identifier="record.scriptIdentifier"
/>
</template>
<template #createUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="createUserFilterVisible"
v-model:status-filters="createUserFilters"
:title="(columnConfig.title as string)"
:list="memberOptions"
@search="loadScenarioList"
>
<template #item="{ item }">
{{ item.label }}
</template>
</TableFilter>
</template>
<template #updateUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="updateUserFilterVisible"
v-model:status-filters="updateUserFilters"
:title="(columnConfig.title as string)"
:list="memberOptions"
@search="loadScenarioList"
>
<template #item="{ item }">
{{ item.label }}
</template>
</TableFilter>
</template>
<template #stepTotal="{ record }">
{{ record.stepTotal }}
</template>
@ -612,7 +505,6 @@
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
import BatchRunModal from '@/views/api-test/scenario/components/batchRunModal.vue';
import operationScenarioModuleTree from '@/views/api-test/scenario/components/operationScenarioModuleTree.vue';
import TableFilter from '@/views/case-management/caseManagementFeature/components/tableFilter.vue';
import { getEnvList, getPoolId, getPoolOption } from '@/api/modules/api-test/management';
import {
@ -633,6 +525,7 @@
import useModal from '@/hooks/useModal';
import useTableStore from '@/hooks/useTableStore';
import useAppStore from '@/store/modules/app';
import { characterLimit } from '@/utils';
import { translateTextToPX } from '@/utils/css';
import { hasAnyPermission } from '@/utils/permission';
@ -649,6 +542,7 @@
import { ApiScenarioStatus } from '@/enums/apiEnum';
import { ReportEnum, ReportStatus } from '@/enums/reportEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { casePriorityOptions } from '@/views/api-test/components/config';
@ -664,14 +558,8 @@
(e: 'createScenario'): void;
}>();
const lastReportStatusFilterVisible = ref(false);
const lastReportStatusListFilters = ref<string[]>([]);
const lastReportStatusFilters = computed(() => {
return Object.keys(ReportStatus[ReportEnum.API_SCENARIO_REPORT]);
});
const createUserFilterVisible = ref(false);
const createUserFilters = ref<string[]>([]);
const updateUserFilterVisible = ref(false);
const updateUserFilters = ref<string[]>([]);
const memberOptions = ref<{ label: string; value: string }[]>([]);
const appStore = useAppStore();
@ -679,7 +567,6 @@
const { openModal } = useModal();
const tableRecord = ref<ApiScenarioTableItem>();
const scheduleModalTitle = ref('');
const priorityFilterVisible = ref(false);
const priorityFilters = ref<string[]>([]);
const scheduleConfig = ref<ApiScenarioScheduleConfig>({
scenarioId: '',
@ -736,6 +623,24 @@
const isBatchCopy = ref(false); //
const showBatchExecute = ref(false);
const requestApiScenarioStatusOptions = computed(() => {
return Object.values(ApiScenarioStatus).map((key) => {
return {
value: key,
label: key,
};
});
});
const statusList = computed(() => {
return Object.keys(ReportStatus[ReportEnum.API_REPORT]).map((key) => {
return {
value: key,
label: t(ReportStatus[ReportEnum.API_REPORT][key].label),
};
});
});
let columns: MsTableColumn = [
{
title: 'ID',
@ -772,7 +677,10 @@
sortDirections: ['ascend', 'descend'],
sorter: true,
},
titleSlotName: 'priorityFilter',
filterConfig: {
options: casePriorityOptions,
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL,
},
width: 140,
},
{
@ -784,16 +692,23 @@
sortDirections: ['ascend', 'descend'],
sorter: true,
},
filterConfig: {
options: requestApiScenarioStatusOptions.value,
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_STATUS,
},
showDrag: true,
width: 140,
},
{
title: 'apiScenario.table.columns.runResult',
titleSlotName: 'lastReportStatusFilter',
dataIndex: 'lastReportStatus',
slotName: 'lastReportStatus',
showTooltip: false,
showDrag: true,
filterConfig: {
options: statusList.value,
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_REPORT_EXECUTE_RESULT,
},
width: 200,
},
{
@ -857,23 +772,37 @@
},
{
title: 'apiScenario.table.columns.createUser',
dataIndex: 'createUserName',
dataIndex: 'createUser',
slotName: 'createUserName',
showInTable: false,
titleSlotName: 'createUserFilter',
showTooltip: true,
showDrag: true,
width: 109,
filterConfig: {
mode: 'remote',
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
placeholderText: t('caseManagement.featureCase.PleaseSelect'),
},
},
{
title: 'apiScenario.table.columns.updateUser',
dataIndex: 'updateUserName',
dataIndex: 'updateUser',
slotName: 'updateUserName',
showInTable: false,
titleSlotName: 'updateUserFilter',
showTooltip: true,
showDrag: true,
width: 109,
filterConfig: {
mode: 'remote',
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
placeholderText: t('caseManagement.featureCase.PleaseSelect'),
},
},
{
title: 'common.operation',
@ -979,8 +908,6 @@
},
];
}
const statusFilterVisible = ref(false);
const statusFilters = ref<string[]>([]);
const tableStore = useTableStore();
@ -999,13 +926,6 @@
keyword: keyword.value,
projectId: appStore.currentProjectId,
moduleIds,
filter: {
lastReportStatus: lastReportStatusListFilters.value,
status: statusFilters.value,
priority: priorityFilters.value,
createUser: createUserFilters.value,
updateUser: updateUserFilters.value,
},
};
setLoadListParams(params);
await loadList();
@ -1014,33 +934,6 @@
}
}
function handleFilterHidden(val: boolean) {
if (!val) {
lastReportStatusFilterVisible.value = false;
statusFilterVisible.value = false;
priorityFilterVisible.value = false;
loadScenarioList(true);
}
}
function resetStatusFilter() {
statusFilterVisible.value = false;
statusFilters.value = [];
loadScenarioList(true);
}
function resetPriorityFilter() {
priorityFilterVisible.value = false;
priorityFilters.value = [];
loadScenarioList(true);
}
function resetLastReportStatusFilter() {
lastReportStatusFilterVisible.value = false;
lastReportStatusListFilters.value = [];
loadScenarioList(true);
}
async function handleStatusChange(record: ApiScenarioUpdateDTO) {
try {
await updateScenarioStatus(record.id, record.status);
@ -1112,7 +1005,7 @@
selectIds,
selectAll: !!params?.selectAll,
excludeIds: params?.excludeIds || [],
condition: { ...params?.condition, keyword: keyword.value },
condition: { ...params?.condition, keyword: keyword.value, filter: propsRes.value.filter },
projectId: appStore.currentProjectId,
moduleIds: params?.moduleIds || [],
deleteAll: true,
@ -1328,13 +1221,7 @@
excludeIds: batchParams.value?.excludeIds || [],
condition: {
keyword: keyword.value,
filter: {
lastReportStatus: lastReportStatusListFilters.value,
status: statusFilters.value,
priority: priorityFilters.value,
createUser: createUserFilters.value,
updateUser: updateUserFilters.value,
},
filter: propsRes.value.filter,
},
projectId: appStore.currentProjectId,
moduleIds: batchParams.value?.moduleIds || [],
@ -1397,13 +1284,7 @@
excludeIds: batchParams.value?.excludeIds || [],
condition: {
keyword: keyword.value,
filter: {
lastReportStatus: lastReportStatusListFilters.value,
status: statusFilters.value,
priority: priorityFilters.value,
createUser: createUserFilters.value,
updateUser: updateUserFilters.value,
},
filter: propsRes.value.filter,
},
projectId: appStore.currentProjectId,
moduleIds: batchParams.value?.moduleIds || [],

View File

@ -110,7 +110,7 @@
const emit = defineEmits(['update:stepList']);
const executionResultList = computed(() =>
Object.values(executionResultMap).filter((item) => item.key !== LastExecuteResults.UN_EXECUTED)
Object.values(executionResultMap).filter((item) => item.key !== LastExecuteResults.PENDING)
);
//

View File

@ -95,7 +95,8 @@
<span>{{ statusIconMap[record.reviewStatus]?.statusText || '' }} </span>
</template>
<template #lastExecuteResult="{ record }">
<ExecuteStatusTag :execute-result="record.lastExecuteResult" />
<ExecuteStatusTag v-if="record.lastExecuteResult" :execute-result="record.lastExecuteResult" />
<span v-else>-</span>
</template>
<template #moduleId="{ record }">
<a-tree-select
@ -571,7 +572,6 @@
title: 'caseManagement.featureCase.tableColumnExecutionResult',
dataIndex: 'lastExecuteResult',
slotName: 'lastExecuteResult',
titleSlotName: 'executeResultFilter',
filterConfig: {
options: executeResultOptions.value,
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT,

View File

@ -57,6 +57,7 @@
const props = defineProps<{
visible: boolean;
caseId: string;
extraParams?: Record<string, any>;
}>();
const emit = defineEmits<{
@ -106,7 +107,7 @@
drawerLoading.value = true;
try {
await createOrUpdateBug({
request: { ...form.value, customFields: templateCustomFields.value, caseId: props.caseId },
request: { ...form.value, customFields: templateCustomFields.value, ...props.extraParams },
fileList: [],
});
emit('success');

View File

@ -12,20 +12,6 @@
</template>
</a-popover>
</template>
<template #severityFilter="{ columnConfig }">
<TableFilter
v-model:visible="severityFilterVisible"
v-model:status-filters="severityFilterValue"
:title="(columnConfig.title as string)"
:list="severityFilterOptions"
value-key="value"
@search="searchData()"
>
<template #item="{ item }">
{{ item.text }}
</template>
</TableFilter>
</template>
<template #statusName="{ record }">
<div class="one-line-text">{{ record.statusName || '-' }}</div>
</template>
@ -65,20 +51,19 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import TableFilter from '@/views/case-management/caseManagementFeature/components/tableFilter.vue';
import { getLinkedCaseBugList } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { BugOptionItem } from '@/models/bug-management';
import type { CommonList, TableQueryParams } from '@/models/common';
const appStore = useAppStore();
const { t } = useI18n();
@ -87,43 +72,41 @@
keyword: string;
bugColumns: MsTableColumn;
bugTotal: number; //
loadParams?: Record<string, any>;
loadBugListApi: (params: TableQueryParams) => Promise<CommonList<Record<string, any>>>; //
}>();
const emit = defineEmits<{
(e: 'link'): void;
(e: 'new'): void;
(e: 'cancelLink', bugId: string): void;
(e: 'update:keyword'): void;
}>();
const severityFilterVisible = ref(false);
const severityFilterOptions = ref<BugOptionItem[]>([]);
const bugTableRef = ref();
const {
propsRes: linkPropsRes,
propsEvent: linkTableEvent,
loadList: loadLinkList,
setLoadListParams: setLinkListParams,
} = useTable(getLinkedCaseBugList, {
} = useTable(props.loadBugListApi, {
columns: props.bugColumns,
scroll: { x: 'auto' },
heightUsed: 340,
enableDrag: false,
});
const severityColumnId = ref('');
const severityFilterValue = ref<string[]>([]);
function initTableParams() {
return {
keyword: props.keyword,
caseId: props.caseId,
const innerKeyword = useVModel(props, 'keyword', emit);
function searchData() {
setLinkListParams({
...props.loadParams,
keyword: innerKeyword.value,
projectId: appStore.currentProjectId,
condition: {
keyword: props.keyword,
filter: { ...linkPropsRes.value.filter, [severityColumnId.value]: severityFilterValue.value },
keyword: innerKeyword.value,
filter: linkPropsRes.value.filter,
},
};
}
function searchData() {
setLinkListParams(initTableParams());
});
loadLinkList();
}
@ -152,6 +135,15 @@
}
);
watch(
() => props.caseId,
(val) => {
if (val) {
searchData();
}
}
);
defineExpose({
searchData,
});

View File

@ -59,10 +59,14 @@
<BugList
v-if="showType === 'link'"
ref="bugTableListRef"
v-model:keyword="keyword"
:case-id="props.caseId"
:keyword="keyword"
:bug-total="total"
:bug-columns="columns"
:load-bug-list-api="getLinkedCaseBugList"
:load-params="{
caseId: props.caseId,
}"
@link="linkDefect"
@new="createDefect"
@cancel-link="cancelLink"
@ -111,7 +115,12 @@
</div>
</template>
</ms-base-table>
<AddDefectDrawer v-model:visible="showDrawer" :case-id="props.caseId" @success="getFetch()" />
<AddDefectDrawer
v-model:visible="showDrawer"
:case-id="props.caseId"
:extra-params="{ caseId: props.caseId }"
@success="getFetch()"
/>
<LinkDefectDrawer
v-model:visible="showLinkDrawer"
:case-id="props.caseId"

View File

@ -56,33 +56,27 @@ export const statusIconMap: Record<string, any> = {
};
// 图标执行结果 TODO:TS 类型 key
export const executionResultMap: Record<string, any> = {
UN_EXECUTED: {
key: 'UN_EXECUTED',
icon: StatusType.UN_EXECUTED,
PENDING: {
key: 'PENDING',
icon: StatusType.PENDING,
statusText: t('caseManagement.featureCase.nonExecution'),
color: 'text-[var(--color-text-brand)]',
},
PASSED: {
key: 'PASSED',
icon: StatusType.PASSED,
SUCCESS: {
key: 'SUCCESS',
icon: StatusType.SUCCESS,
statusText: t('common.success'),
color: '',
},
/* SKIPPED: {
key: 'SKIPPED',
icon: StatusType.SKIPPED,
statusText: t('caseManagement.featureCase.skip'),
color: 'text-[rgb(var(--link-6))]',
}, */
BLOCKED: {
key: 'BLOCKED',
icon: StatusType.BLOCKED,
statusText: t('caseManagement.featureCase.chokeUp'),
color: 'text-[rgb(var(--warning-6))]',
},
FAILED: {
key: 'FAILED',
icon: StatusType.FAILED,
ERROR: {
key: 'ERROR',
icon: StatusType.ERROR,
statusText: t('caseManagement.featureCase.failure'),
color: '',
},

View File

@ -32,40 +32,13 @@
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #resultTitle="{ columnConfig }">
<a-trigger
v-model:popup-visible="statusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<a-button type="text" class="arco-btn-text--secondary p-[8px_4px]" @click="statusFilterVisible = true">
<div class="font-medium">
{{ t(columnConfig.title as string) }}
</div>
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
<a-checkbox v-for="key of Object.keys(reviewResultMap)" :key="key" :value="key">
<a-tag :color="reviewResultMap[key as ReviewResult].color" class="px-[4px]" size="small">
{{ t(reviewResultMap[key as ReviewResult].label) }}
</a-tag>
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetReviewStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_REVIEW_RESULT]="{ filterContent }">
<a-tag :color="reviewResultMap[filterContent.value as ReviewResult].color" class="px-[4px]" size="small">
{{ t(reviewResultMap[filterContent.value as ReviewResult].label) }}
</a-tag>
</template>
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
<caseLevel :case-level="filterContent.text" />
</template>
<template #num="{ record }">
<a-tooltip :content="record.num">
@ -77,20 +50,6 @@
<template #caseLevel="{ record }">
<span class="text-[var(--color-text-2)]"> <caseLevel :case-level="record.caseLevel" /></span>
</template>
<template #caseLevelFilter="{ columnConfig }">
<TableFilter
v-model:visible="caseFilterVisible"
v-model:status-filters="caseFilters"
:title="(columnConfig.title as string)"
:list="caseLevelList"
value-key="value"
@search="searchCase()"
>
<template #item="{ item }">
<div class="flex"> <caseLevel :case-level="item.text" /></div>
</template>
</TableFilter>
</template>
<template #reviewNames="{ record }">
<MsTagGroup
v-if="record.showModuleTree"
@ -304,6 +263,7 @@
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import { FilterFormItem, FilterResult, FilterType } from '@/components/pure/ms-advance-filter/type';
@ -343,12 +303,11 @@
import { BatchApiParams, ModuleTreeNode } from '@/models/common';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { getCaseLevels } from '@/views/case-management/caseManagementFeature/components/utils';
const caseLevelFields = ref<Record<string, any>>({});
const caseFilterVisible = ref(false);
const caseFilters = ref<string[]>([]);
const caseLevelList = computed(() => {
return caseLevelFields.value?.options || [];
});
@ -381,7 +340,16 @@
const hasOperationPermission = computed(() =>
hasAnyPermission(['CASE_REVIEW:READ+REVIEW', 'CASE_REVIEW:READ+RELEVANCE'])
);
const columns: MsTableColumn = [
const reviewResultOptions = computed(() => {
return Object.keys(reviewResultMap).map((key) => {
return {
value: key,
label: t(reviewResultMap[key as ReviewResult].label),
};
});
});
let columns: MsTableColumn = [
{
title: 'ID',
dataIndex: 'num',
@ -408,10 +376,13 @@
title: 'caseManagement.featureCase.tableColumnLevel',
slotName: 'caseLevel',
dataIndex: 'caseLevel',
titleSlotName: 'caseLevelFilter',
showInTable: true,
width: 200,
showDrag: true,
filterConfig: {
options: caseLevelList.value,
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL,
},
},
{
title: 'caseManagement.caseReview.reviewer',
@ -424,7 +395,10 @@
title: 'caseManagement.caseReview.reviewResult',
dataIndex: 'status',
slotName: 'status',
titleSlotName: 'resultTitle',
filterConfig: {
options: reviewResultOptions.value,
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_REVIEW_RESULT,
},
width: 110,
},
// {
@ -500,7 +474,6 @@
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
keyword: keyword.value,
viewFlag: props.onlyMine,
filter: { status: statusFilters.value, caseLevel: caseFilters.value },
combine: filter
? {
...filter.combine,
@ -518,19 +491,6 @@
});
}
function handleFilterHidden(val: boolean) {
if (!val) {
searchCase();
statusFilterVisible.value = false;
}
}
function resetReviewStatusFilter() {
statusFilters.value = [];
statusFilterVisible.value = false;
searchCase();
}
onBeforeMount(() => {
searchCase();
});
@ -677,6 +637,23 @@
async function getCaseLevelFields() {
const result = await getCaseDefaultFields(appStore.currentProjectId);
caseLevelFields.value = result.customFields.find((item: any) => item.internal && item.fieldName === '用例等级');
columns = columns.map((item) => {
if (item.dataIndex === 'caseLevel') {
return {
title: 'caseManagement.featureCase.tableColumnLevel',
slotName: 'caseLevel',
dataIndex: 'caseLevel',
showInTable: true,
width: 200,
showDrag: true,
filterConfig: {
options: cloneDeep(caseLevelFields.value.options),
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL,
},
};
}
return item;
});
}
//
@ -851,7 +828,6 @@
}
onBeforeMount(async () => {
getCaseLevelFields();
const [, memberRes] = await Promise.all([
initReviewers(),
getProjectMemberCommentOptions(appStore.currentProjectId, keyword.value),
@ -905,7 +881,7 @@
searchCase,
resetSelector,
});
await getCaseLevelFields();
await tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_REVIEW_CASE, columns, 'drawer');
</script>

View File

@ -503,7 +503,7 @@
const selectedModuleKeys = ref<string[]>([]);
const tableStore = useTableStore();
await tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_REVIEW, columns, 'drawer', true);
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, resetFilterParams } = useTable(
getReviewList,
{
tableKey: TableKeyEnum.CASE_MANAGEMENT_REVIEW,
@ -540,8 +540,6 @@
],
};
const statusFilterVisible = ref(false);
const statusFilters = ref<string[]>([]);
const tableQueryParams = ref<any>();
async function searchReview(filter?: FilterResult) {
let moduleIds: string[] = [];
@ -583,22 +581,11 @@
watch(
() => innerShowType.value,
() => {
resetFilterParams();
searchReview();
}
);
function handleFilterHidden(val: boolean) {
if (!val) {
searchReview();
}
}
function resetStatusFilter() {
statusFilters.value = [];
statusFilterVisible.value = false;
searchReview();
}
const batchParams = ref<BatchActionQueryParams>({
selectedIds: [],
selectAll: false,

View File

@ -350,7 +350,7 @@
(record) => {
return {
...record,
lastExecResult: record.lastExecResult ?? LastExecuteResults.UN_EXECUTED,
lastExecResult: record.lastExecResult ?? LastExecuteResults.PENDING,
caseLevel: getCaseLevels(record.customFields),
moduleId: getModules(record.moduleId, props.moduleTree),
};

View File

@ -44,7 +44,7 @@
const formRef = ref<FormInstance>();
const executionResultList = computed(() =>
Object.values(executionResultMap).filter((item) => item.key !== LastExecuteResults.UN_EXECUTED)
Object.values(executionResultMap).filter((item) => item.key !== LastExecuteResults.PENDING)
);
async function handleUploadImage(file: File) {

View File

@ -53,10 +53,15 @@
</div>
<BugList
ref="bugTableListRef"
v-model:keyword="keyword"
:case-id="props.caseId"
:keyword="keyword"
:bug-total="total"
:bug-columns="columns"
:load-bug-list-api="associatedBugPage"
:load-params="{
testPlanCaseId: route.query.testPlanCaseId,
caseId: props.caseId,
}"
@link="linkDefect"
@new="createDefect"
@cancel-link="cancelLink"
@ -67,12 +72,22 @@
:drawer-loading="drawerLoading"
@save="saveHandler"
/>
<AddDefectDrawer v-model:visible="showDrawer" :case-id="props.caseId" @success="initData()" />
<AddDefectDrawer
v-model:visible="showDrawer"
:case-id="props.caseId"
::extra-params="{
testPlanCaseId: route.query.testPlanCaseId,
caseId: props.caseId,
testPlanId:props.testPlanId,
}"
@success="initData()"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
@ -82,7 +97,7 @@
import LinkDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/linkDefectDrawer.vue';
import { getBugList, getCustomOptionHeader } from '@/api/modules/bug-management';
import { associatedDrawerDebug, cancelAssociatedDebug } from '@/api/modules/case-management/featureCase';
import { associateBugToPlan, associatedBugPage, testPlanCancelBug } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { hasAnyPermission } from '@/utils/permission';
@ -96,6 +111,7 @@
const appStore = useAppStore();
const props = defineProps<{
caseId: string;
testPlanId: string;
}>();
const keyword = ref<string>('');
@ -216,11 +232,11 @@
}
const cancelLoading = ref<boolean>(false);
//
//
async function cancelLink(id: string) {
cancelLoading.value = true;
try {
await cancelAssociatedDebug(id);
await testPlanCancelBug(id);
Message.success(t('caseManagement.featureCase.cancelLinkSuccess'));
initData();
} catch (error) {
@ -244,12 +260,18 @@
columns.value = makeColumns(optionsMap, columns.value);
}
}
const route = useRoute();
const drawerLoading = ref<boolean>(false);
//
async function saveHandler(params: TableQueryParams) {
try {
drawerLoading.value = true;
await associatedDrawerDebug(params);
await associateBugToPlan({
...params,
caseId: props.caseId,
testPlanId: props.testPlanId,
testPlanCaseId: route.query.testPlanCaseId as string,
});
Message.success(t('caseManagement.featureCase.associatedSuccess'));
initData();
showLinkDrawer.value = false;
@ -260,6 +282,15 @@
}
}
watch(
() => props.caseId,
(val) => {
if (val) {
initBugList();
}
}
);
onBeforeMount(() => {
initFilterOptions();
initData();

View File

@ -62,7 +62,7 @@
const submitLoading = ref(false);
const submitDisabled = computed(
() =>
form.value.lastExecResult !== 'PASSED' &&
form.value.lastExecResult !== 'SUCCESS' &&
(form.value.content === '' || form.value.content?.trim() === '<p style=""></p>')
);
@ -70,12 +70,12 @@
() => props.stepExecutionResult,
() => {
const executionResultList = props.stepExecutionResult?.map((item) => item.executeResult);
if (executionResultList?.includes(LastExecuteResults.FAILED)) {
form.value.lastExecResult = LastExecuteResults.FAILED;
if (executionResultList?.includes(LastExecuteResults.ERROR)) {
form.value.lastExecResult = LastExecuteResults.ERROR;
} else if (executionResultList?.includes(LastExecuteResults.BLOCKED)) {
form.value.lastExecResult = LastExecuteResults.BLOCKED;
} else {
form.value.lastExecResult = LastExecuteResults.PASSED;
form.value.lastExecResult = LastExecuteResults.SUCCESS;
}
},
{ deep: true }

View File

@ -0,0 +1,84 @@
<template>
<!-- TODO: 待联调 -->
<div class="review-history-list">
<div v-for="item of executeHistoryList" :key="item.id" class="review-history-list-item">
<div class="flex items-center">
<MsAvatar :avatar="item.userLogo" />
<div class="ml-[8px] flex items-center">
<a-tooltip :content="item.userName" :mouse-enter-delay="300">
<div class="one-line-text max-w-[300px] font-medium text-[var(--color-text-1)]">{{ item.userName }}</div>
</a-tooltip>
<a-divider direction="vertical" margin="8px"></a-divider>
<div v-if="item.status === 'PASS'" class="flex items-center">
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
{{ t('caseManagement.caseReview.pass') }}
</div>
<div v-else-if="item.status === 'UN_PASS'" class="flex items-center">
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
{{ t('caseManagement.caseReview.fail') }}
</div>
<div v-else-if="item.status === 'UNDER_REVIEWED'" class="flex items-center">
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
{{ t('caseManagement.caseReview.suggestion') }}
</div>
<div v-else-if="item.status === 'RE_REVIEWED'" class="flex items-center">
<MsIcon type="icon-icon_resubmit_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
{{ t('caseManagement.caseReview.reReview') }}
</div>
<div v-if="item.status === 'PASSED'" class="flex items-center">
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
{{ t('caseManagement.featureCase.execute.success') }}
</div>
<div v-if="item.status === 'BLOCKED'" class="flex items-center">
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
{{ t('caseManagement.featureCase.execute.blocked') }}
</div>
<div v-if="item.status === 'FAILED'" class="flex items-center">
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
{{ t('caseManagement.featureCase.execute.failed') }}
</div>
</div>
</div>
<div class="markdown-body" style="margin-left: 48px" v-html="item.contentText"></div>
<div class="ml-[48px] mt-[8px] flex text-[var(--color-text-4)]">
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
<div>
<a-tooltip :content="item.reviewName" :mouse-enter-delay="300">
<span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all">
{{ characterLimit(item.reviewName) }}
</span>
<span
v-else
class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]"
>
{{ characterLimit(item.reviewName) }}
</span>
</a-tooltip>
</div>
</div>
</div>
<MsEmpty v-if="executeHistoryList.length === 0" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import dayjs from 'dayjs';
import MsAvatar from '@/components/pure/ms-avatar/index.vue';
import MsEmpty from '@/components/pure/ms-empty/index.vue';
import { CommentItem, CommentParams } from '@/components/business/ms-comment/types';
import { useI18n } from '@/hooks/useI18n';
import { characterLimit } from '@/utils';
const { t } = useI18n();
const props = defineProps<{
caseId: string;
}>();
const executeHistoryList = ref<CommentItem[]>([]);
</script>
<style scoped></style>

View File

@ -37,7 +37,7 @@
>
<div class="mb-[8px] flex items-center justify-between">
<div class="text-[var(--color-text-4)]">{{ item.num }}</div>
<ExecuteResult :execute-result="item.lastExecResult ?? LastExecuteResults.UN_EXECUTED" />
<ExecuteResult :execute-result="item.lastExecResult ?? LastExecuteResults.PENDING" />
</div>
<a-tooltip :content="item.name">
<div class="one-line-text">{{ item.name }}</div>
@ -120,9 +120,12 @@
/>
</div>
</div>
<BugList v-if="activeTab === 'defectList'" :case-id="caseDetail.id" />
<!-- TODO 待写页面 还未提交 -->
<!-- <ExecutionHistory v-if="activeTab === 'executionHistory'" :case-id="caseDetail.id" /> -->
<BugList
v-if="activeTab === 'defectList'"
:case-id="caseDetail.id"
:test-plan-id="route.query.id as string"
/>
<ExecutionHistory v-if="activeTab === 'executionHistory'" :case-id="caseDetail.id" />
</div>
</a-spin>
</div>
@ -145,9 +148,9 @@
import ExecuteSubmit from './executeSubmit.vue';
import CaseTabDetail from '@/views/case-management/caseManagementFeature/components/tabContent/tabDetail.vue';
import EditCaseDetailDrawer from '@/views/case-management/caseReview/components/editCaseDetailDrawer.vue';
import ExecutionHistory from '@/views/test-plan/testPlan/detail/featureCase/detail/executionHistory/index.vue';
import { getCaseDetail } from '@/api/modules/case-management/featureCase';
// import ExecutionHistory from '@/views/test-plan/testPlan/detail/featureCase/detail/executionHistory/index.vue';
import { getPlanDetailFeatureCaseList, getTestPlanDetail } from '@/api/modules/test-plan/testPlan';
import { testPlanDefaultDetail } from '@/config/testPlan';
import { useI18n } from '@/hooks/useI18n';