feat(接口场景): 场景导入场景&部分问题修复
This commit is contained in:
parent
42d41df3f1
commit
dee6bc7b32
|
@ -18,6 +18,7 @@ import {
|
||||||
GetModuleTreeUrl,
|
GetModuleTreeUrl,
|
||||||
GetScenarioStepUrl,
|
GetScenarioStepUrl,
|
||||||
GetScenarioUrl,
|
GetScenarioUrl,
|
||||||
|
GetSystemRequestUrl,
|
||||||
GetTrashModuleCountUrl,
|
GetTrashModuleCountUrl,
|
||||||
GetTrashModuleTreeUrl,
|
GetTrashModuleTreeUrl,
|
||||||
MoveModuleUrl,
|
MoveModuleUrl,
|
||||||
|
@ -45,6 +46,7 @@ import {
|
||||||
ApiScenarioUpdateDTO,
|
ApiScenarioUpdateDTO,
|
||||||
ExecuteHistoryItem,
|
ExecuteHistoryItem,
|
||||||
ExecutePageParams,
|
ExecutePageParams,
|
||||||
|
GetSystemRequestParams,
|
||||||
Scenario,
|
Scenario,
|
||||||
ScenarioDetail,
|
ScenarioDetail,
|
||||||
ScenarioHistoryItem,
|
ScenarioHistoryItem,
|
||||||
|
@ -231,3 +233,8 @@ export function debugScenario(data: ApiScenarioDebugRequest) {
|
||||||
export function executeScenario(data: ApiScenarioDebugRequest) {
|
export function executeScenario(data: ApiScenarioDebugRequest) {
|
||||||
return MSR.post({ url: ExecuteScenarioUrl, data });
|
return MSR.post({ url: ExecuteScenarioUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取导入的系统请求数据
|
||||||
|
export function getSystemRequest(data: GetSystemRequestParams) {
|
||||||
|
return MSR.post<ApiScenarioTableItem[]>({ url: GetSystemRequestUrl, data });
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ export const ScenarioTransferFileUrl = '/api/scenario/transfer'; // 接口场景
|
||||||
export const ScenarioTransferModuleOptionsUrl = '/api/scenario/transfer/options'; // 接口场景临时文件转存目录
|
export const ScenarioTransferModuleOptionsUrl = '/api/scenario/transfer/options'; // 接口场景临时文件转存目录
|
||||||
export const DebugScenarioUrl = '/api/scenario/debug'; // 接口场景调试(不保存报告)
|
export const DebugScenarioUrl = '/api/scenario/debug'; // 接口场景调试(不保存报告)
|
||||||
export const ExecuteScenarioUrl = '/api/scenario/run'; // 接口场景执行(保存报告)
|
export const ExecuteScenarioUrl = '/api/scenario/run'; // 接口场景执行(保存报告)
|
||||||
|
export const GetSystemRequestUrl = '/api/scenario/get/system-request'; // 获取导入的系统请求数据
|
||||||
export const BatchRecycleScenarioUrl = '/api/scenario/batch-operation/delete-gc'; // 批量删除接口场景
|
export const BatchRecycleScenarioUrl = '/api/scenario/batch-operation/delete-gc'; // 批量删除接口场景
|
||||||
export const BatchMoveScenarioUrl = '/api/scenario/batch-operation/move'; // 批量移动接口场景
|
export const BatchMoveScenarioUrl = '/api/scenario/batch-operation/move'; // 批量移动接口场景
|
||||||
export const BatchCopyScenarioUrl = '/api/scenario/batch-operation/copy'; // 批量复制接口场景
|
export const BatchCopyScenarioUrl = '/api/scenario/batch-operation/copy'; // 批量复制接口场景
|
||||||
|
|
|
@ -407,3 +407,19 @@ export interface ApiScenarioUpdateDTO extends Partial<Scenario> {
|
||||||
deleteFileIds?: string[];
|
deleteFileIds?: string[];
|
||||||
unLinkFileIds?: string[];
|
unLinkFileIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GetSystemRequestTypeParams {
|
||||||
|
moduleIds?: (string | number)[];
|
||||||
|
selectedIds: (string | number)[];
|
||||||
|
unselectedIds: (string | number)[];
|
||||||
|
projectId: string;
|
||||||
|
protocol?: string;
|
||||||
|
versionId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetSystemRequestParams {
|
||||||
|
apiRequest?: GetSystemRequestTypeParams;
|
||||||
|
caseRequest?: GetSystemRequestTypeParams;
|
||||||
|
scenarioRequest?: GetSystemRequestTypeParams;
|
||||||
|
refType: ScenarioStepRefType.COPY | ScenarioStepRefType.REF;
|
||||||
|
}
|
||||||
|
|
|
@ -235,7 +235,7 @@
|
||||||
if (matchesIterator) {
|
if (matchesIterator) {
|
||||||
const matches = Array.from(matchesIterator);
|
const matches = Array.from(matchesIterator);
|
||||||
try {
|
try {
|
||||||
if (expressionForm.value.expressionMatchingRule === 'EXPRESSION') {
|
if (expressionForm.value.expressionMatchingRule === RequestExtractExpressionRuleType.EXPRESSION) {
|
||||||
// 匹配表达式,取第一个匹配结果,是完整匹配结果
|
// 匹配表达式,取第一个匹配结果,是完整匹配结果
|
||||||
matchResult.value = matches.map((e) => e[0]) || [];
|
matchResult.value = matches.map((e) => e[0]) || [];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -380,6 +380,8 @@
|
||||||
title: 'apiTestManagement.paramName',
|
title: 'apiTestManagement.paramName',
|
||||||
dataIndex: 'key',
|
dataIndex: 'key',
|
||||||
inputType: 'text',
|
inputType: 'text',
|
||||||
|
width: 250,
|
||||||
|
showTooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'apiTestManagement.paramVal',
|
title: 'apiTestManagement.paramVal',
|
||||||
|
|
|
@ -362,7 +362,7 @@
|
||||||
|
|
||||||
export type RequestParam = ExecuteApiRequestFullParams & {
|
export type RequestParam = ExecuteApiRequestFullParams & {
|
||||||
response?: RequestTaskResult;
|
response?: RequestTaskResult;
|
||||||
customizeRequestEnvEnable: boolean;
|
customizeRequestEnvEnable?: boolean;
|
||||||
} & RequestCustomAttr;
|
} & RequestCustomAttr;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<MsDrawer
|
<MsDrawer
|
||||||
v-model:visible="visible"
|
v-model:visible="visible"
|
||||||
unmount-on-close
|
|
||||||
:mask="false"
|
|
||||||
:width="900"
|
:width="900"
|
||||||
:footer="false"
|
:footer="false"
|
||||||
show-full-screen
|
show-full-screen
|
||||||
|
@ -88,17 +86,13 @@
|
||||||
import requestAndResponse from '@/views/api-test/components/requestAndResponse.vue';
|
import requestAndResponse from '@/views/api-test/components/requestAndResponse.vue';
|
||||||
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
|
||||||
import { localExecuteApiDebug } from '@/api/modules/api-test/common';
|
|
||||||
import {
|
import {
|
||||||
debugCase,
|
|
||||||
getCaseDetail,
|
getCaseDetail,
|
||||||
getTransferOptionsCase,
|
getTransferOptionsCase,
|
||||||
runCase,
|
|
||||||
transferFileCase,
|
transferFileCase,
|
||||||
uploadTempFileCase,
|
uploadTempFileCase,
|
||||||
} from '@/api/modules/api-test/management';
|
} from '@/api/modules/api-test/management';
|
||||||
import { getSocket } from '@/api/modules/project-management/commonScript';
|
import { characterLimit } from '@/utils';
|
||||||
import { characterLimit, getGenerateId } from '@/utils';
|
|
||||||
|
|
||||||
import { RequestResult } from '@/models/apiTest/common';
|
import { RequestResult } from '@/models/apiTest/common';
|
||||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||||
|
@ -121,6 +115,8 @@
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'applyStep', request: RequestParam): void;
|
(e: 'applyStep', request: RequestParam): void;
|
||||||
(e: 'deleteStep'): void;
|
(e: 'deleteStep'): void;
|
||||||
|
(e: 'execute', request: RequestParam, executeType?: 'localExec' | 'serverExec'): void;
|
||||||
|
(e: 'stopDebug'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -208,11 +204,23 @@
|
||||||
() =>
|
() =>
|
||||||
activeStep.value?.stepType === ScenarioStepType.API_CASE && activeStep.value?.refType === ScenarioStepRefType.REF
|
activeStep.value?.stepType === ScenarioStepType.API_CASE && activeStep.value?.refType === ScenarioStepRefType.REF
|
||||||
);
|
);
|
||||||
|
const isHttpProtocol = computed(() => requestVModel.value.protocol === 'HTTP');
|
||||||
|
|
||||||
const stepName = ref(activeStep.value?.name);
|
const stepName = ref(activeStep.value?.name);
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
stepName.value = activeStep.value?.name;
|
stepName.value = activeStep.value?.name;
|
||||||
});
|
});
|
||||||
|
watch(
|
||||||
|
() => props.stepResponses,
|
||||||
|
(val) => {
|
||||||
|
if (val && val[requestVModel.value.stepId]) {
|
||||||
|
requestVModel.value.executeLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const executeRef = ref<InstanceType<typeof executeButton>>();
|
const executeRef = ref<InstanceType<typeof executeButton>>();
|
||||||
const requestAndResponseRef = ref<InstanceType<typeof requestAndResponse>>();
|
const requestAndResponseRef = ref<InstanceType<typeof requestAndResponse>>();
|
||||||
|
@ -230,66 +238,31 @@
|
||||||
isShowEditStepNameInput.value = false;
|
isShowEditStepNameInput.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reportId = ref('');
|
/**
|
||||||
const websocket = ref<WebSocket>();
|
* 执行调试
|
||||||
const temporaryResponseMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换tab导致报告丢失
|
* @param val 执行类型
|
||||||
// 开启websocket监听,接收执行结果
|
*/
|
||||||
function debugSocket(executeType?: 'localExec' | 'serverExec') {
|
|
||||||
websocket.value = getSocket(
|
|
||||||
reportId.value,
|
|
||||||
executeType === 'localExec' ? '/ws/debug' : '',
|
|
||||||
executeType === 'localExec' ? executeRef.value?.localExecuteUrl : ''
|
|
||||||
);
|
|
||||||
websocket.value.addEventListener('message', (event) => {
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
if (data.msgType === 'EXEC_RESULT') {
|
|
||||||
if (requestVModel.value.reportId === data.reportId) {
|
|
||||||
// 判断当前查看的tab是否是当前返回的报告的tab,是的话直接赋值
|
|
||||||
requestVModel.value.response = data.taskResult; // 渲染出用例详情和创建用例抽屉的响应数据
|
|
||||||
requestVModel.value.executeLoading = false;
|
|
||||||
} else {
|
|
||||||
// 不是则需要把报告缓存起来,等切换到对应的tab再赋值
|
|
||||||
temporaryResponseMap[data.reportId] = data.taskResult;
|
|
||||||
}
|
|
||||||
} else if (data.msgType === 'EXEC_END') {
|
|
||||||
// 执行结束,关闭websocket
|
|
||||||
websocket.value?.close();
|
|
||||||
requestVModel.value.executeLoading = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async function handleExecute(executeType?: 'localExec' | 'serverExec') {
|
async function handleExecute(executeType?: 'localExec' | 'serverExec') {
|
||||||
try {
|
|
||||||
requestVModel.value.executeLoading = true;
|
requestVModel.value.executeLoading = true;
|
||||||
requestVModel.value.response = cloneDeep(defaultResponse);
|
if (isHttpProtocol.value) {
|
||||||
const makeRequestParams = requestAndResponseRef.value?.makeRequestParams(executeType); // 写在reportId之前,防止覆盖reportId
|
emit('execute', requestAndResponseRef.value?.makeRequestParams(executeType), executeType);
|
||||||
reportId.value = getGenerateId();
|
|
||||||
requestVModel.value.reportId = reportId.value; // 存储报告ID
|
|
||||||
debugSocket(executeType); // 开启websocket
|
|
||||||
let res;
|
|
||||||
const params = {
|
|
||||||
apiDefinitionId: requestVModel.value.apiDefinitionId,
|
|
||||||
...makeRequestParams,
|
|
||||||
reportId: reportId.value,
|
|
||||||
};
|
|
||||||
if (!(requestVModel.value.resourceId as string).startsWith('c') && executeType === 'serverExec') {
|
|
||||||
// 已创建的服务端
|
|
||||||
res = await runCase(params);
|
|
||||||
} else {
|
} else {
|
||||||
res = await debugCase(params);
|
// 插件需要校验动态表单
|
||||||
}
|
// fApi.value?.validate(async (valid) => {
|
||||||
if (executeType === 'localExec') {
|
// if (valid === true) {
|
||||||
await localExecuteApiDebug(executeRef.value?.localExecuteUrl ?? '', res);
|
// emit('execute', requestAndResponseRef.value?.makeRequestParams(executeType), executeType);
|
||||||
}
|
// } else {
|
||||||
} catch (error) {
|
// requestVModel.value.activeTab = RequestComposition.PLUGIN;
|
||||||
// eslint-disable-next-line no-console
|
// nextTick(() => {
|
||||||
console.log(error);
|
// scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
|
||||||
requestVModel.value.executeLoading = false;
|
// });
|
||||||
|
// }
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopDebug() {
|
function stopDebug() {
|
||||||
websocket.value?.close();
|
emit('stopDebug');
|
||||||
requestVModel.value.executeLoading = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
<div class="mb-[12px] flex items-center gap-[8px]">
|
<div class="mb-[12px] flex items-center gap-[8px]">
|
||||||
<MsProjectSelect v-model:project="currentProject" @change="resetModule" />
|
<MsProjectSelect v-model:project="currentProject" @change="resetModule" />
|
||||||
<a-select
|
<a-select
|
||||||
|
v-if="activeKey !== 'scenario'"
|
||||||
v-model:model-value="protocol"
|
v-model:model-value="protocol"
|
||||||
:options="protocolOptions"
|
:options="protocolOptions"
|
||||||
class="w-[90px]"
|
class="w-[90px]"
|
||||||
|
@ -70,11 +71,11 @@
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-[12px]">
|
<div class="flex items-center gap-[12px]">
|
||||||
<a-button type="secondary" @click="handleCancel">{{ t('common.cancel') }}</a-button>
|
<a-button type="secondary" :disabled="loading" @click="handleCancel">{{ t('common.cancel') }}</a-button>
|
||||||
<a-button type="primary" :disabled="totalSelected === 0" @click="handleCopy">
|
<a-button type="primary" :loading="loading" :disabled="totalSelected === 0" @click="handleCopy">
|
||||||
{{ t('common.copy') }}
|
{{ t('common.copy') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" :disabled="totalSelected === 0" @click="handleQuote">
|
<a-button type="primary" :loading="loading" :disabled="totalSelected === 0" @click="handleQuote">
|
||||||
{{ t('common.quote') }}
|
{{ t('common.quote') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -96,11 +97,13 @@
|
||||||
import apiTable from './table.vue';
|
import apiTable from './table.vue';
|
||||||
|
|
||||||
import { getProtocolList } from '@/api/modules/api-test/common';
|
import { getProtocolList } from '@/api/modules/api-test/common';
|
||||||
|
import { getSystemRequest } from '@/api/modules/api-test/scenario';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
import { ApiCaseDetail, ApiDefinitionDetail } from '@/models/apiTest/management';
|
import type { ApiCaseDetail, ApiDefinitionDetail } from '@/models/apiTest/management';
|
||||||
import { ApiScenarioTableItem } from '@/models/apiTest/scenario';
|
import type { ApiScenarioTableItem } from '@/models/apiTest/scenario';
|
||||||
|
import { ScenarioStepRefType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
export interface ImportData {
|
export interface ImportData {
|
||||||
api: MsTableDataItem<ApiDefinitionDetail>[];
|
api: MsTableDataItem<ApiDefinitionDetail>[];
|
||||||
|
@ -121,6 +124,7 @@
|
||||||
});
|
});
|
||||||
const activeKey = ref<'api' | 'case' | 'scenario'>('api');
|
const activeKey = ref<'api' | 'case' | 'scenario'>('api');
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
const selectedApis = ref<MsTableDataItem<ApiDefinitionDetail>[]>([]);
|
const selectedApis = ref<MsTableDataItem<ApiDefinitionDetail>[]>([]);
|
||||||
const selectedCases = ref<MsTableDataItem<ApiCaseDetail>[]>([]);
|
const selectedCases = ref<MsTableDataItem<ApiCaseDetail>[]>([]);
|
||||||
const selectedScenarios = ref<MsTableDataItem<ApiScenarioTableItem>[]>([]);
|
const selectedScenarios = ref<MsTableDataItem<ApiScenarioTableItem>[]>([]);
|
||||||
|
@ -167,7 +171,9 @@
|
||||||
const moduleIds = ref<(string | number)[]>([]);
|
const moduleIds = ref<(string | number)[]>([]);
|
||||||
|
|
||||||
function resetModule() {
|
function resetModule() {
|
||||||
|
nextTick(() => {
|
||||||
moduleTreeRef.value?.init(activeKey.value);
|
moduleTreeRef.value?.init(activeKey.value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleModuleSelect(ids: (string | number)[], node: MsTreeNodeData) {
|
function handleModuleSelect(ids: (string | number)[], node: MsTreeNodeData) {
|
||||||
|
@ -187,7 +193,74 @@
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCopy() {
|
async function getScenarioSteps(refType: ScenarioStepRefType.COPY | ScenarioStepRefType.REF) {
|
||||||
|
const scenarioMap: Record<string, MsTableDataItem<ApiScenarioTableItem>[]> = {};
|
||||||
|
selectedScenarios.value.forEach((e) => {
|
||||||
|
if (!scenarioMap[e.projectId]) {
|
||||||
|
scenarioMap[e.projectId] = [];
|
||||||
|
}
|
||||||
|
scenarioMap[e.projectId].push(e);
|
||||||
|
});
|
||||||
|
const scenarioRequestArr: any[] = [];
|
||||||
|
Object.keys(scenarioMap).forEach((projectId) => {
|
||||||
|
scenarioRequestArr.push(
|
||||||
|
getSystemRequest({
|
||||||
|
scenarioRequest: {
|
||||||
|
projectId,
|
||||||
|
unselectedIds: [],
|
||||||
|
selectedIds: scenarioMap[projectId].map((e) => e.id),
|
||||||
|
},
|
||||||
|
refType,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const allRes = await Promise.all(scenarioRequestArr);
|
||||||
|
let fullScenarioArr: MsTableDataItem<ApiScenarioTableItem>[] = [];
|
||||||
|
allRes.forEach((res) => {
|
||||||
|
fullScenarioArr.push(...res);
|
||||||
|
});
|
||||||
|
if (refType === ScenarioStepRefType.COPY) {
|
||||||
|
fullScenarioArr = fullScenarioArr.map((e) => {
|
||||||
|
return {
|
||||||
|
...e,
|
||||||
|
name: `copy-${e.name}`,
|
||||||
|
copyFromStepId: e.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
emit(
|
||||||
|
'copy',
|
||||||
|
cloneDeep({
|
||||||
|
api: selectedApis.value,
|
||||||
|
case: selectedCases.value,
|
||||||
|
scenario: fullScenarioArr,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
handleCancel();
|
||||||
|
} else {
|
||||||
|
emit(
|
||||||
|
'quote',
|
||||||
|
cloneDeep({
|
||||||
|
api: selectedApis.value,
|
||||||
|
case: selectedCases.value,
|
||||||
|
scenario: fullScenarioArr,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCopy() {
|
||||||
|
if (selectedScenarios.value.length > 0) {
|
||||||
|
await getScenarioSteps(ScenarioStepRefType.COPY);
|
||||||
|
} else {
|
||||||
emit(
|
emit(
|
||||||
'copy',
|
'copy',
|
||||||
cloneDeep({
|
cloneDeep({
|
||||||
|
@ -198,8 +271,12 @@
|
||||||
);
|
);
|
||||||
handleCancel();
|
handleCancel();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleQuote() {
|
async function handleQuote() {
|
||||||
|
if (selectedScenarios.value.length > 0) {
|
||||||
|
await getScenarioSteps(ScenarioStepRefType.REF);
|
||||||
|
} else {
|
||||||
emit(
|
emit(
|
||||||
'quote',
|
'quote',
|
||||||
cloneDeep({
|
cloneDeep({
|
||||||
|
@ -210,6 +287,7 @@
|
||||||
);
|
);
|
||||||
handleCancel();
|
handleCancel();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
initProtocolList();
|
initProtocolList();
|
||||||
|
|
|
@ -191,7 +191,72 @@
|
||||||
// 接口用例表格
|
// 接口用例表格
|
||||||
const useCaseTable = useTable(getCasePage, tableConfig);
|
const useCaseTable = useTable(getCasePage, tableConfig);
|
||||||
// 接口场景表格
|
// 接口场景表格
|
||||||
const useScenarioTable = useTable(getScenarioPage, tableConfig);
|
const useScenarioTable = useTable(getScenarioPage, {
|
||||||
|
...tableConfig,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: 'ID',
|
||||||
|
dataIndex: 'num',
|
||||||
|
slotName: 'num',
|
||||||
|
sortIndex: 1,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
fixed: 'left',
|
||||||
|
width: 100,
|
||||||
|
showTooltip: true,
|
||||||
|
columnSelectorDisabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiScenario.table.columns.name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
width: 134,
|
||||||
|
showTooltip: true,
|
||||||
|
columnSelectorDisabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiScenario.table.columns.level',
|
||||||
|
dataIndex: 'priority',
|
||||||
|
slotName: 'priority',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiScenario.table.columns.status',
|
||||||
|
dataIndex: 'status',
|
||||||
|
slotName: 'status',
|
||||||
|
titleSlotName: 'statusFilter',
|
||||||
|
width: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiScenario.table.columns.tags',
|
||||||
|
dataIndex: 'tags',
|
||||||
|
isTag: true,
|
||||||
|
isStringTag: true,
|
||||||
|
width: 240,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiScenario.table.columns.scenarioEnv',
|
||||||
|
dataIndex: 'environmentName',
|
||||||
|
width: 159,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiScenario.table.columns.steps',
|
||||||
|
dataIndex: 'stepTotal',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiScenario.table.columns.module',
|
||||||
|
dataIndex: 'modulePath',
|
||||||
|
width: 120,
|
||||||
|
showTooltip: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const methodFilterVisible = ref(false);
|
const methodFilterVisible = ref(false);
|
||||||
const methodFilters = ref(Object.keys(RequestMethods));
|
const methodFilters = ref(Object.keys(RequestMethods));
|
||||||
|
@ -324,6 +389,7 @@
|
||||||
case 'scenario':
|
case 'scenario':
|
||||||
default:
|
default:
|
||||||
routeName = ApiTestRouteEnum.API_TEST_SCENARIO;
|
routeName = ApiTestRouteEnum.API_TEST_SCENARIO;
|
||||||
|
query.sId = id;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
openNewPage(routeName, query);
|
openNewPage(routeName, query);
|
||||||
|
|
|
@ -133,8 +133,10 @@ export default function useCreateActions() {
|
||||||
...defaultStepItemCommon.config,
|
...defaultStepItemCommon.config,
|
||||||
...config,
|
...config,
|
||||||
},
|
},
|
||||||
|
children: item.children || [],
|
||||||
stepType,
|
stepType,
|
||||||
refType,
|
refType,
|
||||||
|
copyFromStepId: item.copyFromStepId,
|
||||||
...resourceField,
|
...resourceField,
|
||||||
name: name || item.name,
|
name: name || item.name,
|
||||||
sort: startOrder + index,
|
sort: startOrder + index,
|
||||||
|
|
|
@ -127,7 +127,7 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||||
import { deleteNodes, filterTree, getGenerateId } from '@/utils';
|
import { deleteNodes, filterTree, getGenerateId, mapTree } from '@/utils';
|
||||||
import { countNodes } from '@/utils/tree';
|
import { countNodes } from '@/utils/tree';
|
||||||
|
|
||||||
import { ApiScenarioDebugRequest, Scenario } from '@/models/apiTest/scenario';
|
import { ApiScenarioDebugRequest, Scenario } from '@/models/apiTest/scenario';
|
||||||
|
@ -207,16 +207,22 @@
|
||||||
|
|
||||||
async function handleBeforeBatchToggle(done: (closed: boolean) => void) {
|
async function handleBeforeBatchToggle(done: (closed: boolean) => void) {
|
||||||
try {
|
try {
|
||||||
let ids = checkedKeys.value;
|
const ids = new Set(checkedKeys.value);
|
||||||
if (batchToggleRange.value === 'top') {
|
if (batchToggleRange.value === 'top') {
|
||||||
ids = scenario.value.steps.map((item) => item.id);
|
scenario.value.steps = scenario.value.steps.map((item) => {
|
||||||
|
if (ids.has(item.id)) {
|
||||||
|
item.enable = isBatchEnable.value;
|
||||||
}
|
}
|
||||||
console.log('ids', ids);
|
return item;
|
||||||
await new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve(true);
|
|
||||||
}, 1000);
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
scenario.value.steps = mapTree(scenario.value.steps, (node) => {
|
||||||
|
if (ids.has(node.id)) {
|
||||||
|
node.enable = isBatchEnable.value;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
}
|
||||||
done(true);
|
done(true);
|
||||||
Message.success(isBatchEnable.value ? t('common.enableSuccess') : t('common.disableSuccess'));
|
Message.success(isBatchEnable.value ? t('common.enableSuccess') : t('common.disableSuccess'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -228,6 +234,10 @@
|
||||||
function batchDelete() {
|
function batchDelete() {
|
||||||
deleteNodes(scenario.value.steps, checkedKeys.value, 'id');
|
deleteNodes(scenario.value.steps, checkedKeys.value, 'id');
|
||||||
Message.success(t('common.deleteSuccess'));
|
Message.success(t('common.deleteSuccess'));
|
||||||
|
if (scenario.value.steps.length === 0) {
|
||||||
|
checkedAll.value = false;
|
||||||
|
indeterminate.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkReport() {
|
function checkReport() {
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
<div class="mr-[8px] flex items-center gap-[8px]">
|
<div class="mr-[8px] flex items-center gap-[8px]">
|
||||||
<!-- 步骤启用/禁用 -->
|
<!-- 步骤启用/禁用 -->
|
||||||
<a-switch
|
<a-switch
|
||||||
:default-checked="step.enable"
|
v-model:model-value="step.enable"
|
||||||
size="small"
|
size="small"
|
||||||
@click.stop="handleStepToggleEnable(step)"
|
@click.stop="handleStepToggleEnable(step)"
|
||||||
></a-switch>
|
></a-switch>
|
||||||
|
@ -242,6 +242,8 @@
|
||||||
:step-responses="scenario.stepResponses"
|
:step-responses="scenario.stepResponses"
|
||||||
@apply-step="applyApiStep"
|
@apply-step="applyApiStep"
|
||||||
@delete-step="deleteCaseStep"
|
@delete-step="deleteCaseStep"
|
||||||
|
@stop-debug="handleStopExecute(activeStep)"
|
||||||
|
@execute="(request, executeType) => handleApiExecute((request as unknown as RequestParam), executeType)"
|
||||||
/>
|
/>
|
||||||
<importApiDrawer
|
<importApiDrawer
|
||||||
v-if="importApiDrawerVisible"
|
v-if="importApiDrawerVisible"
|
||||||
|
@ -519,6 +521,7 @@
|
||||||
case 'copy':
|
case 'copy':
|
||||||
const id = getGenerateId();
|
const id = getGenerateId();
|
||||||
const stepDetail = stepDetails.value[node.id];
|
const stepDetail = stepDetails.value[node.id];
|
||||||
|
const { isQuoteScenario } = getStepType(node as ScenarioStepItem);
|
||||||
if (stepDetail) {
|
if (stepDetail) {
|
||||||
// 如果复制的步骤还有详情数据,则也复制详情数据
|
// 如果复制的步骤还有详情数据,则也复制详情数据
|
||||||
stepDetails.value[id] = cloneDeep(stepDetail);
|
stepDetails.value[id] = cloneDeep(stepDetail);
|
||||||
|
@ -531,13 +534,25 @@
|
||||||
mapTree<ScenarioStepItem>(node, (childNode) => {
|
mapTree<ScenarioStepItem>(node, (childNode) => {
|
||||||
const childId = getGenerateId();
|
const childId = getGenerateId();
|
||||||
const childStepDetail = stepDetails.value[node.id];
|
const childStepDetail = stepDetails.value[node.id];
|
||||||
|
let childCopyFromStepId = childNode.id;
|
||||||
if (childStepDetail) {
|
if (childStepDetail) {
|
||||||
// 如果复制的步骤下子步骤还有详情数据,则也复制详情数据
|
// 如果复制的步骤下子步骤还有详情数据,则也复制详情数据
|
||||||
stepDetails.value[childId] = cloneDeep(childStepDetail);
|
stepDetails.value[childId] = cloneDeep(childStepDetail);
|
||||||
}
|
}
|
||||||
|
if (!isQuoteScenario) {
|
||||||
|
// 非引用场景才处理复制来源 id
|
||||||
|
if (childStepDetail || (childNode.isNew && childNode.stepRefType === ScenarioStepRefType.REF)) {
|
||||||
|
// 如果子步骤查看过详情,则复制来源直接取它的 id
|
||||||
|
// 如果子步骤没有查看过详情,且是新建的步骤,且子步骤是引用的步骤,则还是取它本身的 id
|
||||||
|
childCopyFromStepId = childNode.id;
|
||||||
|
} else if (childNode.isNew && childNode.stepRefType === ScenarioStepRefType.COPY) {
|
||||||
|
// 如果子步骤没有查看过详情,且是新建的步骤,且子步骤是复制的步骤,则取它的来源 id
|
||||||
|
childCopyFromStepId = childNode.copyFromStepId;
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...cloneDeep(childNode),
|
...cloneDeep(childNode),
|
||||||
copyFromStepId: childNode.id,
|
copyFromStepId: childCopyFromStepId,
|
||||||
id: childId,
|
id: childId,
|
||||||
};
|
};
|
||||||
})[0]
|
})[0]
|
||||||
|
@ -545,7 +560,7 @@
|
||||||
name: `copy-${node.name}`,
|
name: `copy-${node.name}`,
|
||||||
copyFromStepId: node.id,
|
copyFromStepId: node.id,
|
||||||
sort: node.sort + 1,
|
sort: node.sort + 1,
|
||||||
isNew: false,
|
isNew: true,
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
'after',
|
'after',
|
||||||
|
@ -819,7 +834,12 @@
|
||||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, node.id, 'id');
|
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, node.id, 'id');
|
||||||
if (realStep) {
|
if (realStep) {
|
||||||
realStep.reportId = getGenerateId();
|
realStep.reportId = getGenerateId();
|
||||||
|
if (
|
||||||
|
[ScenarioStepType.API, ScenarioStepType.API_CASE, ScenarioStepType.CUSTOM_REQUEST].includes(realStep.stepType)
|
||||||
|
) {
|
||||||
|
// 请求和场景类型才直接显示执行中,其他控制器需要等待执行完毕才结算执行结果
|
||||||
realStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
realStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||||
|
}
|
||||||
const stepDetail = stepDetails.value[realStep.id];
|
const stepDetail = stepDetails.value[realStep.id];
|
||||||
delete scenario.value.stepResponses[realStep.id]; // 先移除上一次的执行结果
|
delete scenario.value.stepResponses[realStep.id]; // 先移除上一次的执行结果
|
||||||
realExecute(
|
realExecute(
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
* @description 接口测试-接口场景主页
|
* @description 接口测试-接口场景主页
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
@ -132,6 +133,7 @@
|
||||||
|
|
||||||
export type ScenarioParams = Scenario & TabItem;
|
export type ScenarioParams = Scenario & TabItem;
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -211,8 +213,6 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(selectRecycleCount);
|
|
||||||
|
|
||||||
const createRef = ref<InstanceType<typeof create>>();
|
const createRef = ref<InstanceType<typeof create>>();
|
||||||
const saveLoading = ref(false);
|
const saveLoading = ref(false);
|
||||||
|
|
||||||
|
@ -278,10 +278,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openScenarioTab(record: ApiScenarioTableItem, isCopy?: boolean) {
|
async function openScenarioTab(record: ApiScenarioTableItem | string, isCopy?: boolean) {
|
||||||
try {
|
try {
|
||||||
appStore.showLoading();
|
appStore.showLoading();
|
||||||
const res = await getScenarioDetail(record.id);
|
const res = await getScenarioDetail(typeof record === 'string' ? record : record.id);
|
||||||
res.stepDetails = {};
|
res.stepDetails = {};
|
||||||
if (!res.steps) {
|
if (!res.steps) {
|
||||||
res.steps = [];
|
res.steps = [];
|
||||||
|
@ -297,6 +297,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
selectRecycleCount();
|
||||||
|
if (route.query.sId) {
|
||||||
|
openScenarioTab(route.query.sId as string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const websocket = ref<WebSocket>();
|
const websocket = ref<WebSocket>();
|
||||||
const temporaryScenarioReportMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换tab导致报告丢失
|
const temporaryScenarioReportMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换tab导致报告丢失
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue