feat(接口测试):接口调试参数对齐&更新接口
This commit is contained in:
parent
a294a486a3
commit
8e2d37021d
|
@ -37,7 +37,7 @@
|
|||
"dependencies": {
|
||||
"@7polo/kity": "2.0.8",
|
||||
"@7polo/kityminder-core": "1.4.53",
|
||||
"@arco-design/web-vue": "^2.54.3",
|
||||
"@arco-design/web-vue": "^2.54.4",
|
||||
"@arco-themes/vue-ms-theme-default": "^0.0.30",
|
||||
"@form-create/arco-design": "^3.1.23",
|
||||
"@halo-dev/richtext-editor": "0.0.0-alpha.33",
|
||||
|
@ -48,7 +48,7 @@
|
|||
"@tiptap/vue-3": "^2.1.13",
|
||||
"@types/color": "^3.0.4",
|
||||
"@types/node": "^20.11.16",
|
||||
"@vueuse/core": "^10.4.1",
|
||||
"@vueuse/core": "^10.7.2",
|
||||
"@xmldom/xmldom": "^0.8.10",
|
||||
"ace-builds": "^1.24.2",
|
||||
"ahooks-vue": "^0.15.1",
|
||||
|
|
|
@ -446,13 +446,13 @@ export const mockFunctions: MockParamItem[] = [
|
|||
inputGroup: [
|
||||
{
|
||||
type: 'number',
|
||||
value: NaN,
|
||||
value: undefined,
|
||||
label: 'start',
|
||||
placeholder: 'ms.paramsInput.substrStartPlaceholder',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
value: NaN,
|
||||
value: undefined,
|
||||
label: 'end',
|
||||
placeholder: 'ms.paramsInput.substrEndPlaceholder',
|
||||
},
|
||||
|
@ -896,6 +896,7 @@ export const JMeterAllVars = [
|
|||
];
|
||||
// 同名函数但参数不同,需要特殊处理
|
||||
export const sameFuncNameVars = [
|
||||
'@county(true)',
|
||||
'@character(pool)',
|
||||
"@character('lower')",
|
||||
"@character('upper')",
|
||||
|
@ -905,4 +906,4 @@ export const sameFuncNameVars = [
|
|||
'@integer(1,100)',
|
||||
];
|
||||
// 带形参的函数集合,指的是函数入参为形参,如果用户未填写实参则不需要填充到入参框中
|
||||
export const formalParameterVars = ['@character(pool)', '@idCard(birth)'];
|
||||
export const formalParameterVars = ['@character(pool)', '@idCard(birth)', '@natural(1,100)', '@integer(1,100)'];
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
v-model:model-value="paramForm.func"
|
||||
:options="paramFuncOptions"
|
||||
:placeholder="t('ms.paramsInput.commonSelectPlaceholder')"
|
||||
allow-clear
|
||||
@change="(val) => handleParamFuncChange(val as string)"
|
||||
>
|
||||
<template #label="{ data }">
|
||||
|
@ -228,8 +229,8 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useEventListener, useVModel } from '@vueuse/core';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useEventListener, useStorage, useVModel } from '@vueuse/core';
|
||||
import { cloneDeep, includes } from 'lodash-es';
|
||||
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsCascader from '@/components/business/ms-cascader/index.vue';
|
||||
|
@ -264,7 +265,7 @@
|
|||
const autoCompleteParams = ref<MockParamItem[]>([]);
|
||||
const isFocusAutoComplete = ref(false);
|
||||
const popoverVisible = ref(false);
|
||||
const lastTenParams = ref<MockParamItem[]>(JSON.parse(localStorage.getItem('ms-lastTenParams') || '[]')); // 用户最近使用的前 10 个变量
|
||||
const lastTenParams = useStorage('ms-lastTenParams', [] as MockParamItem[]); // 用户最近使用的前 10 个变量
|
||||
|
||||
/**
|
||||
* 搜索变量
|
||||
|
@ -306,7 +307,8 @@
|
|||
const lastParamsItem = lastTenParams.value.find((e) => e.value === val);
|
||||
if (index > -1 && lastParamsItem) {
|
||||
// 如果已经存在,移动到第一位
|
||||
lastTenParams.value.splice(index, 1, lastParamsItem);
|
||||
lastTenParams.value.splice(index, 1);
|
||||
lastTenParams.value.unshift(lastParamsItem);
|
||||
} else {
|
||||
// 如果不存在,添加到第一位
|
||||
const mockParamItem = mockAllParams.find((e) => e.value === val);
|
||||
|
@ -318,7 +320,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
localStorage.setItem('ms-lastTenParams', JSON.stringify(lastTenParams.value));
|
||||
}
|
||||
|
||||
function selectAutoComplete(val: string) {
|
||||
|
@ -450,12 +451,13 @@
|
|||
const variableParams = variableMatch[2]?.split(',').map((param) => param.trim());
|
||||
const formalParameterVar = formalParameterVars.find((e) => e.includes(variableName)); // 匹配带形参的函数,监测输入的变量是否是带形参的函数
|
||||
|
||||
if (formalParameterVar && variableParams.length > 0) {
|
||||
// 如果是带形参的函数,则需要使用 原函数名(形式参数) 的变量
|
||||
handleParamTypeChange(formalParameterVar);
|
||||
} else if (sameFuncNameVars.includes(valueArr[0])) {
|
||||
if (sameFuncNameVars.includes(valueArr[0])) {
|
||||
// 先判断是否是同名函数,避免形参函数与变量函数冲突
|
||||
// 如果是同名函数,但可能是不同的变量,所以需要全等匹配
|
||||
handleParamTypeChange(valueArr[0]);
|
||||
} else if (formalParameterVar && variableParams && variableParams.length > 0) {
|
||||
// 如果是带形参的函数,则需要使用 原函数名(形式参数) 的变量
|
||||
handleParamTypeChange(formalParameterVar);
|
||||
} else {
|
||||
handleParamTypeChange(`@${variableName}`); // 设置匹配的变量参数输入框组
|
||||
}
|
||||
|
@ -516,8 +518,13 @@
|
|||
const paramVal = [paramForm.value.param1, paramForm.value.param2, paramForm.value.param3, paramForm.value.param4]
|
||||
.filter((e) => e !== '')
|
||||
.join(',');
|
||||
// 如果变量名是包含了入参的,则替换()内的入参为用户输入的
|
||||
resultStr = paramVal !== '' ? paramForm.value.type.replace(testReg, `(${paramVal})`) : paramForm.value.type;
|
||||
if (!paramForm.value.type.includes('(')) {
|
||||
// 存在部分函数是没有括号()的,但是有入参的
|
||||
resultStr = `${paramForm.value.type}(${paramVal || ''})`;
|
||||
} else {
|
||||
// 如果变量名是包含了入参的,则替换()内的入参为用户输入的
|
||||
resultStr = paramVal !== '' ? paramForm.value.type.replace(testReg, `(${paramVal})`) : paramForm.value.type;
|
||||
}
|
||||
} else {
|
||||
resultStr = paramForm.value.type;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
v-else-if="group.type === 'number'"
|
||||
v-model:model-value="innerForm[`${paramKey}${index + 1}`]"
|
||||
:placeholder="t(group.placeholder || '')"
|
||||
model-event="input"
|
||||
/>
|
||||
<a-input
|
||||
v-else-if="group.type === 'inputAppendSelect'"
|
||||
|
|
|
@ -38,8 +38,10 @@ export interface ResponseTiming {
|
|||
}
|
||||
// key-value参数信息
|
||||
export interface KeyValueParam {
|
||||
id: string; // id用于前端渲染,后台无此字段
|
||||
key: string;
|
||||
value: string;
|
||||
[key: string]: any; // 用于前端渲染时填充的自定义信息,后台无此字段
|
||||
}
|
||||
// 接口请求-带开启关闭的参数集合信息
|
||||
export interface EnableKeyValueParam extends KeyValueParam {
|
||||
|
@ -49,8 +51,8 @@ export interface EnableKeyValueParam extends KeyValueParam {
|
|||
// 接口请求公共参数集合信息
|
||||
export interface ExecuteRequestCommonParam extends EnableKeyValueParam {
|
||||
encode: boolean; // 是否编码
|
||||
maxLength: number;
|
||||
minLength: number;
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
paramType: RequestParamsType; // 参数类型
|
||||
required: boolean;
|
||||
description: string;
|
||||
|
@ -148,6 +150,7 @@ export interface ScriptCommonConfig {
|
|||
enableCommonScript: boolean; // 是否启用公共脚本
|
||||
script: string; // 脚本内容
|
||||
scriptId: string; // 脚本id
|
||||
scriptName: string; // 脚本名称
|
||||
scriptLanguage: RequestConditionScriptLanguageType; // 脚本语言
|
||||
params: KeyValueParam[]; // 公共脚本参数
|
||||
}
|
||||
|
@ -169,14 +172,16 @@ export interface ResponseVariableAssertion {
|
|||
}
|
||||
// 执行请求-前后置条件处理器
|
||||
export interface ExecuteConditionProcessorCommon {
|
||||
id: number; // 处理器ID,前端列表渲染需要,后台无此字段
|
||||
enable: boolean; // 是否启用
|
||||
name: string; // 请求名称
|
||||
name?: string; // 条件处理器名称
|
||||
processorType: RequestConditionProcessor;
|
||||
}
|
||||
// 执行请求-前后置条件-脚本处理器
|
||||
export type ScriptProcessor = ScriptCommonConfig;
|
||||
// 执行请求-前后置条件-SQL脚本处理器
|
||||
export interface SQLProcessor {
|
||||
description: string; // 描述
|
||||
dataSourceId: string; // 数据源ID
|
||||
environmentId: string; // 环境ID
|
||||
queryTimeout: number; // 超时时间
|
||||
|
@ -219,9 +224,9 @@ export interface ExtractProcessor {
|
|||
}
|
||||
// 执行请求-前后置条件配置
|
||||
export type ExecuteConditionProcessor = ExecuteConditionProcessorCommon &
|
||||
(ScriptProcessor | SQLProcessor | TimeWaitingProcessor | ExtractProcessor);
|
||||
Partial<ScriptProcessor & SQLProcessor & TimeWaitingProcessor & ExtractProcessor>;
|
||||
export interface ExecuteConditionConfig {
|
||||
enableGlobal: boolean; // 是否启用全局前置 默认为 true
|
||||
enableGlobal?: boolean; // 是否启用全局前/后置 默认为 true
|
||||
processors: ExecuteConditionProcessor[];
|
||||
}
|
||||
// 执行请求-断言配置子项
|
||||
|
@ -248,7 +253,7 @@ export interface ExecuteCommonChild {
|
|||
export interface ExecuteAuthConfig {
|
||||
authType: RequestAuthType;
|
||||
password: string;
|
||||
username: string;
|
||||
userName: string;
|
||||
}
|
||||
// 执行请求- body 配置-文本格式的 body
|
||||
export interface ExecuteValueBody {
|
||||
|
|
|
@ -447,8 +447,8 @@ export function decodeStringToCharset(str: string, charset = 'UTF-8') {
|
|||
|
||||
interface ParsedCurlOptions {
|
||||
url?: string;
|
||||
queryParameters?: { name: string; value: string }[];
|
||||
headers?: { name: string; value: string }[];
|
||||
queryParameters?: { key: string; value: string }[];
|
||||
headers?: { key: string; value: string }[];
|
||||
}
|
||||
/**
|
||||
* 解析 curl 脚本
|
||||
|
@ -467,8 +467,8 @@ export function parseCurlScript(curlScript: string): ParsedCurlOptions {
|
|||
const queryMatch = curlScript.match(/\?(.*?)'/);
|
||||
if (queryMatch) {
|
||||
const queryParams = queryMatch[1].split('&').map((param) => {
|
||||
const [name, value] = param.split('=');
|
||||
return { name, value };
|
||||
const [key, value] = param.split('=');
|
||||
return { key, value };
|
||||
});
|
||||
options.queryParameters = queryParams;
|
||||
}
|
||||
|
@ -478,10 +478,10 @@ export function parseCurlScript(curlScript: string): ParsedCurlOptions {
|
|||
if (headersMatch) {
|
||||
const headers = headersMatch.map((header) => {
|
||||
const [, value] = header.match(/-H\s+'([^']+)'/) || [];
|
||||
const [name, rawValue] = value.split(':');
|
||||
const trimmedName = name.trim();
|
||||
const [key, rawValue] = value.split(':');
|
||||
const trimmedName = key.trim();
|
||||
const trimmedValue = rawValue ? rawValue.trim() : '';
|
||||
return { name: trimmedName, value: trimmedValue };
|
||||
return { key: trimmedName, value: trimmedValue };
|
||||
});
|
||||
|
||||
// 过滤常用的 HTTP header
|
||||
|
@ -500,12 +500,12 @@ export function parseCurlScript(curlScript: string): ParsedCurlOptions {
|
|||
'sec-fetch-mode',
|
||||
'sec-fetch-site',
|
||||
'user-agent',
|
||||
'Connection',
|
||||
'Host',
|
||||
'Accept-Encoding',
|
||||
'X-Requested-With',
|
||||
'connection',
|
||||
'host',
|
||||
'accept-encoding',
|
||||
'x-requested-with',
|
||||
];
|
||||
options.headers = headers.filter((header) => !commonHeaders.includes(header.name.toLowerCase()));
|
||||
options.headers = headers.filter((header) => !commonHeaders.includes(header.key.toLowerCase()));
|
||||
}
|
||||
|
||||
return options;
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<template>
|
||||
<div class="condition-content">
|
||||
<!-- 脚本操作 -->
|
||||
<template v-if="condition.type === RequestConditionProcessor.SCRIPT">
|
||||
<a-radio-group v-model:model-value="condition.scriptType" size="small" class="mb-[16px]">
|
||||
<a-radio value="manual">{{ t('apiTestDebug.manual') }}</a-radio>
|
||||
<a-radio value="quote">{{ t('apiTestDebug.quote') }}</a-radio>
|
||||
<template v-if="condition.processorType === RequestConditionProcessor.SCRIPT">
|
||||
<a-radio-group v-model:model-value="condition.enableCommonScript" class="mb-[16px]">
|
||||
<a-radio :value="false">{{ t('apiTestDebug.manual') }}</a-radio>
|
||||
<a-radio :value="true">{{ t('apiTestDebug.quote') }}</a-radio>
|
||||
</a-radio-group>
|
||||
<div
|
||||
v-if="condition.scriptType === 'manual'"
|
||||
v-if="!condition.enableCommonScript"
|
||||
class="relative rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]"
|
||||
>
|
||||
<div v-if="isShowEditScriptNameInput" class="absolute left-[12px] z-10 w-[calc(100%-24px)]">
|
||||
<a-input
|
||||
ref="scriptNameInputRef"
|
||||
v-model:model-value="condition.name"
|
||||
v-model:model-value="condition.scriptName"
|
||||
:placeholder="t('apiTestDebug.preconditionScriptNamePlaceholder')"
|
||||
:max-length="255"
|
||||
size="small"
|
||||
|
@ -23,10 +23,10 @@
|
|||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<a-tooltip :content="condition.name">
|
||||
<a-tooltip :content="condition.scriptName">
|
||||
<div class="script-name-container">
|
||||
<div class="one-line-text mr-[4px] max-w-[110px] font-medium text-[var(--color-text-1)]">
|
||||
{{ condition.name }}
|
||||
{{ condition.scriptName }}
|
||||
</div>
|
||||
<MsIcon type="icon-icon_edit_outlined" class="edit-script-name-icon" @click="showEditScriptNameInput" />
|
||||
</div>
|
||||
|
@ -88,7 +88,7 @@
|
|||
<div v-else class="flex h-[calc(100%-47px)] flex-col">
|
||||
<div class="mb-[16px] flex w-full items-center bg-[var(--color-text-n9)] p-[12px]">
|
||||
<div class="text-[var(--color-text-2)]">
|
||||
{{ condition.quoteScript.name || '-' }}
|
||||
{{ condition.scriptName || '-' }}
|
||||
</div>
|
||||
<a-divider margin="8px" direction="vertical" />
|
||||
<MsButton type="text" class="font-medium">
|
||||
|
@ -116,7 +116,7 @@
|
|||
</MsBaseTable>
|
||||
<div v-show="commonScriptShowType === 'scriptContent'" class="h-[calc(100%-76px)]">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="condition.quoteScript.script"
|
||||
v-model:model-value="condition.script"
|
||||
theme="MS-text"
|
||||
height="100%"
|
||||
:show-full-screen="false"
|
||||
|
@ -128,18 +128,18 @@
|
|||
</div>
|
||||
</template>
|
||||
<!-- SQL操作 -->
|
||||
<template v-else-if="condition.type === RequestConditionProcessor.SQL">
|
||||
<template v-else-if="condition.processorType === RequestConditionProcessor.SQL">
|
||||
<div class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('common.desc') }}</div>
|
||||
<a-input
|
||||
v-model:model-value="condition.desc"
|
||||
v-model:model-value="condition.description"
|
||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||
:max-length="255"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-[16px] flex w-full items-center bg-[var(--color-text-n9)] p-[12px]">
|
||||
<div class="text-[var(--color-text-2)]">
|
||||
{{ condition.sqlSource.name || '-' }}
|
||||
{{ condition.scriptName || '-' }}
|
||||
</div>
|
||||
<a-divider margin="8px" direction="vertical" />
|
||||
<MsButton type="text" class="font-medium" @click="quoteSqlSourceDrawerVisible = true">
|
||||
|
@ -149,7 +149,7 @@
|
|||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.sqlScript') }}</div>
|
||||
<div class="mb-[16px] h-[300px]">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="condition.sqlSource.script"
|
||||
v-model:model-value="condition.script"
|
||||
theme="vs"
|
||||
height="276px"
|
||||
:language="LanguageEnum.SQL"
|
||||
|
@ -173,47 +173,43 @@
|
|||
</template>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="condition.sqlSource.storageType" size="small" type="button" class="w-fit">
|
||||
<a-radio value="column">{{ t('apiTestDebug.storageByCol') }}</a-radio>
|
||||
<a-radio value="result">{{ t('apiTestDebug.storageByResult') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div v-if="condition.sqlSource.storageType === 'column'" class="mb-[16px]">
|
||||
<div class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByCol') }}</div>
|
||||
<a-input
|
||||
v-model:model-value="condition.sqlSource.storageByCol"
|
||||
v-model:model-value="condition.variableNames"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiTestDebug.storageByColPlaceholder', { a: '{id_1}', b: '{username_1}' })"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByResult') }}</div>
|
||||
<a-input
|
||||
v-model:model-value="condition.sqlSource.storageByResult"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiTestDebug.storageByResultPlaceholder', { a: '${result}' })"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="condition.sqlSource.storageType === 'column'" class="sql-table-container">
|
||||
<div class="sql-table-container">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.extractParameter') }}</div>
|
||||
<paramTable
|
||||
v-model:params="condition.sqlSource.params"
|
||||
v-model:params="condition.variables"
|
||||
:columns="sqlSourceColumns"
|
||||
:selectable="false"
|
||||
@change="handleSqlSourceParamTableChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByResult') }}</div>
|
||||
<a-input
|
||||
v-model:model-value="condition.resultVariable"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiTestDebug.storageByResultPlaceholder', { a: '${result}' })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 等待时间 -->
|
||||
<div v-else-if="condition.type === RequestConditionProcessor.TIME_WAITING">
|
||||
<div v-else-if="condition.processorType === RequestConditionProcessor.TIME_WAITING">
|
||||
<div class="mb-[8px] flex items-center">
|
||||
{{ t('apiTestDebug.waitTime') }}
|
||||
<div class="text-[var(--color-text-4)]">(ms)</div>
|
||||
</div>
|
||||
<a-input-number v-model:model-value="condition.time" mode="button" :step="100" :min="0" class="w-[160px]" />
|
||||
<a-input-number v-model:model-value="condition.delay" mode="button" :step="100" :min="0" class="w-[160px]" />
|
||||
</div>
|
||||
<!-- 提取参数 -->
|
||||
<div v-else-if="condition.type === RequestConditionProcessor.EXTRACT">
|
||||
<div v-else-if="condition.processorType === RequestConditionProcessor.EXTRACT">
|
||||
<paramTable
|
||||
ref="extractParamsTableRef"
|
||||
v-model:params="condition.extractParams"
|
||||
|
@ -320,11 +316,12 @@
|
|||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { JSONPathExtract, RegexExtract, XPathExtract } from '@/models/apiTest/debug';
|
||||
import { ExecuteConditionProcessor, JSONPathExtract, RegexExtract, XPathExtract } from '@/models/apiTest/debug';
|
||||
import {
|
||||
RequestConditionProcessor,
|
||||
RequestExtractEnvType,
|
||||
RequestExtractExpressionEnum,
|
||||
RequestExtractExpressionRuleType,
|
||||
RequestExtractResultMatchingRule,
|
||||
RequestExtractScope,
|
||||
ResponseBodyXPathAssertionFormat,
|
||||
|
@ -333,14 +330,14 @@
|
|||
export type ExpressionConfig = (RegexExtract | JSONPathExtract | XPathExtract) & Record<string, any>;
|
||||
|
||||
const props = defineProps<{
|
||||
data: Record<string, any>;
|
||||
data: ExecuteConditionProcessor;
|
||||
response?: string; // 响应内容
|
||||
heightUsed?: number;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:data', data: Record<string, any>): void;
|
||||
(e: 'update:data', data: ExecuteConditionProcessor): void;
|
||||
(e: 'copy'): void;
|
||||
(e: 'delete', id: string): void;
|
||||
(e: 'delete', id: number): void;
|
||||
(e: 'change'): void;
|
||||
}>();
|
||||
|
||||
|
@ -358,18 +355,7 @@
|
|||
});
|
||||
}
|
||||
|
||||
const scriptEx = ref(`2023-12-04 11:19:28 INFO 9026fd6a 1-1 Thread started: 9026fd6a 1-1
|
||||
2023-12-04 11:19:28 ERROR 9026fd6a 1-1 Problem in JSR223 script JSR223Sampler, message: {}
|
||||
In file: inline evaluation of: prev.getResponseCode() import java.net.URI; import org.apache.http.client.method . . . '' Encountered "import" at line 2, column 1.
|
||||
in inline evaluation of: prev.getResponseCode() import java.net.URI; import org.apache.http.client.method . . . '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException
|
||||
org.apache.http.client.method . . . '' at line number 2
|
||||
`);
|
||||
const scriptEx = ref('');
|
||||
const { copy, isSupported } = useClipboard();
|
||||
|
||||
function copyScriptEx() {
|
||||
|
@ -382,7 +368,7 @@ org.apache.http.client.method . . . '' at line number 2
|
|||
}
|
||||
|
||||
function clearScript() {
|
||||
condition.value.script = '';
|
||||
condition.value.enable = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -403,7 +389,8 @@ org.apache.http.client.method . . . '' at line number 2
|
|||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
slotName: 'key',
|
||||
dataIndex: 'key',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
|
@ -413,7 +400,8 @@ org.apache.http.client.method . . . '' at line number 2
|
|||
},
|
||||
{
|
||||
title: 'apiTestDebug.desc',
|
||||
dataIndex: 'desc',
|
||||
dataIndex: 'description',
|
||||
slotName: 'description',
|
||||
showTooltip: true,
|
||||
},
|
||||
];
|
||||
|
@ -425,26 +413,26 @@ org.apache.http.client.method . . . '' at line number 2
|
|||
{
|
||||
id: new Date().getTime(),
|
||||
required: false,
|
||||
name: 'asdasd',
|
||||
key: 'asdasd',
|
||||
type: 'string',
|
||||
value: '',
|
||||
desc: '',
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
id: new Date().getTime(),
|
||||
required: true,
|
||||
name: '23d23d',
|
||||
key: '23d23d',
|
||||
type: 'string',
|
||||
value: '',
|
||||
desc: '',
|
||||
description: '',
|
||||
},
|
||||
] as any;
|
||||
|
||||
const sqlSourceColumns: ParamTableColumn[] = [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
dataIndex: 'key',
|
||||
slotName: 'key',
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramValue',
|
||||
|
@ -459,13 +447,14 @@ org.apache.http.client.method . . . '' at line number 2
|
|||
},
|
||||
];
|
||||
const quoteSqlSourceDrawerVisible = ref(false);
|
||||
function handleQuoteSqlSourceApply(sqlSource: any) {
|
||||
condition.value.sqlSource = sqlSource;
|
||||
function handleQuoteSqlSourceApply(sqlSource: Record<string, any>) {
|
||||
condition.value.script = sqlSource.script;
|
||||
condition.value.dataSourceId = sqlSource.id;
|
||||
emit('change');
|
||||
}
|
||||
|
||||
function handleSqlSourceParamTableChange(resultArr: any[], isInit?: boolean) {
|
||||
condition.value.sqlSource.params = [...resultArr];
|
||||
condition.value.variables = [...resultArr];
|
||||
if (!isInit) {
|
||||
emit('change');
|
||||
}
|
||||
|
@ -474,26 +463,26 @@ org.apache.http.client.method . . . '' at line number 2
|
|||
const extractParamsColumns: ParamTableColumn[] = [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
dataIndex: 'variableName',
|
||||
slotName: 'key',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramType',
|
||||
dataIndex: 'type',
|
||||
slotName: 'type',
|
||||
dataIndex: 'variableType',
|
||||
slotName: 'variableType',
|
||||
typeOptions: [
|
||||
{
|
||||
label: t('apiTestDebug.globalParameter'),
|
||||
value: 'global',
|
||||
value: RequestExtractEnvType.GLOBAL,
|
||||
},
|
||||
{
|
||||
label: t('apiTestDebug.envParameter'),
|
||||
value: 'env',
|
||||
value: RequestExtractEnvType.ENVIRONMENT,
|
||||
},
|
||||
{
|
||||
label: t('apiTestDebug.tempParameter'),
|
||||
value: 'temp',
|
||||
value: RequestExtractEnvType.TEMPORARY,
|
||||
},
|
||||
],
|
||||
width: 130,
|
||||
|
@ -505,15 +494,15 @@ org.apache.http.client.method . . . '' at line number 2
|
|||
typeOptions: [
|
||||
{
|
||||
label: t('apiTestDebug.regular'),
|
||||
value: 'regular',
|
||||
value: RequestExtractExpressionEnum.REGEX,
|
||||
},
|
||||
{
|
||||
label: 'JSONPath',
|
||||
value: 'JSONPath',
|
||||
value: RequestExtractExpressionEnum.JSON_PATH,
|
||||
},
|
||||
{
|
||||
label: 'XPath',
|
||||
value: 'XPath',
|
||||
value: RequestExtractExpressionEnum.X_PATH,
|
||||
},
|
||||
],
|
||||
width: 120,
|
||||
|
@ -525,35 +514,35 @@ org.apache.http.client.method . . . '' at line number 2
|
|||
typeOptions: [
|
||||
{
|
||||
label: 'Body',
|
||||
value: 'body',
|
||||
value: RequestExtractScope.BODY,
|
||||
},
|
||||
{
|
||||
label: 'Body (unescaped)',
|
||||
value: 'body_unescaped',
|
||||
value: RequestExtractScope.UNESCAPED_BODY,
|
||||
},
|
||||
{
|
||||
label: 'Body as a Document',
|
||||
value: 'body_document',
|
||||
value: RequestExtractScope.BODY_AS_DOCUMENT,
|
||||
},
|
||||
{
|
||||
label: 'URL',
|
||||
value: 'url',
|
||||
value: RequestExtractScope.URL,
|
||||
},
|
||||
{
|
||||
label: 'Request Headers',
|
||||
value: 'request_headers',
|
||||
value: RequestExtractScope.REQUEST_HEADERS,
|
||||
},
|
||||
{
|
||||
label: 'Response Headers',
|
||||
value: 'response_headers',
|
||||
value: RequestExtractScope.RESPONSE_HEADERS,
|
||||
},
|
||||
{
|
||||
label: 'Response Code',
|
||||
value: 'response_code',
|
||||
value: RequestExtractScope.RESPONSE_CODE,
|
||||
},
|
||||
{
|
||||
label: 'Response Message',
|
||||
value: 'response_message',
|
||||
value: RequestExtractScope.RESPONSE_MESSAGE,
|
||||
},
|
||||
],
|
||||
width: 190,
|
||||
|
@ -598,7 +587,7 @@ org.apache.http.client.method . . . '' at line number 2
|
|||
extractScope: RequestExtractScope.BODY,
|
||||
expression: '',
|
||||
extractType: RequestExtractExpressionEnum.REGEX,
|
||||
regexpMatchRule: 'expression',
|
||||
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
|
||||
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
|
||||
resultMatchingRuleNum: 1,
|
||||
responseFormat: ResponseBodyXPathAssertionFormat.XML,
|
||||
|
@ -632,13 +621,13 @@ org.apache.http.client.method . . . '' at line number 2
|
|||
* 提取参数表格-应用更多设置
|
||||
*/
|
||||
function applyMoreSetting(record: ExpressionConfig) {
|
||||
condition.value.extractParams = condition.value.extractParams.map((e) => {
|
||||
condition.value.extractParams = condition.value.extractParams?.map((e) => {
|
||||
if (e.id === activeRecord.value.id) {
|
||||
record.moreSettingPopoverVisible = false;
|
||||
return {
|
||||
...activeRecord.value,
|
||||
moreSettingPopoverVisible: false,
|
||||
};
|
||||
} as any; // TOOD: 这里的后台类型应该是不对的,需要修改
|
||||
}
|
||||
return e;
|
||||
});
|
||||
|
@ -649,7 +638,7 @@ org.apache.http.client.method . . . '' at line number 2
|
|||
* 提取参数表格-保存快速提取的配置
|
||||
*/
|
||||
function handleFastExtractionApply(config: RegexExtract | JSONPathExtract | XPathExtract) {
|
||||
condition.value.extractParams = condition.value.extractParams.map((e) => {
|
||||
condition.value.extractParams = condition.value.extractParams?.map((e) => {
|
||||
if (e.id === activeRecord.value.id) {
|
||||
return {
|
||||
...e,
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<slot name="titleRight"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="data.length > 0" class="flex h-[calc(100%-40px)] gap-[8px]">
|
||||
<div v-if="data.length > 0" class="flex h-[calc(100%-40px)] gap-[8px]">
|
||||
<div class="h-full w-[20%] min-w-[220px]">
|
||||
<conditionList
|
||||
v-model:list="data"
|
||||
|
@ -46,27 +46,27 @@
|
|||
import { conditionTypeNameMap } from '@/config/apiTest';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ConditionType } from '@/models/apiTest/debug';
|
||||
import { RequestConditionProcessor } from '@/enums/apiEnum';
|
||||
import { ConditionType, ExecuteConditionProcessor } from '@/models/apiTest/debug';
|
||||
import { RequestConditionProcessor, RequestConditionScriptLanguage } from '@/enums/apiEnum';
|
||||
|
||||
const props = defineProps<{
|
||||
list: Array<Record<string, any>>;
|
||||
list: ExecuteConditionProcessor[];
|
||||
conditionTypes: Array<ConditionType>;
|
||||
addText: string;
|
||||
heightUsed?: number;
|
||||
response?: string; // 响应内容
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:list', list: Array<Record<string, any>>): void;
|
||||
(e: 'update:list', list: ExecuteConditionProcessor[]): void;
|
||||
(e: 'change'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const data = useVModel(props, 'list', emit);
|
||||
const activeItem = ref<Record<string, any>>({});
|
||||
const activeItem = ref<ExecuteConditionProcessor>(data.value[0]);
|
||||
|
||||
function handleListActiveChange(item: Record<string, any>) {
|
||||
function handleListActiveChange(item: ExecuteConditionProcessor) {
|
||||
activeItem.value = item;
|
||||
}
|
||||
|
||||
|
@ -78,8 +78,8 @@
|
|||
...activeItem.value,
|
||||
id: new Date().getTime(),
|
||||
};
|
||||
data.value.push(copyItem);
|
||||
activeItem.value = copyItem;
|
||||
data.value.push(copyItem as ExecuteConditionProcessor);
|
||||
activeItem.value = copyItem as ExecuteConditionProcessor;
|
||||
emit('change');
|
||||
}
|
||||
|
||||
|
@ -117,45 +117,44 @@ org.apache.http.client.method . . . '' at line number 2
|
|||
case RequestConditionProcessor.SCRIPT:
|
||||
data.value.push({
|
||||
id,
|
||||
type: RequestConditionProcessor.SCRIPT,
|
||||
name: t('apiTestDebug.preconditionScriptName'),
|
||||
scriptType: 'manual',
|
||||
processorType: RequestConditionProcessor.SCRIPT,
|
||||
scriptName: t('apiTestDebug.preconditionScriptName'),
|
||||
enableCommonScript: false,
|
||||
enable: true,
|
||||
script: '',
|
||||
quoteScript: {
|
||||
name: '',
|
||||
script: scriptEx,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case RequestConditionProcessor.SQL:
|
||||
data.value.push({
|
||||
id,
|
||||
type: RequestConditionProcessor.SQL,
|
||||
desc: '',
|
||||
enable: true,
|
||||
sqlSource: {
|
||||
name: '',
|
||||
script: scriptEx,
|
||||
storageType: 'column',
|
||||
params: [],
|
||||
},
|
||||
script: scriptEx.value,
|
||||
scriptId: '',
|
||||
scriptLanguage: RequestConditionScriptLanguage.BEANSHELL,
|
||||
params: [],
|
||||
});
|
||||
break;
|
||||
// case RequestConditionProcessor.SQL:
|
||||
// data.value.push({
|
||||
// id,
|
||||
// enableCommonScript: false,
|
||||
// desc: '',
|
||||
// enable: true,
|
||||
// sqlSource: {
|
||||
// scriptName: '',
|
||||
// script: scriptEx,
|
||||
// storageType: 'column',
|
||||
// params: [],
|
||||
// },
|
||||
// });
|
||||
// break;
|
||||
case RequestConditionProcessor.TIME_WAITING:
|
||||
data.value.push({
|
||||
id,
|
||||
type: RequestConditionProcessor.TIME_WAITING,
|
||||
processorType: RequestConditionProcessor.TIME_WAITING,
|
||||
enable: true,
|
||||
time: 1000,
|
||||
delay: 1000,
|
||||
});
|
||||
break;
|
||||
case RequestConditionProcessor.EXTRACT:
|
||||
data.value.push({
|
||||
id,
|
||||
type: RequestConditionProcessor.EXTRACT,
|
||||
processorType: RequestConditionProcessor.EXTRACT,
|
||||
enable: true,
|
||||
extractParams: [],
|
||||
extractors: [],
|
||||
});
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -43,13 +43,15 @@
|
|||
import { conditionTypeNameMap } from '@/config/apiTest';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ExecuteConditionProcessor } from '@/models/apiTest/debug';
|
||||
|
||||
const props = defineProps<{
|
||||
list: Array<Record<string, any>>;
|
||||
list: ExecuteConditionProcessor[];
|
||||
activeId?: string | number;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:list', list: Array<Record<string, any>>): void;
|
||||
(e: 'activeChange', item: Record<string, any>): void;
|
||||
(e: 'update:list', list: ExecuteConditionProcessor[]): void;
|
||||
(e: 'activeChange', item: ExecuteConditionProcessor): void;
|
||||
(e: 'change'): void;
|
||||
}>();
|
||||
|
||||
|
@ -58,7 +60,7 @@
|
|||
// 当前聚焦的列表项
|
||||
const focusItemKey = ref<any>('');
|
||||
// 当前选中的列表项
|
||||
const activeItem = ref<Record<string, any>>({});
|
||||
const activeItem = ref<ExecuteConditionProcessor>({} as ExecuteConditionProcessor);
|
||||
const itemMoreActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'common.copy',
|
||||
|
@ -81,7 +83,7 @@
|
|||
}
|
||||
);
|
||||
|
||||
function handleItemClick(item: Record<string, any>) {
|
||||
function handleItemClick(item: ExecuteConditionProcessor) {
|
||||
activeItem.value = item;
|
||||
emit('activeChange', item);
|
||||
}
|
||||
|
@ -90,7 +92,7 @@
|
|||
* 复制列表项
|
||||
* @param item 列表项
|
||||
*/
|
||||
function copyListItem(item: Record<string, any>) {
|
||||
function copyListItem(item: ExecuteConditionProcessor) {
|
||||
const copyItem = {
|
||||
...item,
|
||||
id: new Date().getTime(),
|
||||
|
@ -104,7 +106,7 @@
|
|||
* 删除列表项
|
||||
* @param item 列表项
|
||||
*/
|
||||
function deleteListItem(item: Record<string, any>) {
|
||||
function deleteListItem(item: ExecuteConditionProcessor) {
|
||||
data.value = data.value.filter((precondition) => precondition.id !== item.id);
|
||||
if (activeItem.value.id === item.id) {
|
||||
[activeItem.value] = data.value;
|
||||
|
@ -117,7 +119,7 @@
|
|||
* @param event
|
||||
* @param item
|
||||
*/
|
||||
function handleMoreActionSelect(event: ActionsItem, item: Record<string, any>) {
|
||||
function handleMoreActionSelect(event: ActionsItem, item: ExecuteConditionProcessor) {
|
||||
if (event.eventTag === 'copy') {
|
||||
copyListItem(item);
|
||||
} else if (event.eventTag === 'delete') {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
disabled-width-drag
|
||||
@confirm="emit('apply', expressionForm)"
|
||||
>
|
||||
<div v-if="expressionForm.expressionType === 'regular'" class="h-[400px]">
|
||||
<div v-if="expressionForm.extractType === RequestExtractExpressionEnum.REGEX" class="h-[400px]">
|
||||
<MsCodeEditor
|
||||
:model-value="props.response"
|
||||
theme="vs"
|
||||
|
@ -18,15 +18,15 @@
|
|||
read-only
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="expressionForm.expressionType === 'JSONPath'" class="code-container">
|
||||
<div v-else-if="expressionForm.extractType === RequestExtractExpressionEnum.JSON_PATH" class="code-container">
|
||||
<MsJsonPathPicker :data="props.response || ''" class="bg-white" @pick="handlePathPick" />
|
||||
</div>
|
||||
<div v-else-if="expressionForm.expressionType === 'XPath'" class="code-container">
|
||||
<div v-else-if="expressionForm.extractType === RequestExtractExpressionEnum.X_PATH" class="code-container">
|
||||
<MsXPathPicker :xml-string="props.response || ''" class="bg-white" @pick="handlePathPick" />
|
||||
</div>
|
||||
<a-form ref="expressionFormRef" :model="expressionForm" layout="vertical" class="mt-[16px]">
|
||||
<a-form-item
|
||||
v-if="expressionForm.expressionType === 'regular'"
|
||||
v-if="expressionForm.extractType === RequestExtractExpressionEnum.REGEX"
|
||||
field="expression"
|
||||
:label="t('apiTestDebug.regularExpression')"
|
||||
:rules="[{ required: true, message: t('apiTestDebug.regularExpressionRequired') }]"
|
||||
|
@ -45,7 +45,7 @@
|
|||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-else-if="expressionForm.expressionType === 'JSONPath'"
|
||||
v-else-if="expressionForm.extractType === RequestExtractExpressionEnum.JSON_PATH"
|
||||
field="expression"
|
||||
label="JSONPath"
|
||||
:rules="[{ required: true, message: t('apiTestDebug.JSONPathRequired') }]"
|
||||
|
@ -101,13 +101,15 @@
|
|||
</a-tooltip>
|
||||
</div>
|
||||
<a-radio-group
|
||||
v-if="expressionForm.expressionType === 'regular'"
|
||||
v-model:model-value="expressionForm.regexpMatchRule"
|
||||
v-if="expressionForm.extractType === RequestExtractExpressionEnum.REGEX"
|
||||
v-model:model-value="expressionForm.expressionMatchingRule"
|
||||
type="button"
|
||||
size="small"
|
||||
>
|
||||
<a-radio value="expression">{{ t('apiTestDebug.matchExpression') }}</a-radio>
|
||||
<a-radio value="group">{{ t('apiTestDebug.matchGroup') }}</a-radio>
|
||||
<a-radio :value="RequestExtractExpressionRuleType.EXPRESSION">
|
||||
{{ t('apiTestDebug.matchExpression') }}
|
||||
</a-radio>
|
||||
<a-radio :value="RequestExtractExpressionRuleType.GROUP">{{ t('apiTestDebug.matchGroup') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div class="match-result">
|
||||
|
@ -151,7 +153,8 @@
|
|||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { matchXMLWithXPath } from '@/utils/xpath';
|
||||
|
||||
import { JSONPathExtract, RegexExtract, XPathExtract } from '@/models/apiTest/debug';
|
||||
import type { JSONPathExtract, RegexExtract, XPathExtract } from '@/models/apiTest/debug';
|
||||
import { RequestExtractExpressionEnum, RequestExtractExpressionRuleType } from '@/enums/apiEnum';
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
|
@ -190,8 +193,8 @@
|
|||
* 测试表达式
|
||||
*/
|
||||
function testExpression() {
|
||||
switch (props.config.expressionType) {
|
||||
case 'XPath':
|
||||
switch (props.config.extractType) {
|
||||
case RequestExtractExpressionEnum.X_PATH:
|
||||
const nodes = matchXMLWithXPath(props.response || '', expressionForm.value.expression);
|
||||
if (nodes) {
|
||||
// 直接匹配到文本信息
|
||||
|
@ -211,7 +214,7 @@
|
|||
matchResult.value = [];
|
||||
}
|
||||
break;
|
||||
case 'JSONPath':
|
||||
case RequestExtractExpressionEnum.JSON_PATH:
|
||||
try {
|
||||
matchResult.value = JSONPath({
|
||||
json: props.response ? JSON.parse(props.response) : '',
|
||||
|
@ -221,7 +224,7 @@
|
|||
matchResult.value = JSONPath({ json: props.response || '', path: expressionForm.value.expression });
|
||||
}
|
||||
break;
|
||||
case 'regular':
|
||||
case RequestExtractExpressionEnum.REGEX:
|
||||
default:
|
||||
// 先把前后的/和g去掉才能生成正则表达式
|
||||
const matchesIterator = props.response?.matchAll(
|
||||
|
@ -230,7 +233,7 @@
|
|||
if (matchesIterator) {
|
||||
const matches = Array.from(matchesIterator);
|
||||
try {
|
||||
if (expressionForm.value.regexpMatchRule === 'expression') {
|
||||
if (expressionForm.value.expressionMatchingRule === 'expression') {
|
||||
// 匹配表达式,取第一个匹配结果,是完整匹配结果
|
||||
matchResult.value = matches.map((e) => e[0]) || [];
|
||||
} else {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="expressionForm.expressionType === 'regular' && props.isPopover" class="mb-[16px]">
|
||||
<div v-if="expressionForm.extractType === RequestExtractExpressionEnum.REGEX && props.isPopover" class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[14px] text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.expressionMatchRule') }}
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="expressionForm.regexpMatchRule" size="small">
|
||||
<a-radio value="expression">
|
||||
<a-radio-group v-model:model-value="expressionForm.expressionMatchingRule" size="small">
|
||||
<a-radio :value="RequestExtractExpressionRuleType.EXPRESSION.toLowerCase()">
|
||||
<div class="flex items-center">
|
||||
{{ t('apiTestDebug.matchExpression') }}
|
||||
<a-tooltip :content="t('apiTestDebug.matchExpressionTip')" :content-style="{ maxWidth: '500px' }">
|
||||
|
@ -16,7 +16,7 @@
|
|||
</a-tooltip>
|
||||
</div>
|
||||
</a-radio>
|
||||
<a-radio value="group">
|
||||
<a-radio :value="RequestExtractExpressionRuleType.GROUP.toLowerCase()">
|
||||
<div class="flex items-center">
|
||||
{{ t('apiTestDebug.matchGroup') }}
|
||||
<a-tooltip :content="t('apiTestDebug.matchGroupTip')" :content-style="{ maxWidth: '500px' }">
|
||||
|
@ -33,8 +33,8 @@
|
|||
<div class="mb-[8px] text-[14px] text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.resultMatchRule') }}
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="expressionForm.resultMatchRule" size="small">
|
||||
<a-radio value="random">
|
||||
<a-radio-group v-model:model-value="expressionForm.resultMatchingRule" size="small">
|
||||
<a-radio :value="RequestExtractResultMatchingRule.RANDOM.toLowerCase()">
|
||||
<div class="flex items-center">
|
||||
{{ t('apiTestDebug.randomMatch') }}
|
||||
<a-tooltip :content="t('apiTestDebug.randomMatchTip')" :content-style="{ maxWidth: '400px' }">
|
||||
|
@ -45,7 +45,7 @@
|
|||
</a-tooltip>
|
||||
</div>
|
||||
</a-radio>
|
||||
<a-radio value="specify">
|
||||
<a-radio :value="RequestExtractResultMatchingRule.SPECIFIC.toLowerCase()">
|
||||
<div class="flex items-center">
|
||||
{{ t('apiTestDebug.specifyMatch') }}
|
||||
<a-tooltip :content="t('apiTestDebug.specifyMatchTip')" :content-style="{ maxWidth: '400px' }">
|
||||
|
@ -56,7 +56,7 @@
|
|||
</a-tooltip>
|
||||
</div>
|
||||
</a-radio>
|
||||
<a-radio value="all">
|
||||
<a-radio :value="RequestExtractResultMatchingRule.ALL.toLowerCase()">
|
||||
<div class="flex items-center">
|
||||
{{ t('apiTestDebug.allMatch') }}
|
||||
<a-tooltip :content="t('apiTestDebug.allMatchTip')" :content-style="{ maxWidth: '400px' }">
|
||||
|
@ -69,7 +69,7 @@
|
|||
</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div v-if="expressionForm.resultMatchRule === 'specify'" class="mb-[16px]">
|
||||
<div v-if="expressionForm.resultMatchingRule === RequestExtractResultMatchingRule.SPECIFIC" class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.specifyMatchResult') }}
|
||||
</div>
|
||||
|
@ -79,13 +79,13 @@
|
|||
{{ t('apiTestDebug.unit') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="expressionForm.expressionType === 'XPath'" class="mb-[16px]">
|
||||
<div v-if="expressionForm.extractType === RequestExtractExpressionEnum.X_PATH" class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.contentType') }}
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="expressionForm.xmlMatchContentType" size="small">
|
||||
<a-radio value="xml"> XML </a-radio>
|
||||
<a-radio value="html"> HTML </a-radio>
|
||||
<a-radio-group v-model:model-value="expressionForm.responseFormat" size="small">
|
||||
<a-radio :value="ResponseBodyXPathAssertionFormat.XML"> {{ ResponseBodyXPathAssertionFormat.XML }} </a-radio>
|
||||
<a-radio :value="ResponseBodyXPathAssertionFormat.HTML"> {{ ResponseBodyXPathAssertionFormat.HTML }} </a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -97,6 +97,12 @@
|
|||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { JSONPathExtract, RegexExtract, XPathExtract } from '@/models/apiTest/debug';
|
||||
import {
|
||||
RequestExtractExpressionEnum,
|
||||
RequestExtractExpressionRuleType,
|
||||
RequestExtractResultMatchingRule,
|
||||
ResponseBodyXPathAssertionFormat,
|
||||
} from '@/enums/apiEnum';
|
||||
|
||||
export type ExpressionConfig = (RegexExtract | JSONPathExtract | XPathExtract) & Record<string, any>;
|
||||
|
||||
|
|
|
@ -29,7 +29,11 @@
|
|||
</template>
|
||||
<!-- 表格列 slot -->
|
||||
<template #key="{ record, columnConfig }">
|
||||
<a-popover position="tl" :disabled="!record.key || record.key.trim() === ''" class="ms-params-input-popover">
|
||||
<a-popover
|
||||
position="tl"
|
||||
:disabled="!record[columnConfig.dataIndex as string] || record[columnConfig.dataIndex as string].trim() === ''"
|
||||
class="ms-params-input-popover"
|
||||
>
|
||||
<template #content>
|
||||
<div class="param-popover-title">
|
||||
{{ t('apiTestDebug.paramName') }}
|
||||
|
@ -47,7 +51,7 @@
|
|||
/>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #type="{ record, columnConfig }">
|
||||
<template #paramType="{ record, columnConfig }">
|
||||
<a-tooltip
|
||||
v-if="columnConfig.hasRequired"
|
||||
:content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')"
|
||||
|
@ -64,7 +68,7 @@
|
|||
</MsButton>
|
||||
</a-tooltip>
|
||||
<a-select
|
||||
v-model:model-value="record.type"
|
||||
v-model:model-value="record.paramType"
|
||||
:options="columnConfig.typeOptions || []"
|
||||
class="param-input w-full"
|
||||
@change="(val) => handleTypeChange(val, record)"
|
||||
|
@ -78,9 +82,17 @@
|
|||
@change="(val) => handleExpressionTypeChange(val)"
|
||||
/>
|
||||
</template>
|
||||
<template #range="{ record, columnConfig }">
|
||||
<template #variableType="{ record, columnConfig }">
|
||||
<a-select
|
||||
v-model:model-value="record.range"
|
||||
v-model:model-value="record.variableType"
|
||||
:options="columnConfig.typeOptions || []"
|
||||
class="param-input w-[110px]"
|
||||
@change="(val) => handleVariableTypeChange(val)"
|
||||
/>
|
||||
</template>
|
||||
<template #extractScope="{ record, columnConfig }">
|
||||
<a-select
|
||||
v-model:model-value="record.extractScope"
|
||||
:options="columnConfig.typeOptions || []"
|
||||
class="param-input w-[180px]"
|
||||
@change="(val) => handleRangeChange(val)"
|
||||
|
@ -123,19 +135,19 @@
|
|||
<template #lengthRange="{ record }">
|
||||
<div class="flex items-center justify-between">
|
||||
<a-input-number
|
||||
v-model:model-value="record.min"
|
||||
v-model:model-value="record.minLength"
|
||||
:placeholder="t('apiTestDebug.paramMin')"
|
||||
:min="0"
|
||||
class="param-input param-input-number"
|
||||
@change="(val) => addTableLine(val || '', 'min')"
|
||||
@change="(val) => addTableLine(val, 'minLength')"
|
||||
/>
|
||||
<div class="mx-[4px]">~</div>
|
||||
<a-input-number
|
||||
v-model:model-value="record.max"
|
||||
v-model:model-value="record.maxLength"
|
||||
:placeholder="t('apiTestDebug.paramMax')"
|
||||
:min="0"
|
||||
class="param-input"
|
||||
@change="(val) => addTableLine(val || '', 'max')"
|
||||
@change="(val) => addTableLine(val, 'maxLength')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -314,23 +326,6 @@
|
|||
import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
|
||||
import { SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
interface Param {
|
||||
id: number;
|
||||
required: boolean;
|
||||
key: string;
|
||||
type: string;
|
||||
value: string;
|
||||
min: number | undefined;
|
||||
max: number | undefined;
|
||||
contentType: RequestContentTypeEnum;
|
||||
description: string;
|
||||
encode: boolean;
|
||||
tag: string[];
|
||||
enable: boolean;
|
||||
mustContain: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export type ParamTableColumn = MsTableColumnData & {
|
||||
isNormal?: boolean; // 用于 value 列区分是普通输入框还是 MsParamsInput
|
||||
hasRequired?: boolean; // 用于 type 列区分是否有 required 星号
|
||||
|
@ -343,8 +338,8 @@
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
params: any[];
|
||||
defaultParamItem?: Partial<Param>; // 默认参数项,用于添加新行时的默认值
|
||||
params?: any[];
|
||||
defaultParamItem?: Record<string, any>; // 默认参数项,用于添加新行时的默认值
|
||||
columns: ParamTableColumn[];
|
||||
scroll?: {
|
||||
x?: number | string;
|
||||
|
@ -363,6 +358,7 @@
|
|||
response?: string; // 响应内容
|
||||
}>(),
|
||||
{
|
||||
params: () => [],
|
||||
selectable: true,
|
||||
showSetting: false,
|
||||
tableKey: undefined,
|
||||
|
@ -370,10 +366,10 @@
|
|||
defaultParamItem: () => ({
|
||||
required: false,
|
||||
key: '',
|
||||
type: RequestParamsType.STRING,
|
||||
paramType: RequestParamsType.STRING,
|
||||
value: '',
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
minLength: undefined,
|
||||
maxLength: undefined,
|
||||
contentType: RequestContentTypeEnum.TEXT,
|
||||
tag: [],
|
||||
description: '',
|
||||
|
@ -570,9 +566,9 @@
|
|||
|
||||
function handleTypeChange(
|
||||
val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[],
|
||||
record: Partial<Param>
|
||||
record: Record<string, any>
|
||||
) {
|
||||
addTableLine(val as string, 'type');
|
||||
addTableLine(val as string, 'paramType');
|
||||
// 根据参数类型自动推断 Content-Type 类型
|
||||
if (record.contentType) {
|
||||
if (val === 'file') {
|
||||
|
@ -591,10 +587,16 @@
|
|||
addTableLine(val as string, 'expressionType');
|
||||
}
|
||||
|
||||
function handleVariableTypeChange(
|
||||
val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
|
||||
) {
|
||||
addTableLine(val as string, 'variableType');
|
||||
}
|
||||
|
||||
function handleRangeChange(
|
||||
val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
|
||||
) {
|
||||
addTableLine(val as string, 'range');
|
||||
addTableLine(val as string, 'extractScope');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<a-form v-if="authForm.authType !== 'NONE'" ref="authFormRef" :model="authForm" layout="vertical">
|
||||
<a-form-item :label="t('apiTestDebug.username')">
|
||||
<a-input
|
||||
v-model:model-value="authForm.username"
|
||||
v-model:model-value="authForm.userName"
|
||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||
class="w-[450px]"
|
||||
:max-length="255"
|
||||
|
@ -58,7 +58,7 @@
|
|||
|
||||
function authTypeChange(val: string | number | boolean) {
|
||||
if (val === 'none') {
|
||||
authForm.value.username = '';
|
||||
authForm.value.userName = '';
|
||||
authForm.value.password = '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,18 @@
|
|||
v-model:model-value="innerParams.bodyType"
|
||||
type="button"
|
||||
size="small"
|
||||
@change="(val) => changeBodyFormat(val as string)"
|
||||
@change="(val) => changeBodyFormat(val as RequestBodyFormat)"
|
||||
>
|
||||
<a-radio v-for="item of RequestBodyFormat" :key="item" :value="item">{{ requestBodyTypeMap[item] }}</a-radio>
|
||||
<a-radio v-for="item of RequestBodyFormat" :key="item" :value="item">
|
||||
{{ requestBodyTypeMap[item] }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
<batchAddKeyVal v-if="showParamTable" :params="currentTableParams" @apply="handleBatchParamApply" />
|
||||
<batchAddKeyVal
|
||||
v-if="showParamTable"
|
||||
:params="currentTableParams"
|
||||
:default-param-item="defaultParamItem"
|
||||
@apply="handleBatchParamApply"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="innerParams.bodyType === RequestBodyFormat.NONE"
|
||||
|
@ -24,6 +31,7 @@
|
|||
:height-used="heightUsed"
|
||||
:show-setting="true"
|
||||
:table-key="TableKeyEnum.API_TEST_DEBUG_FORM_DATA"
|
||||
:default-param-item="defaultParamItem"
|
||||
@change="handleParamTableChange"
|
||||
/>
|
||||
<paramTable
|
||||
|
@ -34,6 +42,7 @@
|
|||
:height-used="heightUsed"
|
||||
:show-setting="true"
|
||||
:table-key="TableKeyEnum.API_TEST_DEBUG_FORM_URL_ENCODE"
|
||||
:default-param-item="defaultParamItem"
|
||||
@change="handleParamTableChange"
|
||||
/>
|
||||
<div v-else-if="innerParams.bodyType === RequestBodyFormat.BINARY">
|
||||
|
@ -94,7 +103,7 @@
|
|||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ExecuteBody } from '@/models/apiTest/debug';
|
||||
import { RequestBodyFormat, RequestParamsType } from '@/enums/apiEnum';
|
||||
import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -110,21 +119,33 @@
|
|||
const { t } = useI18n();
|
||||
|
||||
const innerParams = useVModel(props, 'params', emit);
|
||||
const defaultParamItem = {
|
||||
key: '',
|
||||
value: '',
|
||||
paramType: RequestParamsType.STRING,
|
||||
description: '',
|
||||
required: false,
|
||||
maxLength: undefined,
|
||||
minLength: undefined,
|
||||
encode: false,
|
||||
enable: true,
|
||||
contentType: RequestContentTypeEnum.TEXT,
|
||||
};
|
||||
|
||||
const columns = computed<ParamTableColumn[]>(() => [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
dataIndex: 'key',
|
||||
slotName: 'key',
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramType',
|
||||
dataIndex: 'type',
|
||||
slotName: 'type',
|
||||
dataIndex: 'paramType',
|
||||
slotName: 'paramType',
|
||||
hasRequired: true,
|
||||
typeOptions: Object.keys(RequestParamsType).map((key) => ({
|
||||
label: RequestParamsType[key],
|
||||
value: key,
|
||||
typeOptions: Object.values(RequestParamsType).map((val) => ({
|
||||
label: val,
|
||||
value: val,
|
||||
})),
|
||||
width: 120,
|
||||
},
|
||||
|
@ -141,16 +162,17 @@
|
|||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.desc',
|
||||
dataIndex: 'description',
|
||||
slotName: 'description',
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.encode',
|
||||
dataIndex: 'encode',
|
||||
slotName: 'encode',
|
||||
titleSlotName: 'encodeTitle',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.desc',
|
||||
dataIndex: 'description',
|
||||
slotName: 'description',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
|
@ -254,8 +276,8 @@
|
|||
emit('change');
|
||||
}
|
||||
|
||||
function changeBodyFormat(val: string) {
|
||||
innerParams.value.bodyType = val as RequestBodyFormat;
|
||||
function changeBodyFormat(val: RequestBodyFormat) {
|
||||
innerParams.value.bodyType = val;
|
||||
emit('change');
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -38,12 +38,12 @@
|
|||
const { t } = useI18n();
|
||||
|
||||
const innerParams = useVModel(props, 'params', emit);
|
||||
const defaultParamItem = ref<EnableKeyValueParam>({
|
||||
const defaultParamItem = {
|
||||
key: '',
|
||||
value: '',
|
||||
description: '',
|
||||
enable: true,
|
||||
});
|
||||
};
|
||||
|
||||
const columns: ParamTableColumn[] = [
|
||||
{
|
||||
|
|
|
@ -122,12 +122,12 @@
|
|||
/>
|
||||
<precondition
|
||||
v-else-if="activeDebug.activeTab === RequestComposition.PRECONDITION"
|
||||
v-model:params="activeDebug.children[0].preProcessorConfig.processors"
|
||||
v-model:config="activeDebug.children[0].preProcessorConfig"
|
||||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
<postcondition
|
||||
v-else-if="activeDebug.activeTab === RequestComposition.POST_CONDITION"
|
||||
v-model:params="activeDebug.children[0].postProcessorConfig.processors"
|
||||
v-model:config="activeDebug.children[0].postProcessorConfig"
|
||||
:response="activeDebug.response.requestResults[0]?.responseResult.body"
|
||||
:layout="activeLayout"
|
||||
:second-box-height="secondBoxHeight"
|
||||
|
@ -199,6 +199,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
|
||||
import { cloneDeep, debounce } from 'lodash-es';
|
||||
|
||||
|
@ -215,7 +216,7 @@
|
|||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue';
|
||||
|
||||
import { addDebug, executeDebug, getDebugDetail } from '@/api/modules/api-test/debug';
|
||||
import { addDebug, executeDebug, getDebugDetail, updateDebug } from '@/api/modules/api-test/debug';
|
||||
import { getPluginScript, getProtocolList } from '@/api/modules/api-test/management';
|
||||
import { getSocket } from '@/api/modules/project-management/commonScript';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
@ -244,11 +245,14 @@
|
|||
|
||||
const props = defineProps<{
|
||||
moduleTree: ModuleTreeNode[]; // 接口模块树
|
||||
detailLoading: boolean; // 接口详情加载状态
|
||||
}>();
|
||||
const emit = defineEmits(['update:detailLoading', 'addDone']);
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const loading = useVModel(props, 'detailLoading', emit);
|
||||
const initDefaultId = `debug-${Date.now()}`;
|
||||
const activeRequestTab = ref<string | number>(initDefaultId);
|
||||
const defaultBodyParams: ExecuteBody = {
|
||||
|
@ -313,7 +317,7 @@
|
|||
linkFileIds: [],
|
||||
authConfig: {
|
||||
authType: RequestAuthType.NONE,
|
||||
username: '',
|
||||
userName: '',
|
||||
password: '',
|
||||
},
|
||||
children: [
|
||||
|
@ -365,7 +369,10 @@
|
|||
}
|
||||
|
||||
function handleActiveDebugChange() {
|
||||
activeDebug.value.unSaved = true;
|
||||
if (!loading.value) {
|
||||
// 如果是因为加载详情触发的change则不需要标记为未保存
|
||||
activeDebug.value.unSaved = true;
|
||||
}
|
||||
}
|
||||
|
||||
function addDebugTab(defaultProps?: Partial<TabItem>) {
|
||||
|
@ -373,6 +380,7 @@
|
|||
debugTabs.value.push({
|
||||
...cloneDeep(defaultDebugParams),
|
||||
id,
|
||||
isNew: !defaultProps?.id, // 新开的tab标记为前端新增的调试,因为此时都已经有id了;但是如果是查看打开的会有携带id
|
||||
...defaultProps,
|
||||
});
|
||||
activeRequestTab.value = defaultProps?.id || id;
|
||||
|
@ -593,18 +601,30 @@
|
|||
|
||||
function makeRequestParams() {
|
||||
const polymorphicName = protocolOptions.value.find((e) => e.value === activeDebug.value.protocol)?.polymorphicName; // 协议多态名称
|
||||
|
||||
let requestParams;
|
||||
if (isHttpProtocol.value) {
|
||||
requestParams = {
|
||||
authConfig: activeDebug.value.authConfig,
|
||||
body: { ...activeDebug.value.body, binaryBody: undefined },
|
||||
headers: activeDebug.value.headers,
|
||||
body: {
|
||||
...activeDebug.value.body,
|
||||
binaryBody: undefined,
|
||||
formDataBody: {
|
||||
formValues: activeDebug.value.body.formDataBody.formValues.filter(
|
||||
(e, i) => i !== activeDebug.value.body.formDataBody.formValues.length - 1
|
||||
), // 去掉最后一行空行
|
||||
},
|
||||
wwwFormBody: {
|
||||
formValues: activeDebug.value.body.wwwFormBody.formValues.filter(
|
||||
(e, i) => i !== activeDebug.value.body.wwwFormBody.formValues.length - 1
|
||||
), // 去掉最后一行空行
|
||||
},
|
||||
}, // TODO:binaryBody还没对接
|
||||
headers: activeDebug.value.headers.filter((e, i) => i !== activeDebug.value.headers.length - 1), // 去掉最后一行空行
|
||||
method: activeDebug.value.method,
|
||||
otherConfig: activeDebug.value.otherConfig,
|
||||
path: activeDebug.value.url,
|
||||
query: activeDebug.value.query,
|
||||
rest: activeDebug.value.rest,
|
||||
query: activeDebug.value.query.filter((e, i) => i !== activeDebug.value.query.length - 1), // 去掉最后一行空行
|
||||
rest: activeDebug.value.rest.filter((e, i) => i !== activeDebug.value.rest.length - 1), // 去掉最后一行空行
|
||||
url: activeDebug.value.url,
|
||||
polymorphicName,
|
||||
};
|
||||
|
@ -631,14 +651,8 @@
|
|||
enableGlobal: false,
|
||||
assertions: [],
|
||||
},
|
||||
postProcessorConfig: {
|
||||
enableGlobal: false,
|
||||
processors: [],
|
||||
},
|
||||
preProcessorConfig: {
|
||||
enableGlobal: false,
|
||||
processors: [],
|
||||
},
|
||||
postProcessorConfig: activeDebug.value.children[0].postProcessorConfig,
|
||||
preProcessorConfig: activeDebug.value.children[0].preProcessorConfig,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -709,7 +723,7 @@
|
|||
await fApi.value?.validate();
|
||||
}
|
||||
saveModalForm.value = {
|
||||
name: '',
|
||||
name: activeDebug.value.name || '',
|
||||
path: activeDebug.value.url || '',
|
||||
moduleId: 'root',
|
||||
};
|
||||
|
@ -734,19 +748,36 @@
|
|||
if (!errors) {
|
||||
try {
|
||||
saveLoading.value = true;
|
||||
await addDebug({
|
||||
...makeRequestParams(),
|
||||
...saveModalForm.value,
|
||||
protocol: activeDebug.value.protocol,
|
||||
method: isHttpProtocol.value ? activeDebug.value.method : activeDebug.value.protocol,
|
||||
uploadFileIds: [],
|
||||
linkFileIds: [],
|
||||
});
|
||||
if (activeDebug.value.isNew) {
|
||||
// 若是新建的调试,走添加
|
||||
await addDebug({
|
||||
...makeRequestParams(),
|
||||
...saveModalForm.value,
|
||||
protocol: activeDebug.value.protocol,
|
||||
method: isHttpProtocol.value ? activeDebug.value.method : activeDebug.value.protocol,
|
||||
uploadFileIds: [],
|
||||
linkFileIds: [],
|
||||
});
|
||||
} else {
|
||||
await updateDebug({
|
||||
...makeRequestParams(),
|
||||
...saveModalForm.value,
|
||||
protocol: activeDebug.value.protocol,
|
||||
method: isHttpProtocol.value ? activeDebug.value.method : activeDebug.value.protocol,
|
||||
uploadFileIds: [],
|
||||
linkFileIds: [],
|
||||
deleteFileIds: [], // TODO:删除文件集合
|
||||
unLinkRefIds: [], // TODO:取消关联文件集合
|
||||
});
|
||||
}
|
||||
saveLoading.value = false;
|
||||
saveModalVisible.value = false;
|
||||
done(true);
|
||||
activeDebug.value.unSaved = false;
|
||||
Message.success(t('common.saveSuccess'));
|
||||
activeDebug.value.name = saveModalForm.value.name;
|
||||
activeDebug.value.label = saveModalForm.value.name;
|
||||
emit('addDone');
|
||||
Message.success(activeDebug.value.isNew ? t('common.saveSuccess') : t('common.updateSuccess'));
|
||||
} catch (error) {
|
||||
saveLoading.value = false;
|
||||
}
|
||||
|
@ -755,10 +786,15 @@
|
|||
done(false);
|
||||
}
|
||||
|
||||
const apiDetailLoading = ref(false);
|
||||
async function openApiTab(apiInfo: ModuleTreeNode) {
|
||||
const isLoadedTabIndex = debugTabs.value.findIndex((e) => e.id === apiInfo.id);
|
||||
if (isLoadedTabIndex > -1) {
|
||||
// 如果点击的请求在tab中已经存在,则直接切换到该tab
|
||||
activeRequestTab.value = apiInfo.id;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
apiDetailLoading.value = true;
|
||||
loading.value = true;
|
||||
const res = await getDebugDetail(apiInfo.id);
|
||||
addDebugTab({
|
||||
label: apiInfo.name,
|
||||
|
@ -766,12 +802,16 @@
|
|||
response: cloneDeep(defaultResponse),
|
||||
...res.request,
|
||||
url: res.path,
|
||||
name: res.name, // request里面还有个name但是是null
|
||||
});
|
||||
nextTick(() => {
|
||||
// 等待内容渲染出来再隐藏loading
|
||||
loading.value = false;
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
apiDetailLoading.value = false;
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<condition
|
||||
v-model:list="postConditions"
|
||||
v-model:list="innerConfig.processors"
|
||||
:condition-types="[RequestConditionProcessor.SCRIPT]"
|
||||
add-text="apiTestDebug.postCondition"
|
||||
:response="props.response"
|
||||
|
@ -8,7 +8,7 @@
|
|||
@change="emit('change')"
|
||||
>
|
||||
<!-- <template #titleRight>
|
||||
<a-switch v-model:model-value="openGlobalPostCondition" size="small" type="line"></a-switch>
|
||||
<a-switch v-model:model-value="innerConfig.enableGlobal" size="small" type="line"></a-switch>
|
||||
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.openGlobalPostCondition') }}</div>
|
||||
<a-tooltip :content="t('apiTestDebug.openGlobalPostConditionTip')" position="left">
|
||||
<icon-question-circle
|
||||
|
@ -25,13 +25,13 @@
|
|||
|
||||
import condition from '@/views/api-test/components/condition/index.vue';
|
||||
|
||||
import { ExecuteConditionProcessor } from '@/models/apiTest/debug';
|
||||
import { ExecuteConditionConfig, ExecuteConditionProcessor } from '@/models/apiTest/debug';
|
||||
import { RequestConditionProcessor } from '@/enums/apiEnum';
|
||||
|
||||
// import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const props = defineProps<{
|
||||
params: ExecuteConditionProcessor[];
|
||||
config: ExecuteConditionConfig;
|
||||
secondBoxHeight?: number;
|
||||
layout: 'horizontal' | 'vertical';
|
||||
response?: string; // 响应内容
|
||||
|
@ -42,9 +42,7 @@
|
|||
}>();
|
||||
|
||||
// const { t } = useI18n();
|
||||
// 是否开启全局后置条件
|
||||
// const openGlobalPostCondition = ref(false);
|
||||
const postConditions = useVModel(props, 'params', emit);
|
||||
const innerConfig = useVModel(props, 'config', emit);
|
||||
const heightUsed = computed(() => {
|
||||
if (props.layout === 'horizontal') {
|
||||
return 428;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<condition
|
||||
v-model:list="preconditions"
|
||||
:condition-types="['SCRIPT', 'TIME_WAITING']"
|
||||
v-model:list="innerConfig.processors"
|
||||
:condition-types="[RequestConditionProcessor.SCRIPT, RequestConditionProcessor.TIME_WAITING]"
|
||||
add-text="apiTestDebug.precondition"
|
||||
@change="emit('change')"
|
||||
>
|
||||
<!-- <template #titleRight>
|
||||
<a-switch v-model:model-value="openGlobalPrecondition" size="small" type="line"></a-switch>
|
||||
<a-switch v-model:model-value="innerConfig.enableGlobal" size="small" type="line"></a-switch>
|
||||
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.openGlobalPrecondition') }}</div>
|
||||
<a-tooltip :content="t('apiTestDebug.openGlobalPreconditionTip')" position="left">
|
||||
<icon-question-circle
|
||||
|
@ -23,22 +23,21 @@
|
|||
|
||||
import condition from '@/views/api-test/components/condition/index.vue';
|
||||
|
||||
import { ExecuteConditionProcessor } from '@/models/apiTest/debug';
|
||||
import { ExecuteConditionConfig } from '@/models/apiTest/debug';
|
||||
import { RequestConditionProcessor } from '@/enums/apiEnum';
|
||||
|
||||
// import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const props = defineProps<{
|
||||
params: ExecuteConditionProcessor[];
|
||||
config: ExecuteConditionConfig;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:params', params: ExecuteConditionProcessor[]): void;
|
||||
(e: 'update:config', params: ExecuteConditionConfig): void;
|
||||
(e: 'change'): void;
|
||||
}>();
|
||||
|
||||
// const { t } = useI18n();
|
||||
// 是否开启全局前置条件
|
||||
// const openGlobalPrecondition = ref(false);
|
||||
const preconditions = useVModel(props, 'params', emit);
|
||||
const innerConfig = useVModel(props, 'config', emit);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -9,13 +9,14 @@
|
|||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" />
|
||||
<batchAddKeyVal :params="innerParams" :default-param-item="defaultParamItem" @apply="handleBatchParamApply" />
|
||||
</div>
|
||||
<paramTable
|
||||
v-model:params="innerParams"
|
||||
:columns="columns"
|
||||
:height-used="heightUsed"
|
||||
:scroll="{ minWidth: 1160 }"
|
||||
:default-param-item="defaultParamItem"
|
||||
@change="handleParamTableChange"
|
||||
/>
|
||||
</template>
|
||||
|
@ -44,23 +45,34 @@
|
|||
const { t } = useI18n();
|
||||
|
||||
const innerParams = useVModel(props, 'params', emit);
|
||||
const defaultParamItem = {
|
||||
key: '',
|
||||
value: '',
|
||||
paramType: RequestParamsType.STRING,
|
||||
description: '',
|
||||
required: false,
|
||||
maxLength: undefined,
|
||||
minLength: undefined,
|
||||
encode: false,
|
||||
enable: true,
|
||||
};
|
||||
|
||||
const columns: ParamTableColumn[] = [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
dataIndex: 'key',
|
||||
slotName: 'key',
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramType',
|
||||
dataIndex: 'type',
|
||||
slotName: 'type',
|
||||
dataIndex: 'paramType',
|
||||
slotName: 'paramType',
|
||||
hasRequired: true,
|
||||
typeOptions: Object.keys(RequestParamsType)
|
||||
.filter((key) => ![RequestParamsType.JSON, RequestParamsType.FILE].includes(key as RequestParamsType))
|
||||
.map((key) => ({
|
||||
label: RequestParamsType[key],
|
||||
value: key,
|
||||
typeOptions: Object.values(RequestParamsType)
|
||||
.filter((val) => ![RequestParamsType.JSON, RequestParamsType.FILE].includes(val))
|
||||
.map((val) => ({
|
||||
label: val,
|
||||
value: val,
|
||||
})),
|
||||
width: 120,
|
||||
},
|
||||
|
@ -81,6 +93,7 @@
|
|||
dataIndex: 'encode',
|
||||
slotName: 'encode',
|
||||
titleSlotName: 'encodeTitle',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.desc',
|
||||
|
|
|
@ -9,13 +9,14 @@
|
|||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" />
|
||||
<batchAddKeyVal :params="innerParams" :default-param-item="defaultParamItem" @apply="handleBatchParamApply" />
|
||||
</div>
|
||||
<paramTable
|
||||
v-model:params="innerParams"
|
||||
:columns="columns"
|
||||
:height-used="heightUsed"
|
||||
:scroll="{ minWidth: 1160 }"
|
||||
:default-param-item="defaultParamItem"
|
||||
@change="handleParamTableChange"
|
||||
/>
|
||||
</template>
|
||||
|
@ -44,23 +45,34 @@
|
|||
const { t } = useI18n();
|
||||
|
||||
const innerParams = useVModel(props, 'params', emit);
|
||||
const defaultParamItem = {
|
||||
key: '',
|
||||
value: '',
|
||||
paramType: RequestParamsType.STRING,
|
||||
description: '',
|
||||
required: false,
|
||||
maxLength: undefined,
|
||||
minLength: undefined,
|
||||
encode: false,
|
||||
enable: true,
|
||||
};
|
||||
|
||||
const columns: ParamTableColumn[] = [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
dataIndex: 'key',
|
||||
slotName: 'key',
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramType',
|
||||
dataIndex: 'type',
|
||||
slotName: 'type',
|
||||
dataIndex: 'paramType',
|
||||
slotName: 'paramType',
|
||||
hasRequired: true,
|
||||
typeOptions: Object.keys(RequestParamsType)
|
||||
.filter((key) => ![RequestParamsType.JSON, RequestParamsType.FILE].includes(key as RequestParamsType))
|
||||
.map((key) => ({
|
||||
label: RequestParamsType[key],
|
||||
value: key,
|
||||
typeOptions: Object.values(RequestParamsType)
|
||||
.filter((val) => ![RequestParamsType.JSON, RequestParamsType.FILE].includes(val as RequestParamsType))
|
||||
.map((val) => ({
|
||||
label: val,
|
||||
value: val,
|
||||
})),
|
||||
width: 120,
|
||||
},
|
||||
|
@ -81,6 +93,7 @@
|
|||
dataIndex: 'encode',
|
||||
slotName: 'encode',
|
||||
titleSlotName: 'encodeTitle',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.desc',
|
||||
|
|
|
@ -42,8 +42,17 @@
|
|||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('apiTestDebug.redirect')">
|
||||
<a-radio v-model:model-value="settingForm.followRedirects">{{ t('apiTestDebug.follow') }}</a-radio>
|
||||
<a-radio v-model:model-value="settingForm.autoRedirects" class="ml-[24px]">
|
||||
<a-radio
|
||||
v-model:model-value="settingForm.followRedirects"
|
||||
@change="(val) => handleFollowRedirectsChange(val as boolean)"
|
||||
>
|
||||
{{ t('apiTestDebug.follow') }}
|
||||
</a-radio>
|
||||
<a-radio
|
||||
v-model:model-value="settingForm.autoRedirects"
|
||||
class="ml-[24px]"
|
||||
@change="val => handleAutoRedirectsChange(val as boolean)"
|
||||
>
|
||||
{{ t('apiTestDebug.auto') }}
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
@ -76,6 +85,18 @@
|
|||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
function handleFollowRedirectsChange(val: boolean) {
|
||||
if (val) {
|
||||
settingForm.value.autoRedirects = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleAutoRedirectsChange(val: boolean) {
|
||||
if (val) {
|
||||
settingForm.value.followRedirects = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -337,6 +337,7 @@
|
|||
|
||||
defineExpose({
|
||||
initModules,
|
||||
initModuleCount,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<template>
|
||||
<MsCard simple no-content-padding>
|
||||
<!-- TODO:接口请求超过5S以上可展示取消请求按钮,避免用户过长等待 -->
|
||||
<MsCard :loading="loading" simple no-content-padding>
|
||||
<MsSplitBox :size="0.25" :max="0.5">
|
||||
<template #first>
|
||||
<div class="p-[24px]">
|
||||
<moduleTree
|
||||
ref="moduleTreeRef"
|
||||
@init="(val) => (folderTree = val)"
|
||||
@new-api="newApi"
|
||||
@click-api-node="handleApiNodeClick"
|
||||
|
@ -13,7 +15,12 @@
|
|||
</template>
|
||||
<template #second>
|
||||
<div class="flex h-full flex-col">
|
||||
<debug ref="debugRef" :module-tree="folderTree" />
|
||||
<debug
|
||||
ref="debugRef"
|
||||
v-model:detail-loading="loading"
|
||||
:module-tree="folderTree"
|
||||
@add-done="handleDebugAddDone"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
|
@ -62,14 +69,16 @@
|
|||
import { parseCurlScript } from '@/utils';
|
||||
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import { RequestContentTypeEnum } from '@/enums/apiEnum';
|
||||
import { RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const moduleTreeRef = ref<InstanceType<typeof moduleTree>>();
|
||||
const debugRef = ref<InstanceType<typeof debug>>();
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
const importDrawerVisible = ref(false);
|
||||
const curlCode = ref('');
|
||||
const loading = ref(false);
|
||||
|
||||
function newApi() {
|
||||
debugRef.value?.addDebugTab();
|
||||
|
@ -79,31 +88,20 @@
|
|||
const { url, headers, queryParameters } = parseCurlScript(curlCode.value);
|
||||
debugRef.value?.addDebugTab({
|
||||
url,
|
||||
headerParams: headers?.map((e) => ({
|
||||
required: false,
|
||||
type: 'string',
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
headers: headers?.map((e) => ({
|
||||
contentType: RequestContentTypeEnum.TEXT,
|
||||
tag: [],
|
||||
desc: '',
|
||||
encode: false,
|
||||
enable: false,
|
||||
mustContain: false,
|
||||
description: '',
|
||||
enable: true,
|
||||
...e,
|
||||
})),
|
||||
value: '',
|
||||
queryParams: queryParameters?.map((e) => ({
|
||||
query: queryParameters?.map((e) => ({
|
||||
paramType: RequestParamsType.STRING,
|
||||
description: '',
|
||||
required: false,
|
||||
type: 'string',
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
contentType: RequestContentTypeEnum.TEXT,
|
||||
tag: [],
|
||||
desc: '',
|
||||
maxLength: undefined,
|
||||
minLength: undefined,
|
||||
encode: false,
|
||||
enable: false,
|
||||
mustContain: false,
|
||||
enable: true,
|
||||
...e,
|
||||
})),
|
||||
});
|
||||
|
@ -114,6 +112,11 @@
|
|||
function handleApiNodeClick(node: ModuleTreeNode) {
|
||||
debugRef.value?.openApiTab(node);
|
||||
}
|
||||
|
||||
function handleDebugAddDone() {
|
||||
moduleTreeRef.value?.initModules();
|
||||
moduleTreeRef.value?.initModuleCount();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
|
Loading…
Reference in New Issue