feat(接口测试): 接口调试-导入curl

This commit is contained in:
baiqi 2024-01-24 09:36:25 +08:00 committed by Craftsman
parent 9de485fe9e
commit 8f4ddbbe87
13 changed files with 355 additions and 36 deletions

View File

@ -102,7 +102,7 @@ export const editorProps = {
// 是否显示字符集切换 // 是否显示字符集切换
showCharsetChange: { showCharsetChange: {
type: Boolean as PropType<boolean>, type: Boolean as PropType<boolean>,
default: true, default: false,
}, },
// 是否显示主题切换 // 是否显示主题切换
showThemeChange: { showThemeChange: {

View File

@ -87,7 +87,7 @@
</div> </div>
</template> </template>
<template #cell="{ column, record, rowIndex }"> <template #cell="{ column, record, rowIndex }">
<div :class="{ 'flex flex-row items-center': !item.isTag && !item.align }"> <div :class="{ 'flex w-full flex-row items-center': !item.isTag && !item.align }">
<template v-if="item.dataIndex === SpecialColumnEnum.ENABLE"> <template v-if="item.dataIndex === SpecialColumnEnum.ENABLE">
<slot name="enable" v-bind="{ record }"> <slot name="enable" v-bind="{ record }">
<div v-if="record.enable" class="flex flex-row flex-nowrap items-center gap-[2px]"> <div v-if="record.enable" class="flex flex-row flex-nowrap items-center gap-[2px]">

View File

@ -370,3 +370,69 @@ export function decodeStringToCharset(str: string, charset = 'UTF-8') {
const decoder = new TextDecoder(charset); const decoder = new TextDecoder(charset);
return decoder.decode(encoder.encode(str)); return decoder.decode(encoder.encode(str));
} }
interface ParsedCurlOptions {
url?: string;
queryParameters?: { name: string; value: string }[];
headers?: { name: string; value: string }[];
}
/**
* curl
* @param curlScript curl
*/
export function parseCurlScript(curlScript: string): ParsedCurlOptions {
const options: ParsedCurlOptions = {};
// 提取 URL
const [_, url] = curlScript.match(/curl\s+'([^']+)'/) || [];
if (url) {
options.url = url;
}
// 提取 query 参数
const queryMatch = curlScript.match(/\?(.*?)'/);
if (queryMatch) {
const queryParams = queryMatch[1].split('&').map((param) => {
const [name, value] = param.split('=');
return { name, value };
});
options.queryParameters = queryParams;
}
// 提取 header
const headersMatch = curlScript.match(/-H\s+'([^']+)'/g);
if (headersMatch) {
const headers = headersMatch.map((header) => {
const [, value] = header.match(/-H\s+'([^']+)'/) || [];
const [name, rawValue] = value.split(':');
const trimmedName = name.trim();
const trimmedValue = rawValue ? rawValue.trim() : '';
return { name: trimmedName, value: trimmedValue };
});
// 过滤常用的 HTTP header
const commonHeaders = [
'accept',
'accept-language',
'cache-control',
'content-type',
'origin',
'pragma',
'referer',
'sec-ch-ua',
'sec-ch-ua-mobile',
'sec-ch-ua-platform',
'sec-fetch-dest',
'sec-fetch-mode',
'sec-fetch-site',
'user-agent',
'Connection',
'Host',
'Accept-Encoding',
'X-Requested-With',
];
options.headers = headers.filter((header) => !commonHeaders.includes(header.name.toLowerCase()));
}
return options;
}

View File

@ -485,7 +485,7 @@ org.apache.http.client.method . . . '' at line number 2
value: 'temp', value: 'temp',
}, },
], ],
width: 110, width: 130,
}, },
{ {
title: 'apiTestDebug.mode', title: 'apiTestDebug.mode',

View File

@ -65,7 +65,7 @@
<a-select <a-select
v-model:model-value="record.type" v-model:model-value="record.type"
:options="columnConfig.typeOptions || []" :options="columnConfig.typeOptions || []"
class="param-input" class="param-input w-full"
@change="(val) => handleTypeChange(val, record)" @change="(val) => handleTypeChange(val, record)"
/> />
</template> </template>
@ -281,6 +281,8 @@
</template> </template>
<script async setup lang="ts"> <script async setup lang="ts">
import { isEqual } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue'; import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
@ -369,8 +371,7 @@
} }
); );
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:params', value: any[]): void; (e: 'change', data: any[], isInit?: boolean): void; //
(e: 'change', data: any[], isInit?: boolean): void;
(e: 'moreActionSelect', event: ActionsItem, record: Record<string, any>): void; (e: 'moreActionSelect', event: ActionsItem, record: Record<string, any>): void;
}>(); }>();
@ -397,24 +398,6 @@
isSimpleSetting: props.isSimpleSetting, isSimpleSetting: props.isSimpleSetting,
}); });
watch(
() => props.params,
(val) => {
if (val.length > 0) {
propsRes.value.data = val;
} else {
propsRes.value.data = props.params.concat({
id: new Date().getTime(), // id props.defaultParamItem id
...props.defaultParamItem,
});
emit('change', propsRes.value.data, true);
}
},
{
immediate: true,
}
);
watch( watch(
() => props.heightUsed, () => props.heightUsed,
(val) => { (val) => {
@ -440,12 +423,13 @@
key?: string, key?: string,
isForce?: boolean isForce?: boolean
) { ) {
const lastData = propsRes.value.data[propsRes.value.data.length - 1]; const lastData = { ...propsRes.value.data[propsRes.value.data.length - 1] };
delete lastData.id;
// key key // key key
const isNotChange = const isNotChange =
val === undefined || key === undefined val === undefined || key === undefined
? Object.keys(props.defaultParamItem).every((e) => lastData[e] === props.defaultParamItem[e]) ? isEqual(lastData, props.defaultParamItem)
: JSON.stringify(lastData[key]) === JSON.stringify(props.defaultParamItem[key]); : isEqual(lastData[key], props.defaultParamItem[key]);
if (isForce || (val !== '' && !isNotChange)) { if (isForce || (val !== '' && !isNotChange)) {
propsRes.value.data.push({ propsRes.value.data.push({
id: new Date().getTime(), id: new Date().getTime(),
@ -455,6 +439,32 @@
} }
} }
watch(
() => props.params,
(val) => {
if (val.length > 0) {
const lastData = { ...val[val.length - 1] };
delete lastData.id; // id
const isNotChange = isEqual(lastData, props.defaultParamItem);
propsRes.value.data = val;
if (!isNotChange) {
addTableLine();
}
} else {
propsRes.value.data = [
{
id: new Date().getTime(), // id props.defaultParamItem id
...props.defaultParamItem,
},
] as any[];
emit('change', propsRes.value.data, true);
}
},
{
immediate: true,
}
);
const showQuickInputParam = ref(false); const showQuickInputParam = ref(false);
const activeQuickInputRecord = ref<any>({}); const activeQuickInputRecord = ref<any>({});
const quickInputParamValue = ref(''); const quickInputParamValue = ref('');

View File

@ -72,7 +72,7 @@
{ {
title: 'apiTestDebug.maxConnection', title: 'apiTestDebug.maxConnection',
dataIndex: 'maxConnection', dataIndex: 'maxConnection',
width: 110, width: 140,
}, },
{ {
title: 'apiTestDebug.timeout', title: 'apiTestDebug.timeout',

View File

@ -329,14 +329,20 @@ Date: Wed, 13 Dec 2023 08:53:25 GMT`,
activeDebug.value.unSaved = true; activeDebug.value.unSaved = true;
} }
function addDebugTab() { function addDebugTab(defaultProps?: Partial<TabItem>) {
const id = `debug-${Date.now()}`; const id = `debug-${Date.now()}`;
debugTabs.value.push({ debugTabs.value.push({
...cloneDeep(defaultDebugParams), ...cloneDeep(defaultDebugParams),
module: props.module, module: props.module,
id, id,
...defaultProps,
}); });
activeRequestTab.value = id; activeRequestTab.value = id;
nextTick(() => {
if (defaultProps) {
handleActiveDebugChange();
}
});
} }
function closeDebugTab(tab: TabItem) { function closeDebugTab(tab: TabItem) {

View File

@ -82,6 +82,7 @@
title: 'apiTestDebug.paramLengthRange', title: 'apiTestDebug.paramLengthRange',
dataIndex: 'lengthRange', dataIndex: 'lengthRange',
slotName: 'lengthRange', slotName: 'lengthRange',
align: 'center',
width: 200, width: 200,
}, },
{ {

View File

@ -82,6 +82,7 @@
title: 'apiTestDebug.paramLengthRange', title: 'apiTestDebug.paramLengthRange',
dataIndex: 'lengthRange', dataIndex: 'lengthRange',
slotName: 'lengthRange', slotName: 'lengthRange',
align: 'center',
width: 200, width: 200,
}, },
{ {

View File

@ -120,7 +120,7 @@
modulesCount?: Record<string, number>; // modulesCount?: Record<string, number>; //
isExpandAll?: boolean; // isExpandAll?: boolean; //
}>(); }>();
const emit = defineEmits(['init', 'change', 'newApi']); const emit = defineEmits(['init', 'change', 'newApi', 'import']);
const appStore = useAppStore(); const appStore = useAppStore();
const { t } = useI18n(); const { t } = useI18n();
@ -132,6 +132,7 @@
emit('newApi'); emit('newApi');
break; break;
case 'import': case 'import':
emit('import');
break; break;
default: default:

View File

@ -3,7 +3,12 @@
<MsSplitBox :size="0.25" :max="0.5"> <MsSplitBox :size="0.25" :max="0.5">
<template #first> <template #first>
<div class="p-[24px]"> <div class="p-[24px]">
<moduleTree @init="(val) => (folderTree = val)" @new-api="newApi" @change="(val) => (activeModule = val)" /> <moduleTree
@init="(val) => (folderTree = val)"
@new-api="newApi"
@change="(val) => (activeModule = val)"
@import="importDrawerVisible = true"
/>
</div> </div>
</template> </template>
<template #second> <template #second>
@ -13,23 +18,99 @@
</template> </template>
</MsSplitBox> </MsSplitBox>
</MsCard> </MsCard>
<MsDrawer
v-model:visible="importDrawerVisible"
:width="680"
:ok-disabled="curlCode.trim() === ''"
disabled-width-drag
@cancel="curlCode = ''"
@confirm="handleCurlImportConfirm"
>
<template #title>
<a-tooltip position="right" :content="t('apiTestDebug.importByCURLTip')">
{{ t('apiTestDebug.importByCURL') }}
<icon-exclamation-circle
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</a-tooltip>
</template>
<div class="h-full">
<MsCodeEditor
v-if="importDrawerVisible"
v-model:model-value="curlCode"
theme="MS-text"
height="100%"
language="plaintext"
:show-theme-change="false"
:show-full-screen="false"
>
</MsCodeEditor>
</div>
</MsDrawer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsSplitBox from '@/components/pure/ms-split-box/index.vue'; import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
import debug from './components/debug/index.vue'; import debug from './components/debug/index.vue';
import moduleTree from './components/moduleTree.vue'; import moduleTree from './components/moduleTree.vue';
import { useI18n } from '@/hooks/useI18n';
import { parseCurlScript } from '@/utils';
import { ModuleTreeNode } from '@/models/projectManagement/file'; import { ModuleTreeNode } from '@/models/projectManagement/file';
import { RequestContentTypeEnum } from '@/enums/apiEnum';
const { t } = useI18n();
const debugRef = ref<InstanceType<typeof debug>>(); const debugRef = ref<InstanceType<typeof debug>>();
const activeModule = ref<string>('root'); const activeModule = ref<string>('root');
const folderTree = ref<ModuleTreeNode[]>([]); const folderTree = ref<ModuleTreeNode[]>([]);
const importDrawerVisible = ref(false);
const curlCode = ref('');
function newApi() { function newApi() {
debugRef.value?.addDebugTab(); debugRef.value?.addDebugTab();
} }
function handleCurlImportConfirm() {
const { url, headers, queryParameters } = parseCurlScript(curlCode.value);
debugRef.value?.addDebugTab({
url,
headerParams: headers?.map((e) => ({
required: false,
type: 'string',
min: undefined,
max: undefined,
contentType: RequestContentTypeEnum.TEXT,
tag: [],
desc: '',
encode: false,
enable: false,
mustContain: false,
...e,
})),
value: '',
queryParams: queryParameters?.map((e) => ({
required: false,
type: 'string',
min: undefined,
max: undefined,
contentType: RequestContentTypeEnum.TEXT,
tag: [],
desc: '',
encode: false,
enable: false,
mustContain: false,
...e,
})),
});
curlCode.value = '';
importDrawerVisible.value = false;
}
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -7,8 +7,8 @@ export default {
'apiTestDebug.noMatchModule': 'No matching module data yet', 'apiTestDebug.noMatchModule': 'No matching module data yet',
'apiTestDebug.header': 'Header', 'apiTestDebug.header': 'Header',
'apiTestDebug.body': 'Body', 'apiTestDebug.body': 'Body',
'apiTestDebug.prefix': 'Prefix', 'apiTestDebug.prefix': 'Precondition',
'apiTestDebug.post': 'Post', 'apiTestDebug.post': 'Postcondition',
'apiTestDebug.assertion': 'Assertion', 'apiTestDebug.assertion': 'Assertion',
'apiTestDebug.auth': 'Auth', 'apiTestDebug.auth': 'Auth',
'apiTestDebug.setting': 'Setting', 'apiTestDebug.setting': 'Setting',
@ -22,6 +22,15 @@ export default {
'apiTestDebug.paramValuePlaceholder': 'Starting with {at}, double-click to quickly enter', 'apiTestDebug.paramValuePlaceholder': 'Starting with {at}, double-click to quickly enter',
'apiTestDebug.paramValuePreview': 'Parameter preview', 'apiTestDebug.paramValuePreview': 'Parameter preview',
'apiTestDebug.desc': 'Description', 'apiTestDebug.desc': 'Description',
'apiTestDebug.paramRequired': 'Required',
'apiTestDebug.paramNotRequired': 'Optional',
'apiTestDebug.paramType': 'Param type',
'apiTestDebug.paramLengthRange': 'Length range',
'apiTestDebug.paramMin': 'Min',
'apiTestDebug.paramMax': 'Max',
'apiTestDebug.encode': 'Encoding',
'apiTestDebug.encodeTip1': 'On: Use encoding',
'apiTestDebug.encodeTip2': 'Off: No encoding is used',
'apiTestDebug.apply': 'Apply', 'apiTestDebug.apply': 'Apply',
'apiTestDebug.batchAddParamsTip': 'Writing format: parameter name: parameter value; such as nama: natural', 'apiTestDebug.batchAddParamsTip': 'Writing format: parameter name: parameter value; such as nama: natural',
'apiTestDebug.batchAddParamsTip2': 'Multiple records are separated by newlines.', 'apiTestDebug.batchAddParamsTip2': 'Multiple records are separated by newlines.',
@ -29,4 +38,146 @@ export default {
'Parameter names in batch addition are repeated. By default, the last data is the latest data.', 'Parameter names in batch addition are repeated. By default, the last data is the latest data.',
'apiTestDebug.quickInputParamsTip': 'Support Mock/JMeter/Json/Text/String, etc.', 'apiTestDebug.quickInputParamsTip': 'Support Mock/JMeter/Json/Text/String, etc.',
'apiTestDebug.descPlaceholder': 'Please enter content', 'apiTestDebug.descPlaceholder': 'Please enter content',
'apiTestDebug.noneBody': 'Request without Body',
'apiTestDebug.sendAsMainText': 'Send as main text',
'apiTestDebug.sendAsMainTextTip1':
'Enable: Directly read the file content and display it in the response body, such as: files in image format',
'apiTestDebug.sendAsMainTextTip2': 'Close: Return as download file',
'apiTestDebug.queryTip': 'In the address bar followed by ? The following parameters, such as updateapi?id=112',
'apiTestDebug.restTip': 'Parameters separated by slash/ in the address bar, such as updateapi/{id}',
'apiTestDebug.authType': 'Authentication',
'apiTestDebug.account': 'Account',
'apiTestDebug.accountRequired': 'Account cannot be empty',
'apiTestDebug.password': 'Password',
'apiTestDebug.passwordRequired': 'Password cannot be empty',
'apiTestDebug.commonPlaceholder': 'Please enter',
'apiTestDebug.connectTimeout': 'Connection timed out',
'apiTestDebug.responseTimeout': 'Response timeout',
'apiTestDebug.certificateAlias': 'Certificate alias',
'apiTestDebug.redirect': 'Redirect',
'apiTestDebug.follow': 'Follow',
'apiTestDebug.auto': 'Auto',
'apiTestDebug.precondition': 'Precondition',
'apiTestDebug.openGlobalPrecondition': 'Enable global precondition',
'apiTestDebug.openGlobalPreconditionTip':
'It is enabled by default. If it is disabled, the global precondition will not be executed when running this interface.',
'apiTestDebug.sql': 'SQL',
'apiTestDebug.sqlScript': 'SQL script',
'apiTestDebug.waitTime': 'Wait time',
'apiTestDebug.script': 'Script',
'apiTestDebug.preconditionScriptName': 'Pre-script name',
'apiTestDebug.preconditionScriptNamePlaceholder': 'Please enter the pre-script name',
'apiTestDebug.manual': 'Manual entry',
'apiTestDebug.quote': 'Quoting public scripts',
'apiTestDebug.commonScriptList': 'Public script list',
'apiTestDebug.scriptEx': 'Script case',
'apiTestDebug.copyNotSupport': 'Your browser does not support automatic copying, please copy manually',
'apiTestDebug.scriptExCopySuccess': 'Script case copied',
'apiTestDebug.parameters': 'Pass parameters',
'apiTestDebug.scriptContent': 'Script content',
'apiTestDebug.introduceSource': 'Introduce data sources',
'apiTestDebug.quoteSource': 'Reference data source',
'apiTestDebug.sourceList': 'Data source list',
'apiTestDebug.quoteSourcePlaceholder': 'Please select a data source',
'apiTestDebug.storageType': 'Storage method',
'apiTestDebug.storageTypeTip1':
'Store by column: Specify the names of columns extracted from the database result set; multiple columns can be separated by ","',
'apiTestDebug.storageTypeTip2':
'Store by result: Save the entire result set as a variable instead of saving each column value as a separate variable',
'apiTestDebug.storageByCol': 'Store by columns',
'apiTestDebug.storageByColPlaceholder': 'For example, {a} is changed to {b}',
'apiTestDebug.storageByResult': 'Store by result',
'apiTestDebug.storageByResultPlaceholder': 'Such as {a}',
'apiTestDebug.extractParameter': 'Extract',
'apiTestDebug.searchTip': 'Please enter a group name',
'apiTestDebug.allRequest': 'All requests',
'apiTestDebug.deleteFolderTipTitle': 'Remove the `{name}` module?',
'apiTestDebug.deleteFolderTipContent':
'This operation will delete the module and all resources under it, please operate with caution!',
'apiTestDebug.deleteConfirm': 'Confirm delete',
'apiTestDebug.deleteSuccess': 'Successfully deleted',
'apiTestDebug.moduleMoveSuccess': 'Module moved successfully',
'apiTestDebug.sqlSourceName': 'Data source name',
'apiTestDebug.driver': 'Drive',
'apiTestDebug.username': 'Username',
'apiTestDebug.maxConnection': 'Max connections',
'apiTestDebug.timeout': 'Timeout (ms)',
'apiTestDebug.postCondition': 'Postcondition',
'apiTestDebug.openGlobalPostCondition': 'Enable global postcondition',
'apiTestDebug.openGlobalPostConditionTip':
'It is enabled by default. If it is disabled, the global post-processing will not be executed when running this interface.',
'apiTestDebug.globalParameter': 'Global parameter',
'apiTestDebug.envParameter': 'Env parameters',
'apiTestDebug.tempParameter': 'Temporary parameters',
'apiTestDebug.mode': 'Type',
'apiTestDebug.range': 'Scope',
'apiTestDebug.expression': 'Expression',
'apiTestDebug.expressionTip1': 'Reason for unavailability:',
'apiTestDebug.expressionTip2': '1. Interface not implemented',
'apiTestDebug.expressionTip3': '2. There is no data in the response content',
'apiTestDebug.regular': 'Regular',
'apiTestDebug.fastExtraction': 'Quick extraction',
'apiTestDebug.regularExpression': 'Regular expression',
'apiTestDebug.regularExpressionRequired': 'Regular expression cannot be empty',
'apiTestDebug.regularExpressionPlaceholder': 'Such as {ex}',
'apiTestDebug.test': 'Test',
'apiTestDebug.JSONPathRequired': 'JSONPath cannot be empty',
'apiTestDebug.JSONPathPlaceholder': 'Such as $.users',
'apiTestDebug.XPathRequired': 'XPath cannot be empty',
'apiTestDebug.XPathPlaceholder': 'Such as /books/book[1]/title',
'apiTestDebug.matchResult': 'Match results',
'apiTestDebug.noMatchResult': 'No matching results',
'apiTestDebug.matchExpressionTip':
'{prefix} Gets the complete expression for matching, including all tag contents in the expression',
'apiTestDebug.matchGroupTip':
'{prefix} Gets the matching group in the expression for matching, including only the regular content in the expression',
'apiTestDebug.matchExpression': 'Matching expression',
'apiTestDebug.matchGroup': 'Matching group',
'apiTestDebug.moreSetting': 'More settings',
'apiTestDebug.expressionMatchRule': 'Expression matching rules',
'apiTestDebug.resultMatchRule': 'Result matching rules',
'apiTestDebug.randomMatch': 'Random match',
'apiTestDebug.randomMatchTip': 'Get any matching result',
'apiTestDebug.specifyMatch': 'Specify match',
'apiTestDebug.specifyMatchResult': 'Specify matching results',
'apiTestDebug.index': 'No.',
'apiTestDebug.unit': 'item',
'apiTestDebug.specifyMatchTip':
'The Nth matching result needs to be specified. If it exceeds the specified number, it will return empty.',
'apiTestDebug.allMatch': 'Match all',
'apiTestDebug.allMatchTip': 'The regular return is an array of matching results.',
'apiTestDebug.contentType': 'Response content format',
'apiTestDebug.responseTime': 'Response time',
'apiTestDebug.responseStage': 'Stage',
'apiTestDebug.time': 'Duration',
'apiTestDebug.ready': 'Preparation stage',
'apiTestDebug.socketInit': 'Socket init',
'apiTestDebug.dnsQuery': 'DNS query',
'apiTestDebug.tcpHandshake': 'TCP handshake',
'apiTestDebug.sslHandshake': 'SSL handshake',
'apiTestDebug.waitingTTFB': 'Waiting (TTFB)',
'apiTestDebug.downloadContent': 'Content download',
'apiTestDebug.deal': 'Deal with',
'apiTestDebug.total': 'Total',
'apiTestDebug.responseBody': 'Response body',
'apiTestDebug.responseHeader': 'Response header',
'apiTestDebug.realRequest': 'Real request',
'apiTestDebug.console': 'Console',
'apiTestDebug.extract': 'Extract',
'apiTestDebug.statusCode': 'Status code',
'apiTestDebug.responseSize': 'Response size',
'apiTestDebug.runningEnv': 'Operating environment',
'apiTestDebug.resourcePool': 'Resource pool',
'apiTestDebug.content': 'Content',
'apiTestDebug.status': 'Status',
'apiTestDebug.requestName': 'Request name',
'apiTestDebug.requestNameRequired': 'Request name cannot be empty',
'apiTestDebug.requestNamePlaceholder': 'Please enter a request name',
'apiTestDebug.requestUrl': 'Request URL',
'apiTestDebug.requestUrlRequired': 'Request URL cannot be empty',
'apiTestDebug.requestModule': 'Belonging module',
'apiTestDebug.closeOther': 'Close other',
'apiTestDebug.importByCURL': 'Import cURL',
'apiTestDebug.importByCURLTip':
'Supports quick import of packet capture data from tools such as Chrome, Charles or Fiddler',
}; };

View File

@ -63,7 +63,7 @@ export default {
'apiTestDebug.waitTime': '等待时间', 'apiTestDebug.waitTime': '等待时间',
'apiTestDebug.script': '脚本操作', 'apiTestDebug.script': '脚本操作',
'apiTestDebug.preconditionScriptName': '前置脚本名称', 'apiTestDebug.preconditionScriptName': '前置脚本名称',
'apiTestDebug.preconditionScriptNamePlaceholder': '前置脚本名称', 'apiTestDebug.preconditionScriptNamePlaceholder': '请输入前置脚本名称',
'apiTestDebug.manual': '手动录入', 'apiTestDebug.manual': '手动录入',
'apiTestDebug.quote': '引用公共脚本', 'apiTestDebug.quote': '引用公共脚本',
'apiTestDebug.commonScriptList': '公共脚本列表', 'apiTestDebug.commonScriptList': '公共脚本列表',
@ -139,8 +139,8 @@ export default {
'apiTestDebug.contentType': '响应内容格式', 'apiTestDebug.contentType': '响应内容格式',
'apiTestDebug.responseTime': '响应时间', 'apiTestDebug.responseTime': '响应时间',
'apiTestDebug.responseStage': '阶段', 'apiTestDebug.responseStage': '阶段',
'apiTestDebug.time': '', 'apiTestDebug.time': '时',
'apiTestDebug.ready': '准备', 'apiTestDebug.ready': '准备阶段',
'apiTestDebug.socketInit': 'Socket 初始化', 'apiTestDebug.socketInit': 'Socket 初始化',
'apiTestDebug.dnsQuery': 'DNS 查询', 'apiTestDebug.dnsQuery': 'DNS 查询',
'apiTestDebug.tcpHandshake': 'TCP 握手', 'apiTestDebug.tcpHandshake': 'TCP 握手',
@ -167,4 +167,6 @@ export default {
'apiTestDebug.requestUrlRequired': '请求 URL不能为空', 'apiTestDebug.requestUrlRequired': '请求 URL不能为空',
'apiTestDebug.requestModule': '请求所属模块', 'apiTestDebug.requestModule': '请求所属模块',
'apiTestDebug.closeOther': '关闭其他请求', 'apiTestDebug.closeOther': '关闭其他请求',
'apiTestDebug.importByCURL': '导入 cURL',
'apiTestDebug.importByCURLTip': '支持快速导入 Chrome、Charles 或 Fiddler 等工具中的抓包数据',
}; };