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