feat(接口测试): 接口测试-请求体&代码编辑器支持切换主题
This commit is contained in:
parent
de2de8ce05
commit
1a7c1c2410
|
@ -332,6 +332,7 @@
|
|||
emit('dblclick');
|
||||
});
|
||||
const autoCompleteInput = (autoCompleteRef.value?.inputRef as any)?.$el.querySelector('.arco-input');
|
||||
// 设置输入框聚焦状态,聚焦时不显示参数预览的 popover
|
||||
useEventListener(autoCompleteInput, 'focus', () => {
|
||||
isFocusAutoComplete.value = true;
|
||||
popoverVisible.value = false;
|
||||
|
@ -515,7 +516,7 @@
|
|||
if (currentParamsFuncInputGroup.value.length > 0 && !Number.isNaN(paramForm.value.funcParam1)) {
|
||||
// 如果添加的函数还有入参
|
||||
resultStr = `${resultStr}(${[paramForm.value.funcParam1, paramForm.value.funcParam2]
|
||||
.filter((e) => !Number.isNaN(e))
|
||||
.filter((e) => e !== '' && !Number.isNaN(e))
|
||||
.join(',')})`;
|
||||
}
|
||||
}
|
||||
|
@ -579,6 +580,10 @@
|
|||
.ms-params-input:not(.arco-input-focus) {
|
||||
border-color: transparent;
|
||||
&:not(:hover) {
|
||||
.arco-input::placeholder {
|
||||
@apply invisible;
|
||||
}
|
||||
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,15 @@
|
|||
<slot name="title">
|
||||
<span class="font-medium">{{ title }}</span>
|
||||
</slot>
|
||||
<div v-if="showThemeChange">
|
||||
<a-select
|
||||
v-model:model-value="currentTheme"
|
||||
:options="themeOptions"
|
||||
class="w-[100px]"
|
||||
size="small"
|
||||
@change="(val) => handleThemeChange(val as Theme)"
|
||||
></a-select>
|
||||
</div>
|
||||
<div v-if="showFullScreen" class="w-[96px] cursor-pointer text-right !text-[var(--color-text-4)]" @click="toggle">
|
||||
<MsIcon v-if="isFullscreen" type="icon-icon_minify_outlined" />
|
||||
<MsIcon v-else type="icon-icon_magnify_outlined" />
|
||||
|
@ -22,7 +31,7 @@
|
|||
|
||||
import './userWorker';
|
||||
import MsCodeEditorTheme from './themes';
|
||||
import { CustomTheme, editorProps } from './types';
|
||||
import { CustomTheme, editorProps, Theme } from './types';
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -34,12 +43,34 @@
|
|||
let editor: monaco.editor.IStandaloneCodeEditor;
|
||||
const codeEditBox = ref();
|
||||
const fullRef = ref<HTMLElement | null>();
|
||||
const currentTheme = ref<Theme>(props.theme);
|
||||
const themeOptions = [
|
||||
{ label: 'vs', value: 'vs' },
|
||||
{ label: 'vs-dark', value: 'vs-dark' },
|
||||
{ label: 'hc-black', value: 'hc-black' },
|
||||
].concat(
|
||||
Object.keys(MsCodeEditorTheme).map((item) => ({
|
||||
label: item,
|
||||
value: item,
|
||||
}))
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.theme,
|
||||
(val) => {
|
||||
currentTheme.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
function handleThemeChange(val: Theme) {
|
||||
monaco.editor.setTheme(val);
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
// 注册自定义主题
|
||||
if (MsCodeEditorTheme[props.theme as CustomTheme]) {
|
||||
monaco.editor.defineTheme(props.theme, MsCodeEditorTheme[props.theme as CustomTheme]);
|
||||
}
|
||||
Object.keys(MsCodeEditorTheme).forEach((e) => {
|
||||
monaco.editor.defineTheme(e, MsCodeEditorTheme[e as CustomTheme]);
|
||||
});
|
||||
editor = monaco.editor.create(codeEditBox.value, {
|
||||
value: props.modelValue,
|
||||
automaticLayout: true,
|
||||
|
@ -90,12 +121,12 @@
|
|||
{ deep: true }
|
||||
);
|
||||
|
||||
// watch(
|
||||
// () => props.language,
|
||||
// (newValue) => {
|
||||
// monaco.editor.setModelLanguage(editor.getModel()!, newValue);
|
||||
// }
|
||||
// );
|
||||
watch(
|
||||
() => props.language,
|
||||
(newValue) => {
|
||||
monaco.editor.setModelLanguage(editor.getModel()!, newValue);
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
editor.dispose();
|
||||
|
@ -106,7 +137,7 @@
|
|||
setEditBoxBg();
|
||||
});
|
||||
|
||||
return { codeEditBox, fullRef, isFullscreen, toggle, t };
|
||||
return { codeEditBox, fullRef, isFullscreen, currentTheme, themeOptions, toggle, t, handleThemeChange };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -87,4 +87,8 @@ export const editorProps = {
|
|||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
showThemeChange: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -44,9 +44,20 @@
|
|||
<MsIcon type="icon-icon_right_outlined" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<MsButton type="icon" status="secondary" class="tab-button !mr-[4px]" @click="addTab">
|
||||
<MsIcon type="icon-icon_add_outlined" />
|
||||
</MsButton>
|
||||
<a-tooltip
|
||||
:content="t('ms.editableTab.limitTip', { max: props.limit })"
|
||||
:disabled="!props.limit || props.tabs.length >= props.limit"
|
||||
>
|
||||
<MsButton
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="tab-button !mr-[4px]"
|
||||
:disabled="!!props.limit && props.tabs.length >= props.limit"
|
||||
@click="addTab"
|
||||
>
|
||||
<MsIcon type="icon-icon_add_outlined" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<MsMoreAction v-if="props.moreActionList" :list="props.moreActionList">
|
||||
<MsButton type="icon" status="secondary" class="tab-button">
|
||||
<MsIcon type="icon-icon_more_outlined" />
|
||||
|
@ -72,12 +83,13 @@
|
|||
tabs: TabItem[];
|
||||
activeTab: string | number;
|
||||
moreActionList?: ActionsItem[];
|
||||
limit?: number; // 最多可打开的tab数量
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:activeTab', activeTab: string | number): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'close', item: TabItem): void;
|
||||
(e: 'click', item: TabItem): void;
|
||||
(e: 'change', item: TabItem): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -119,6 +131,13 @@
|
|||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.activeTab,
|
||||
(val) => {
|
||||
emit('change', props.tabs.find((item) => item.id === val) as TabItem);
|
||||
}
|
||||
);
|
||||
|
||||
watch(props.tabs, () => {
|
||||
nextTick(() => {
|
||||
scrollToActiveTab();
|
||||
|
@ -141,6 +160,7 @@
|
|||
}
|
||||
|
||||
function handleTabClick(item: TabItem) {
|
||||
emit('change', item);
|
||||
innerActiveTab.value = item.id;
|
||||
nextTick(() => {
|
||||
tabNav.value?.querySelector('.tab.active')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export default {
|
||||
'ms.editableTab.arrivedLeft': 'Already reached the far left~',
|
||||
'ms.editableTab.arrivedRight': 'Already reached the far right~',
|
||||
'ms.editableTab.limitTip': 'Up to {max} tabs can currently be open',
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export default {
|
||||
'ms.editableTab.arrivedLeft': '到最左侧啦~',
|
||||
'ms.editableTab.arrivedRight': '到最右侧啦~',
|
||||
'ms.editableTab.limitTip': '当前最多可打开 {max} 个标签页',
|
||||
};
|
||||
|
|
|
@ -162,7 +162,7 @@
|
|||
.ms-scroll-bar();
|
||||
}
|
||||
.ms-split-box--left {
|
||||
width: calc(v-bind(innerSize) - 4px);
|
||||
width: calc(v-bind(innerSize) - 2px);
|
||||
}
|
||||
.expand-icon {
|
||||
@apply relative z-20 flex cursor-pointer justify-center;
|
||||
|
@ -185,7 +185,7 @@
|
|||
border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
|
||||
}
|
||||
.horizontal-expand-line {
|
||||
padding: 0 1px;
|
||||
padding-left: 2px;
|
||||
height: 100%;
|
||||
.expand-color-line {
|
||||
width: 1px;
|
||||
|
|
|
@ -392,22 +392,22 @@ export default function useTableProps<T>(
|
|||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props?.heightUsed) {
|
||||
const { heightUsed, showPagination, selectedKeys, msPagination } = propsRes.value;
|
||||
let hasFooterAction = false;
|
||||
if (showPagination) {
|
||||
const { pageSize, total } = msPagination as Pagination;
|
||||
/*
|
||||
* 是否有底部操作栏 包括 批量操作 和 分页器
|
||||
* 1. 有分页器,且总条数大于每页条数
|
||||
* 2. 有选中项
|
||||
*/
|
||||
hasFooterAction = total > pageSize || selectedKeys.size > 0;
|
||||
}
|
||||
const { heightUsed, showPagination, selectedKeys, msPagination } = propsRes.value;
|
||||
let hasFooterAction = false;
|
||||
if (showPagination) {
|
||||
const { pageSize, total } = msPagination as Pagination;
|
||||
/*
|
||||
* 是否有底部操作栏 包括 批量操作 和 分页器
|
||||
* 1. 有分页器,且总条数大于每页条数
|
||||
* 2. 有选中项
|
||||
*/
|
||||
hasFooterAction = total > pageSize || selectedKeys.size > 0;
|
||||
}
|
||||
|
||||
propsRes.value.showFooterActionWrap = hasFooterAction;
|
||||
if (props?.heightUsed) {
|
||||
const currentY =
|
||||
appStore.innerHeight - (heightUsed || defaultHeightUsed) + (hasFooterAction ? 0 : footerActionWrapHeight);
|
||||
propsRes.value.showFooterActionWrap = hasFooterAction;
|
||||
propsRes.value.scroll = { ...propsRes.value.scroll, y: currentY };
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// 接口请求方法
|
||||
export enum RequestMethods {
|
||||
GET = 'GET',
|
||||
POST = 'POST',
|
||||
|
@ -8,7 +9,7 @@ export enum RequestMethods {
|
|||
HEAD = 'HEAD',
|
||||
CONNECT = 'CONNECT',
|
||||
}
|
||||
|
||||
// 接口组成部分
|
||||
export enum RequestComposition {
|
||||
HEADER = 'HEADER',
|
||||
BODY = 'BODY',
|
||||
|
@ -20,3 +21,25 @@ export enum RequestComposition {
|
|||
AUTH = 'AUTH',
|
||||
SETTING = 'SETTING',
|
||||
}
|
||||
// 接口请求体格式
|
||||
export enum RequestBodyFormat {
|
||||
NONE = 'none',
|
||||
FORM_DATA = 'form-data',
|
||||
X_WWW_FORM_URLENCODED = 'x-www-form-urlencoded',
|
||||
JSON = 'json',
|
||||
XML = 'xml',
|
||||
RAW = 'raw',
|
||||
BINARY = 'binary',
|
||||
}
|
||||
// 接口响应体格式
|
||||
export enum RequestContentTypeEnum {
|
||||
JSON = 'application/json',
|
||||
TEXT = 'application/text',
|
||||
OGG = 'application/ogg',
|
||||
PDF = 'application/pdf',
|
||||
JAVASCRIPT = 'application/javascript',
|
||||
OCTET_STREAM = 'application/octet-stream',
|
||||
VND_API_JSON = 'application/vnd.api+json',
|
||||
ATOM_XML = 'application/atom+xml',
|
||||
ECMASCRIPT = 'application/ecmascript',
|
||||
}
|
||||
|
|
|
@ -8,7 +8,13 @@
|
|||
{{ props.desc }}
|
||||
</div>
|
||||
</template>
|
||||
<a-input ref="inputRef" v-model:model-value="innerValue" class="param-input" @input="(val) => emit('input', val)" />
|
||||
<a-input
|
||||
ref="inputRef"
|
||||
v-model:model-value="innerValue"
|
||||
class="param-input"
|
||||
@input="(val) => emit('input', val)"
|
||||
@change="(val) => emit('change', val)"
|
||||
/>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
|
@ -21,8 +27,9 @@
|
|||
desc: string;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', val: string): void;
|
||||
(e: 'update:desc', val: string): void;
|
||||
(e: 'input', val: string): void;
|
||||
(e: 'change', val: string): void;
|
||||
(e: 'dblclick'): void;
|
||||
}>();
|
||||
|
|
@ -0,0 +1,466 @@
|
|||
<template>
|
||||
<MsBaseTable v-bind="propsRes" id="headerTable" :hoverable="false" v-on="propsEvent">
|
||||
<template #encodeTitle>
|
||||
<div class="flex items-center text-[var(--color-text-3)]">
|
||||
{{ t('ms.apiTestDebug.encode') }}
|
||||
<a-tooltip>
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
<template #content>
|
||||
<div>{{ t('ms.apiTestDebug.encodeTip1') }}</div>
|
||||
<div>{{ t('ms.apiTestDebug.encodeTip2') }}</div>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<template #name="{ record }">
|
||||
<a-popover position="tl" :disabled="!record.name || record.name.trim() === ''" class="ms-params-input-popover">
|
||||
<template #content>
|
||||
<div class="param-popover-title">
|
||||
{{ t('ms.apiTestDebug.paramName') }}
|
||||
</div>
|
||||
<div class="param-popover-value">
|
||||
{{ record.name }}
|
||||
</div>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:model-value="record.name"
|
||||
:placeholder="t('ms.apiTestDebug.paramNamePlaceholder')"
|
||||
class="param-input"
|
||||
@input="(val) => addTableLine(val)"
|
||||
/>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #type="{ record }">
|
||||
<a-tooltip :content="t(record.required ? 'ms.apiTestDebug.paramRequired' : 'ms.apiTestDebug.paramNotRequired')">
|
||||
<MsButton
|
||||
type="icon"
|
||||
:class="[
|
||||
record.required ? '!text-[rgb(var(--danger-5))]' : '!text-[var(--color-text-brand)]',
|
||||
'!mr-[4px] !p-[4px]',
|
||||
]"
|
||||
@click="record.required = !record.required"
|
||||
>
|
||||
<div>*</div>
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<a-select
|
||||
v-model:model-value="record.type"
|
||||
:options="typeOptions"
|
||||
class="param-input"
|
||||
@change="(val) => handleTypeChange(val, record)"
|
||||
></a-select>
|
||||
</template>
|
||||
<template #value="{ record }">
|
||||
<MsParamsInput
|
||||
v-model:value="record.value"
|
||||
@change="addTableLine"
|
||||
@dblclick="quickInputParams(record)"
|
||||
@apply="handleParamSettingApply"
|
||||
/>
|
||||
</template>
|
||||
<template #lengthRange="{ record }">
|
||||
<div class="flex items-center justify-between">
|
||||
<a-input-number
|
||||
v-model:model-value="record.min"
|
||||
:placeholder="t('ms.apiTestDebug.paramMin')"
|
||||
class="param-input"
|
||||
@input="(val) => addTableLine(val)"
|
||||
></a-input-number>
|
||||
<div class="mx-[4px]">~</div>
|
||||
<a-input-number
|
||||
v-model:model-value="record.max"
|
||||
:placeholder="t('ms.apiTestDebug.paramMax')"
|
||||
class="param-input"
|
||||
@input="(val) => addTableLine(val)"
|
||||
></a-input-number>
|
||||
</div>
|
||||
</template>
|
||||
<template #desc="{ record }">
|
||||
<paramDescInput
|
||||
v-model:desc="record.desc"
|
||||
@input="addTableLine"
|
||||
@dblclick="quickInputDesc(record)"
|
||||
@change="handleDescChange"
|
||||
/>
|
||||
</template>
|
||||
<template #encode="{ record }">
|
||||
<a-switch
|
||||
v-model:model-value="record.encode"
|
||||
size="small"
|
||||
class="param-input-switch"
|
||||
@change="(val) => addTableLine(val.toString())"
|
||||
></a-switch>
|
||||
</template>
|
||||
<template #operation="{ record, rowIndex }">
|
||||
<a-trigger
|
||||
v-if="props.format && props.format !== RequestBodyFormat.X_WWW_FORM_URLENCODED"
|
||||
trigger="click"
|
||||
position="br"
|
||||
>
|
||||
<MsButton type="icon" class="mr-[8px]"><icon-more /></MsButton>
|
||||
<template #content>
|
||||
<div class="content-type-trigger-content">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">Content-Type</div>
|
||||
<a-select
|
||||
v-model:model-value="record.contentType"
|
||||
:options="Object.values(RequestContentTypeEnum).map((e) => ({ label: e, value: e }))"
|
||||
allow-create
|
||||
@change="(val) => addTableLine(val as string)"
|
||||
></a-select>
|
||||
</div>
|
||||
</template>
|
||||
</a-trigger>
|
||||
<icon-minus-circle
|
||||
v-if="paramsLength > 1 && rowIndex !== paramsLength - 1"
|
||||
class="cursor-pointer text-[var(--color-text-4)]"
|
||||
size="20"
|
||||
@click="deleteParam(rowIndex)"
|
||||
/>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
<a-modal
|
||||
v-model:visible="showQuickInputParam"
|
||||
:title="t('ms.paramsInput.value')"
|
||||
:ok-text="t('ms.apiTestDebug.apply')"
|
||||
class="ms-modal-form"
|
||||
body-class="!p-0"
|
||||
:width="680"
|
||||
title-align="start"
|
||||
@ok="applyQuickInputParam"
|
||||
@close="clearQuickInputParam"
|
||||
>
|
||||
<MsCodeEditor
|
||||
v-if="showQuickInputParam"
|
||||
v-model:model-value="quickInputParamValue"
|
||||
theme="MS-text"
|
||||
height="300px"
|
||||
:show-full-screen="false"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex justify-between">
|
||||
<div class="text-[var(--color-text-1)]">
|
||||
{{ t('ms.apiTestDebug.quickInputParamsTip') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsCodeEditor>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
v-model:visible="showQuickInputDesc"
|
||||
:title="t('ms.apiTestDebug.desc')"
|
||||
:ok-text="t('common.save')"
|
||||
:ok-button-props="{ disabled: !quickInputDescValue || quickInputDescValue.trim() === '' }"
|
||||
class="ms-modal-form"
|
||||
body-class="!p-0"
|
||||
:width="480"
|
||||
title-align="start"
|
||||
:auto-size="{ minRows: 2 }"
|
||||
@ok="applyQuickInputDesc"
|
||||
@close="clearQuickInputDesc"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:model-value="quickInputDescValue"
|
||||
:placeholder="t('ms.apiTestDebug.descPlaceholder')"
|
||||
:max-length="255"
|
||||
show-word-limit
|
||||
></a-textarea>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsParamsInput from '@/components/business/ms-params-input/index.vue';
|
||||
import paramDescInput from './paramDescInput.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { RequestBodyFormat, RequestContentTypeEnum } from '@/enums/apiEnum';
|
||||
|
||||
interface Param {
|
||||
id: number;
|
||||
required: boolean;
|
||||
name: string;
|
||||
type: string;
|
||||
value: string;
|
||||
min: number | undefined;
|
||||
max: number | undefined;
|
||||
contentType: RequestContentTypeEnum;
|
||||
desc: string;
|
||||
encode: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
params: any[];
|
||||
columns: MsTableColumn;
|
||||
format?: RequestBodyFormat;
|
||||
scroll?: {
|
||||
x?: number | string;
|
||||
y?: number | string;
|
||||
maxHeight?: number | string;
|
||||
minWidth?: number | string;
|
||||
};
|
||||
heightUsed?: number;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:params', value: any[]): void;
|
||||
(e: 'change', data: any[], isInit?: boolean): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const defaultParams: Omit<Param, 'id'> = {
|
||||
required: false,
|
||||
name: '',
|
||||
type: 'string',
|
||||
value: '',
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
contentType: RequestContentTypeEnum.TEXT,
|
||||
desc: '',
|
||||
encode: false,
|
||||
};
|
||||
const allType = [
|
||||
{
|
||||
label: 'string',
|
||||
value: 'string',
|
||||
},
|
||||
{
|
||||
label: 'integer',
|
||||
value: 'integer',
|
||||
},
|
||||
{
|
||||
label: 'number',
|
||||
value: 'number',
|
||||
},
|
||||
{
|
||||
label: 'array',
|
||||
value: 'array',
|
||||
},
|
||||
{
|
||||
label: 'json',
|
||||
value: 'json',
|
||||
},
|
||||
{
|
||||
label: 'file',
|
||||
value: 'file',
|
||||
},
|
||||
];
|
||||
const typeOptions = computed(() => {
|
||||
if (props.format === RequestBodyFormat.X_WWW_FORM_URLENCODED) {
|
||||
return allType.filter((e) => e.value !== 'file' && e.value !== 'json');
|
||||
}
|
||||
return allType;
|
||||
});
|
||||
|
||||
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
|
||||
scroll: props.scroll,
|
||||
heightUsed: props.heightUsed,
|
||||
columns: props.columns,
|
||||
selectable: true,
|
||||
draggable: { type: 'handle', width: 24 },
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.params,
|
||||
(val) => {
|
||||
if (val.length > 0) {
|
||||
propsRes.value.data = val;
|
||||
} else {
|
||||
propsRes.value.data = props.params.concat({
|
||||
id: new Date().getTime(),
|
||||
required: false,
|
||||
name: '',
|
||||
type: 'string',
|
||||
value: '',
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
contentType: RequestContentTypeEnum.TEXT,
|
||||
desc: '',
|
||||
encode: false,
|
||||
});
|
||||
emit('change', propsRes.value.data, true);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.heightUsed,
|
||||
(val) => {
|
||||
propsRes.value.heightUsed = val;
|
||||
}
|
||||
);
|
||||
|
||||
const paramsLength = computed(() => propsRes.value.data.length);
|
||||
|
||||
function deleteParam(rowIndex: number) {
|
||||
propsRes.value.data.splice(rowIndex, 1);
|
||||
emit('change', propsRes.value.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当表格输入框变化时,给参数表格添加一行数据行
|
||||
* @param val 输入值
|
||||
* @param isForce 是否强制添加
|
||||
*/
|
||||
function addTableLine(val?: string | number, isForce?: boolean) {
|
||||
const lastData = propsRes.value.data[propsRes.value.data.length - 1];
|
||||
const isNotChange = Object.keys(defaultParams).every((key) => lastData[key] === defaultParams[key as any]);
|
||||
if (isForce || (val !== '' && val !== undefined && !isNotChange)) {
|
||||
propsRes.value.data.push({
|
||||
id: new Date().getTime(),
|
||||
...defaultParams,
|
||||
} as any);
|
||||
emit('change', propsRes.value.data);
|
||||
}
|
||||
}
|
||||
|
||||
const showQuickInputParam = ref(false);
|
||||
const activeQuickInputRecord = ref<any>({});
|
||||
const quickInputParamValue = ref('');
|
||||
|
||||
function quickInputParams(record: any) {
|
||||
activeQuickInputRecord.value = record;
|
||||
showQuickInputParam.value = true;
|
||||
quickInputParamValue.value = record.value;
|
||||
}
|
||||
|
||||
function clearQuickInputParam() {
|
||||
activeQuickInputRecord.value = {};
|
||||
quickInputParamValue.value = '';
|
||||
}
|
||||
|
||||
function applyQuickInputParam() {
|
||||
activeQuickInputRecord.value.value = quickInputParamValue.value;
|
||||
showQuickInputParam.value = false;
|
||||
clearQuickInputParam();
|
||||
addTableLine(quickInputParamValue.value, true);
|
||||
emit('change', propsRes.value.data);
|
||||
}
|
||||
|
||||
function handleParamSettingApply(val: string | number) {
|
||||
addTableLine(val);
|
||||
}
|
||||
|
||||
const showQuickInputDesc = ref(false);
|
||||
const quickInputDescValue = ref('');
|
||||
|
||||
function quickInputDesc(record: any) {
|
||||
activeQuickInputRecord.value = record;
|
||||
showQuickInputDesc.value = true;
|
||||
quickInputDescValue.value = record.desc;
|
||||
}
|
||||
|
||||
function clearQuickInputDesc() {
|
||||
activeQuickInputRecord.value = {};
|
||||
quickInputDescValue.value = '';
|
||||
}
|
||||
|
||||
function applyQuickInputDesc() {
|
||||
activeQuickInputRecord.value.desc = quickInputDescValue.value;
|
||||
showQuickInputDesc.value = false;
|
||||
clearQuickInputDesc();
|
||||
addTableLine(quickInputDescValue.value, true);
|
||||
emit('change', propsRes.value.data);
|
||||
}
|
||||
|
||||
function handleDescChange() {
|
||||
emit('change', propsRes.value.data);
|
||||
}
|
||||
|
||||
function handleTypeChange(
|
||||
val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[],
|
||||
record: Param
|
||||
) {
|
||||
addTableLine(val as string);
|
||||
if (val === 'file') {
|
||||
record.contentType = RequestContentTypeEnum.OCTET_STREAM;
|
||||
} else if (val === 'json') {
|
||||
record.contentType = RequestContentTypeEnum.JSON;
|
||||
} else {
|
||||
record.contentType = RequestContentTypeEnum.TEXT;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-table-th) {
|
||||
background-color: var(--color-text-n9);
|
||||
}
|
||||
:deep(.arco-table-cell-align-left) {
|
||||
padding: 16px 4px;
|
||||
}
|
||||
:deep(.arco-table-cell) {
|
||||
padding: 11px 4px;
|
||||
}
|
||||
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
|
||||
&:not(:hover) {
|
||||
border-color: transparent !important;
|
||||
.arco-input::placeholder {
|
||||
@apply invisible;
|
||||
}
|
||||
.arco-select-view-icon {
|
||||
@apply invisible;
|
||||
}
|
||||
.arco-select-view-value {
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
}
|
||||
}
|
||||
.param-input-switch:not(:hover).arco-switch-checked {
|
||||
background-color: rgb(var(--primary-3)) !important;
|
||||
}
|
||||
.content-type-trigger-content {
|
||||
@apply bg-white;
|
||||
|
||||
padding: 8px;
|
||||
border-radius: var(--border-radius-small);
|
||||
box-shadow: 0 4px 10px -1px rgb(100 100 102 / 15%);
|
||||
}
|
||||
.param-input {
|
||||
.param-input-mock-icon {
|
||||
@apply invisible;
|
||||
}
|
||||
&:hover,
|
||||
&.arco-input-focus {
|
||||
.param-input-mock-icon {
|
||||
@apply visible cursor-pointer;
|
||||
&:hover {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.param-popover-title {
|
||||
@apply font-medium;
|
||||
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.param-popover-subtitle {
|
||||
margin-bottom: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.param-popover-value {
|
||||
min-width: 100px;
|
||||
max-width: 280px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<a-button type="outline" size="mini" @click="showBatchAddParamDrawer = true">
|
||||
{{ t('ms.apiTestDebug.batchAdd') }}
|
||||
</a-button>
|
||||
<MsDrawer
|
||||
v-model:visible="showBatchAddParamDrawer"
|
||||
:title="t('common.batchAdd')"
|
||||
:width="680"
|
||||
:ok-text="t('ms.apiTestDebug.apply')"
|
||||
disabled-width-drag
|
||||
@confirm="applyBatchParams"
|
||||
>
|
||||
<div class="flex h-full">
|
||||
<MsCodeEditor
|
||||
v-if="showBatchAddParamDrawer"
|
||||
v-model:model-value="batchParamsCode"
|
||||
class="flex-1"
|
||||
theme="MS-text"
|
||||
height="calc(100% - 48px)"
|
||||
:show-full-screen="false"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
{{ t('ms.apiTestDebug.batchAddParamsTip') }}
|
||||
</div>
|
||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
{{ t('ms.apiTestDebug.batchAddParamsTip2') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { RequestContentTypeEnum } from '@/enums/apiEnum';
|
||||
|
||||
const props = defineProps<{
|
||||
params: Record<string, any>[];
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'apply', resultArr: (Record<string, any> | null)[]): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const showBatchAddParamDrawer = ref(false);
|
||||
const batchParamsCode = ref('');
|
||||
|
||||
watch(
|
||||
() => showBatchAddParamDrawer.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
batchParamsCode.value = props.params
|
||||
.filter((e) => e && (e.name !== '' || e.value !== ''))
|
||||
.map((item) => `${item.name}:${item.value}`)
|
||||
.join('\n');
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 批量参数代码转换为参数表格数据
|
||||
*/
|
||||
function applyBatchParams() {
|
||||
const arr = batchParamsCode.value.replaceAll('\r', '\n').split('\n'); // 先将回车符替换成换行符,避免粘贴的代码是以回车符分割的,然后以换行符分割
|
||||
const resultArr = arr
|
||||
.map((item, i) => {
|
||||
const [name, value] = item.split(':');
|
||||
if (name || value) {
|
||||
return {
|
||||
id: new Date().getTime() + i,
|
||||
name: name?.trim(),
|
||||
value: value?.trim(),
|
||||
required: false,
|
||||
type: 'string',
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
contentType: RequestContentTypeEnum.TEXT,
|
||||
desc: '',
|
||||
encode: false,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((item) => item);
|
||||
showBatchAddParamDrawer.value = false;
|
||||
batchParamsCode.value = '';
|
||||
emit('apply', resultArr);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,222 @@
|
|||
<template>
|
||||
<div class="mb-[8px] flex items-center justify-between">
|
||||
<div class="font-medium">{{ t('ms.apiTestDebug.body') }}</div>
|
||||
<div class="flex items-center gap-[16px]">
|
||||
<batchAddKeyVal v-if="showParamTable" :params="currentTableParams" @apply="handleBatchParamApply" />
|
||||
<a-radio-group v-model:model-value="format" type="button" size="small" @change="formatChange">
|
||||
<a-radio v-for="item of RequestBodyFormat" :key="item" :value="item">{{ item }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="format === RequestBodyFormat.NONE"
|
||||
class="flex h-[100px] items-center justify-center rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] text-[var(--color-text-4)]"
|
||||
>
|
||||
{{ t('ms.apiTestDebug.noneBody') }}
|
||||
</div>
|
||||
<paramTable
|
||||
v-else-if="showParamTable"
|
||||
v-model:params="currentTableParams"
|
||||
:scroll="{ minWidth: 1160 }"
|
||||
:format="format"
|
||||
:columns="columns"
|
||||
:height-used="heightUsed"
|
||||
@change="handleParamTableChange"
|
||||
/>
|
||||
<div v-else class="flex h-[calc(100%-100px)]">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="currentBodyCode"
|
||||
class="flex-1"
|
||||
theme="vs-dark"
|
||||
height="calc(100% - 48px)"
|
||||
:show-full-screen="false"
|
||||
:language="currentCodeLanguage"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
{{ t('ms.apiTestDebug.batchAddParamsTip') }}
|
||||
</div>
|
||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
{{ t('ms.apiTestDebug.batchAddParamsTip2') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import paramTable from '../../../components/paramTable.vue';
|
||||
import batchAddKeyVal from './batchAddKeyVal.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { RequestBodyFormat } from '@/enums/apiEnum';
|
||||
|
||||
export interface BodyParams {
|
||||
format: RequestBodyFormat;
|
||||
formData: Record<string, any>[];
|
||||
formUrlEncode: Record<string, any>[];
|
||||
json: string;
|
||||
xml: string;
|
||||
binary: string;
|
||||
raw: string;
|
||||
}
|
||||
const props = defineProps<{
|
||||
params: BodyParams;
|
||||
layout: 'horizontal' | 'vertical';
|
||||
secondBoxHeight: number;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:params', value: any[]): void;
|
||||
(e: 'change'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const innerParams = useVModel(props, 'params', emit);
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'ms.apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
},
|
||||
{
|
||||
title: 'ms.apiTestDebug.paramType',
|
||||
dataIndex: 'type',
|
||||
slotName: 'type',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'ms.apiTestDebug.paramValue',
|
||||
dataIndex: 'value',
|
||||
slotName: 'value',
|
||||
width: 240,
|
||||
},
|
||||
{
|
||||
title: 'ms.apiTestDebug.paramLengthRange',
|
||||
dataIndex: 'lengthRange',
|
||||
slotName: 'lengthRange',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'ms.apiTestDebug.desc',
|
||||
dataIndex: 'desc',
|
||||
slotName: 'desc',
|
||||
},
|
||||
{
|
||||
title: 'ms.apiTestDebug.encode',
|
||||
dataIndex: 'encode',
|
||||
slotName: 'encode',
|
||||
titleSlotName: 'encodeTitle',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
slotName: 'operation',
|
||||
fixed: 'right',
|
||||
width: 80,
|
||||
},
|
||||
];
|
||||
|
||||
const heightUsed = ref<number | undefined>(undefined);
|
||||
|
||||
watch(
|
||||
() => props.layout,
|
||||
(val) => {
|
||||
heightUsed.value = val === 'horizontal' ? 422 : 422 + props.secondBoxHeight;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.secondBoxHeight,
|
||||
(val) => {
|
||||
if (props.layout === 'vertical') {
|
||||
heightUsed.value = 422 + val;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
const format = ref(RequestBodyFormat.NONE);
|
||||
const showParamTable = computed(() => {
|
||||
return [RequestBodyFormat.FORM_DATA, RequestBodyFormat.X_WWW_FORM_URLENCODED].includes(format.value);
|
||||
});
|
||||
const currentTableParams = computed({
|
||||
get() {
|
||||
if (format.value === RequestBodyFormat.FORM_DATA) {
|
||||
return innerParams.value.formData;
|
||||
}
|
||||
return innerParams.value.formUrlEncode;
|
||||
},
|
||||
set(val) {
|
||||
if (format.value === RequestBodyFormat.FORM_DATA) {
|
||||
innerParams.value.formData = val;
|
||||
} else {
|
||||
innerParams.value.formUrlEncode = val;
|
||||
}
|
||||
},
|
||||
});
|
||||
const currentBodyCode = computed({
|
||||
get() {
|
||||
if (format.value === RequestBodyFormat.JSON) {
|
||||
return innerParams.value.json;
|
||||
}
|
||||
if (format.value === RequestBodyFormat.XML) {
|
||||
return innerParams.value.xml;
|
||||
}
|
||||
return innerParams.value.raw;
|
||||
},
|
||||
set(val) {
|
||||
if (format.value === RequestBodyFormat.JSON) {
|
||||
innerParams.value.json = val;
|
||||
} else if (format.value === RequestBodyFormat.XML) {
|
||||
innerParams.value.xml = val;
|
||||
} else {
|
||||
innerParams.value.raw = val;
|
||||
}
|
||||
},
|
||||
});
|
||||
const currentCodeLanguage = computed(() => {
|
||||
if (format.value === RequestBodyFormat.JSON) {
|
||||
return 'json';
|
||||
}
|
||||
if (format.value === RequestBodyFormat.XML) {
|
||||
return 'xml';
|
||||
}
|
||||
return 'plaintext';
|
||||
});
|
||||
|
||||
function formatChange() {
|
||||
console.log('formatChange', format.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量参数代码转换为参数表格数据
|
||||
*/
|
||||
function handleBatchParamApply(resultArr: any[]) {
|
||||
if (resultArr.length < currentTableParams.value.length) {
|
||||
currentTableParams.value.splice(0, currentTableParams.value.length - 1, ...resultArr);
|
||||
} else {
|
||||
currentTableParams.value = [...resultArr, currentTableParams.value[currentTableParams.value.length - 1]];
|
||||
}
|
||||
emit('change');
|
||||
}
|
||||
|
||||
function handleParamTableChange(resultArr: any[]) {
|
||||
currentTableParams.value = [...resultArr];
|
||||
emit('change');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -1,131 +1,23 @@
|
|||
<template>
|
||||
<div class="mb-[8px] flex items-center justify-between">
|
||||
<div class="font-medium">{{ t('ms.apiTestDebug.header') }}</div>
|
||||
<a-button type="outline" size="mini" @click="showBatchAddParamDrawer = true">
|
||||
{{ t('ms.apiTestDebug.batchAdd') }}
|
||||
</a-button>
|
||||
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" />
|
||||
</div>
|
||||
<div class="relative">
|
||||
<MsBaseTable v-bind="propsRes" id="headerTable" v-on="propsEvent">
|
||||
<template #name="{ record }">
|
||||
<a-popover position="tl" :disabled="!record.name || record.name.trim() === ''" class="ms-params-input-popover">
|
||||
<template #content>
|
||||
<div class="param-popover-title">
|
||||
{{ t('ms.apiTestDebug.paramName') }}
|
||||
</div>
|
||||
<div class="param-popover-value">
|
||||
{{ record.name }}
|
||||
</div>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:model-value="record.name"
|
||||
:placeholder="t('ms.apiTestDebug.paramNamePlaceholder')"
|
||||
class="param-input"
|
||||
@input="addTableLine"
|
||||
/>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #value="{ record }">
|
||||
<MsParamsInput
|
||||
v-model:value="record.value"
|
||||
@change="addTableLine"
|
||||
@dblclick="quickInputParams(record)"
|
||||
@apply="handleParamSettingApply"
|
||||
/>
|
||||
</template>
|
||||
<template #desc="{ record }">
|
||||
<paramDescInput v-model:desc="record.desc" @input="addTableLine" @dblclick="quickInputDesc(record)" />
|
||||
</template>
|
||||
<template #operation="{ rowIndex }">
|
||||
<icon-minus-circle
|
||||
v-if="paramsLength > 1 && rowIndex !== paramsLength - 1"
|
||||
class="cursor-pointer text-[var(--color-text-4)]"
|
||||
size="20"
|
||||
@click="deleteParam(rowIndex)"
|
||||
/>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
</div>
|
||||
<MsDrawer
|
||||
v-model:visible="showBatchAddParamDrawer"
|
||||
:title="t('common.batchAdd')"
|
||||
:width="680"
|
||||
:ok-text="t('ms.apiTestDebug.apply')"
|
||||
disabled-width-drag
|
||||
@confirm="applyBatchParams"
|
||||
>
|
||||
<div class="flex h-full">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="batchParamsCode"
|
||||
class="flex-1"
|
||||
theme="MS-text"
|
||||
height="calc(100% - 48px)"
|
||||
:show-full-screen="false"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
{{ t('ms.apiTestDebug.batchAddParamsTip') }}
|
||||
</div>
|
||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
{{ t('ms.apiTestDebug.batchAddParamsTip2') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
</MsDrawer>
|
||||
<a-modal
|
||||
v-model:visible="showQuickInputParam"
|
||||
:title="t('ms.paramsInput.value')"
|
||||
:ok-text="t('ms.apiTestDebug.apply')"
|
||||
class="ms-modal-form"
|
||||
body-class="!p-0"
|
||||
:width="680"
|
||||
title-align="start"
|
||||
@ok="applyQuickInputParam"
|
||||
@close="clearQuickInputParam"
|
||||
>
|
||||
<MsCodeEditor v-model:model-value="quickInputParamValue" theme="MS-text" height="300px" :show-full-screen="false">
|
||||
<template #title>
|
||||
<div class="flex justify-between">
|
||||
<div class="text-[var(--color-text-1)]">
|
||||
{{ t('ms.apiTestDebug.quickInputParamsTip') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsCodeEditor>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
v-model:visible="showQuickInputDesc"
|
||||
:title="t('ms.apiTestDebug.desc')"
|
||||
:ok-text="t('common.save')"
|
||||
:ok-button-props="{ disabled: !quickInputDescValue || quickInputDescValue.trim() === '' }"
|
||||
class="ms-modal-form"
|
||||
body-class="!p-0"
|
||||
:width="480"
|
||||
title-align="start"
|
||||
:auto-size="{ minRows: 2 }"
|
||||
@ok="applyQuickInputDesc"
|
||||
@close="clearQuickInputDesc"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:model-value="quickInputDescValue"
|
||||
:placeholder="t('ms.apiTestDebug.descPlaceholder')"
|
||||
:max-length="255"
|
||||
show-word-limit
|
||||
></a-textarea>
|
||||
</a-modal>
|
||||
<paramTable
|
||||
v-model:params="innerParams"
|
||||
:columns="columns"
|
||||
:height-used="heightUsed"
|
||||
:scroll="scroll"
|
||||
@change="handleParamTableChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsParamsInput from '@/components/business/ms-params-input/index.vue';
|
||||
import paramDescInput from './paramDescInput.vue';
|
||||
import paramTable from '../../../components/paramTable.vue';
|
||||
import batchAddKeyVal from './batchAddKeyVal.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
|
@ -134,9 +26,15 @@
|
|||
layout: 'horizontal' | 'vertical';
|
||||
secondBoxHeight: number;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:params', value: any[]): void;
|
||||
(e: 'change'): void; // 数据发生变化
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const innerParams = useVModel(props, 'params', emit);
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'ms.apiTestDebug.paramName',
|
||||
|
@ -159,25 +57,14 @@
|
|||
width: 50,
|
||||
},
|
||||
];
|
||||
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
|
||||
scroll: props.layout === 'horizontal' ? { x: '700px' } : { x: '100%' },
|
||||
heightUsed: props.layout === 'horizontal' ? 422 : 422 + props.secondBoxHeight,
|
||||
columns,
|
||||
selectable: true,
|
||||
draggable: { type: 'handle', width: 24 },
|
||||
});
|
||||
|
||||
propsRes.value.data = props.params.concat({
|
||||
id: new Date().getTime(),
|
||||
name: '',
|
||||
value: '',
|
||||
desc: '',
|
||||
});
|
||||
const heightUsed = ref<number | undefined>(undefined);
|
||||
const scroll = computed(() => (props.layout === 'horizontal' ? { x: '700px' } : { x: '100%' }));
|
||||
|
||||
watch(
|
||||
() => props.layout,
|
||||
(val) => {
|
||||
propsRes.value.heightUsed = val === 'horizontal' ? 422 : 422 + props.secondBoxHeight;
|
||||
heightUsed.value = val === 'horizontal' ? 422 : 422 + props.secondBoxHeight;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
|
@ -188,7 +75,7 @@
|
|||
() => props.secondBoxHeight,
|
||||
(val) => {
|
||||
if (props.layout === 'vertical') {
|
||||
propsRes.value.heightUsed = 422 + val;
|
||||
heightUsed.value = 422 + val;
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -196,150 +83,24 @@
|
|||
}
|
||||
);
|
||||
|
||||
const paramsLength = computed(() => propsRes.value.data.length);
|
||||
|
||||
function deleteParam(rowIndex: number) {
|
||||
propsRes.value.data.splice(rowIndex, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当表格输入框变化时,给参数表格添加一行数据行
|
||||
* @param val 输入值
|
||||
*/
|
||||
function addTableLine(val: string | number) {
|
||||
const lastData = propsRes.value.data[propsRes.value.data.length - 1];
|
||||
if (val && (lastData.name || lastData.value || lastData.desc)) {
|
||||
propsRes.value.data.push({
|
||||
id: new Date().getTime(),
|
||||
name: '',
|
||||
value: '',
|
||||
desc: '',
|
||||
} as any);
|
||||
}
|
||||
}
|
||||
|
||||
const showBatchAddParamDrawer = ref(false);
|
||||
const batchParamsCode = ref('');
|
||||
|
||||
/**
|
||||
* 批量参数代码转换为参数表格数据
|
||||
*/
|
||||
function applyBatchParams() {
|
||||
const arr = batchParamsCode.value.replaceAll('\r', '\n').split('\n'); // 先将回车符替换成换行符,避免粘贴的代码是以回车符分割的,然后以换行符分割
|
||||
const resultArr = arr
|
||||
.map((item, i) => {
|
||||
const [name, value] = item.split(':');
|
||||
if (name && value) {
|
||||
return {
|
||||
id: new Date().getTime() + i,
|
||||
name: name.trim(),
|
||||
value: value.trim(),
|
||||
desc: '',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((item) => item);
|
||||
propsRes.value.data.splice(propsRes.value.data.length - 1, 0, ...(resultArr as any[]));
|
||||
showBatchAddParamDrawer.value = false;
|
||||
batchParamsCode.value = '';
|
||||
function handleBatchParamApply(resultArr: any[]) {
|
||||
if (resultArr.length < innerParams.value.length) {
|
||||
innerParams.value.splice(0, innerParams.value.length - 1, ...resultArr);
|
||||
} else {
|
||||
innerParams.value = [...resultArr, innerParams.value[innerParams.value.length - 1]];
|
||||
}
|
||||
emit('change');
|
||||
}
|
||||
|
||||
const showQuickInputParam = ref(false);
|
||||
const activeQuickInputRecord = ref<any>({});
|
||||
const quickInputParamValue = ref('');
|
||||
|
||||
function quickInputParams(record: any) {
|
||||
activeQuickInputRecord.value = record;
|
||||
showQuickInputParam.value = true;
|
||||
quickInputParamValue.value = record.value;
|
||||
}
|
||||
|
||||
function clearQuickInputParam() {
|
||||
activeQuickInputRecord.value = {};
|
||||
quickInputParamValue.value = '';
|
||||
}
|
||||
|
||||
function applyQuickInputParam() {
|
||||
activeQuickInputRecord.value.value = quickInputParamValue.value;
|
||||
showQuickInputParam.value = false;
|
||||
clearQuickInputParam();
|
||||
}
|
||||
|
||||
function handleParamSettingApply(val: string | number) {
|
||||
addTableLine(val);
|
||||
}
|
||||
|
||||
const showQuickInputDesc = ref(false);
|
||||
const quickInputDescValue = ref('');
|
||||
|
||||
function quickInputDesc(record: any) {
|
||||
activeQuickInputRecord.value = record;
|
||||
showQuickInputDesc.value = true;
|
||||
quickInputDescValue.value = record.desc;
|
||||
}
|
||||
|
||||
function clearQuickInputDesc() {
|
||||
activeQuickInputRecord.value = {};
|
||||
quickInputDescValue.value = '';
|
||||
}
|
||||
|
||||
function applyQuickInputDesc() {
|
||||
activeQuickInputRecord.value.desc = quickInputDescValue.value;
|
||||
showQuickInputDesc.value = false;
|
||||
clearQuickInputDesc();
|
||||
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
|
||||
innerParams.value = [...resultArr];
|
||||
if (!isInit) {
|
||||
emit('change');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-table-th) {
|
||||
background-color: var(--color-text-n9);
|
||||
}
|
||||
:deep(.arco-table-cell-align-left) {
|
||||
padding: 16px 4px;
|
||||
}
|
||||
:deep(.arco-table-cell) {
|
||||
padding: 11px 4px;
|
||||
}
|
||||
.param-input:not(.arco-input-focus) {
|
||||
&:not(:hover) {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
.param-input {
|
||||
.param-input-mock-icon {
|
||||
@apply invisible;
|
||||
}
|
||||
&:hover,
|
||||
&.arco-input-focus {
|
||||
.param-input-mock-icon {
|
||||
@apply visible cursor-pointer;
|
||||
&:hover {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.param-popover-title {
|
||||
@apply font-medium;
|
||||
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.param-popover-subtitle {
|
||||
margin-bottom: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.param-popover-value {
|
||||
min-width: 100px;
|
||||
max-width: 280px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -6,28 +6,40 @@
|
|||
:more-action-list="moreActionList"
|
||||
@add="addDebugTab"
|
||||
@close="closeDebugTab"
|
||||
@click="setActiveDebug"
|
||||
@change="setActiveDebug"
|
||||
>
|
||||
<template #label="{ tab }">
|
||||
<apiMethodName :method="tab.method" class="mr-[4px]" />
|
||||
{{ tab.label }}
|
||||
<div class="ml-[8px] h-[8px] w-[8px] rounded-full bg-[rgb(var(--primary-5))]"></div>
|
||||
<div v-if="tab.unSave" class="ml-[8px] h-[8px] w-[8px] rounded-full bg-[rgb(var(--primary-5))]"></div>
|
||||
</template>
|
||||
</MsEditableTab>
|
||||
</div>
|
||||
<div class="px-[24px] pt-[16px]">
|
||||
<div class="mb-[4px] flex items-center justify-between">
|
||||
<a-input-group class="flex-1">
|
||||
<a-select v-model:model-value="activeDebug.method" class="w-[140px]">
|
||||
<template #label="{ data }">
|
||||
<apiMethodName :method="data.value" class="inline-block" />
|
||||
</template>
|
||||
<a-option v-for="method of RequestMethods" :key="method" :value="method">
|
||||
<apiMethodName :method="method" />
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-input v-model:model-value="debugUrl" :placeholder="t('ms.apiTestDebug.urlPlaceholder')" />
|
||||
</a-input-group>
|
||||
<div class="flex flex-1">
|
||||
<a-select
|
||||
v-model:model-value="activeDebug.moduleProtocol"
|
||||
:options="moduleProtocolOptions"
|
||||
class="mr-[4px] w-[90px]"
|
||||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
<a-input-group class="flex-1">
|
||||
<a-select v-model:model-value="activeDebug.method" class="w-[140px]" @change="handleActiveDebugChange">
|
||||
<template #label="{ data }">
|
||||
<apiMethodName :method="data.value" class="inline-block" />
|
||||
</template>
|
||||
<a-option v-for="method of RequestMethods" :key="method" :value="method">
|
||||
<apiMethodName :method="method" />
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-input
|
||||
v-model:model-value="debugUrl"
|
||||
:placeholder="t('ms.apiTestDebug.urlPlaceholder')"
|
||||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
</a-input-group>
|
||||
</div>
|
||||
<div class="ml-[16px]">
|
||||
<a-dropdown-button class="exec-btn">
|
||||
{{ t('ms.apiTestDebug.serverExec') }}
|
||||
|
@ -57,12 +69,25 @@
|
|||
@expand-change="handleExpandChange"
|
||||
>
|
||||
<template #first>
|
||||
<div :class="`h-full min-w-[500px] px-[24px] pb-[16px] ${activeLayout === 'horizontal' ? ' pr-[16px]' : ''}`">
|
||||
<a-tabs v-model:active-key="contentTab" class="no-content">
|
||||
<div :class="`h-full min-w-[700px] px-[24px] pb-[16px] ${activeLayout === 'horizontal' ? ' pr-[16px]' : ''}`">
|
||||
<a-tabs v-model:active-key="activeDebug.activeTab" class="no-content">
|
||||
<a-tab-pane v-for="item of contentTabList" :key="item.value" :title="item.label" />
|
||||
</a-tabs>
|
||||
<a-divider margin="0" class="!mb-[16px]"></a-divider>
|
||||
<debugHeader :params="activeDebug.params" :layout="activeLayout" :second-box-height="secondBoxHeight" />
|
||||
<debugHeader
|
||||
v-if="activeDebug.activeTab === RequestComposition.HEADER"
|
||||
v-model:params="activeDebug.headerParams"
|
||||
:layout="activeLayout"
|
||||
:second-box-height="secondBoxHeight"
|
||||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
<debugBody
|
||||
v-else-if="activeDebug.activeTab === RequestComposition.BODY"
|
||||
v-model:params="activeDebug.bodyParams"
|
||||
:layout="activeLayout"
|
||||
:second-box-height="secondBoxHeight"
|
||||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #second>
|
||||
|
@ -99,31 +124,45 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { debounce } from 'lodash-es';
|
||||
import { cloneDeep, debounce } from 'lodash-es';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
||||
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import apiMethodName from '../../../components/apiMethodName.vue';
|
||||
import debugBody, { BodyParams } from './body.vue';
|
||||
import debugHeader from './header.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
|
||||
|
||||
import { RequestComposition, RequestMethods } from '@/enums/apiEnum';
|
||||
import { RequestBodyFormat, RequestComposition, RequestMethods } from '@/enums/apiEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const initDefaultId = `debug-${Date.now()}`;
|
||||
const activeTab = ref<string | number>(initDefaultId);
|
||||
const defaultBodyParams: BodyParams = {
|
||||
format: RequestBodyFormat.NONE,
|
||||
formData: [],
|
||||
formUrlEncode: [],
|
||||
json: '',
|
||||
xml: '',
|
||||
binary: '',
|
||||
raw: '',
|
||||
};
|
||||
const debugTabs = ref<TabItem[]>([
|
||||
{
|
||||
id: initDefaultId,
|
||||
moduleProtocol: 'http',
|
||||
activeTab: RequestComposition.HEADER,
|
||||
label: t('ms.apiTestDebug.newApi'),
|
||||
closable: true,
|
||||
method: RequestMethods.GET,
|
||||
unSave: true,
|
||||
params: [],
|
||||
unSave: false,
|
||||
headerParams: [],
|
||||
bodyParams: cloneDeep(defaultBodyParams),
|
||||
},
|
||||
]);
|
||||
const debugUrl = ref('');
|
||||
|
@ -133,15 +172,22 @@
|
|||
activeDebug.value = item;
|
||||
}
|
||||
|
||||
function handleActiveDebugChange() {
|
||||
activeDebug.value.unSave = true;
|
||||
}
|
||||
|
||||
function addDebugTab() {
|
||||
const id = `debug-${Date.now()}`;
|
||||
debugTabs.value.push({
|
||||
id,
|
||||
moduleProtocol: 'http',
|
||||
activeTab: RequestComposition.HEADER,
|
||||
label: t('ms.apiTestDebug.newApi'),
|
||||
closable: true,
|
||||
method: RequestMethods.GET,
|
||||
unSave: true,
|
||||
params: [],
|
||||
unSave: false,
|
||||
headerParams: [],
|
||||
bodyParams: cloneDeep(defaultBodyParams),
|
||||
});
|
||||
activeTab.value = id;
|
||||
}
|
||||
|
@ -165,7 +211,6 @@
|
|||
},
|
||||
];
|
||||
|
||||
const contentTab = ref(RequestComposition.HEADER);
|
||||
const contentTabList = [
|
||||
{
|
||||
value: RequestComposition.HEADER,
|
||||
|
@ -205,6 +250,13 @@
|
|||
},
|
||||
];
|
||||
|
||||
const moduleProtocolOptions = ref([
|
||||
{
|
||||
label: 'HTTP',
|
||||
value: 'http',
|
||||
},
|
||||
]);
|
||||
|
||||
const splitBoxSize = ref<string | number>(0.6);
|
||||
const activeLayout = ref<'horizontal' | 'vertical'>('vertical');
|
||||
const splitContainerRef = ref<HTMLElement>();
|
||||
|
@ -241,6 +293,22 @@
|
|||
isExpanded.value = true;
|
||||
splitBoxSize.value = 0.6;
|
||||
}
|
||||
|
||||
function saveDebug() {
|
||||
activeDebug.value.unSave = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
registerCatchSaveShortcut(saveDebug);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
removeCatchSaveShortcut(saveDebug);
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
addDebugTab,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-[8px] flex items-center gap-[8px]">
|
||||
<a-select v-model:model-value="moduleProtocol" :options="moduleProtocolOptions" class="w-[90px]"></a-select>
|
||||
<a-input
|
||||
v-model:model-value="moduleKeyword"
|
||||
:placeholder="t('caseManagement.caseReview.folderSearchPlaceholder')"
|
||||
allow-clear
|
||||
/>
|
||||
<a-dropdown @select="handleSelect">
|
||||
<a-button type="primary">{{ t('ms.apiTestDebug.newApi') }}</a-button>
|
||||
<template #content>
|
||||
<a-doption value="newApi">{{ t('ms.apiTestDebug.newApi') }}</a-doption>
|
||||
<a-doption value="import">{{ t('ms.apiTestDebug.importApi') }}</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<div v-if="!props.isModal" class="folder">
|
||||
<div class="folder-text">
|
||||
|
@ -118,12 +124,25 @@
|
|||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||
isExpandAll?: boolean; // 是否展开所有节点
|
||||
}>();
|
||||
const emit = defineEmits(['init', 'folderNodeSelect']);
|
||||
const emit = defineEmits(['init', 'folderNodeSelect', 'newApi']);
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
||||
function handleSelect(value: string | number | Record<string, any> | undefined) {
|
||||
switch (value) {
|
||||
case 'newApi':
|
||||
emit('newApi');
|
||||
break;
|
||||
case 'import':
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const virtualListProps = computed(() => {
|
||||
if (props.isModal) {
|
||||
return {
|
||||
|
@ -151,13 +170,6 @@
|
|||
isExpandAll.value = !isExpandAll.value;
|
||||
}
|
||||
|
||||
const moduleProtocol = ref('http');
|
||||
const moduleProtocolOptions = ref([
|
||||
{
|
||||
label: 'HTTP',
|
||||
value: 'http',
|
||||
},
|
||||
]);
|
||||
const moduleKeyword = ref('');
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
const focusNodeKey = ref<string | number>('');
|
||||
|
|
|
@ -2,17 +2,13 @@
|
|||
<MsCard simple no-content-padding>
|
||||
<MsSplitBox :size="0.25" :max="0.5">
|
||||
<template #first>
|
||||
<div class="border-b border-[var(--color-text-n8)] p-[24px_24px_16px_24px]">
|
||||
<a-button type="primary" class="mr-[12px]">{{ t('ms.apiTestDebug.createDebug') }}</a-button>
|
||||
<a-button type="outline">{{ t('common.import') }}</a-button>
|
||||
</div>
|
||||
<div class="px-[24px] py-[16px]">
|
||||
<moduleTree />
|
||||
<div class="p-[24px]">
|
||||
<moduleTree @new-api="newApi" />
|
||||
</div>
|
||||
</template>
|
||||
<template #second>
|
||||
<div class="flex h-full flex-col">
|
||||
<debug />
|
||||
<debug ref="debugRef" />
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
|
@ -25,22 +21,11 @@
|
|||
import debug from './components/debug/index.vue';
|
||||
import moduleTree from './components/moduleTree.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
|
||||
const debugRef = ref<InstanceType<typeof debug>>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
function saveDebug() {
|
||||
console.log('save');
|
||||
function newApi() {
|
||||
debugRef.value?.addDebugTab();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
registerCatchSaveShortcut(saveDebug);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
removeCatchSaveShortcut(saveDebug);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
'ms.apiTestDebug.createDebug': 'New debug',
|
||||
'ms.apiTestDebug.newApi': 'New request',
|
||||
'ms.apiTestDebug.importApi': 'Import request',
|
||||
'ms.apiTestDebug.urlPlaceholder': 'Please enter the full URL including http or https',
|
||||
'ms.apiTestDebug.serverExec': 'Server execution',
|
||||
'ms.apiTestDebug.localExec': 'Local execution',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
'ms.apiTestDebug.createDebug': '新建调试',
|
||||
'ms.apiTestDebug.newApi': '新建请求',
|
||||
'ms.apiTestDebug.importApi': '导入请求',
|
||||
'ms.apiTestDebug.urlPlaceholder': '请输入包含 http 或 https 的完整URL',
|
||||
'ms.apiTestDebug.serverExec': '服务端执行',
|
||||
'ms.apiTestDebug.localExec': '本地执行',
|
||||
|
@ -18,13 +18,23 @@ export default {
|
|||
'ms.apiTestDebug.horizontal': '左右布局',
|
||||
'ms.apiTestDebug.paramName': '参数名称',
|
||||
'ms.apiTestDebug.paramNamePlaceholder': '请输入参数名称',
|
||||
'ms.apiTestDebug.paramRequired': '必填',
|
||||
'ms.apiTestDebug.paramNotRequired': '非必填',
|
||||
'ms.apiTestDebug.paramType': '类型',
|
||||
'ms.apiTestDebug.paramValue': '参数值',
|
||||
'ms.apiTestDebug.paramValuePlaceholder': '以{at}开始,双击可快速输入',
|
||||
'ms.apiTestDebug.paramLengthRange': '长度区间',
|
||||
'ms.apiTestDebug.paramMin': '最小值',
|
||||
'ms.apiTestDebug.paramMax': '最大值',
|
||||
'ms.apiTestDebug.paramValuePreview': '参数预览',
|
||||
'ms.apiTestDebug.desc': '描述',
|
||||
'ms.apiTestDebug.encode': '编码',
|
||||
'ms.apiTestDebug.encodeTip1': '开启:使用编码',
|
||||
'ms.apiTestDebug.encodeTip2': '关闭:不使用编码',
|
||||
'ms.apiTestDebug.apply': '应用',
|
||||
'ms.apiTestDebug.batchAddParamsTip': '书写格式:参数名:参数值;如 nama:natural',
|
||||
'ms.apiTestDebug.batchAddParamsTip2': '注: 多条记录以换行分隔,批量添加里的参数名重复,默认以最后一条数据为最新数据',
|
||||
'ms.apiTestDebug.quickInputParamsTip': '支持Mock/JMeter/Json/Text/String等',
|
||||
'ms.apiTestDebug.descPlaceholder': '请输入内容',
|
||||
'ms.apiTestDebug.noneBody': '请求没有 Body',
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue