feat(接口管理): 接口用例-用例列表
This commit is contained in:
parent
7f63b45620
commit
e8cb0e6fe2
|
@ -4,15 +4,19 @@ import {
|
|||
AddDefinitionUrl,
|
||||
AddModuleUrl,
|
||||
BatchCleanOutApiUrl,
|
||||
BatchDeleteCaseUrl,
|
||||
BatchDeleteDefinitionUrl,
|
||||
BatchEditCaseUrl,
|
||||
BatchMoveDefinitionUrl,
|
||||
BatchRecoverApiUrl,
|
||||
BatchUpdateDefinitionUrl,
|
||||
CasePageUrl,
|
||||
CheckDefinitionScheduleUrl,
|
||||
DebugDefinitionUrl,
|
||||
DefinitionMockPageUrl,
|
||||
DefinitionPageUrl,
|
||||
DefinitionReferenceUrl,
|
||||
DeleteCaseUrl,
|
||||
DeleteDefinitionScheduleUrl,
|
||||
DeleteDefinitionUrl,
|
||||
DeleteMockUrl,
|
||||
|
@ -32,11 +36,14 @@ import {
|
|||
RecoverDefinitionUrl,
|
||||
RecoverOperationHistoryUrl,
|
||||
SaveOperationHistoryUrl,
|
||||
SortCaseUrl,
|
||||
SortDefinitionUrl,
|
||||
SwitchDefinitionScheduleUrl,
|
||||
ToggleFollowDefinitionUrl,
|
||||
TransferFileModuleOptionUrl,
|
||||
TransferFileUrl,
|
||||
UpdateCasePriorityUrl,
|
||||
UpdateCaseStatusUrl,
|
||||
UpdateDefinitionScheduleUrl,
|
||||
UpdateDefinitionUrl,
|
||||
UpdateMockStatusUrl,
|
||||
|
@ -46,6 +53,10 @@ import {
|
|||
|
||||
import { ExecuteRequestParams } from '@/models/apiTest/common';
|
||||
import {
|
||||
ApiCaseBatchEditParams,
|
||||
ApiCaseBatchParams,
|
||||
ApiCaseDetail,
|
||||
ApiCasePageParams,
|
||||
ApiDefinitionBatchDeleteParams,
|
||||
ApiDefinitionBatchMoveParams,
|
||||
ApiDefinitionBatchUpdateParams,
|
||||
|
@ -295,3 +306,39 @@ export function getTrashModuleTree(data: ApiDefinitionGetModuleParams) {
|
|||
export function getTrashModuleCount(data: ApiDefinitionGetModuleParams) {
|
||||
return MSR.post({ url: GetTrashModuleCountUrl, data });
|
||||
}
|
||||
|
||||
// --------------------用例
|
||||
// 获取接口用例列表
|
||||
export function getCasePage(data: ApiCasePageParams) {
|
||||
return MSR.post<CommonList<ApiCaseDetail>>({ url: CasePageUrl, data });
|
||||
}
|
||||
|
||||
// 接口用例更新状态
|
||||
export function updateCaseStatus(id: string, status: string) {
|
||||
return MSR.get({ url: `${UpdateCaseStatusUrl}/${id}/${status}` });
|
||||
}
|
||||
|
||||
// 接口用例更新等级
|
||||
export function updateCasePriority(id: string, priority: string) {
|
||||
return MSR.get({ url: `${UpdateCasePriorityUrl}/${id}/${priority}` });
|
||||
}
|
||||
|
||||
// 删除接口用例
|
||||
export function deleteCase(id: string) {
|
||||
return MSR.get({ url: DeleteCaseUrl, params: id });
|
||||
}
|
||||
|
||||
// 批量删除接口用例
|
||||
export function batchDeleteCase(data: ApiCaseBatchParams) {
|
||||
return MSR.post({ url: BatchDeleteCaseUrl, data });
|
||||
}
|
||||
|
||||
// 批量编辑接口用例
|
||||
export function batchEditCase(data: ApiCaseBatchEditParams) {
|
||||
return MSR.post({ url: BatchEditCaseUrl, data });
|
||||
}
|
||||
|
||||
// 拖拽排序
|
||||
export function dragSort(data: DragSortParams) {
|
||||
return MSR.post({ url: SortCaseUrl, data });
|
||||
}
|
||||
|
|
|
@ -56,3 +56,12 @@ export const BatchRecoverApiUrl = '/api/definition/batch-recover'; // 回收站-
|
|||
export const BatchCleanOutApiUrl = '/api/definition/batch/delete'; // 回收站-接口定义-批量彻底删除
|
||||
export const GetTrashModuleTreeUrl = '/api/definition/module/trash/tree'; // 回收站查找模块
|
||||
export const GetTrashModuleCountUrl = '/api/definition/module/trash/count'; // 获取回收站模块统计数量
|
||||
|
||||
// --------------------用例
|
||||
export const CasePageUrl = '/api/case/page'; // 接口用例列表
|
||||
export const UpdateCaseStatusUrl = '/api/case/update-status'; // 接口用例更新状态
|
||||
export const UpdateCasePriorityUrl = '/api/case/update-priority'; // 接口用例更新等级
|
||||
export const DeleteCaseUrl = '/api/case/delete'; // 删除接口用例
|
||||
export const BatchDeleteCaseUrl = '/api/case/batch/delete'; // 批量删除接口用例
|
||||
export const BatchEditCaseUrl = '/api/case/batch/edit'; // 批量编辑接口用例
|
||||
export const SortCaseUrl = '/api/case/edit/pos'; // 接口用例拖拽
|
||||
|
|
|
@ -8,6 +8,7 @@ export enum TableModuleEnum {
|
|||
|
||||
export enum TableKeyEnum {
|
||||
API_TEST = 'apiTest',
|
||||
API_TEST_MANAGEMENT_CASE = 'apiTestMenagementCase',
|
||||
API_TEST_DEBUG_FORM_DATA = 'apiTestDebugFormData',
|
||||
API_TEST_DEBUG_FORM_URL_ENCODE = 'apiTestDebugFormUrlEncoded',
|
||||
API_TEST_REPORT = 'apiTestReport',
|
||||
|
|
|
@ -276,3 +276,58 @@ export interface BatchRecoverApiParams extends ApiDefinitionBatchParams {
|
|||
projectId: string;
|
||||
moduleIds?: string[];
|
||||
}
|
||||
|
||||
// --------------------用例
|
||||
// 用例列表查询参数
|
||||
export interface ApiCasePageParams extends TableQueryParams {
|
||||
protocol: string;
|
||||
projectId: string;
|
||||
versionId?: string;
|
||||
refId?: string;
|
||||
moduleIds?: string[];
|
||||
apiDefinitionId?: string;
|
||||
}
|
||||
// 用例列表
|
||||
export interface ApiCaseDetail {
|
||||
id: string;
|
||||
name: string;
|
||||
priority: string;
|
||||
num: number;
|
||||
status: string;
|
||||
lastReportStatus: string;
|
||||
lastReportId: string;
|
||||
projectId: string;
|
||||
apiDefinitionId: string;
|
||||
environmentId: string;
|
||||
environmentName: string;
|
||||
follow: boolean;
|
||||
method: string;
|
||||
path: string;
|
||||
tags: string[];
|
||||
passRate: string;
|
||||
modulePath: string;
|
||||
moduleId: string;
|
||||
createTime: number;
|
||||
createUser: string;
|
||||
createName: string;
|
||||
updateTime: number;
|
||||
updateUser: string;
|
||||
updateName: string;
|
||||
deleteTime: number;
|
||||
deleteUser: string;
|
||||
deleteName: string;
|
||||
}
|
||||
// 批量操作参数
|
||||
export interface ApiCaseBatchParams extends BatchApiParams {
|
||||
protocol: string;
|
||||
apiDefinitionId?: string[];
|
||||
versionId?: string;
|
||||
}
|
||||
// 用例批量编辑参数
|
||||
export interface ApiCaseBatchEditParams extends ApiCaseBatchParams {
|
||||
priority?: string;
|
||||
tags?: string[];
|
||||
status?: string;
|
||||
environmentId?: string;
|
||||
type: string;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,754 @@
|
|||
<template>
|
||||
<div class="p-[16px_22px]">
|
||||
<div class="mb-[16px] flex items-center gap-[8px]">
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('apiTestManagement.searchPlaceholder')"
|
||||
allow-clear
|
||||
class="mr-[8px] w-[240px]"
|
||||
@search="loadCaseList"
|
||||
@press-enter="loadCaseList"
|
||||
/>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary !p-[8px]" @click="loadCaseList">
|
||||
<template #icon>
|
||||
<icon-refresh class="text-[var(--color-text-4)]" />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
<ms-base-table
|
||||
v-bind="propsRes"
|
||||
:action-config="batchActions"
|
||||
:first-column-width="44"
|
||||
no-disable
|
||||
filter-icon-align-left
|
||||
v-on="propsEvent"
|
||||
@selected-change="handleTableSelect"
|
||||
@batch-action="handleTableBatch"
|
||||
@change="changeHandler"
|
||||
>
|
||||
<template #num="{ record }">
|
||||
<MsButton type="text">{{ record.num }}</MsButton>
|
||||
</template>
|
||||
<template #caseLevel="{ record }">
|
||||
<a-select
|
||||
v-model:model-value="record.priority"
|
||||
:placeholder="t('common.pleaseSelect')"
|
||||
class="param-input w-full"
|
||||
@change="() => handleCaseLevelChange(record)"
|
||||
>
|
||||
<template #label>
|
||||
<span class="text-[var(--color-text-2)]"> <caseLevel :case-level="record.priority" /></span>
|
||||
</template>
|
||||
<a-option v-for="item of caseLevelList" :key="item.value" :value="item.value">
|
||||
<caseLevel :case-level="item.text" />
|
||||
</a-option>
|
||||
</a-select>
|
||||
</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="flex items-center justify-center px-[6px] py-[2px]">
|
||||
<a-checkbox-group v-model:model-value="caseFilters" direction="vertical" size="small">
|
||||
<a-checkbox v-for="item of caseLevelList" :key="item.value" :value="item.value">
|
||||
<caseLevel :case-level="item.text" />
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-trigger>
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<a-select
|
||||
v-model:model-value="record.status"
|
||||
:placeholder="t('common.pleaseSelect')"
|
||||
class="param-input w-full"
|
||||
@change="() => handleStatusChange(record)"
|
||||
>
|
||||
<template #label>
|
||||
<apiStatus :status="record.status" />
|
||||
</template>
|
||||
<a-option v-for="item of Object.values(RequestDefinitionStatus)" :key="item" :value="item">
|
||||
<apiStatus :status="item" />
|
||||
</a-option>
|
||||
</a-select>
|
||||
</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="flex items-center justify-center px-[6px] py-[2px]">
|
||||
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
|
||||
<a-checkbox v-for="val of Object.values(RequestDefinitionStatus)" :key="val" :value="val">
|
||||
<apiStatus :status="val" />
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-trigger>
|
||||
</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="flex items-center justify-center 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">
|
||||
<span>{{ val }}</span>
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-trigger>
|
||||
</template>
|
||||
<template #passRateColumn>
|
||||
<div class="flex items-center text-[var(--color-text-3)]">
|
||||
{{ t('case.passRate') }}
|
||||
<a-tooltip :content="t('case.passRateTip')" position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<MsButton type="text" class="!mr-0">
|
||||
{{ t('apiTestManagement.execute') }}
|
||||
</MsButton>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
<MsButton type="text" class="!mr-0">
|
||||
{{ t('common.copy') }}
|
||||
</MsButton>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
<MsTableMoreAction :list="tableMoreActionList" @select="handleTableMoreActionSelect($event, record)" />
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</div>
|
||||
<a-modal
|
||||
v-model:visible="showBatchEditModal"
|
||||
title-align="start"
|
||||
class="ms-modal-upload ms-modal-medium"
|
||||
:width="480"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('common.edit') }}
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
{{
|
||||
t('case.batchModalSubTitle', {
|
||||
count: batchParams.currentSelectCount || tableSelected.length,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
<a-form ref="batchFormRef" class="rounded-[4px]" :model="batchForm" layout="vertical">
|
||||
<a-form-item
|
||||
field="attr"
|
||||
:label="t('apiTestManagement.chooseAttr')"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.attrRequired') }]"
|
||||
asterisk-position="end"
|
||||
>
|
||||
<a-select v-model="batchForm.attr" :placeholder="t('common.pleaseSelect')">
|
||||
<a-option v-for="item of attrOptions" :key="item.value" :value="item.value">
|
||||
{{ t(item.name) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="batchForm.attr === 'tags'"
|
||||
field="values"
|
||||
:label="t('apiTestManagement.batchUpdate')"
|
||||
:validate-trigger="['blur', 'input']"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
||||
asterisk-position="end"
|
||||
class="mb-0"
|
||||
required
|
||||
>
|
||||
<MsTagsInput
|
||||
v-model:model-value="batchForm.values"
|
||||
placeholder="common.tagsInputPlaceholder"
|
||||
allow-clear
|
||||
unique-value
|
||||
retain-input-value
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-else
|
||||
field="value"
|
||||
:label="t('apiTestManagement.batchUpdate')"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
||||
asterisk-position="end"
|
||||
class="mb-0"
|
||||
>
|
||||
<a-select v-model="batchForm.value" :placeholder="t('common.pleaseSelect')" :disabled="batchForm.attr === ''">
|
||||
<a-option v-for="item of valueOptions" :key="item.value" :value="item.value">
|
||||
{{ t(item.text) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button type="secondary" :disabled="batchEditLoading" @click="cancelBatchEdit">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="batchEditLoading" @click="handleBatchEditCase">
|
||||
{{ t('common.update') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormInstance, Message, TableChangeExtra, TableData } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||
|
||||
import {
|
||||
batchDeleteCase,
|
||||
batchEditCase,
|
||||
deleteCase,
|
||||
dragSort,
|
||||
getCasePage,
|
||||
updateCasePriority,
|
||||
updateCaseStatus,
|
||||
} from '@/api/modules/api-test/management';
|
||||
import { getCaseDefaultFields } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import useTableStore from '@/hooks/useTableStore';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { ApiCaseDetail } from '@/models/apiTest/management';
|
||||
import { DragSortParams } from '@/models/common';
|
||||
import { RequestDefinitionStatus } from '@/enums/apiEnum';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const props = defineProps<{
|
||||
activeModule: string;
|
||||
offspringIds: string[];
|
||||
protocol: string; // 查看的协议类型
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const tableStore = useTableStore();
|
||||
const { openModal } = useModal();
|
||||
|
||||
const keyword = ref('');
|
||||
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'num',
|
||||
slotName: 'num',
|
||||
sortIndex: 1,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
fixed: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'case.caseName',
|
||||
dataIndex: 'name',
|
||||
showTooltip: true,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: 'case.caseLevel',
|
||||
dataIndex: 'priority',
|
||||
slotName: 'caseLevel',
|
||||
titleSlotName: 'caseLevelFilter',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.apiStatus',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
titleSlotName: 'statusFilter',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.path',
|
||||
dataIndex: 'path',
|
||||
showTooltip: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'common.tag',
|
||||
dataIndex: 'tags',
|
||||
isTag: true,
|
||||
isStringTag: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'case.lastReportStatus',
|
||||
dataIndex: 'lastReportStatus',
|
||||
titleSlotName: 'lastReportStatusFilter',
|
||||
showInTable: false,
|
||||
showTooltip: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'case.passRate',
|
||||
dataIndex: 'passRate',
|
||||
titleSlotName: 'passRateColumn',
|
||||
showInTable: false,
|
||||
showTooltip: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'case.caseEnvironment',
|
||||
dataIndex: 'environmentName',
|
||||
showTooltip: true,
|
||||
showInTable: false,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'case.tableColumnUpdateUser',
|
||||
dataIndex: 'updateUser',
|
||||
showInTable: false,
|
||||
showTooltip: true,
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: 'case.tableColumnUpdateTime',
|
||||
dataIndex: 'updateTime',
|
||||
showInTable: false,
|
||||
showTooltip: true,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: 'case.tableColumnCreateUser',
|
||||
dataIndex: 'createName',
|
||||
showTooltip: true,
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: 'case.tableColumnCreateTime',
|
||||
dataIndex: 'createTime',
|
||||
showTooltip: true,
|
||||
showInTable: false,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: 'common.operation',
|
||||
slotName: 'action',
|
||||
dataIndex: 'operation',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
await tableStore.initColumn(TableKeyEnum.API_TEST_MANAGEMENT_CASE, columns, 'drawer');
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getCasePage, {
|
||||
columns,
|
||||
scroll: { x: '100%' },
|
||||
tableKey: TableKeyEnum.API_TEST_MANAGEMENT_CASE,
|
||||
showSetting: true,
|
||||
selectable: true,
|
||||
showSelectAll: true,
|
||||
draggable: { type: 'handle', width: 32 },
|
||||
});
|
||||
const batchActions = {
|
||||
baseAction: [
|
||||
{
|
||||
label: 'common.edit',
|
||||
eventTag: 'edit',
|
||||
permission: ['PROJECT_API_DEFINITION_CASE:READ+UPDATE'],
|
||||
},
|
||||
{
|
||||
label: 'common.delete',
|
||||
eventTag: 'delete',
|
||||
danger: true,
|
||||
permission: ['PROJECT_API_DEFINITION_CASE:READ+DELETE'],
|
||||
},
|
||||
],
|
||||
};
|
||||
const tableMoreActionList = [
|
||||
{
|
||||
eventTag: 'delete',
|
||||
label: t('common.delete'),
|
||||
danger: true,
|
||||
permission: ['PROJECT_API_DEFINITION_CASE:READ+DELETE'],
|
||||
},
|
||||
];
|
||||
|
||||
const statusFilterVisible = ref(false);
|
||||
const statusFilters = ref(Object.keys(RequestDefinitionStatus));
|
||||
const caseLevelFields = ref<Record<string, any>>({});
|
||||
const caseFilterVisible = ref(false);
|
||||
const caseFilters = ref<string[]>([]);
|
||||
const caseLevelList = computed(() => {
|
||||
return caseLevelFields.value?.options || [];
|
||||
});
|
||||
const lastReportStatusFilterVisible = ref(false);
|
||||
const lastReportStatusList = ['error', 'FakeError', 'success'];
|
||||
const lastReportStatusFilters = ref<string[]>([...lastReportStatusList]);
|
||||
|
||||
const moduleIds = computed(() => {
|
||||
return props.activeModule === 'all' ? [] : [props.activeModule];
|
||||
});
|
||||
function loadCaseList() {
|
||||
const params = {
|
||||
keyword: keyword.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleIds: moduleIds.value,
|
||||
protocol: props.protocol,
|
||||
filter: {
|
||||
status: statusFilters.value,
|
||||
priority: caseFilters.value,
|
||||
lastReportStatus: lastReportStatusFilters.value,
|
||||
},
|
||||
};
|
||||
setLoadListParams(params);
|
||||
loadList();
|
||||
}
|
||||
function loadCaseListAndResetSelector() {
|
||||
resetSelector();
|
||||
loadCaseList();
|
||||
}
|
||||
|
||||
// 获取用例等级数据
|
||||
async function getCaseLevelFields() {
|
||||
const result = await getCaseDefaultFields(appStore.currentProjectId);
|
||||
caseLevelFields.value = result.customFields.find((item: any) => item.internal && item.fieldName === '用例等级');
|
||||
caseFilters.value = caseLevelFields.value?.options.map((item: any) => item.value);
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
loadCaseList();
|
||||
getCaseLevelFields();
|
||||
});
|
||||
|
||||
function handleFilterHidden(val: boolean) {
|
||||
if (!val) {
|
||||
loadCaseList();
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.activeModule,
|
||||
() => {
|
||||
loadCaseListAndResetSelector();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.protocol,
|
||||
() => {
|
||||
loadCaseListAndResetSelector();
|
||||
}
|
||||
);
|
||||
|
||||
async function handleStatusChange(record: ApiCaseDetail) {
|
||||
try {
|
||||
await updateCaseStatus(record.id, record.status);
|
||||
Message.success(t('common.updateSuccess'));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCaseLevelChange(record: ApiCaseDetail) {
|
||||
try {
|
||||
await updateCasePriority(record.id, record.priority);
|
||||
Message.success(t('common.updateSuccess'));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽排序
|
||||
async function changeHandler(data: TableData[], extra: TableChangeExtra, currentData: TableData[]) {
|
||||
if (!currentData || currentData.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (extra && extra.dragTarget?.id) {
|
||||
const params: DragSortParams = {
|
||||
projectId: appStore.currentProjectId,
|
||||
targetId: '', // 放置目标id
|
||||
moveMode: 'BEFORE',
|
||||
moveId: extra.dragTarget.id as string, // 拖拽id
|
||||
};
|
||||
const index = currentData.findIndex((item: any) => item.key === extra.dragTarget?.id);
|
||||
|
||||
if (index > -1 && currentData[index + 1]) {
|
||||
params.moveMode = 'BEFORE';
|
||||
params.targetId = currentData[index + 1].raw.id;
|
||||
} else if (index > -1 && !currentData[index + 1]) {
|
||||
if (index > -1 && currentData[index - 1]) {
|
||||
params.moveMode = 'AFTER';
|
||||
params.targetId = currentData[index - 1].raw.id;
|
||||
}
|
||||
}
|
||||
try {
|
||||
await dragSort(params);
|
||||
Message.success(t('caseManagement.featureCase.sortSuccess'));
|
||||
loadCaseList();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tableSelected = ref<(string | number)[]>([]); // 表格选中的
|
||||
const batchParams = ref<BatchActionQueryParams>({
|
||||
selectedIds: [],
|
||||
selectAll: false,
|
||||
excludeIds: [],
|
||||
currentSelectCount: 0,
|
||||
});
|
||||
const batchConditionParams = computed(() => {
|
||||
return {
|
||||
condition: {
|
||||
keyword: keyword.value,
|
||||
filter: {
|
||||
status: statusFilters.value,
|
||||
priority: caseFilters.value,
|
||||
lastReportStatus: lastReportStatusFilters.value,
|
||||
},
|
||||
},
|
||||
projectId: appStore.currentProjectId,
|
||||
protocol: props.protocol,
|
||||
moduleIds: moduleIds.value,
|
||||
};
|
||||
});
|
||||
|
||||
function handleDeleteCase(record?: ApiCaseDetail, isBatch?: boolean) {
|
||||
const title = isBatch
|
||||
? t('case.batchDeleteCaseTip', {
|
||||
count: batchParams.value.currentSelectCount || tableSelected.value.length,
|
||||
})
|
||||
: t('apiTestManagement.deleteApiTipTitle', { name: record?.name });
|
||||
|
||||
openModal({
|
||||
type: 'error',
|
||||
title,
|
||||
content: t('case.deleteCaseTip'),
|
||||
okText: t('common.confirmDelete'),
|
||||
cancelText: t('common.cancel'),
|
||||
okButtonProps: {
|
||||
status: 'danger',
|
||||
},
|
||||
maskClosable: false,
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
if (isBatch) {
|
||||
await batchDeleteCase({
|
||||
selectIds: tableSelected.value as string[],
|
||||
selectAll: batchParams.value.selectAll,
|
||||
excludeIds: batchParams.value?.excludeIds || [],
|
||||
...batchConditionParams.value,
|
||||
});
|
||||
} else {
|
||||
await deleteCase(record?.id as string);
|
||||
}
|
||||
Message.success(t('common.deleteSuccess'));
|
||||
loadCaseListAndResetSelector();
|
||||
if (typeof refreshModuleTree === 'function') {
|
||||
refreshModuleTree();
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
}
|
||||
|
||||
// 处理表格更多按钮事件
|
||||
function handleTableMoreActionSelect(item: ActionsItem, record: ApiCaseDetail) {
|
||||
switch (item.eventTag) {
|
||||
case 'delete':
|
||||
handleDeleteCase(record);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function handleTableSelect(arr: (string | number)[]) {
|
||||
tableSelected.value = arr;
|
||||
}
|
||||
|
||||
// 用例编辑
|
||||
const showBatchEditModal = ref(false);
|
||||
const batchEditLoading = ref(false);
|
||||
const batchFormRef = ref<FormInstance>();
|
||||
const batchForm = ref({
|
||||
attr: '',
|
||||
value: '',
|
||||
values: [],
|
||||
});
|
||||
const attrOptions = [
|
||||
{
|
||||
name: 'case.caseLevel',
|
||||
value: 'priority',
|
||||
},
|
||||
{
|
||||
name: 'apiTestManagement.apiStatus',
|
||||
value: 'status',
|
||||
},
|
||||
{
|
||||
name: 'common.tag',
|
||||
value: 'tags',
|
||||
},
|
||||
];
|
||||
const valueOptions = computed(() => {
|
||||
batchForm.value.value = '';
|
||||
switch (batchForm.value.attr) {
|
||||
case 'priority':
|
||||
return caseLevelList.value;
|
||||
case 'status':
|
||||
return [
|
||||
{
|
||||
text: 'apiTestManagement.processing',
|
||||
value: RequestDefinitionStatus.PROCESSING,
|
||||
},
|
||||
{
|
||||
text: 'apiTestManagement.done',
|
||||
value: RequestDefinitionStatus.DONE,
|
||||
},
|
||||
{
|
||||
text: 'apiTestManagement.deprecate',
|
||||
value: RequestDefinitionStatus.DEPRECATED,
|
||||
},
|
||||
{
|
||||
text: 'apiTestManagement.debugging',
|
||||
value: RequestDefinitionStatus.DEBUGGING,
|
||||
},
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
});
|
||||
function cancelBatchEdit() {
|
||||
showBatchEditModal.value = false;
|
||||
batchFormRef.value?.resetFields();
|
||||
batchForm.value = {
|
||||
attr: '',
|
||||
value: '',
|
||||
values: [],
|
||||
};
|
||||
}
|
||||
function handleBatchEditCase() {
|
||||
batchFormRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
batchEditLoading.value = true;
|
||||
await batchEditCase({
|
||||
selectIds: batchParams.value?.selectedIds || [],
|
||||
selectAll: !!batchParams.value?.selectAll,
|
||||
excludeIds: batchParams.value?.excludeIds || [],
|
||||
...batchConditionParams.value,
|
||||
type: batchForm.value.attr.charAt(0).toUpperCase() + batchForm.value.attr.slice(1), // 首字母大写
|
||||
[batchForm.value.attr]: batchForm.value.attr === 'tags' ? batchForm.value.values : batchForm.value.value,
|
||||
});
|
||||
Message.success(t('common.updateSuccess'));
|
||||
cancelBatchEdit();
|
||||
loadCaseListAndResetSelector();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
batchEditLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 处理表格选中后批量操作
|
||||
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
|
||||
tableSelected.value = params?.selectedIds || [];
|
||||
batchParams.value = params;
|
||||
switch (event.eventTag) {
|
||||
case 'delete':
|
||||
handleDeleteCase(undefined, true);
|
||||
break;
|
||||
case 'edit':
|
||||
showBatchEditModal.value = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
|
||||
&:not(:hover) {
|
||||
border-color: transparent !important;
|
||||
.arco-input::placeholder {
|
||||
@apply invisible;
|
||||
}
|
||||
.arco-select-view-icon {
|
||||
@apply invisible;
|
||||
}
|
||||
.arco-select-view-value {
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="border-b border-[var(--color-text-n8)] px-[22px] pb-[16px]">
|
||||
<MsEditableTab v-model:active-tab="activeCaseTab" v-model:tabs="caseTabs">
|
||||
<template #label="{ tab }">
|
||||
<apiMethodName
|
||||
v-if="tab.id !== 'all'"
|
||||
:method="tab.protocol === 'HTTP' ? tab.method : tab.protocol"
|
||||
class="mr-[4px]"
|
||||
/>
|
||||
<a-tooltip :content="tab.name || tab.label" :mouse-enter-delay="500">
|
||||
<div class="one-line-text max-w-[144px]">
|
||||
{{ tab.name || tab.label }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</MsEditableTab>
|
||||
</div>
|
||||
<div v-show="activeCaseTab.id === 'all'" class="flex-1">
|
||||
<caseTable :active-module="props.activeModule" :offspring-ids="props.offspringIds" :protocol="props.protocol" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
||||
import caseTable from './caseTable.vue';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
activeModule: string;
|
||||
offspringIds: string[];
|
||||
protocol: string;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const caseTabs = ref<RequestParam[]>([
|
||||
{
|
||||
id: 'all',
|
||||
label: t('case.allCase'),
|
||||
closable: false,
|
||||
} as RequestParam,
|
||||
]);
|
||||
const activeCaseTab = ref<RequestParam>(caseTabs.value[0] as RequestParam);
|
||||
</script>
|
|
@ -9,7 +9,9 @@
|
|||
:protocol="protocol"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="case" title="CASE" class="ms-api-tab-pane"> </a-tab-pane>
|
||||
<a-tab-pane key="case" title="CASE" class="ms-api-tab-pane">
|
||||
<apiCase :active-module="props.activeModule" :offspring-ids="props.offspringIds" :protocol="protocol" />
|
||||
</a-tab-pane>
|
||||
<!-- <a-tab-pane key="mock" title="MOCK" class="ms-api-tab-pane">
|
||||
<mock-table
|
||||
ref="mockRef"
|
||||
|
@ -47,6 +49,7 @@
|
|||
|
||||
import MsSelect from '@/components/business/ms-select';
|
||||
import api from './api/index.vue';
|
||||
import apiCase from './case/index.vue';
|
||||
|
||||
// import MockTable from '@/views/api-test/management/components/management/mock/mockTable.vue';
|
||||
import { getEnvironment, getEnvList } from '@/api/modules/api-test/common';
|
||||
|
|
|
@ -153,4 +153,19 @@ export default {
|
|||
'apiTestManagement.belongOrg': 'Organization',
|
||||
'apiTestManagement.belongProject': 'Project',
|
||||
'apiTestManagement.quoteSearchPlaceholder': 'Enter ID or name to search',
|
||||
'case.allCase': 'All Case',
|
||||
'case.caseName': 'Case Name',
|
||||
'case.caseLevel': 'Case Level',
|
||||
'case.caseEnvironment': 'Case Environment',
|
||||
'case.tableColumnCreateUser': 'CreateUser',
|
||||
'case.tableColumnCreateTime': 'CreateTime',
|
||||
'case.tableColumnUpdateUser': 'UpdateUser',
|
||||
'case.tableColumnUpdateTime': 'UpdateTime',
|
||||
'case.lastReportStatus': 'Results Of Execution',
|
||||
'case.passRate': 'Pass Rate',
|
||||
'case.passRateTip': 'Number of success case executions/Total number of case executions *%',
|
||||
'case.batchModalSubTitle': '({count} cases selected)',
|
||||
'case.batchDeleteCaseTip': 'Are you sure you want to delete {count} selected cases?',
|
||||
'case.deleteCaseTip':
|
||||
'Deleting an case will result in the execution failure of the test task that references the use case. Please be cautious!',
|
||||
};
|
||||
|
|
|
@ -147,4 +147,18 @@ export default {
|
|||
'apiTestManagement.quoteSearchPlaceholder': '输入 ID 或名称搜索',
|
||||
'apiTestManagement.click': '点击',
|
||||
'apiTestManagement.getResponse': '获取响应内容',
|
||||
'case.allCase': '全部CASE',
|
||||
'case.caseName': '用例名称',
|
||||
'case.caseLevel': '用例等级',
|
||||
'case.caseEnvironment': '用例环境',
|
||||
'case.tableColumnCreateUser': '创建人',
|
||||
'case.tableColumnCreateTime': '创建时间',
|
||||
'case.tableColumnUpdateUser': '更新人',
|
||||
'case.tableColumnUpdateTime': '更新时间',
|
||||
'case.lastReportStatus': '执行结果',
|
||||
'case.passRate': '用例通过率',
|
||||
'case.passRateTip': '用例执行success数/用例执行总数*%',
|
||||
'case.batchModalSubTitle': '(已选 {count} 个用例)',
|
||||
'case.batchDeleteCaseTip': '确认删除已选中的 {count} 个用例吗?',
|
||||
'case.deleteCaseTip': '删除用例会导致引用了该用例的测试任务执行失败,请谨慎操作!',
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue