feat(接口管理): 创建用例&用例详情
This commit is contained in:
parent
7e6edf8352
commit
1f1b43f108
|
@ -16,6 +16,7 @@ import {
|
||||||
BatchUpdateDefinitionUrl,
|
BatchUpdateDefinitionUrl,
|
||||||
CasePageUrl,
|
CasePageUrl,
|
||||||
CheckDefinitionScheduleUrl,
|
CheckDefinitionScheduleUrl,
|
||||||
|
DebugCaseUrl,
|
||||||
DebugDefinitionUrl,
|
DebugDefinitionUrl,
|
||||||
DefinitionMockPageUrl,
|
DefinitionMockPageUrl,
|
||||||
DefinitionPageUrl,
|
DefinitionPageUrl,
|
||||||
|
@ -28,6 +29,7 @@ import {
|
||||||
DeleteRecycleApiUrl,
|
DeleteRecycleApiUrl,
|
||||||
DeleteRecycleCaseUrl,
|
DeleteRecycleCaseUrl,
|
||||||
ExecuteCaseUrl,
|
ExecuteCaseUrl,
|
||||||
|
GetCaseDetailUrl,
|
||||||
GetChangeHistoryUrl,
|
GetChangeHistoryUrl,
|
||||||
GetDefinitionDetailUrl,
|
GetDefinitionDetailUrl,
|
||||||
GetDefinitionScheduleUrl,
|
GetDefinitionScheduleUrl,
|
||||||
|
@ -52,14 +54,18 @@ import {
|
||||||
SortDefinitionUrl,
|
SortDefinitionUrl,
|
||||||
SwitchDefinitionScheduleUrl,
|
SwitchDefinitionScheduleUrl,
|
||||||
ToggleFollowDefinitionUrl,
|
ToggleFollowDefinitionUrl,
|
||||||
|
TransferFileCaseUrl,
|
||||||
|
TransferFileModuleOptionCaseUrl,
|
||||||
TransferFileModuleOptionUrl,
|
TransferFileModuleOptionUrl,
|
||||||
TransferFileUrl,
|
TransferFileUrl,
|
||||||
UpdateCasePriorityUrl,
|
UpdateCasePriorityUrl,
|
||||||
UpdateCaseStatusUrl,
|
UpdateCaseStatusUrl,
|
||||||
|
UpdateCaseUrl,
|
||||||
UpdateDefinitionScheduleUrl,
|
UpdateDefinitionScheduleUrl,
|
||||||
UpdateDefinitionUrl,
|
UpdateDefinitionUrl,
|
||||||
UpdateMockStatusUrl,
|
UpdateMockStatusUrl,
|
||||||
UpdateModuleUrl,
|
UpdateModuleUrl,
|
||||||
|
UploadTempFileCaseUrl,
|
||||||
UploadTempFileUrl,
|
UploadTempFileUrl,
|
||||||
} from '@/api/requrls/api-test/management';
|
} from '@/api/requrls/api-test/management';
|
||||||
|
|
||||||
|
@ -68,8 +74,11 @@ import {
|
||||||
AddApiCaseParams,
|
AddApiCaseParams,
|
||||||
ApiCaseBatchEditParams,
|
ApiCaseBatchEditParams,
|
||||||
ApiCaseBatchExecuteParams,
|
ApiCaseBatchExecuteParams,
|
||||||
ApiCaseBatchParams, ApiCaseChangeHistoryParams, ApiCaseDependencyParams,
|
ApiCaseBatchParams,
|
||||||
ApiCaseDetail, ApiCaseExecuteHistoryParams,
|
ApiCaseChangeHistoryParams,
|
||||||
|
ApiCaseDependencyParams,
|
||||||
|
ApiCaseDetail,
|
||||||
|
ApiCaseExecuteHistoryParams,
|
||||||
ApiCasePageParams,
|
ApiCasePageParams,
|
||||||
ApiDefinitionBatchDeleteParams,
|
ApiDefinitionBatchDeleteParams,
|
||||||
ApiDefinitionBatchMoveParams,
|
ApiDefinitionBatchMoveParams,
|
||||||
|
@ -102,7 +111,8 @@ import {
|
||||||
CommonList,
|
CommonList,
|
||||||
DragSortParams,
|
DragSortParams,
|
||||||
ModuleTreeNode,
|
ModuleTreeNode,
|
||||||
MoveModules, TableQueryParams,
|
MoveModules,
|
||||||
|
TableQueryParams,
|
||||||
TransferFileParams,
|
TransferFileParams,
|
||||||
} from '@/models/common';
|
} from '@/models/common';
|
||||||
|
|
||||||
|
@ -358,6 +368,36 @@ export function dragSort(data: DragSortParams) {
|
||||||
return MSR.post({ url: SortCaseUrl, data });
|
return MSR.post({ url: SortCaseUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新接口用例
|
||||||
|
export function updateCase(data: AddApiCaseParams) {
|
||||||
|
return MSR.post({ url: UpdateCaseUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接口用例调试
|
||||||
|
export function debugCase(data: ExecuteRequestParams) {
|
||||||
|
return MSR.post({ url: DebugCaseUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件转存
|
||||||
|
export function transferFileCase(data: TransferFileParams) {
|
||||||
|
return MSR.post({ url: TransferFileCaseUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件转存目录
|
||||||
|
export function getTransferOptionsCase(projectId: string) {
|
||||||
|
return MSR.get<ModuleTreeNode[]>({ url: TransferFileModuleOptionCaseUrl, params: projectId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传文件
|
||||||
|
export function uploadTempFileCase(file: File) {
|
||||||
|
return MSR.uploadFile({ url: UploadTempFileCaseUrl }, { fileList: [file] }, 'file');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取接口用例详情
|
||||||
|
export function getCaseDetail(id: string) {
|
||||||
|
return MSR.get<ApiCaseDetail>({ url: GetCaseDetailUrl, params: id });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接口用例回收站
|
* 接口用例回收站
|
||||||
*/
|
*/
|
||||||
|
@ -419,4 +459,4 @@ export function getApiCaseChangeHistory(data: ApiCaseChangeHistoryParams) {
|
||||||
// 获取接口用例-依赖关系
|
// 获取接口用例-依赖关系
|
||||||
export function getApiCaseDependency(data: ApiCaseDependencyParams) {
|
export function getApiCaseDependency(data: ApiCaseDependencyParams) {
|
||||||
return MSR.post({ url: GetDependencyUrl, data });
|
return MSR.post({ url: GetDependencyUrl, data });
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,12 +55,18 @@ export const GetTrashModuleCountUrl = '/api/definition/module/trash/count'; //
|
||||||
|
|
||||||
// --------------------用例
|
// --------------------用例
|
||||||
export const CasePageUrl = '/api/case/page'; // 接口用例列表
|
export const CasePageUrl = '/api/case/page'; // 接口用例列表
|
||||||
|
export const UpdateCaseUrl = '/api/case/update'; // 接口用例更新
|
||||||
export const UpdateCaseStatusUrl = '/api/case/update-status'; // 接口用例更新状态
|
export const UpdateCaseStatusUrl = '/api/case/update-status'; // 接口用例更新状态
|
||||||
export const UpdateCasePriorityUrl = '/api/case/update-priority'; // 接口用例更新等级
|
export const UpdateCasePriorityUrl = '/api/case/update-priority'; // 接口用例更新等级
|
||||||
export const DeleteCaseUrl = '/api/case/delete-to-gc'; // 删除接口用例
|
export const DeleteCaseUrl = '/api/case/delete-to-gc'; // 删除接口用例
|
||||||
export const BatchDeleteCaseUrl = '/api/case/batch/delete-to-gc'; // 批量删除接口用例
|
export const BatchDeleteCaseUrl = '/api/case/batch/delete-to-gc'; // 批量删除接口用例
|
||||||
export const BatchEditCaseUrl = '/api/case/batch/edit'; // 批量编辑接口用例
|
export const BatchEditCaseUrl = '/api/case/batch/edit'; // 批量编辑接口用例
|
||||||
export const SortCaseUrl = '/api/case/edit/pos'; // 接口用例拖拽
|
export const SortCaseUrl = '/api/case/edit/pos'; // 接口用例拖拽
|
||||||
|
export const DebugCaseUrl = '/api/case/debug'; // 接口用例调试
|
||||||
|
export const TransferFileCaseUrl = '/api/case/transfer'; // 文件转存
|
||||||
|
export const TransferFileModuleOptionCaseUrl = '/api/case/transfer/options'; // 文件转存目录
|
||||||
|
export const UploadTempFileCaseUrl = '/api/case/upload/temp/file'; // 临时文件上传
|
||||||
|
export const GetCaseDetailUrl = '/api/case/get-detail'; // 获取接口用例详情
|
||||||
export const GetEnvListUrl = '/api/test/env-list'; // 接口测试-环境列表
|
export const GetEnvListUrl = '/api/test/env-list'; // 接口测试-环境列表
|
||||||
export const BatchExecuteCaseUrl = '/api/case/batch/run'; // 批量执行接口用例
|
export const BatchExecuteCaseUrl = '/api/case/batch/run'; // 批量执行接口用例
|
||||||
export const ExecuteCaseUrl = '/api/case/run/'; // 单独执行接口用例
|
export const ExecuteCaseUrl = '/api/case/run/'; // 单独执行接口用例
|
||||||
|
@ -68,8 +74,6 @@ export const GetExecuteHistoryUrl = 'api/case/execute/page'; // 获取用的执
|
||||||
export const GetDependencyUrl = '/api/case/get-reference'; // 获取用例的依赖关系
|
export const GetDependencyUrl = '/api/case/get-reference'; // 获取用例的依赖关系
|
||||||
export const GetChangeHistoryUrl = '/api/case/operation-history/page'; // 获取用例的依赖关系
|
export const GetChangeHistoryUrl = '/api/case/operation-history/page'; // 获取用例的依赖关系
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接口用例回收站
|
* 接口用例回收站
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="ms-detail-card">
|
<div class="ms-detail-card">
|
||||||
<div class="flex items-center justify-between">
|
<div class="ms-detail-card-title flex items-center justify-between">
|
||||||
<div class="flex items-center gap-[4px]">
|
<div class="flex items-center gap-[4px]">
|
||||||
<a-tooltip :content="t(props.title)">
|
<a-tooltip :content="t(props.title)">
|
||||||
<div class="one-line-text flex-1 font-medium text-[var(--color-text-1)]">
|
<div class="one-line-text flex-1 font-medium text-[var(--color-text-1)]">
|
||||||
|
|
|
@ -294,8 +294,8 @@ export interface ApiCasePageParams extends TableQueryParams {
|
||||||
moduleIds?: string[];
|
moduleIds?: string[];
|
||||||
apiDefinitionId?: string;
|
apiDefinitionId?: string;
|
||||||
}
|
}
|
||||||
// 用例列表
|
// 用例列表和用例详情
|
||||||
export interface ApiCaseDetail {
|
export interface ApiCaseDetail extends ExecuteRequestParams {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
priority: string;
|
priority: string;
|
||||||
|
@ -327,7 +327,7 @@ export interface ApiCaseDetail {
|
||||||
// 批量操作参数
|
// 批量操作参数
|
||||||
export interface ApiCaseBatchParams extends BatchApiParams {
|
export interface ApiCaseBatchParams extends BatchApiParams {
|
||||||
protocol: string;
|
protocol: string;
|
||||||
apiDefinitionId?: string[];
|
apiDefinitionId?: string;
|
||||||
versionId?: string;
|
versionId?: string;
|
||||||
}
|
}
|
||||||
// 用例批量编辑参数
|
// 用例批量编辑参数
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</template>
|
</template>
|
||||||
</a-empty>
|
</a-empty>
|
||||||
<div v-show="!pluginError || isHttpProtocol" class="flex h-full flex-col">
|
<div v-show="!pluginError || isHttpProtocol" class="flex h-full flex-col">
|
||||||
<div class="px-[18px] pt-[8px]">
|
<div v-if="!props.isCase" class="px-[18px] pt-[8px]">
|
||||||
<div class="flex flex-wrap items-center justify-between gap-[12px]">
|
<div class="flex flex-wrap items-center justify-between gap-[12px]">
|
||||||
<div class="flex flex-1 items-center gap-[16px]">
|
<div class="flex flex-1 items-center gap-[16px]">
|
||||||
<a-select
|
<a-select
|
||||||
|
@ -68,6 +68,7 @@
|
||||||
<template
|
<template
|
||||||
v-if="
|
v-if="
|
||||||
(!props.isDefinition || (props.isDefinition && requestVModel.mode === 'debug')) &&
|
(!props.isDefinition || (props.isDefinition && requestVModel.mode === 'debug')) &&
|
||||||
|
props.permissionMap &&
|
||||||
hasAnyPermission([props.permissionMap.execute])
|
hasAnyPermission([props.permissionMap.execute])
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
@ -95,8 +96,8 @@
|
||||||
v-if="
|
v-if="
|
||||||
props.isDefinition &&
|
props.isDefinition &&
|
||||||
(requestVModel.isNew
|
(requestVModel.isNew
|
||||||
? hasAnyPermission([props.permissionMap.create])
|
? props.permissionMap && hasAnyPermission([props.permissionMap.create])
|
||||||
: hasAnyPermission([props.permissionMap.update]))
|
: props.permissionMap && hasAnyPermission([props.permissionMap.update]))
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<!-- 接口定义-调试模式,可保存或保存为新用例 -->
|
<!-- 接口定义-调试模式,可保存或保存为新用例 -->
|
||||||
|
@ -122,8 +123,8 @@
|
||||||
<a-button
|
<a-button
|
||||||
v-else-if="
|
v-else-if="
|
||||||
requestVModel.isNew
|
requestVModel.isNew
|
||||||
? hasAnyPermission([props.permissionMap.create])
|
? props.permissionMap && hasAnyPermission([props.permissionMap.create])
|
||||||
: hasAnyPermission([props.permissionMap.update])
|
: props.permissionMap && hasAnyPermission([props.permissionMap.update])
|
||||||
"
|
"
|
||||||
type="secondary"
|
type="secondary"
|
||||||
:disabled="isHttpProtocol && !requestVModel.url"
|
:disabled="isHttpProtocol && !requestVModel.url"
|
||||||
|
@ -138,7 +139,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-[16px]">
|
<div class="request-params-tab px-[16px]">
|
||||||
<MsTab
|
<MsTab
|
||||||
v-model:active-key="requestVModel.activeTab"
|
v-model:active-key="requestVModel.activeTab"
|
||||||
:content-tab-list="contentTabList"
|
:content-tab-list="contentTabList"
|
||||||
|
@ -146,15 +147,15 @@
|
||||||
class="no-content relative mt-[8px] border-b"
|
class="no-content relative mt-[8px] border-b"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div ref="splitContainerRef" class="h-[calc(100%-87px)]">
|
<div ref="splitContainerRef" class="request-and-response h-[calc(100%-87px)]">
|
||||||
<MsSplitBox
|
<MsSplitBox
|
||||||
ref="horizontalSplitBoxRef"
|
ref="horizontalSplitBoxRef"
|
||||||
:size="props.isDefinition ? 0.7 : 1"
|
:size="!props.isCase && props.isDefinition ? 0.7 : 1"
|
||||||
:max="props.isDefinition ? 0.9 : 1"
|
:max="props.isDefinition && !props.isCase ? 0.9 : 1"
|
||||||
:min="props.isDefinition ? 0.7 : 1"
|
:min="props.isDefinition && !props.isCase ? 0.7 : 1"
|
||||||
:disabled="!props.isDefinition"
|
:disabled="props.isCase && !props.isDefinition"
|
||||||
:class="!props.isDefinition ? 'hidden-second' : ''"
|
:class="props.isCase && !props.isDefinition ? 'hidden-second' : ''"
|
||||||
:first-container-class="!props.isDefinition ? 'border-r-0' : ''"
|
:first-container-class="props.isCase && !props.isDefinition ? 'border-r-0' : ''"
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
expand-direction="right"
|
expand-direction="right"
|
||||||
>
|
>
|
||||||
|
@ -286,7 +287,7 @@
|
||||||
</template>
|
</template>
|
||||||
</MsSplitBox>
|
</MsSplitBox>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="props.isDefinition" #second>
|
<template v-if="!props.isCase && props.isDefinition" #second>
|
||||||
<div class="p-[16px]">
|
<div class="p-[16px]">
|
||||||
<!-- TODO:第一版没有模板 -->
|
<!-- TODO:第一版没有模板 -->
|
||||||
<!-- <MsFormCreate v-model:api="fApi" :rule="currentApiTemplateRules" :option="options" /> -->
|
<!-- <MsFormCreate v-model:api="fApi" :rule="currentApiTemplateRules" :option="options" /> -->
|
||||||
|
@ -308,7 +309,7 @@
|
||||||
<a-form-item :label="t('apiTestManagement.belongModule')" class="mb-[16px]">
|
<a-form-item :label="t('apiTestManagement.belongModule')" class="mb-[16px]">
|
||||||
<a-tree-select
|
<a-tree-select
|
||||||
v-model:modelValue="requestVModel.moduleId"
|
v-model:modelValue="requestVModel.moduleId"
|
||||||
:data="selectTree"
|
:data="selectTree as ModuleTreeNode[]"
|
||||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||||
:tree-props="{
|
:tree-props="{
|
||||||
virtualListProps: {
|
virtualListProps: {
|
||||||
|
@ -395,6 +396,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a-modal
|
<a-modal
|
||||||
|
v-if="!isCase"
|
||||||
v-model:visible="saveModalVisible"
|
v-model:visible="saveModalVisible"
|
||||||
:title="t('common.save')"
|
:title="t('common.save')"
|
||||||
:ok-loading="saveLoading"
|
:ok-loading="saveLoading"
|
||||||
|
@ -433,7 +435,7 @@
|
||||||
<a-form-item :label="t('apiTestDebug.requestModule')" class="mb-0">
|
<a-form-item :label="t('apiTestDebug.requestModule')" class="mb-0">
|
||||||
<a-tree-select
|
<a-tree-select
|
||||||
v-model:modelValue="saveModalForm.moduleId"
|
v-model:modelValue="saveModalForm.moduleId"
|
||||||
:data="selectTree"
|
:data="selectTree as ModuleTreeNode[]"
|
||||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||||
:tree-props="{
|
:tree-props="{
|
||||||
virtualListProps: {
|
virtualListProps: {
|
||||||
|
@ -447,6 +449,7 @@
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<a-modal
|
<a-modal
|
||||||
|
v-if="!isCase"
|
||||||
v-model:visible="saveCaseModalVisible"
|
v-model:visible="saveCaseModalVisible"
|
||||||
:title="t('common.save')"
|
:title="t('common.save')"
|
||||||
:ok-loading="saveCaseLoading"
|
:ok-loading="saveCaseLoading"
|
||||||
|
@ -563,7 +566,7 @@
|
||||||
isNew: boolean;
|
isNew: boolean;
|
||||||
protocol: string;
|
protocol: string;
|
||||||
activeTab: RequestComposition;
|
activeTab: RequestComposition;
|
||||||
mode?: 'definition' | 'debug';
|
mode?: 'definition' | 'debug' | 'case';
|
||||||
executeLoading: boolean; // 执行中loading
|
executeLoading: boolean; // 执行中loading
|
||||||
isCopy?: boolean; // 是否是复制
|
isCopy?: boolean; // 是否是复制
|
||||||
isExecute?: boolean; // 是否是执行
|
isExecute?: boolean; // 是否是执行
|
||||||
|
@ -576,21 +579,22 @@
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
request: RequestParam; // 请求参数集合
|
request: RequestParam; // 请求参数集合
|
||||||
moduleTree: ModuleTreeNode[]; // 模块树
|
moduleTree?: ModuleTreeNode[]; // 模块树
|
||||||
|
isCase?: boolean; // 是否是用例引用的组件
|
||||||
detailLoading?: boolean; // 详情加载状态
|
detailLoading?: boolean; // 详情加载状态
|
||||||
isDefinition?: boolean; // 是否是接口定义模式
|
isDefinition?: boolean; // 是否是接口定义模式
|
||||||
hideResponseLayoutSwitch?: boolean; // 是否隐藏响应体的布局切换
|
hideResponseLayoutSwitch?: boolean; // 是否隐藏响应体的布局切换
|
||||||
otherParams?: Record<string, any>; // 保存请求时的其他参数
|
otherParams?: Record<string, any>; // 保存请求时的其他参数
|
||||||
currentEnvConfig?: EnvConfig;
|
currentEnvConfig?: EnvConfig;
|
||||||
executeApi: (params: ExecuteRequestParams) => Promise<any>; // 执行接口
|
executeApi?: (params: ExecuteRequestParams) => Promise<any>; // 执行接口
|
||||||
localExecuteApi: (url: string, params: ExecuteRequestParams) => Promise<any>; // 本地执行接口
|
localExecuteApi?: (url: string, params: ExecuteRequestParams) => Promise<any>; // 本地执行接口
|
||||||
createApi: (...args) => Promise<any>; // 创建接口
|
createApi?: (...args) => Promise<any>; // 创建接口
|
||||||
updateApi: (...args) => Promise<any>; // 更新接口
|
updateApi?: (...args) => Promise<any>; // 更新接口
|
||||||
uploadTempFileApi?: (...args) => Promise<any>; // 上传临时文件接口
|
uploadTempFileApi?: (...args) => Promise<any>; // 上传临时文件接口
|
||||||
fileSaveAsSourceId?: string | number; // 文件转存关联的资源id
|
fileSaveAsSourceId?: string | number; // 文件转存关联的资源id
|
||||||
fileSaveAsApi?: (params: TransferFileParams) => Promise<string>; // 文件转存接口
|
fileSaveAsApi?: (params: TransferFileParams) => Promise<string>; // 文件转存接口
|
||||||
fileModuleOptionsApi?: (projectId: string) => Promise<ModuleTreeNode[]>; // 文件转存目录下拉框接口
|
fileModuleOptionsApi?: (projectId: string) => Promise<ModuleTreeNode[]>; // 文件转存目录下拉框接口
|
||||||
permissionMap: {
|
permissionMap?: {
|
||||||
execute: string;
|
execute: string;
|
||||||
create: string;
|
create: string;
|
||||||
update: string;
|
update: string;
|
||||||
|
@ -1145,10 +1149,11 @@
|
||||||
async function execute(executeType?: 'localExec' | 'serverExec') {
|
async function execute(executeType?: 'localExec' | 'serverExec') {
|
||||||
if (isHttpProtocol.value) {
|
if (isHttpProtocol.value) {
|
||||||
try {
|
try {
|
||||||
|
if (!props.executeApi) return;
|
||||||
requestVModel.value.executeLoading = true;
|
requestVModel.value.executeLoading = true;
|
||||||
requestVModel.value.response = cloneDeep(defaultResponse);
|
requestVModel.value.response = cloneDeep(defaultResponse);
|
||||||
const res = await props.executeApi(makeRequestParams(executeType));
|
const res = await props.executeApi(makeRequestParams(executeType));
|
||||||
if (executeType === 'localExec') {
|
if (executeType === 'localExec' && props.localExecuteApi) {
|
||||||
await props.localExecuteApi(localExecuteUrl.value, res);
|
await props.localExecuteApi(localExecuteUrl.value, res);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1161,10 +1166,11 @@
|
||||||
fApi.value?.validate(async (valid) => {
|
fApi.value?.validate(async (valid) => {
|
||||||
if (valid === true) {
|
if (valid === true) {
|
||||||
try {
|
try {
|
||||||
|
if (!props.executeApi) return;
|
||||||
requestVModel.value.executeLoading = true;
|
requestVModel.value.executeLoading = true;
|
||||||
requestVModel.value.response = cloneDeep(defaultResponse);
|
requestVModel.value.response = cloneDeep(defaultResponse);
|
||||||
const res = await props.executeApi(makeRequestParams(executeType));
|
const res = await props.executeApi(makeRequestParams(executeType));
|
||||||
if (executeType === 'localExec') {
|
if (executeType === 'localExec' && props.localExecuteApi) {
|
||||||
await props.localExecuteApi(localExecuteUrl.value, res);
|
await props.localExecuteApi(localExecuteUrl.value, res);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1217,6 +1223,7 @@
|
||||||
|
|
||||||
async function updateRequest() {
|
async function updateRequest() {
|
||||||
try {
|
try {
|
||||||
|
if (!props.updateApi) return;
|
||||||
saveLoading.value = true;
|
saveLoading.value = true;
|
||||||
await props.updateApi({
|
await props.updateApi({
|
||||||
...makeRequestParams(),
|
...makeRequestParams(),
|
||||||
|
@ -1238,6 +1245,7 @@
|
||||||
*/
|
*/
|
||||||
async function realSave(fullParams?: Record<string, any>, silence?: boolean) {
|
async function realSave(fullParams?: Record<string, any>, silence?: boolean) {
|
||||||
try {
|
try {
|
||||||
|
if (!props.createApi) return;
|
||||||
if (!silence) {
|
if (!silence) {
|
||||||
saveLoading.value = true;
|
saveLoading.value = true;
|
||||||
}
|
}
|
||||||
|
@ -1485,6 +1493,10 @@
|
||||||
removeCatchSaveShortcut(handleSaveShortcut);
|
removeCatchSaveShortcut(handleSaveShortcut);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
makeRequestParams,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@ -1513,6 +1525,9 @@
|
||||||
:deep(.arco-tabs-tab) {
|
:deep(.arco-tabs-tab) {
|
||||||
@apply leading-none;
|
@apply leading-none;
|
||||||
}
|
}
|
||||||
|
.request-params-tab :deep(.arco-tabs-nav-tab) {
|
||||||
|
border-bottom: 1px solid var(--color-text-n8) !important;
|
||||||
|
}
|
||||||
.hidden-second {
|
.hidden-second {
|
||||||
:deep(.arco-split-trigger) {
|
:deep(.arco-split-trigger) {
|
||||||
@apply hidden;
|
@apply hidden;
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-select
|
||||||
|
v-model:model-value="currentEnv"
|
||||||
|
:options="envOptions"
|
||||||
|
class="!w-[200px] pl-0 pr-[8px]"
|
||||||
|
:loading="envLoading"
|
||||||
|
allow-search
|
||||||
|
@change="initEnvironment"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<div class="flex cursor-pointer p-[8px]" @click.stop="goEnv">
|
||||||
|
<icon-location class="text-[var(--color-text-4)]" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { SelectOptionData } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import { getEnvironment, getEnvList } from '@/api/modules/api-test/common';
|
||||||
|
import router from '@/router';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
|
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||||
|
import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const currentEnv = ref('');
|
||||||
|
|
||||||
|
const currentEnvConfig = ref<EnvConfig>();
|
||||||
|
const envLoading = ref(false);
|
||||||
|
const envOptions = ref<SelectOptionData[]>([]);
|
||||||
|
|
||||||
|
async function initEnvironment() {
|
||||||
|
try {
|
||||||
|
currentEnvConfig.value = await getEnvironment(currentEnv.value);
|
||||||
|
currentEnvConfig.value.id = currentEnv.value;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initEnvList() {
|
||||||
|
try {
|
||||||
|
envLoading.value = true;
|
||||||
|
const res = await getEnvList(appStore.currentProjectId);
|
||||||
|
envOptions.value = res.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
currentEnv.value = res[0]?.id || '';
|
||||||
|
initEnvironment();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
envLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goEnv() {
|
||||||
|
router.push({
|
||||||
|
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_ENVIRONMENT_MANAGEMENT,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initEnvList();
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
currentEnvConfig,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.ms-input-group--prepend();
|
||||||
|
:deep(.arco-select-view-prefix) {
|
||||||
|
margin-right: 8px;
|
||||||
|
padding-right: 0;
|
||||||
|
border-right: 1px solid var(--color-text-input-border);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -51,6 +51,12 @@
|
||||||
/>
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane v-if="!activeApiTab.isNew" key="case" :title="t('apiTestManagement.case')" class="ms-api-tab-pane">
|
<a-tab-pane v-if="!activeApiTab.isNew" key="case" :title="t('apiTestManagement.case')" class="ms-api-tab-pane">
|
||||||
|
<caseTable
|
||||||
|
:is-api="true"
|
||||||
|
:active-module="props.activeModule"
|
||||||
|
:protocol="props.protocol"
|
||||||
|
:api-detail="activeApiTab"
|
||||||
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<!-- <a-tab-pane v-if="!activeApiTab.isNew" key="mock" title="MOCK" class="ms-api-tab-pane"> </a-tab-pane> -->
|
<!-- <a-tab-pane v-if="!activeApiTab.isNew" key="mock" title="MOCK" class="ms-api-tab-pane"> </a-tab-pane> -->
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
|
@ -63,6 +69,7 @@
|
||||||
|
|
||||||
// import MsButton from '@/components/pure/ms-button/index.vue';
|
// import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||||
|
import caseTable from '../case/caseTable.vue';
|
||||||
// import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
|
// import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
|
||||||
import apiTable from './apiTable.vue';
|
import apiTable from './apiTable.vue';
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,19 @@
|
||||||
<div class="h-full w-full overflow-hidden">
|
<div class="h-full w-full overflow-hidden">
|
||||||
<div class="px-[18px] pt-[16px]">
|
<div class="px-[18px] pt-[16px]">
|
||||||
<MsDetailCard
|
<MsDetailCard
|
||||||
|
v-if="props.isCaseDetail"
|
||||||
|
:title="`【${previewDetail.num}】${previewDetail.name}`"
|
||||||
|
:description="description"
|
||||||
|
>
|
||||||
|
<template #type="{ value }">
|
||||||
|
<apiMethodName :method="value as RequestMethods" tag-size="small" is-tag />
|
||||||
|
</template>
|
||||||
|
<template #priority="{ value }">
|
||||||
|
<caseLevel :case-level="value as CaseLevel" />
|
||||||
|
</template>
|
||||||
|
</MsDetailCard>
|
||||||
|
<MsDetailCard
|
||||||
|
v-else
|
||||||
:title="`【${previewDetail.num}】${previewDetail.name}`"
|
:title="`【${previewDetail.num}】${previewDetail.name}`"
|
||||||
:description="description"
|
:description="description"
|
||||||
:simple-show-count="4"
|
:simple-show-count="4"
|
||||||
|
@ -65,6 +78,8 @@
|
||||||
|
|
||||||
import MsDetailCard from '@/components/pure/ms-detail-card/index.vue';
|
import MsDetailCard from '@/components/pure/ms-detail-card/index.vue';
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
|
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||||
|
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||||
import detailTab from './detail.vue';
|
import detailTab from './detail.vue';
|
||||||
import history from './history.vue';
|
import history from './history.vue';
|
||||||
import quote from './quote.vue';
|
import quote from './quote.vue';
|
||||||
|
@ -85,6 +100,7 @@
|
||||||
detail: RequestParam;
|
detail: RequestParam;
|
||||||
moduleTree: ModuleTreeNode[];
|
moduleTree: ModuleTreeNode[];
|
||||||
protocols: ProtocolItem[];
|
protocols: ProtocolItem[];
|
||||||
|
isCaseDetail?: boolean; // 在用例详情里显示
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['updateFollow']);
|
const emit = defineEmits(['updateFollow']);
|
||||||
|
|
||||||
|
@ -95,6 +111,7 @@
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
previewDetail.value = cloneDeep(props.detail); // props.detail是嵌套的引用类型,防止不必要的修改来源影响props.detail的数据
|
previewDetail.value = cloneDeep(props.detail); // props.detail是嵌套的引用类型,防止不必要的修改来源影响props.detail的数据
|
||||||
|
if (props.isCaseDetail) return;
|
||||||
const tableParam = getValidRequestTableParams(previewDetail.value); // 在编辑props.detail时,参数表格会多出一行默认数据,需要去除
|
const tableParam = getValidRequestTableParams(previewDetail.value); // 在编辑props.detail时,参数表格会多出一行默认数据,需要去除
|
||||||
previewDetail.value = {
|
previewDetail.value = {
|
||||||
...previewDetail.value,
|
...previewDetail.value,
|
||||||
|
@ -114,49 +131,66 @@
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const description = computed(() => [
|
const description = computed(() => {
|
||||||
{
|
const commonDescription = [
|
||||||
key: 'type',
|
{
|
||||||
locale: 'apiTestManagement.apiType',
|
key: 'type',
|
||||||
value: previewDetail.value.method,
|
locale: 'apiTestManagement.apiType',
|
||||||
},
|
value: previewDetail.value.method,
|
||||||
{
|
},
|
||||||
key: 'path',
|
{
|
||||||
locale: 'apiTestManagement.path',
|
key: 'path',
|
||||||
value: previewDetail.value.url || previewDetail.value.path,
|
locale: 'apiTestManagement.path',
|
||||||
},
|
value: previewDetail.value.url || previewDetail.value.path,
|
||||||
{
|
},
|
||||||
key: 'tags',
|
{
|
||||||
locale: 'common.tag',
|
key: 'tags',
|
||||||
value: previewDetail.value.tags,
|
locale: 'common.tag',
|
||||||
},
|
value: previewDetail.value.tags,
|
||||||
{
|
},
|
||||||
key: 'description',
|
];
|
||||||
locale: 'common.desc',
|
if (!props.isCaseDetail) {
|
||||||
value: previewDetail.value.description,
|
return [
|
||||||
width: '100%',
|
...commonDescription,
|
||||||
},
|
...[
|
||||||
{
|
{
|
||||||
key: 'belongModule',
|
key: 'description',
|
||||||
locale: 'apiTestManagement.belongModule',
|
locale: 'common.desc',
|
||||||
value: findNodeByKey<ModuleTreeNode>(props.moduleTree, previewDetail.value.moduleId, 'id')?.path,
|
value: previewDetail.value.description,
|
||||||
},
|
width: '100%',
|
||||||
{
|
},
|
||||||
key: 'creator',
|
{
|
||||||
locale: 'common.creator',
|
key: 'belongModule',
|
||||||
value: previewDetail.value.createUserName,
|
locale: 'apiTestManagement.belongModule',
|
||||||
},
|
value: findNodeByKey<ModuleTreeNode>(props.moduleTree, previewDetail.value.moduleId, 'id')?.path,
|
||||||
{
|
},
|
||||||
key: 'createTime',
|
{
|
||||||
locale: 'apiTestManagement.createTime',
|
key: 'creator',
|
||||||
value: dayjs(previewDetail.value.createTime).format('YYYY-MM-DD HH:mm:ss'),
|
locale: 'common.creator',
|
||||||
},
|
value: previewDetail.value.createUserName,
|
||||||
{
|
},
|
||||||
key: 'updateTime',
|
{
|
||||||
locale: 'apiTestManagement.updateTime',
|
key: 'createTime',
|
||||||
value: dayjs(previewDetail.value.updateTime).format('YYYY-MM-DD HH:mm:ss'),
|
locale: 'apiTestManagement.createTime',
|
||||||
},
|
value: dayjs(previewDetail.value.createTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
]);
|
},
|
||||||
|
{
|
||||||
|
key: 'updateTime',
|
||||||
|
locale: 'apiTestManagement.updateTime',
|
||||||
|
value: dayjs(previewDetail.value.updateTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// 处理用例详情的
|
||||||
|
const caseDescription = commonDescription.slice();
|
||||||
|
caseDescription.splice(1, 0, {
|
||||||
|
key: 'priority',
|
||||||
|
locale: 'case.caseLevel',
|
||||||
|
value: previewDetail.value.priority,
|
||||||
|
});
|
||||||
|
return caseDescription;
|
||||||
|
});
|
||||||
|
|
||||||
const followLoading = ref(false);
|
const followLoading = ref(false);
|
||||||
async function toggleFollowReview() {
|
async function toggleFollowReview() {
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
<template>
|
||||||
|
<preview
|
||||||
|
:detail="activeApiTab"
|
||||||
|
:module-tree="props.moduleTree"
|
||||||
|
:protocols="protocols"
|
||||||
|
is-case-detail
|
||||||
|
@update-follow="activeApiTab.follow = !activeApiTab.follow"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getProtocolList } from '@/api/modules/api-test/common';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
|
import { ProtocolItem } from '@/models/apiTest/common';
|
||||||
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
|
||||||
|
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
|
||||||
|
const preview = defineAsyncComponent(() => import('../api/preview/index.vue'));
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
moduleTree: ModuleTreeNode[]; // 模块树
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const activeApiTab = defineModel<RequestParam>('activeApiTab', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const protocols = ref<ProtocolItem[]>([]);
|
||||||
|
async function initProtocolList() {
|
||||||
|
try {
|
||||||
|
protocols.value = await getProtocolList(appStore.currentOrgId);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initProtocolList();
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -1,19 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-[16px_22px]">
|
<div class="overflow-hidden p-[16px_22px]">
|
||||||
<div class="mb-[16px] flex items-center gap-[8px]">
|
<div class="mb-[16px] flex items-center justify-between">
|
||||||
<a-input-search
|
<a-button
|
||||||
v-model:model-value="keyword"
|
v-show="props.isApi"
|
||||||
:placeholder="t('apiTestManagement.searchPlaceholder')"
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+ADD']"
|
||||||
allow-clear
|
type="primary"
|
||||||
class="mr-[8px] w-[240px]"
|
@click="createCase"
|
||||||
@search="loadCaseList"
|
>
|
||||||
@press-enter="loadCaseList"
|
{{ t('caseManagement.featureCase.creatingCase') }}
|
||||||
/>
|
|
||||||
<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>
|
</a-button>
|
||||||
|
<div class="flex 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>
|
||||||
</div>
|
</div>
|
||||||
<ms-base-table
|
<ms-base-table
|
||||||
v-bind="propsRes"
|
v-bind="propsRes"
|
||||||
|
@ -27,13 +37,14 @@
|
||||||
@drag-change="handleDragChange"
|
@drag-change="handleDragChange"
|
||||||
>
|
>
|
||||||
<template #num="{ record }">
|
<template #num="{ record }">
|
||||||
<MsButton type="text">{{ record.num }}</MsButton>
|
<MsButton type="text" @click="openCaseTab(record)">{{ record.num }}</MsButton>
|
||||||
</template>
|
</template>
|
||||||
<template #caseLevel="{ record }">
|
<template #caseLevel="{ record }">
|
||||||
<a-select
|
<a-select
|
||||||
v-model:model-value="record.priority"
|
v-model:model-value="record.priority"
|
||||||
:placeholder="t('common.pleaseSelect')"
|
:placeholder="t('common.pleaseSelect')"
|
||||||
class="param-input w-full"
|
class="param-input w-full"
|
||||||
|
size="mini"
|
||||||
@change="() => handleCaseLevelChange(record)"
|
@change="() => handleCaseLevelChange(record)"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
|
@ -68,13 +79,14 @@
|
||||||
v-model:model-value="record.status"
|
v-model:model-value="record.status"
|
||||||
:placeholder="t('common.pleaseSelect')"
|
:placeholder="t('common.pleaseSelect')"
|
||||||
class="param-input w-full"
|
class="param-input w-full"
|
||||||
|
size="mini"
|
||||||
@change="() => handleStatusChange(record)"
|
@change="() => handleStatusChange(record)"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<apiStatus :status="record.status" />
|
<apiStatus :status="record.status" size="small" />
|
||||||
</template>
|
</template>
|
||||||
<a-option v-for="item of Object.values(RequestDefinitionStatus)" :key="item" :value="item">
|
<a-option v-for="item of Object.values(RequestDefinitionStatus)" :key="item" :value="item">
|
||||||
<apiStatus :status="item" />
|
<apiStatus :status="item" size="small" />
|
||||||
</a-option>
|
</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
|
@ -144,7 +156,7 @@
|
||||||
{{ t('apiTestManagement.execute') }}
|
{{ t('apiTestManagement.execute') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
<MsButton type="text" class="!mr-0">
|
<MsButton type="text" class="!mr-0" @click="copyCase(record)">
|
||||||
{{ t('common.copy') }}
|
{{ t('common.copy') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
|
@ -223,6 +235,13 @@
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
<createAndEditCaseDrawer
|
||||||
|
v-if="props.isApi"
|
||||||
|
ref="createAndEditCaseDrawerRef"
|
||||||
|
:protocol="props.protocol"
|
||||||
|
:api-detail="apiDetail as RequestParam"
|
||||||
|
@load-case="loadCaseListAndResetSelector()"
|
||||||
|
/>
|
||||||
<a-modal v-model:visible="showBatchExecute" title-align="start" class="ms-modal-upload ms-modal-medium" :width="480">
|
<a-modal v-model:visible="showBatchExecute" title-align="start" class="ms-modal-upload ms-modal-medium" :width="480">
|
||||||
<template #title>
|
<template #title>
|
||||||
{{ t('report.trigger.batch.execution') }}
|
{{ t('report.trigger.batch.execution') }}
|
||||||
|
@ -327,6 +346,7 @@
|
||||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||||
|
import createAndEditCaseDrawer from './createAndEditCaseDrawer.vue';
|
||||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -354,9 +374,17 @@
|
||||||
import { RequestDefinitionStatus } from '@/enums/apiEnum';
|
import { RequestDefinitionStatus } from '@/enums/apiEnum';
|
||||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
|
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
isApi: boolean; // 接口定义详情的case tab下
|
||||||
activeModule: string;
|
activeModule: string;
|
||||||
protocol: string; // 查看的协议类型
|
protocol: string; // 查看的协议类型
|
||||||
|
apiDetail?: RequestParam;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'openCaseTab', record: ApiCaseDetail): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
@ -378,7 +406,9 @@
|
||||||
sorter: true,
|
sorter: true,
|
||||||
},
|
},
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
width: 100,
|
width: 130,
|
||||||
|
ellipsis: true,
|
||||||
|
showTooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'case.caseName',
|
title: 'case.caseName',
|
||||||
|
@ -500,6 +530,7 @@
|
||||||
selectable: true,
|
selectable: true,
|
||||||
showSelectAll: true,
|
showSelectAll: true,
|
||||||
draggable: { type: 'handle', width: 32 },
|
draggable: { type: 'handle', width: 32 },
|
||||||
|
heightUsed: 308,
|
||||||
});
|
});
|
||||||
const batchActions = {
|
const batchActions = {
|
||||||
baseAction: [
|
baseAction: [
|
||||||
|
@ -551,6 +582,7 @@
|
||||||
});
|
});
|
||||||
function loadCaseList() {
|
function loadCaseList() {
|
||||||
const params = {
|
const params = {
|
||||||
|
apiDefinitionId: props.apiDetail?.id,
|
||||||
keyword: keyword.value,
|
keyword: keyword.value,
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
moduleIds: moduleIds.value,
|
moduleIds: moduleIds.value,
|
||||||
|
@ -673,6 +705,7 @@
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
protocol: props.protocol,
|
protocol: props.protocol,
|
||||||
moduleIds: moduleIds.value,
|
moduleIds: moduleIds.value,
|
||||||
|
apiDefinitionId: props.apiDetail?.id as string,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -909,6 +942,18 @@
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createAndEditCaseDrawerRef = ref<InstanceType<typeof createAndEditCaseDrawer>>();
|
||||||
|
function createCase() {
|
||||||
|
createAndEditCaseDrawerRef.value?.open();
|
||||||
|
}
|
||||||
|
function copyCase(record: ApiCaseDetail) {
|
||||||
|
createAndEditCaseDrawerRef.value?.open(record, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCaseTab(record: ApiCaseDetail) {
|
||||||
|
emit('openCaseTab', record);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -0,0 +1,218 @@
|
||||||
|
<template>
|
||||||
|
<MsDrawer
|
||||||
|
v-model:visible="innerVisible"
|
||||||
|
:title="t('case.createCase')"
|
||||||
|
:width="894"
|
||||||
|
no-content-padding
|
||||||
|
:ok-text="t('common.create')"
|
||||||
|
:ok-loading="drawerLoading"
|
||||||
|
:save-continue-text="t('case.saveContinueText')"
|
||||||
|
:show-continue="true"
|
||||||
|
@confirm="handleDrawerConfirm"
|
||||||
|
@continue="handleDrawerConfirm(true)"
|
||||||
|
@cancel="handleSaveCaseCancel"
|
||||||
|
>
|
||||||
|
<template #headerLeft>
|
||||||
|
<environmentSelect ref="environmentSelectRef" class="ml-[16px]" />
|
||||||
|
</template>
|
||||||
|
<div class="flex h-full flex-col overflow-hidden">
|
||||||
|
<div class="px-[16px] pt-[16px]">
|
||||||
|
<MsDetailCard
|
||||||
|
:title="`【${apiDataDetail.num}】${apiDataDetail.name}`"
|
||||||
|
:description="description"
|
||||||
|
class="!flex-row justify-between"
|
||||||
|
>
|
||||||
|
<template #type="{ value }">
|
||||||
|
<apiMethodName :method="value as RequestMethods" tag-size="small" is-tag />
|
||||||
|
</template>
|
||||||
|
</MsDetailCard>
|
||||||
|
<a-form ref="formRef" class="mt-[16px]" :model="caseModalForm" layout="vertical">
|
||||||
|
<a-form-item field="name" label="" :rules="[{ required: true, message: t('case.caseNameRequired') }]">
|
||||||
|
<div class="flex w-full items-center gap-[8px]">
|
||||||
|
<a-input
|
||||||
|
v-model:model-value="caseModalForm.name"
|
||||||
|
:placeholder="t('case.caseNamePlaceholder')"
|
||||||
|
allow-clear
|
||||||
|
:max-length="255"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
<a-button type="primary">
|
||||||
|
{{ t('apiTestManagement.execute') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
<div class="flex gap-[16px]">
|
||||||
|
<a-form-item field="priority" :label="t('case.caseLevel')">
|
||||||
|
<a-select v-model:model-value="caseModalForm.priority" :placeholder="t('common.pleaseSelect')">
|
||||||
|
<template #label>
|
||||||
|
<span class="text-[var(--color-text-2)]"> <caseLevel :case-level="caseModalForm.priority" /></span>
|
||||||
|
</template>
|
||||||
|
<a-option v-for="item of casePriorityOptions" :key="item.value" :value="item.value">
|
||||||
|
<caseLevel :case-level="item.label as CaseLevel" />
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="status" :label="t('apiTestManagement.apiStatus')">
|
||||||
|
<a-select v-model:model-value="caseModalForm.status" :placeholder="t('common.pleaseSelect')">
|
||||||
|
<template #label>
|
||||||
|
<apiStatus :status="caseModalForm.status" />
|
||||||
|
</template>
|
||||||
|
<a-option v-for="item of Object.values(RequestDefinitionStatus)" :key="item" :value="item">
|
||||||
|
<apiStatus :status="item" />
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="tags" :label="t('common.tag')">
|
||||||
|
<MsTagsInput v-model:model-value="caseModalForm.tags" />
|
||||||
|
</a-form-item>
|
||||||
|
</div>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
<div class="px-[16px] font-medium">{{ t('apiTestManagement.requestParams') }}</div>
|
||||||
|
<div class="flex-1 overflow-hidden">
|
||||||
|
<requestComposition
|
||||||
|
ref="requestCompositionRef"
|
||||||
|
v-model:request="apiDataDetail"
|
||||||
|
:is-case="true"
|
||||||
|
hide-response-layout-switch
|
||||||
|
:upload-temp-file-api="uploadTempFileCase"
|
||||||
|
:file-save-as-source-id="apiDataDetail.id"
|
||||||
|
:file-module-options-api="getTransferOptionsCase"
|
||||||
|
:file-save-as-api="transferFileCase"
|
||||||
|
:current-env-config="currentEnvConfig"
|
||||||
|
:is-definition="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MsDrawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
import MsDetailCard from '@/components/pure/ms-detail-card/index.vue';
|
||||||
|
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||||
|
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||||
|
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||||
|
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||||
|
import environmentSelect from '../../environmentSelect.vue';
|
||||||
|
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||||
|
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||||
|
import requestComposition, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
addCase,
|
||||||
|
getTransferOptionsCase,
|
||||||
|
transferFileCase,
|
||||||
|
uploadTempFileCase,
|
||||||
|
} from '@/api/modules/api-test/management';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
import { ApiCaseDetail } from '@/models/apiTest/management';
|
||||||
|
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||||
|
import { RequestDefinitionStatus, RequestMethods } from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
import { casePriorityOptions } from '@/views/api-test/components/config';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
apiDetail: RequestParam;
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits(['loadCase']);
|
||||||
|
|
||||||
|
const apiDataDetail = ref<RequestParam>(cloneDeep(props.apiDetail));
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const innerVisible = ref(false);
|
||||||
|
|
||||||
|
const drawerLoading = ref(false);
|
||||||
|
|
||||||
|
const description = computed(() => [
|
||||||
|
{
|
||||||
|
key: 'type',
|
||||||
|
locale: 'apiTestManagement.apiType',
|
||||||
|
value: apiDataDetail.value.method,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'path',
|
||||||
|
locale: 'apiTestManagement.path',
|
||||||
|
value: apiDataDetail.value.url || apiDataDetail.value.path,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const environmentSelectRef = ref<InstanceType<typeof environmentSelect>>();
|
||||||
|
const currentEnvConfig = computed<EnvConfig | undefined>(() => environmentSelectRef.value?.currentEnvConfig);
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const initForm: any = {
|
||||||
|
apiDefinitionId: apiDataDetail.value.id as string,
|
||||||
|
name: '',
|
||||||
|
priority: 'P0',
|
||||||
|
tags: [],
|
||||||
|
status: RequestDefinitionStatus.PROCESSING,
|
||||||
|
};
|
||||||
|
const caseModalForm = ref({ ...initForm });
|
||||||
|
|
||||||
|
const requestCompositionRef = ref<InstanceType<typeof requestComposition>>();
|
||||||
|
|
||||||
|
function open(record?: ApiCaseDetail, isCopy?: boolean) {
|
||||||
|
innerVisible.value = true;
|
||||||
|
if (isCopy) {
|
||||||
|
caseModalForm.value.name = record?.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSaveCaseCancel() {
|
||||||
|
innerVisible.value = false;
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
caseModalForm.value = { ...initForm };
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDrawerConfirm(isContinue: boolean) {
|
||||||
|
formRef.value?.validate(async (errors) => {
|
||||||
|
if (!errors) {
|
||||||
|
drawerLoading.value = true;
|
||||||
|
const params = { ...requestCompositionRef.value?.makeRequestParams(), ...caseModalForm.value };
|
||||||
|
try {
|
||||||
|
await addCase(params);
|
||||||
|
Message.success(t('common.updateSuccess'));
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
if (!isContinue) {
|
||||||
|
emit('loadCase');
|
||||||
|
handleSaveCaseCancel();
|
||||||
|
}
|
||||||
|
caseModalForm.value = { ...initForm };
|
||||||
|
drawerLoading.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(.arco-select-view-value) {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
:deep(.ms-detail-card-title) {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
:deep(.ms-detail-card-desc) {
|
||||||
|
gap: 16px;
|
||||||
|
& > div {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.arco-form > .arco-form-item):nth-child(1) .arco-form-item-label-col {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:deep(.request-and-response) {
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,22 +1,156 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-full flex-col">
|
<div class="flex flex-1 flex-col overflow-hidden">
|
||||||
<div v-show="activeApiTab.id === 'all'" class="flex-1">
|
<div v-show="activeApiTab.id === 'all'" class="flex-1 overflow-hidden">
|
||||||
<caseTable :active-module="props.activeModule" :protocol="props.protocol" />
|
<caseTable
|
||||||
|
:is-api="false"
|
||||||
|
:active-module="props.activeModule"
|
||||||
|
:protocol="props.protocol"
|
||||||
|
@open-case-tab="openCaseTab"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-show="activeApiTab.id !== 'all'" class="flex-1 overflow-hidden">
|
||||||
|
<caseDetail :active-api-tab="activeApiTab" :module-tree="props.moduleTree" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||||
|
import caseDetail from './caseDetail.vue';
|
||||||
import caseTable from './caseTable.vue';
|
import caseTable from './caseTable.vue';
|
||||||
|
|
||||||
|
import { getCaseDetail } from '@/api/modules/api-test/management';
|
||||||
|
|
||||||
|
import { ApiCaseDetail } from '@/models/apiTest/management';
|
||||||
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
import { RequestAuthType, RequestComposition, RequestMethods, ResponseComposition } from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
import { defaultBodyParams, defaultResponse, defaultResponseItem } from '@/views/api-test/components/config';
|
||||||
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
import { parseRequestBodyFiles } from '@/views/api-test/components/utils';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
activeModule: string;
|
activeModule: string;
|
||||||
protocol: string;
|
protocol: string;
|
||||||
|
moduleTree: ModuleTreeNode[]; // 模块树
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const apiTabs = defineModel<RequestParam[]>('apiTabs', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
|
||||||
const activeApiTab = defineModel<RequestParam>('activeApiTab', {
|
const activeApiTab = defineModel<RequestParam>('activeApiTab', {
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const initDefaultId = `case-${Date.now()}`;
|
||||||
|
const defaultCaseParams: RequestParam = {
|
||||||
|
id: initDefaultId,
|
||||||
|
moduleId: props.activeModule === 'all' ? 'root' : props.activeModule,
|
||||||
|
protocol: 'HTTP',
|
||||||
|
tags: [],
|
||||||
|
description: '',
|
||||||
|
url: '',
|
||||||
|
activeTab: RequestComposition.HEADER,
|
||||||
|
closable: true,
|
||||||
|
method: RequestMethods.GET,
|
||||||
|
headers: [],
|
||||||
|
body: cloneDeep(defaultBodyParams),
|
||||||
|
query: [],
|
||||||
|
rest: [],
|
||||||
|
polymorphicName: '',
|
||||||
|
name: '',
|
||||||
|
path: '',
|
||||||
|
projectId: '',
|
||||||
|
uploadFileIds: [],
|
||||||
|
linkFileIds: [],
|
||||||
|
authConfig: {
|
||||||
|
authType: RequestAuthType.NONE,
|
||||||
|
basicAuth: {
|
||||||
|
userName: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
digestAuth: {
|
||||||
|
userName: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
polymorphicName: 'MsCommonElement', // 协议多态名称,写死MsCommonElement
|
||||||
|
assertionConfig: {
|
||||||
|
enableGlobal: false,
|
||||||
|
assertions: [],
|
||||||
|
},
|
||||||
|
postProcessorConfig: {
|
||||||
|
enableGlobal: false,
|
||||||
|
processors: [],
|
||||||
|
},
|
||||||
|
preProcessorConfig: {
|
||||||
|
enableGlobal: false,
|
||||||
|
processors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
otherConfig: {
|
||||||
|
connectTimeout: 60000,
|
||||||
|
responseTimeout: 60000,
|
||||||
|
certificateAlias: '',
|
||||||
|
followRedirects: true,
|
||||||
|
autoRedirects: false,
|
||||||
|
},
|
||||||
|
responseActiveTab: ResponseComposition.BODY,
|
||||||
|
response: cloneDeep(defaultResponse),
|
||||||
|
responseDefinition: [cloneDeep(defaultResponseItem)],
|
||||||
|
isNew: true,
|
||||||
|
mode: 'case',
|
||||||
|
executeLoading: false,
|
||||||
|
preDependency: [], // 前置依赖
|
||||||
|
postDependency: [], // 后置依赖
|
||||||
|
};
|
||||||
|
|
||||||
|
function addTab(defaultProps?: Partial<TabItem>) {
|
||||||
|
apiTabs.value.push({
|
||||||
|
...cloneDeep(defaultCaseParams),
|
||||||
|
...defaultProps,
|
||||||
|
});
|
||||||
|
activeApiTab.value = apiTabs.value[apiTabs.value.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
async function openCaseTab(apiInfo: ApiCaseDetail) {
|
||||||
|
const isLoadedTabIndex = apiTabs.value.findIndex(
|
||||||
|
(e) => e.id === (typeof apiInfo === 'string' ? apiInfo : apiInfo.id)
|
||||||
|
);
|
||||||
|
if (isLoadedTabIndex > -1) {
|
||||||
|
// 如果点击的请求在tab中已经存在,则直接切换到该tab
|
||||||
|
activeApiTab.value = apiTabs.value[isLoadedTabIndex] as RequestParam;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await getCaseDetail(typeof apiInfo === 'string' ? apiInfo : apiInfo.id);
|
||||||
|
const parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件;
|
||||||
|
// if (res.protocol === 'HTTP') { // TODO: 后端没protocol字段,问一下
|
||||||
|
// parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||||
|
// }
|
||||||
|
addTab({
|
||||||
|
...res.request,
|
||||||
|
...res,
|
||||||
|
response: cloneDeep(defaultResponse),
|
||||||
|
// responseDefinition: res.response.map((e) => ({ ...e, responseActiveTab: ResponseComposition.BODY })), // TODO: 后端没response字段,问一下
|
||||||
|
url: res.path,
|
||||||
|
...parseRequestBodyResult,
|
||||||
|
});
|
||||||
|
nextTick(() => {
|
||||||
|
loading.value = false; // 等待内容渲染出来再隐藏loading
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
v-model:active-tab="activeApiTab"
|
v-model:active-tab="activeApiTab"
|
||||||
v-model:tabs="apiTabs"
|
v-model:tabs="apiTabs"
|
||||||
class="flex-1 overflow-hidden"
|
class="flex-1 overflow-hidden"
|
||||||
|
:show-add="currentTab === 'api'"
|
||||||
@add="newTab"
|
@add="newTab"
|
||||||
>
|
>
|
||||||
<template #label="{ tab }">
|
<template #label="{ tab }">
|
||||||
|
@ -36,7 +37,7 @@
|
||||||
</a-select>
|
</a-select>
|
||||||
</div>
|
</div>
|
||||||
<api
|
<api
|
||||||
v-if="currentTab === 'api'"
|
v-show="(activeApiTab.id === 'all' && currentTab === 'api') || activeApiTab.mode === 'definition'"
|
||||||
ref="apiRef"
|
ref="apiRef"
|
||||||
v-model:active-api-tab="activeApiTab"
|
v-model:active-api-tab="activeApiTab"
|
||||||
v-model:api-tabs="apiTabs"
|
v-model:api-tabs="apiTabs"
|
||||||
|
@ -46,10 +47,12 @@
|
||||||
:module-tree="props.moduleTree"
|
:module-tree="props.moduleTree"
|
||||||
/>
|
/>
|
||||||
<apiCase
|
<apiCase
|
||||||
v-show="currentTab === 'case'"
|
v-show="(activeApiTab.id === 'all' && currentTab === 'case') || activeApiTab.mode === 'case'"
|
||||||
|
v-model:api-tabs="apiTabs"
|
||||||
v-model:active-api-tab="activeApiTab"
|
v-model:active-api-tab="activeApiTab"
|
||||||
:active-module="props.activeModule"
|
:active-module="props.activeModule"
|
||||||
:protocol="props.protocol"
|
:protocol="props.protocol"
|
||||||
|
:module-tree="props.moduleTree"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -124,6 +127,7 @@
|
||||||
// 下拉框切换
|
// 下拉框切换
|
||||||
function currentTabChange(val: any) {
|
function currentTabChange(val: any) {
|
||||||
apiTabs.value[0].label = val === 'api' ? t('apiTestManagement.allApi') : t('case.allCase');
|
apiTabs.value[0].label = val === 'api' ? t('apiTestManagement.allApi') : t('case.allCase');
|
||||||
|
activeApiTab.value = apiTabs.value[0] as RequestParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|
|
@ -190,6 +190,8 @@ export default {
|
||||||
'case.batchRecoverCaseTip': 'Are you sure you want to recover {count} selected cases?',
|
'case.batchRecoverCaseTip': 'Are you sure you want to recover {count} selected cases?',
|
||||||
'case.recycle.recoverCaseTip': 'When restoring the case, the deleted API will be restored simultaneously.',
|
'case.recycle.recoverCaseTip': 'When restoring the case, the deleted API will be restored simultaneously.',
|
||||||
'case.recycle.confirmRecovery': 'Confirm recovery',
|
'case.recycle.confirmRecovery': 'Confirm recovery',
|
||||||
|
'case.createCase': 'Create Case',
|
||||||
|
'case.saveContinueText': 'Save & continue',
|
||||||
'case.detail.changeHistoryTip': `View and compare historical changes. According to the administrator's setting rules, historical changes will be automatically deleted`,
|
'case.detail.changeHistoryTip': `View and compare historical changes. According to the administrator's setting rules, historical changes will be automatically deleted`,
|
||||||
'case.detail.noReminders': 'No longer remind',
|
'case.detail.noReminders': 'No longer remind',
|
||||||
'case.detail.changeNumber': 'Change sequence',
|
'case.detail.changeNumber': 'Change sequence',
|
||||||
|
|
|
@ -182,6 +182,8 @@ export default {
|
||||||
'case.batchRecoverCaseTip': '确认恢复已选中的 {count} 个用例吗?',
|
'case.batchRecoverCaseTip': '确认恢复已选中的 {count} 个用例吗?',
|
||||||
'case.recycle.recoverCaseTip': '恢复case时会同步恢复被删除的api',
|
'case.recycle.recoverCaseTip': '恢复case时会同步恢复被删除的api',
|
||||||
'case.recycle.confirmRecovery': '确认恢复',
|
'case.recycle.confirmRecovery': '确认恢复',
|
||||||
|
'case.createCase': '创建用例',
|
||||||
|
'case.saveContinueText': '保存并继续创建',
|
||||||
'case.detail.changeHistoryTip': '查看、对比历史修改,根据管理员设置规则,变更历史数据将自动删除',
|
'case.detail.changeHistoryTip': '查看、对比历史修改,根据管理员设置规则,变更历史数据将自动删除',
|
||||||
'case.detail.noReminders': '不再提醒',
|
'case.detail.noReminders': '不再提醒',
|
||||||
'case.detail.changeNumber': '变更序号',
|
'case.detail.changeNumber': '变更序号',
|
||||||
|
|
Loading…
Reference in New Issue