feat(接口测试): 接口调试-执行+保存接口
This commit is contained in:
parent
31c9897746
commit
ac9a0e5c78
|
@ -1,29 +0,0 @@
|
||||||
import Footer from '@/components/pure/footer/index.vue';
|
|
||||||
|
|
||||||
import { mount } from '@vue/test-utils';
|
|
||||||
import { describe, expect, test } from 'vitest';
|
|
||||||
|
|
||||||
describe('Footer', () => {
|
|
||||||
test('renders the correct text', () => {
|
|
||||||
const wrapper = mount(Footer, {
|
|
||||||
props: {
|
|
||||||
text: 'Custom Text',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.text()).toBe('Custom Text');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('renders the default text if no prop is provided', () => {
|
|
||||||
const wrapper = mount(Footer);
|
|
||||||
|
|
||||||
expect(wrapper.text()).toBe('MeterSphere');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('applies the correct styles', () => {
|
|
||||||
const wrapper = mount(Footer);
|
|
||||||
|
|
||||||
expect(wrapper.find('.footer').exists()).toBe(true);
|
|
||||||
expect(wrapper.classes()).toContain('footer');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,99 +0,0 @@
|
||||||
import { nextTick } from 'vue';
|
|
||||||
|
|
||||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
|
||||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
|
||||||
|
|
||||||
import { getTableList } from '@/api/modules/api-test/index';
|
|
||||||
|
|
||||||
import { mount } from '@vue/test-utils';
|
|
||||||
import { describe, expect, test } from 'vitest';
|
|
||||||
|
|
||||||
const columns: MsTableColumn = [
|
|
||||||
{
|
|
||||||
title: 'ID',
|
|
||||||
dataIndex: 'num',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '接口名称',
|
|
||||||
dataIndex: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '请求类型',
|
|
||||||
dataIndex: 'method',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '责任人',
|
|
||||||
dataIndex: 'username',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '路径',
|
|
||||||
dataIndex: 'path',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '标签',
|
|
||||||
dataIndex: 'tags',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '更新时间',
|
|
||||||
slotName: 'updataTime',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '用例数',
|
|
||||||
dataIndex: 'caseTotal',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '用例状态',
|
|
||||||
dataIndex: 'caseStatus',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '用例通过率',
|
|
||||||
dataIndex: 'casePassingRate',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '接口状态',
|
|
||||||
dataIndex: 'status',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '创建时间',
|
|
||||||
slotName: 'createTime',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '描述',
|
|
||||||
dataIndex: 'description',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
slotName: 'action',
|
|
||||||
fixed: 'right',
|
|
||||||
width: 200,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
describe('MS-Table', () => {
|
|
||||||
test('init table with useTable', async () => {
|
|
||||||
const { propsRes, propsEvent, loadList, setProps } = useTable(getTableList, {
|
|
||||||
columns,
|
|
||||||
scroll: { y: 750, x: 2000 },
|
|
||||||
selectable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapper = mount(MsBaseTable as any, {
|
|
||||||
vOn: propsEvent,
|
|
||||||
vBind: propsRes,
|
|
||||||
});
|
|
||||||
loadList();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
let content = wrapper.find('.arco-table-td-content').element.innerHTML;
|
|
||||||
expect(propsRes.value.data.length).toBe(20);
|
|
||||||
expect(content).toBe('e7bd7179-d63a-43a5-1a65-218473ee69ca');
|
|
||||||
|
|
||||||
setProps({});
|
|
||||||
loadList();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
content = wrapper.find('.arco-table-td-content').element.innerHTML;
|
|
||||||
expect(content).toBe('937be890-79bb-1b68-e03e-7d37a8b0a607');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,121 +0,0 @@
|
||||||
import logo from '@/assets/svg/logo.svg';
|
|
||||||
|
|
||||||
import {
|
|
||||||
isArray,
|
|
||||||
isBlob,
|
|
||||||
isEmptyObject,
|
|
||||||
isExist,
|
|
||||||
isFile,
|
|
||||||
isFunction,
|
|
||||||
isNull,
|
|
||||||
isNumber,
|
|
||||||
isObject,
|
|
||||||
isRegExp,
|
|
||||||
isString,
|
|
||||||
isUndefined,
|
|
||||||
} from '@/utils/is';
|
|
||||||
|
|
||||||
import { describe, expect, test } from 'vitest';
|
|
||||||
|
|
||||||
describe('Is tool', () => {
|
|
||||||
test('isArray', () => {
|
|
||||||
const res = isArray([]);
|
|
||||||
expect(res).toBe(true);
|
|
||||||
|
|
||||||
const res2 = isArray('');
|
|
||||||
expect(res2).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isObject', () => {
|
|
||||||
const res = isObject({ a: 'a' });
|
|
||||||
expect(res).toBe(true);
|
|
||||||
|
|
||||||
const res2 = isObject([]);
|
|
||||||
expect(res2).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isEmptyObject', () => {
|
|
||||||
const res = isEmptyObject({});
|
|
||||||
expect(res).toBe(true);
|
|
||||||
|
|
||||||
const res2 = isEmptyObject({ a: 'a' });
|
|
||||||
expect(res2).toBe(false);
|
|
||||||
|
|
||||||
const res3 = isEmptyObject([]);
|
|
||||||
expect(res3).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isExist', () => {
|
|
||||||
const res = isExist(0);
|
|
||||||
expect(res).toBe(true);
|
|
||||||
|
|
||||||
const res2 = isExist(null);
|
|
||||||
expect(res2).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isFunction', () => {
|
|
||||||
const res = isFunction(() => ({}));
|
|
||||||
expect(res).toBe(true);
|
|
||||||
|
|
||||||
const res2 = isFunction({});
|
|
||||||
expect(res2).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isNull', () => {
|
|
||||||
const res = isNull(null);
|
|
||||||
expect(res).toBe(true);
|
|
||||||
|
|
||||||
const res2 = isNull(undefined);
|
|
||||||
expect(res2).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isUndefined', () => {
|
|
||||||
const res = isUndefined(undefined);
|
|
||||||
expect(res).toBe(true);
|
|
||||||
|
|
||||||
const res2 = isUndefined(null);
|
|
||||||
expect(res2).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isNumber', () => {
|
|
||||||
const res = isNumber(0);
|
|
||||||
expect(res).toBe(true);
|
|
||||||
|
|
||||||
const res2 = isNumber(null);
|
|
||||||
expect(res2).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isString', () => {
|
|
||||||
const res = isString('');
|
|
||||||
expect(res).toBe(true);
|
|
||||||
|
|
||||||
const res2 = isString(0);
|
|
||||||
expect(res2).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isRegExp', () => {
|
|
||||||
const res = isRegExp(/^a/);
|
|
||||||
expect(res).toBe(true);
|
|
||||||
|
|
||||||
const res2 = isRegExp('');
|
|
||||||
expect(res2).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isFile', () => {
|
|
||||||
const file = new File([logo], 'logo.svg');
|
|
||||||
const res = isFile(file);
|
|
||||||
expect(res).toBe(true);
|
|
||||||
|
|
||||||
const res2 = isFile({});
|
|
||||||
expect(res2).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isBlob', () => {
|
|
||||||
const blob = new Blob();
|
|
||||||
const res = isBlob(blob);
|
|
||||||
expect(res).toBe(true);
|
|
||||||
|
|
||||||
const res2 = isBlob(logo);
|
|
||||||
expect(res2).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -47,6 +47,7 @@
|
||||||
"@tiptap/suggestion": "^2.1.13",
|
"@tiptap/suggestion": "^2.1.13",
|
||||||
"@tiptap/vue-3": "^2.1.13",
|
"@tiptap/vue-3": "^2.1.13",
|
||||||
"@types/color": "^3.0.4",
|
"@types/color": "^3.0.4",
|
||||||
|
"@types/node": "^20.11.16",
|
||||||
"@vueuse/core": "^10.4.1",
|
"@vueuse/core": "^10.4.1",
|
||||||
"@xmldom/xmldom": "^0.8.10",
|
"@xmldom/xmldom": "^0.8.10",
|
||||||
"ace-builds": "^1.24.2",
|
"ace-builds": "^1.24.2",
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import MSR from '@/api/http/index';
|
||||||
|
import {
|
||||||
|
AddApiDebugUrl,
|
||||||
|
AddDebugModuleUrl,
|
||||||
|
DeleteDebugModuleUrl,
|
||||||
|
ExecuteApiDebugUrl,
|
||||||
|
GetDebugModuleCountUrl,
|
||||||
|
GetDebugModulesUrl,
|
||||||
|
MoveDebugModuleUrl,
|
||||||
|
UpdateApiDebugUrl,
|
||||||
|
UpdateDebugModuleUrl,
|
||||||
|
} from '@/api/requrls/api-test/debug';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AddDebugModuleParams,
|
||||||
|
ExecuteRequestParams,
|
||||||
|
SaveDebugParams,
|
||||||
|
UpdateDebugModule,
|
||||||
|
UpdateDebugParams,
|
||||||
|
} from '@/models/apiTest/debug';
|
||||||
|
import { ModuleTreeNode, MoveModules } from '@/models/common';
|
||||||
|
|
||||||
|
// 获取模块树
|
||||||
|
export function getDebugModules() {
|
||||||
|
return MSR.get<ModuleTreeNode[]>({ url: GetDebugModulesUrl });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除模块
|
||||||
|
export function deleteDebugModule(deleteId: string) {
|
||||||
|
return MSR.get({ url: DeleteDebugModuleUrl, params: deleteId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加模块
|
||||||
|
export function addDebugModule(data: AddDebugModuleParams) {
|
||||||
|
return MSR.post({ url: AddDebugModuleUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动模块
|
||||||
|
export function moveDebugModule(data: MoveModules) {
|
||||||
|
return MSR.post({ url: MoveDebugModuleUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新模块
|
||||||
|
export function updateDebugModule(data: UpdateDebugModule) {
|
||||||
|
return MSR.post({ url: UpdateDebugModuleUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模块数量统计
|
||||||
|
export function getDebugModuleCount(data: { keyword: string }) {
|
||||||
|
return MSR.post({ url: GetDebugModuleCountUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行调试
|
||||||
|
export function executeDebug(data: ExecuteRequestParams) {
|
||||||
|
return MSR.post<ExecuteRequestParams>({ url: ExecuteApiDebugUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增调试
|
||||||
|
export function addDebug(data: SaveDebugParams) {
|
||||||
|
return MSR.post({ url: AddApiDebugUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新调试
|
||||||
|
export function updateDebug(data: UpdateDebugParams) {
|
||||||
|
return MSR.post({ url: UpdateApiDebugUrl, data });
|
||||||
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
import MSR from '@/api/http/index';
|
|
||||||
import { GetApiTestList, GetApiTestListUrl } from '@/api/requrls/api-test';
|
|
||||||
|
|
||||||
import { CommonList, TableQueryParams } from '@/models/common';
|
|
||||||
|
|
||||||
export function getTableList(params: TableQueryParams) {
|
|
||||||
const { current, pageSize, sort, filter, keyword } = params;
|
|
||||||
return MSR.post<CommonList<any>>({
|
|
||||||
url: GetApiTestList,
|
|
||||||
data: { current, pageSize, sort, filter, keyword, projectId: 'test-project-id' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getlist() {
|
|
||||||
return MSR.get<CommonList<any>>({ url: GetApiTestListUrl });
|
|
||||||
}
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import MSR from '@/api/http/index';
|
||||||
|
import { GetPluginOptionsUrl, GetPluginScriptUrl, GetProtocolListUrl } from '@/api/requrls/api-test/management';
|
||||||
|
|
||||||
|
import { GetPluginOptionsParams, PluginOption, ProtocolItem } from '@/models/apiTest/common';
|
||||||
|
|
||||||
|
// 获取协议列表
|
||||||
|
export function getProtocolList(organizationId: string) {
|
||||||
|
return MSR.get<ProtocolItem[]>({ url: GetProtocolListUrl, params: organizationId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取插件表单选项
|
||||||
|
export function getPluginOptions(data: GetPluginOptionsParams) {
|
||||||
|
return MSR.get<PluginOption[]>({ url: GetPluginOptionsUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取插件配置
|
||||||
|
export function getPluginScript(pluginId: string) {
|
||||||
|
return MSR.get({ url: GetPluginScriptUrl, params: pluginId });
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
export const ExecuteApiDebugUrl = '/api/debug/debug'; // 执行调试
|
||||||
|
export const AddApiDebugUrl = '/api/debug/add'; // 新增调试
|
||||||
|
export const UpdateApiDebugUrl = '/api/debug/update'; // 更新调试
|
||||||
|
export const UpdateDebugModuleUrl = '/api/debug/module/update'; // 更新模块
|
||||||
|
export const MoveDebugModuleUrl = '/api/debug/module/move'; // 移动模块
|
||||||
|
export const GetDebugModuleCountUrl = '/api/debug/module/count'; // 模块统计数量
|
||||||
|
export const AddDebugModuleUrl = '/api/debug/module/add'; // 添加模块
|
||||||
|
export const GetDebugModulesUrl = '/api/debug/module/tree'; // 查询模块树
|
||||||
|
export const DeleteDebugModuleUrl = '/api/debug/module/delete'; // 删除模块
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const GetProtocolListUrl = '/api/test/protocol'; // 获取协议列表
|
||||||
|
export const GetPluginOptionsUrl = '/api/test/plugin/form/option'; // 获取插件表单选项
|
||||||
|
export const GetPluginScriptUrl = '/api/test/plugin/script'; // 获取插件表单选项
|
|
@ -369,6 +369,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.arco-checkbox {
|
.arco-checkbox {
|
||||||
|
padding-left: 0;
|
||||||
.arco-checkbox-icon {
|
.arco-checkbox-icon {
|
||||||
border: 1px solid var(--color-text-input-border);
|
border: 1px solid var(--color-text-input-border);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
v-model:model-value="currentOrg"
|
v-model:model-value="currentOrg"
|
||||||
:options="orgOptions"
|
:options="orgOptions"
|
||||||
:loading="orgLoading"
|
:loading="orgLoading"
|
||||||
class="w-[300px]"
|
class="!w-[300px]"
|
||||||
@change="handleOrgChange"
|
@change="handleOrgChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -49,6 +49,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { defineModel } from 'vue';
|
||||||
|
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import MsTag from '../ms-tag/ms-tag.vue';
|
import MsTag from '../ms-tag/ms-tag.vue';
|
||||||
import FilterForm from './FilterForm.vue';
|
import FilterForm from './FilterForm.vue';
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听值的变化
|
// 监听值的变化
|
||||||
editor.onDidBlurEditorText(() => {
|
editor.onDidChangeModelContent(() => {
|
||||||
const value = editor.getValue(); // 给父组件实时返回最新文本
|
const value = editor.getValue(); // 给父组件实时返回最新文本
|
||||||
emit('update:modelValue', value);
|
emit('update:modelValue', value);
|
||||||
emit('change', value);
|
emit('change', value);
|
||||||
|
@ -219,6 +219,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function format() {
|
||||||
|
if (editor) {
|
||||||
|
// 格式化代码
|
||||||
|
editor.getAction('editor.action.formatDocument')?.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
|
@ -274,6 +281,7 @@
|
||||||
insertContent,
|
insertContent,
|
||||||
undo,
|
undo,
|
||||||
redo,
|
redo,
|
||||||
|
format,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
v-if="attrs.selectable && props.selectedKeys"
|
v-if="attrs.selectable && props.selectedKeys"
|
||||||
:width="props.firstColumnWidth || 60"
|
:width="props.firstColumnWidth || 60"
|
||||||
fixed="left"
|
fixed="left"
|
||||||
|
cell-class="arco-table-operation"
|
||||||
|
body-cell-class="arco-table-operation"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<SelectALL
|
<SelectALL
|
||||||
|
@ -322,11 +324,11 @@
|
||||||
// 全选按钮-总条数
|
// 全选按钮-总条数
|
||||||
const selectTotal = computed(() => {
|
const selectTotal = computed(() => {
|
||||||
const { selectorStatus } = props;
|
const { selectorStatus } = props;
|
||||||
|
if (!attrs.showPagination) {
|
||||||
|
// 不展示分页时直接返回total
|
||||||
|
return (attrs.data as MsTableDataItem<TableData>[]).length;
|
||||||
|
}
|
||||||
if (selectorStatus === SelectAllEnum.CURRENT) {
|
if (selectorStatus === SelectAllEnum.CURRENT) {
|
||||||
if (!attrs.showPagination) {
|
|
||||||
// 不展示分页时直接返回total
|
|
||||||
return (attrs.data as MsTableDataItem<TableData>[]).length;
|
|
||||||
}
|
|
||||||
const { pageSize, total } = attrs.msPagination as MsPaginationI;
|
const { pageSize, total } = attrs.msPagination as MsPaginationI;
|
||||||
if (pageSize > total) {
|
if (pageSize > total) {
|
||||||
return total;
|
return total;
|
||||||
|
@ -606,6 +608,11 @@
|
||||||
background-color: var(--color-text-n9);
|
background-color: var(--color-text-n9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
:deep(.arco-table-operation) {
|
||||||
|
.arco-table-td-content {
|
||||||
|
@apply justify-center;
|
||||||
|
}
|
||||||
|
}
|
||||||
:deep(.ms-table-select-all) {
|
:deep(.ms-table-select-all) {
|
||||||
.dropdown-icon {
|
.dropdown-icon {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
|
|
|
@ -465,5 +465,6 @@ export default function useTableProps<T>(
|
||||||
getSelectedCount,
|
getSelectedCount,
|
||||||
resetSelector,
|
resetSelector,
|
||||||
getTableQueryParams,
|
getTableQueryParams,
|
||||||
|
setTableSelected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
import { ConditionType } from '@/models/apiTest/debug';
|
import { ConditionType } from '@/models/apiTest/debug';
|
||||||
|
import { RequestBodyFormat, RequestConditionProcessor } from '@/enums/apiEnum';
|
||||||
|
|
||||||
// 条件操作类型
|
// 条件操作类型
|
||||||
export type ConditionTypeNameMap = Record<ConditionType, string>;
|
export type ConditionTypeNameMap = Record<ConditionType, string>;
|
||||||
export const conditionTypeNameMap = {
|
export const conditionTypeNameMap = {
|
||||||
script: 'apiTestDebug.script',
|
[RequestConditionProcessor.SCRIPT]: 'apiTestDebug.script',
|
||||||
sql: 'apiTestDebug.sql',
|
[RequestConditionProcessor.SQL]: 'apiTestDebug.sql',
|
||||||
waitTime: 'apiTestDebug.waitTime',
|
[RequestConditionProcessor.TIME_WAITING]: 'apiTestDebug.waitTime',
|
||||||
extract: 'apiTestDebug.extractParameter',
|
[RequestConditionProcessor.EXTRACT]: 'apiTestDebug.extractParameter',
|
||||||
};
|
};
|
||||||
// 代码字符集
|
// 代码字符集
|
||||||
export const codeCharset = ['UTF-8', 'UTF-16', 'GBK', 'GB2312', 'ISO-8859-1', 'Shift_JIS', 'ASCII', 'BIG5', 'KOI8-R'];
|
export const codeCharset = ['UTF-8', 'UTF-16', 'GBK', 'GB2312', 'ISO-8859-1', 'Shift_JIS', 'ASCII', 'BIG5', 'KOI8-R'];
|
||||||
|
// 请求体类型显示映射
|
||||||
|
export const requestBodyTypeMap = {
|
||||||
|
[RequestBodyFormat.FORM_DATA]: 'form-data',
|
||||||
|
[RequestBodyFormat.WWW_FORM]: 'x-www-form-urlencoded',
|
||||||
|
[RequestBodyFormat.RAW]: 'raw',
|
||||||
|
[RequestBodyFormat.BINARY]: 'binary',
|
||||||
|
[RequestBodyFormat.JSON]: 'json',
|
||||||
|
[RequestBodyFormat.XML]: 'xml',
|
||||||
|
[RequestBodyFormat.NONE]: 'none',
|
||||||
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ export enum RequestMethods {
|
||||||
}
|
}
|
||||||
// 接口组成部分
|
// 接口组成部分
|
||||||
export enum RequestComposition {
|
export enum RequestComposition {
|
||||||
|
PLUGIN = 'PLUGIN',
|
||||||
HEADER = 'HEADER',
|
HEADER = 'HEADER',
|
||||||
BODY = 'BODY',
|
BODY = 'BODY',
|
||||||
QUERY = 'QUERY',
|
QUERY = 'QUERY',
|
||||||
|
@ -23,13 +24,13 @@ export enum RequestComposition {
|
||||||
}
|
}
|
||||||
// 接口请求体格式
|
// 接口请求体格式
|
||||||
export enum RequestBodyFormat {
|
export enum RequestBodyFormat {
|
||||||
NONE = 'none',
|
NONE = 'NONE',
|
||||||
FORM_DATA = 'form-data',
|
FORM_DATA = 'FORM_DATA',
|
||||||
X_WWW_FORM_URLENCODED = 'x-www-form-urlencoded',
|
WWW_FORM = 'WWW_FORM',
|
||||||
JSON = 'json',
|
JSON = 'JSON',
|
||||||
XML = 'xml',
|
XML = 'XML',
|
||||||
RAW = 'raw',
|
RAW = 'RAW',
|
||||||
BINARY = 'binary',
|
BINARY = 'BINARY',
|
||||||
}
|
}
|
||||||
// 接口响应体格式
|
// 接口响应体格式
|
||||||
export enum RequestContentTypeEnum {
|
export enum RequestContentTypeEnum {
|
||||||
|
@ -63,3 +64,124 @@ export enum RequestDefinitionStatus {
|
||||||
export enum RequestImportFormat {
|
export enum RequestImportFormat {
|
||||||
SWAGGER = 'SWAGGER',
|
SWAGGER = 'SWAGGER',
|
||||||
}
|
}
|
||||||
|
// 接口认证设置类型
|
||||||
|
export enum RequestAuthType {
|
||||||
|
BASIC = 'BASIC',
|
||||||
|
DIGEST = 'DIGEST',
|
||||||
|
NONE = 'NONE',
|
||||||
|
}
|
||||||
|
// 接口参数表格的参数类型
|
||||||
|
export enum RequestParamsType {
|
||||||
|
STRING = 'string',
|
||||||
|
INTEGER = 'integer',
|
||||||
|
NUMBER = 'number',
|
||||||
|
ARRAY = 'array',
|
||||||
|
JSON = 'json',
|
||||||
|
FILE = 'file',
|
||||||
|
}
|
||||||
|
// 接口断言类型
|
||||||
|
export enum ResponseAssertionType {
|
||||||
|
RESPONSE_CODE = 'RESPONSE_CODE',
|
||||||
|
RESPONSE_HEADER = 'RESPONSE_HEADER',
|
||||||
|
RESPONSE_BODY = 'RESPONSE_BODY',
|
||||||
|
RESPONSE_TIME = 'RESPONSE_TIME',
|
||||||
|
SCRIPT = 'SCRIPT',
|
||||||
|
VARIABLE = 'VARIABLE',
|
||||||
|
}
|
||||||
|
// 接口断言-响应体断言类型
|
||||||
|
export enum ResponseBodyAssertionType {
|
||||||
|
DOCUMENT = 'DOCUMENT',
|
||||||
|
JSON_PATH = 'JSON_PATH',
|
||||||
|
REGEX = 'REGEX', // 正则表达式
|
||||||
|
XPATH = 'XPATH',
|
||||||
|
}
|
||||||
|
// 接口断言-响应体断言-文档类型
|
||||||
|
export enum ResponseBodyAssertionDocumentType {
|
||||||
|
JSON = 'JSON',
|
||||||
|
XML = 'XML',
|
||||||
|
}
|
||||||
|
// 接口断言-响应体断言-文档断言类型
|
||||||
|
export enum ResponseBodyDocumentAssertionType {
|
||||||
|
ARRAY = 'array',
|
||||||
|
BOOLEAN = 'boolean',
|
||||||
|
INTEGER = 'integer',
|
||||||
|
NUMBER = 'number',
|
||||||
|
STRING = 'string',
|
||||||
|
}
|
||||||
|
// 接口断言-响应体断言-Xpath断言类型
|
||||||
|
export enum ResponseBodyXPathAssertionFormat {
|
||||||
|
HTML = 'HTML',
|
||||||
|
XML = 'XML',
|
||||||
|
}
|
||||||
|
// 接口断言-断言匹配条件
|
||||||
|
export enum RequestAssertionCondition {
|
||||||
|
CONTAINS = 'CONTAINS', // 包含
|
||||||
|
EMPTY = 'EMPTY', // 为空
|
||||||
|
END_WITH = 'END_WITH', // 以 xx 结尾
|
||||||
|
EQUALS = 'EQUALS', // 等于
|
||||||
|
GT = 'GT', // 大于
|
||||||
|
GT_OR_EQUALS = 'GT_OR_EQUALS', // 大于等于
|
||||||
|
LENGTH_EQUALS = 'LENGTH_EQUALS', // 长度等于
|
||||||
|
LENGTH_GT = 'LENGTH_GT', // 长度大于
|
||||||
|
LENGTH_GT_OR_EQUALS = 'LENGTH_GT_OR_EQUALS', // 长度大于等于
|
||||||
|
LENGTH_LT = 'LENGTH_LT', // 长度小于
|
||||||
|
LENGTH_LT_OR_EQUALS = 'LENGTH_LT_OR_EQUALS', // 长度小于等于
|
||||||
|
LENGTH_NOT_EQUALS = 'LENGTH_NOT_EQUALS', // 长度不等于
|
||||||
|
LT = 'LT', // 小于
|
||||||
|
LT_OR_EQUALS = 'LT_OR_EQUALS', // 小于等于
|
||||||
|
NOT_CONTAINS = 'NOT_CONTAINS', // 不包含
|
||||||
|
NOT_EMPTY = 'NOT_EMPTY', // 不为空
|
||||||
|
NOT_EQUALS = 'NOT_EQUALS', // 不等于
|
||||||
|
REGEX = 'REGEX', // 正则表达式
|
||||||
|
START_WITH = 'START_WITH', // 以 xx 开头
|
||||||
|
UNCHECKED = 'UNCHECKED', // 不校验
|
||||||
|
}
|
||||||
|
// 接口请求-前后置条件-处理器类型
|
||||||
|
export enum RequestConditionProcessor {
|
||||||
|
SCRIPT = 'SCRIPT', // 脚本操作
|
||||||
|
SQL = 'SQL', // SQL操作
|
||||||
|
TIME_WAITING = 'TIME_WAITING', // 等待时间
|
||||||
|
EXTRACT = 'EXTRACT', // 参数提取
|
||||||
|
}
|
||||||
|
// 接口请求-前后置条件-脚本处理器语言
|
||||||
|
export enum RequestConditionScriptLanguage {
|
||||||
|
BEANSHELL = 'BEANSHELL', // Beanshell
|
||||||
|
BEANSHELL_JSR233 = 'BEANSHELL_JSR233', // Beanshell JSR233
|
||||||
|
GROOVY = 'GROOVY', // Groovy
|
||||||
|
JAVASCRIPT = 'JAVASCRIPT', // JavaScript
|
||||||
|
PYTHON = 'PYTHON', // Python
|
||||||
|
}
|
||||||
|
// 接口请求-参数提取-环境类型
|
||||||
|
export enum RequestExtractEnvType {
|
||||||
|
ENVIRONMENT = 'ENVIRONMENT', // 环境参数
|
||||||
|
GLOBAL = 'GLOBAL', // 全局参数
|
||||||
|
TEMPORARY = 'TEMPORARY', // 临时参数
|
||||||
|
}
|
||||||
|
// 接口请求-参数提取-表达式类型
|
||||||
|
export enum RequestExtractExpressionEnum {
|
||||||
|
REGEX = 'REGEX', // 正则表达式
|
||||||
|
JSON_PATH = 'JSON_PATH', // JSONPath
|
||||||
|
X_PATH = 'X_PATH', // Xpath
|
||||||
|
}
|
||||||
|
// 接口请求-参数提取-表达式匹配规则类型
|
||||||
|
export enum RequestExtractExpressionRuleType {
|
||||||
|
EXPRESSION = 'EXPRESSION', // 匹配表达式
|
||||||
|
GROUP = 'GROUP', // 匹配组
|
||||||
|
}
|
||||||
|
// 接口请求-参数提取-提取范围
|
||||||
|
export enum RequestExtractScope {
|
||||||
|
BODY = 'BODY',
|
||||||
|
BODY_AS_DOCUMENT = 'BODY_AS_DOCUMENT',
|
||||||
|
UNESCAPED_BODY = 'UNESCAPED_BODY',
|
||||||
|
REQUEST_HEADERS = 'REQUEST_HEADERS',
|
||||||
|
RESPONSE_CODE = 'RESPONSE_CODE',
|
||||||
|
RESPONSE_HEADERS = 'RESPONSE_HEADERS',
|
||||||
|
RESPONSE_MESSAGE = 'RESPONSE_MESSAGE',
|
||||||
|
URL = 'URL',
|
||||||
|
}
|
||||||
|
// 接口请求-参数提取-表达式匹配结果规则类型
|
||||||
|
export enum RequestExtractResultMatchingRule {
|
||||||
|
ALL = 'ALL', // 全部匹配
|
||||||
|
RANDOM = 'RANDOM', // 随机匹配
|
||||||
|
SPECIFIC = 'SPECIFIC', // 指定匹配
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
// 获取插件表单选项参数
|
||||||
|
export interface GetPluginOptionsParams {
|
||||||
|
orgId: string;
|
||||||
|
pluginId: string;
|
||||||
|
optionMethod: string;
|
||||||
|
queryParam: Record<string, any>;
|
||||||
|
}
|
||||||
|
// 插件表单选项子项
|
||||||
|
export interface PluginOption {
|
||||||
|
text: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
// 协议列表子项
|
||||||
|
export interface ProtocolItem {
|
||||||
|
protocol: string;
|
||||||
|
polymorphicName: string;
|
||||||
|
pluginId: string;
|
||||||
|
}
|
|
@ -1,25 +1,324 @@
|
||||||
|
import {
|
||||||
|
RequestAssertionCondition,
|
||||||
|
RequestAuthType,
|
||||||
|
RequestBodyFormat,
|
||||||
|
RequestConditionProcessor,
|
||||||
|
RequestConditionScriptLanguage,
|
||||||
|
RequestContentTypeEnum,
|
||||||
|
RequestExtractEnvType,
|
||||||
|
RequestExtractExpressionEnum,
|
||||||
|
RequestExtractExpressionRuleType,
|
||||||
|
RequestExtractResultMatchingRule,
|
||||||
|
RequestExtractScope,
|
||||||
|
RequestMethods,
|
||||||
|
RequestParamsType,
|
||||||
|
ResponseAssertionType,
|
||||||
|
ResponseBodyAssertionDocumentType,
|
||||||
|
ResponseBodyAssertionType,
|
||||||
|
ResponseBodyDocumentAssertionType,
|
||||||
|
ResponseBodyXPathAssertionFormat,
|
||||||
|
} from '@/enums/apiEnum';
|
||||||
|
|
||||||
// 条件操作类型
|
// 条件操作类型
|
||||||
export type ConditionType = 'script' | 'sql' | 'waitTime' | 'extract';
|
export type ConditionType = keyof typeof RequestConditionProcessor;
|
||||||
// 表达式类型
|
// 断言-匹配条件规则
|
||||||
export type ExpressionType = 'regular' | 'JSONPath' | 'XPath';
|
export type RequestAssertionConditionType = keyof typeof RequestAssertionCondition;
|
||||||
// 表达式配置
|
// 前后置条件-脚本语言类型
|
||||||
export interface ExpressionConfig {
|
export type RequestConditionScriptLanguageType = keyof typeof RequestConditionScriptLanguage;
|
||||||
expression: string;
|
|
||||||
expressionType?: ExpressionType;
|
|
||||||
regexpMatchRule?: 'expression' | 'group'; // 正则表达式匹配规则
|
|
||||||
resultMatchRule?: 'random' | 'specify' | 'all'; // 结果匹配规则
|
|
||||||
specifyMatchNum?: number; // 指定匹配下标
|
|
||||||
xmlMatchContentType?: 'xml' | 'html'; // 响应内容格式
|
|
||||||
}
|
|
||||||
// 响应时间信息
|
// 响应时间信息
|
||||||
export interface ResponseTiming {
|
export interface ResponseTiming {
|
||||||
ready: number;
|
dnsLookupTime: number;
|
||||||
socketInit: number;
|
tcpHandshakeTime: number;
|
||||||
dnsQuery: number;
|
sslHandshakeTime: number;
|
||||||
tcpHandshake: number;
|
socketInitTime: number;
|
||||||
sslHandshake: number;
|
latency: number;
|
||||||
waitingTTFB: number;
|
downloadTime: number;
|
||||||
downloadContent: number;
|
transferStartTime: number;
|
||||||
deal: number;
|
responseTime: number;
|
||||||
total: number;
|
}
|
||||||
|
// key-value参数信息
|
||||||
|
export interface KeyValueParam {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
// 接口请求公共参数集合信息
|
||||||
|
export interface ExecuteRequestCommonParam {
|
||||||
|
encode: boolean; // 是否编码
|
||||||
|
maxLength: number;
|
||||||
|
minLength: number;
|
||||||
|
paramType: keyof typeof RequestParamsType; // 参数类型
|
||||||
|
required: boolean;
|
||||||
|
description: string;
|
||||||
|
enable: boolean; // 参数是否启用
|
||||||
|
}
|
||||||
|
// 接口请求form-data、x-www-form-urlencoded参数集合信息
|
||||||
|
export type ExecuteRequestFormBodyFormValue = ExecuteRequestCommonParam & {
|
||||||
|
files?: {
|
||||||
|
fileId: string;
|
||||||
|
fileName: string;
|
||||||
|
}[];
|
||||||
|
contentType?: keyof typeof RequestContentTypeEnum & string;
|
||||||
|
};
|
||||||
|
export interface ExecuteRequestFormBody {
|
||||||
|
formValues: ExecuteRequestFormBodyFormValue[];
|
||||||
|
}
|
||||||
|
// 接口请求binary-body参数集合信息
|
||||||
|
export interface ExecuteBinaryBody {
|
||||||
|
description: string;
|
||||||
|
file?: {
|
||||||
|
fileId: string;
|
||||||
|
fileName: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 接口请求json-body参数集合信息
|
||||||
|
export interface ExecuteJsonBody {
|
||||||
|
enableJsonSchema?: boolean;
|
||||||
|
enableTransition?: boolean;
|
||||||
|
jsonSchema?: string;
|
||||||
|
jsonValue: string;
|
||||||
|
}
|
||||||
|
// 接口请求-带开启关闭的参数集合信息
|
||||||
|
export interface EnableKeyValueParam extends KeyValueParam {
|
||||||
|
description: string;
|
||||||
|
enable: boolean; // 参数是否启用
|
||||||
|
}
|
||||||
|
// 执行请求配置
|
||||||
|
export interface ExecuteOtherConfig {
|
||||||
|
autoRedirects: boolean; // 是否自动重定向 默认 false
|
||||||
|
certificateAlias: string; // 证书别名
|
||||||
|
connectTimeout: number; // 连接超时时间
|
||||||
|
followRedirects: boolean; // 是否跟随重定向 默认 false
|
||||||
|
responseTimeout: number; // 响应超时时间
|
||||||
|
}
|
||||||
|
// 断言-断言公共信息
|
||||||
|
export interface ResponseAssertionCommon {
|
||||||
|
name: string; // 断言名称
|
||||||
|
enable: boolean; // 是否启用断言
|
||||||
|
assertionType: keyof typeof ResponseAssertionType; // 断言类型
|
||||||
|
}
|
||||||
|
// 断言-断言列表泛型
|
||||||
|
export interface ResponseAssertionGenerics<T> {
|
||||||
|
assertions: T[];
|
||||||
|
responseFormat?: keyof typeof ResponseBodyXPathAssertionFormat;
|
||||||
|
}
|
||||||
|
// 断言-响应头断言子项
|
||||||
|
export interface ResponseHeaderAssertionItem {
|
||||||
|
condition: RequestAssertionConditionType;
|
||||||
|
enable: boolean;
|
||||||
|
expectedValue: string;
|
||||||
|
header: string; // 响应头
|
||||||
|
}
|
||||||
|
// 断言-状态码断言
|
||||||
|
export type ResponseCodeAssertion = Pick<ResponseHeaderAssertionItem, 'condition' | 'expectedValue'>;
|
||||||
|
// 断言-文档断言-JSON断言\XML断言
|
||||||
|
export interface ResponseDocumentAssertionElement {
|
||||||
|
id?: string;
|
||||||
|
arrayVerification: boolean; // 是否组内验证
|
||||||
|
children: ResponseDocumentAssertionElement[];
|
||||||
|
condition: RequestAssertionConditionType;
|
||||||
|
expectedResult: Record<string, any>; // 匹配值 即预期结果
|
||||||
|
include: boolean; // 是否必含
|
||||||
|
paramName: string; // 参数名
|
||||||
|
type: keyof typeof ResponseBodyDocumentAssertionType; // 断言类型
|
||||||
|
typeVerification: boolean; // 是否类型验证
|
||||||
|
}
|
||||||
|
// 断言-文档断言
|
||||||
|
export interface ResponseDocumentAssertion {
|
||||||
|
enable: boolean; // 是否启用
|
||||||
|
documentType: keyof typeof ResponseBodyAssertionDocumentType; // 文档类型
|
||||||
|
followApiId: string; // 跟随定义的apiId 传空为不跟随接口定义
|
||||||
|
jsonAssertion: ResponseDocumentAssertionElement;
|
||||||
|
xmlAssertion: ResponseDocumentAssertionElement;
|
||||||
|
}
|
||||||
|
// 断言-断言列表的断言子项
|
||||||
|
export interface ResponseAssertionItem {
|
||||||
|
condition: RequestAssertionConditionType;
|
||||||
|
expectedValue: string;
|
||||||
|
expression: string;
|
||||||
|
enable?: boolean;
|
||||||
|
}
|
||||||
|
// 断言-JSONPath断言子项
|
||||||
|
export type ResponseJSONPathAssertionItem = ResponseAssertionItem;
|
||||||
|
// 断言-正则断言子项
|
||||||
|
export type ResponseRegexAssertionItem = Pick<ResponseAssertionItem, 'expression'>;
|
||||||
|
// 断言-Xpath断言子项
|
||||||
|
export type ResponseXPathAssertionItem = Pick<ResponseAssertionItem, 'expression' | 'expectedValue'>;
|
||||||
|
// 脚本公共配置
|
||||||
|
export interface ScriptCommonConfig {
|
||||||
|
enableCommonScript: boolean; // 是否启用公共脚本
|
||||||
|
script: string; // 脚本内容
|
||||||
|
scriptId: string; // 脚本id
|
||||||
|
scriptLanguage: RequestConditionScriptLanguageType; // 脚本语言
|
||||||
|
params: KeyValueParam[]; // 公共脚本参数
|
||||||
|
}
|
||||||
|
// 断言-响应体断言
|
||||||
|
export interface ResponseBodyAssertion {
|
||||||
|
assertionBodyType: keyof typeof ResponseBodyAssertionType; // 断言类型
|
||||||
|
documentAssertion: ResponseDocumentAssertion; // 文档断言
|
||||||
|
jsonPathAssertion: ResponseAssertionGenerics<ResponseJSONPathAssertionItem>; // JSONPath断言
|
||||||
|
regexAssertion: ResponseAssertionGenerics<ResponseRegexAssertionItem>; // 正则断言
|
||||||
|
xpathAssertion: ResponseAssertionGenerics<ResponseXPathAssertionItem>; // XPath断言
|
||||||
|
}
|
||||||
|
// 断言-响应时间断言
|
||||||
|
export type ResponseTimeAssertion = Pick<ResponseAssertionItem, 'expectedValue'>;
|
||||||
|
// 断言-脚本断言
|
||||||
|
export type ResponseScriptAssertion = ScriptCommonConfig;
|
||||||
|
// 断言-变量断言
|
||||||
|
export interface ResponseVariableAssertion {
|
||||||
|
variableAssertionItems: ResponseAssertionItem[];
|
||||||
|
}
|
||||||
|
// 执行请求-前后置条件处理器
|
||||||
|
export interface ExecuteConditionProcessorCommon {
|
||||||
|
enable: boolean; // 是否启用
|
||||||
|
name: string; // 请求名称
|
||||||
|
processorType: keyof typeof RequestConditionProcessor;
|
||||||
|
}
|
||||||
|
// 执行请求-前后置条件-脚本处理器
|
||||||
|
export type ScriptProcessor = ScriptCommonConfig;
|
||||||
|
// 执行请求-前后置条件-SQL脚本处理器
|
||||||
|
export interface SQLProcessor {
|
||||||
|
dataSourceId: string; // 数据源ID
|
||||||
|
environmentId: string; // 环境ID
|
||||||
|
queryTimeout: number; // 超时时间
|
||||||
|
resultVariable: string; // 按结果存储时的结果变量
|
||||||
|
script: string; // 脚本内容
|
||||||
|
variableNames: string; // 按列存储时的变量名集合,多个列可以使用逗号分隔
|
||||||
|
variables: EnableKeyValueParam[]; // 变量列表
|
||||||
|
extractParams: KeyValueParam[]; // 提取参数列表
|
||||||
|
}
|
||||||
|
// 执行请求-前后置条件-等待时间处理器
|
||||||
|
export interface TimeWaitingProcessor {
|
||||||
|
delay: number; // 等待时间 单位:毫秒
|
||||||
|
}
|
||||||
|
// 表达式类型
|
||||||
|
export type ExpressionType = keyof typeof RequestExtractExpressionEnum;
|
||||||
|
// 表达式配置
|
||||||
|
export interface ExpressionCommonConfig {
|
||||||
|
enable: boolean; // 是否启用
|
||||||
|
expression: string;
|
||||||
|
extractType: ExpressionType; // 表达式类型
|
||||||
|
variableName: string;
|
||||||
|
variableType: keyof typeof RequestExtractEnvType;
|
||||||
|
resultMatchingRule: keyof typeof RequestExtractResultMatchingRule; // 结果匹配规则
|
||||||
|
resultMatchingRuleNum: number; // 匹配第几条结果
|
||||||
|
}
|
||||||
|
// 正则提取配置
|
||||||
|
export interface RegexExtract extends ExpressionCommonConfig {
|
||||||
|
expressionMatchingRule: keyof typeof RequestExtractExpressionRuleType; // 正则表达式匹配规则
|
||||||
|
extractScope: keyof typeof RequestExtractScope; // 正则提取范围
|
||||||
|
}
|
||||||
|
// JSONPath提取配置
|
||||||
|
export type JSONPathExtract = ExpressionCommonConfig;
|
||||||
|
// XPath提取配置
|
||||||
|
export interface XPathExtract extends ExpressionCommonConfig {
|
||||||
|
responseFormat: keyof typeof ResponseBodyXPathAssertionFormat; // 响应格式
|
||||||
|
}
|
||||||
|
// 执行请求-前后置条件-参数提取处理器
|
||||||
|
export interface ExtractProcessor {
|
||||||
|
extractors: (RegexExtract | JSONPathExtract | XPathExtract)[];
|
||||||
|
}
|
||||||
|
// 执行请求-前后置条件配置
|
||||||
|
export type ExecuteConditionProcessor = ExecuteConditionProcessorCommon &
|
||||||
|
(ScriptProcessor | SQLProcessor | TimeWaitingProcessor | ExtractProcessor);
|
||||||
|
export interface ExecuteConditionConfig {
|
||||||
|
enableGlobal: boolean; // 是否启用全局前置 默认为 true
|
||||||
|
processors: ExecuteConditionProcessor[];
|
||||||
|
}
|
||||||
|
// 执行请求-断言配置子项
|
||||||
|
export type ExecuteAssertionItem = ResponseAssertionCommon &
|
||||||
|
ResponseCodeAssertion &
|
||||||
|
ResponseAssertionGenerics<ResponseHeaderAssertionItem> &
|
||||||
|
ResponseBodyAssertion &
|
||||||
|
ResponseTimeAssertion &
|
||||||
|
ResponseScriptAssertion &
|
||||||
|
ResponseVariableAssertion;
|
||||||
|
// 执行请求-断言配置
|
||||||
|
export interface ExecuteAssertionConfig {
|
||||||
|
enableGlobal: boolean; // 是否启用全局断言
|
||||||
|
assertions: ExecuteAssertionItem[];
|
||||||
|
}
|
||||||
|
// 执行请求-共用配置子项
|
||||||
|
export interface ExecuteCommonChild {
|
||||||
|
polymorphicName: 'MsCommonElement'; // 协议多态名称,写死MsCommonElement
|
||||||
|
assertionConfig: ExecuteAssertionConfig;
|
||||||
|
postProcessorConfig: ExecuteConditionConfig; // 后置处理器配置
|
||||||
|
preProcessorConfig: ExecuteConditionConfig; // 前置处理器配置
|
||||||
|
}
|
||||||
|
// 执行请求-认证配置
|
||||||
|
export interface ExecuteAuthConfig {
|
||||||
|
authType: keyof typeof RequestAuthType;
|
||||||
|
password: string;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
// 执行请求- body 配置-文本格式的 body
|
||||||
|
export interface ExecuteValueBody {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
// 执行请求- body 配置
|
||||||
|
export interface ExecuteBody {
|
||||||
|
bodyType: keyof typeof RequestBodyFormat;
|
||||||
|
binaryBody: ExecuteBinaryBody;
|
||||||
|
formDataBody: ExecuteRequestFormBody;
|
||||||
|
jsonBody: ExecuteJsonBody;
|
||||||
|
rawBody: ExecuteValueBody;
|
||||||
|
wwwFormBody: ExecuteRequestFormBody;
|
||||||
|
xmlBody: ExecuteValueBody;
|
||||||
|
}
|
||||||
|
// 执行HTTP请求入参
|
||||||
|
export interface ExecuteHTTPRequestFullParams {
|
||||||
|
authConfig: ExecuteAuthConfig;
|
||||||
|
body: ExecuteBody;
|
||||||
|
headers: EnableKeyValueParam[];
|
||||||
|
method: keyof typeof RequestMethods;
|
||||||
|
otherConfig: ExecuteOtherConfig;
|
||||||
|
path: string;
|
||||||
|
query: ExecuteRequestCommonParam[];
|
||||||
|
rest: ExecuteRequestCommonParam[];
|
||||||
|
url: string;
|
||||||
|
polymorphicName: string; // 协议多态名称
|
||||||
|
children: ExecuteCommonChild[]; // 协议共有的子项配置
|
||||||
|
}
|
||||||
|
// 执行插件请求入参
|
||||||
|
export interface ExecutePluginRequestParams {
|
||||||
|
[key: string]: any; // key-value形式的插件参数
|
||||||
|
polymorphicName: string; // 协议多态名称
|
||||||
|
children: ExecuteCommonChild[]; // 协议共有的子项配置
|
||||||
|
}
|
||||||
|
// 执行接口调试入参
|
||||||
|
export interface ExecuteRequestParams {
|
||||||
|
id?: string;
|
||||||
|
reportId: string;
|
||||||
|
environmentId: string;
|
||||||
|
tempFileIds: string[];
|
||||||
|
request: ExecuteHTTPRequestFullParams | ExecutePluginRequestParams;
|
||||||
|
projectId: string;
|
||||||
|
}
|
||||||
|
// 保存接口调试入参
|
||||||
|
export interface SaveDebugParams {
|
||||||
|
name: string;
|
||||||
|
protocol: string;
|
||||||
|
method: keyof typeof RequestMethods;
|
||||||
|
path: string;
|
||||||
|
projectId: string;
|
||||||
|
moduleId: string;
|
||||||
|
request: ExecuteHTTPRequestFullParams | ExecutePluginRequestParams;
|
||||||
|
uploadFileIds: string[];
|
||||||
|
linkFileIds: string[];
|
||||||
|
}
|
||||||
|
// 更新接口调试入参
|
||||||
|
export interface UpdateDebugParams extends SaveDebugParams {
|
||||||
|
id: string;
|
||||||
|
deleteFileIds: string[];
|
||||||
|
unLinkRefIds: string[];
|
||||||
|
}
|
||||||
|
// 更新模块入参
|
||||||
|
export interface UpdateDebugModule {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
// 添加模块入参
|
||||||
|
export interface AddDebugModuleParams {
|
||||||
|
projectId: string;
|
||||||
|
name: string;
|
||||||
|
parentId: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="condition-content">
|
<div class="condition-content">
|
||||||
<!-- 脚本操作 -->
|
<!-- 脚本操作 -->
|
||||||
<template v-if="condition.type === 'script'">
|
<template v-if="condition.type === RequestConditionProcessor.SCRIPT">
|
||||||
<a-radio-group v-model:model-value="condition.scriptType" size="small" class="mb-[16px]">
|
<a-radio-group v-model:model-value="condition.scriptType" size="small" class="mb-[16px]">
|
||||||
<a-radio value="manual">{{ t('apiTestDebug.manual') }}</a-radio>
|
<a-radio value="manual">{{ t('apiTestDebug.manual') }}</a-radio>
|
||||||
<a-radio value="quote">{{ t('apiTestDebug.quote') }}</a-radio>
|
<a-radio value="quote">{{ t('apiTestDebug.quote') }}</a-radio>
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- SQL操作 -->
|
<!-- SQL操作 -->
|
||||||
<template v-else-if="condition.type === 'sql'">
|
<template v-else-if="condition.type === RequestConditionProcessor.SQL">
|
||||||
<div class="mb-[16px]">
|
<div class="mb-[16px]">
|
||||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('common.desc') }}</div>
|
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('common.desc') }}</div>
|
||||||
<a-input
|
<a-input
|
||||||
|
@ -205,7 +205,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- 等待时间 -->
|
<!-- 等待时间 -->
|
||||||
<div v-else-if="condition.type === 'waitTime'">
|
<div v-else-if="condition.type === RequestConditionProcessor.TIME_WAITING">
|
||||||
<div class="mb-[8px] flex items-center">
|
<div class="mb-[8px] flex items-center">
|
||||||
{{ t('apiTestDebug.waitTime') }}
|
{{ t('apiTestDebug.waitTime') }}
|
||||||
<div class="text-[var(--color-text-4)]">(ms)</div>
|
<div class="text-[var(--color-text-4)]">(ms)</div>
|
||||||
|
@ -213,7 +213,7 @@
|
||||||
<a-input-number v-model:model-value="condition.time" mode="button" :step="100" :min="0" class="w-[160px]" />
|
<a-input-number v-model:model-value="condition.time" mode="button" :step="100" :min="0" class="w-[160px]" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 提取参数 -->
|
<!-- 提取参数 -->
|
||||||
<div v-else-if="condition.type === 'extract'">
|
<div v-else-if="condition.type === RequestConditionProcessor.EXTRACT">
|
||||||
<paramTable
|
<paramTable
|
||||||
ref="extractParamsTableRef"
|
ref="extractParamsTableRef"
|
||||||
v-model:params="condition.extractParams"
|
v-model:params="condition.extractParams"
|
||||||
|
@ -224,7 +224,7 @@
|
||||||
:response="props.response"
|
:response="props.response"
|
||||||
:height-used="(props.heightUsed || 0) + 62"
|
:height-used="(props.heightUsed || 0) + 62"
|
||||||
@change="handleExtractParamTableChange"
|
@change="handleExtractParamTableChange"
|
||||||
@more-action-select="handleExtractParamMoreActionSelect"
|
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
|
||||||
>
|
>
|
||||||
<template #expression="{ record }">
|
<template #expression="{ record }">
|
||||||
<a-popover
|
<a-popover
|
||||||
|
@ -320,7 +320,17 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { ExpressionConfig } from '@/models/apiTest/debug';
|
import { JSONPathExtract, RegexExtract, XPathExtract } from '@/models/apiTest/debug';
|
||||||
|
import {
|
||||||
|
RequestConditionProcessor,
|
||||||
|
RequestExtractEnvType,
|
||||||
|
RequestExtractExpressionEnum,
|
||||||
|
RequestExtractResultMatchingRule,
|
||||||
|
RequestExtractScope,
|
||||||
|
ResponseBodyXPathAssertionFormat,
|
||||||
|
} from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
export type ExpressionConfig = (RegexExtract | JSONPathExtract | XPathExtract) & Record<string, any>;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: Record<string, any>;
|
data: Record<string, any>;
|
||||||
|
@ -490,8 +500,8 @@ org.apache.http.client.method . . . '' at line number 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'apiTestDebug.mode',
|
title: 'apiTestDebug.mode',
|
||||||
dataIndex: 'expressionType',
|
dataIndex: 'extractType',
|
||||||
slotName: 'expressionType',
|
slotName: 'extractType',
|
||||||
typeOptions: [
|
typeOptions: [
|
||||||
{
|
{
|
||||||
label: t('apiTestDebug.regular'),
|
label: t('apiTestDebug.regular'),
|
||||||
|
@ -510,8 +520,8 @@ org.apache.http.client.method . . . '' at line number 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'apiTestDebug.range',
|
title: 'apiTestDebug.range',
|
||||||
dataIndex: 'range',
|
dataIndex: 'extractScope',
|
||||||
slotName: 'range',
|
slotName: 'extractScope',
|
||||||
typeOptions: [
|
typeOptions: [
|
||||||
{
|
{
|
||||||
label: 'Body',
|
label: 'Body',
|
||||||
|
@ -581,22 +591,23 @@ org.apache.http.client.method . . . '' at line number 2
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractParamsTableRef = ref<InstanceType<typeof paramTable>>();
|
const extractParamsTableRef = ref<InstanceType<typeof paramTable>>();
|
||||||
const defaultExtractParamItem: Record<string, any> = {
|
const defaultExtractParamItem: ExpressionConfig = {
|
||||||
name: '',
|
enable: true,
|
||||||
type: 'temp',
|
variableName: '',
|
||||||
range: 'body',
|
variableType: RequestExtractEnvType.TEMPORARY,
|
||||||
|
extractScope: RequestExtractScope.BODY,
|
||||||
expression: '',
|
expression: '',
|
||||||
expressionType: 'regular',
|
extractType: RequestExtractExpressionEnum.REGEX,
|
||||||
regexpMatchRule: 'expression',
|
regexpMatchRule: 'expression',
|
||||||
resultMatchRule: 'random',
|
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
|
||||||
specifyMatchNum: 1,
|
resultMatchingRuleNum: 1,
|
||||||
xmlMatchContentType: 'xml',
|
responseFormat: ResponseBodyXPathAssertionFormat.XML,
|
||||||
moreSettingPopoverVisible: false,
|
moreSettingPopoverVisible: false,
|
||||||
};
|
};
|
||||||
const fastExtractionVisible = ref(false);
|
const fastExtractionVisible = ref(false);
|
||||||
const activeRecord = ref<any>({ ...defaultExtractParamItem }); // 用于暂存当前操作的提取参数表格项
|
const activeRecord = ref({ ...defaultExtractParamItem }); // 用于暂存当前操作的提取参数表格项
|
||||||
|
|
||||||
function showFastExtraction(record: Record<string, any>) {
|
function showFastExtraction(record: ExpressionConfig) {
|
||||||
activeRecord.value = { ...record };
|
activeRecord.value = { ...record };
|
||||||
fastExtractionVisible.value = true;
|
fastExtractionVisible.value = true;
|
||||||
}
|
}
|
||||||
|
@ -608,7 +619,7 @@ org.apache.http.client.method . . . '' at line number 2
|
||||||
/**
|
/**
|
||||||
* 处理提取参数表格更多操作
|
* 处理提取参数表格更多操作
|
||||||
*/
|
*/
|
||||||
function handleExtractParamMoreActionSelect(event: ActionsItem, record: Record<string, any>) {
|
function handleExtractParamMoreActionSelect(event: ActionsItem, record: ExpressionConfig) {
|
||||||
activeRecord.value = { ...record };
|
activeRecord.value = { ...record };
|
||||||
if (event.eventTag === 'copy') {
|
if (event.eventTag === 'copy') {
|
||||||
emit('copy');
|
emit('copy');
|
||||||
|
@ -620,7 +631,7 @@ org.apache.http.client.method . . . '' at line number 2
|
||||||
/**
|
/**
|
||||||
* 提取参数表格-应用更多设置
|
* 提取参数表格-应用更多设置
|
||||||
*/
|
*/
|
||||||
function applyMoreSetting(record: Record<string, any>) {
|
function applyMoreSetting(record: ExpressionConfig) {
|
||||||
condition.value.extractParams = condition.value.extractParams.map((e) => {
|
condition.value.extractParams = condition.value.extractParams.map((e) => {
|
||||||
if (e.id === activeRecord.value.id) {
|
if (e.id === activeRecord.value.id) {
|
||||||
record.moreSettingPopoverVisible = false;
|
record.moreSettingPopoverVisible = false;
|
||||||
|
@ -637,7 +648,7 @@ org.apache.http.client.method . . . '' at line number 2
|
||||||
/**
|
/**
|
||||||
* 提取参数表格-保存快速提取的配置
|
* 提取参数表格-保存快速提取的配置
|
||||||
*/
|
*/
|
||||||
function handleFastExtractionApply(config: ExpressionConfig) {
|
function handleFastExtractionApply(config: RegexExtract | JSONPathExtract | XPathExtract) {
|
||||||
condition.value.extractParams = condition.value.extractParams.map((e) => {
|
condition.value.extractParams = condition.value.extractParams.map((e) => {
|
||||||
if (e.id === activeRecord.value.id) {
|
if (e.id === activeRecord.value.id) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<slot name="titleRight"></slot>
|
<slot name="titleRight"></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="data.length > 0" class="flex h-[calc(100%-110px)] gap-[8px]">
|
<div v-show="data.length > 0" class="flex h-[calc(100%-40px)] gap-[8px]">
|
||||||
<div class="h-full w-[20%] min-w-[220px]">
|
<div class="h-full w-[20%] min-w-[220px]">
|
||||||
<conditionList
|
<conditionList
|
||||||
v-model:list="data"
|
v-model:list="data"
|
||||||
|
@ -47,6 +47,7 @@
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { ConditionType } from '@/models/apiTest/debug';
|
import { ConditionType } from '@/models/apiTest/debug';
|
||||||
|
import { RequestConditionProcessor } from '@/enums/apiEnum';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
list: Array<Record<string, any>>;
|
list: Array<Record<string, any>>;
|
||||||
|
@ -113,10 +114,10 @@ org.apache.http.client.method . . . '' at line number 2
|
||||||
function addPrecondition(value: string | number | Record<string, any> | undefined) {
|
function addPrecondition(value: string | number | Record<string, any> | undefined) {
|
||||||
const id = new Date().getTime();
|
const id = new Date().getTime();
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'script':
|
case RequestConditionProcessor.SCRIPT:
|
||||||
data.value.push({
|
data.value.push({
|
||||||
id,
|
id,
|
||||||
type: 'script',
|
type: RequestConditionProcessor.SCRIPT,
|
||||||
name: t('apiTestDebug.preconditionScriptName'),
|
name: t('apiTestDebug.preconditionScriptName'),
|
||||||
scriptType: 'manual',
|
scriptType: 'manual',
|
||||||
enable: true,
|
enable: true,
|
||||||
|
@ -127,10 +128,10 @@ org.apache.http.client.method . . . '' at line number 2
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'sql':
|
case RequestConditionProcessor.SQL:
|
||||||
data.value.push({
|
data.value.push({
|
||||||
id,
|
id,
|
||||||
type: 'sql',
|
type: RequestConditionProcessor.SQL,
|
||||||
desc: '',
|
desc: '',
|
||||||
enable: true,
|
enable: true,
|
||||||
sqlSource: {
|
sqlSource: {
|
||||||
|
@ -141,18 +142,18 @@ org.apache.http.client.method . . . '' at line number 2
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'waitTime':
|
case RequestConditionProcessor.TIME_WAITING:
|
||||||
data.value.push({
|
data.value.push({
|
||||||
id,
|
id,
|
||||||
type: 'waitTime',
|
type: RequestConditionProcessor.TIME_WAITING,
|
||||||
enable: true,
|
enable: true,
|
||||||
time: 1000,
|
time: 1000,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'extract':
|
case RequestConditionProcessor.EXTRACT:
|
||||||
data.value.push({
|
data.value.push({
|
||||||
id,
|
id,
|
||||||
type: 'extract',
|
type: RequestConditionProcessor.EXTRACT,
|
||||||
enable: true,
|
enable: true,
|
||||||
extractParams: [],
|
extractParams: [],
|
||||||
});
|
});
|
||||||
|
|
|
@ -151,16 +151,16 @@
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { matchXMLWithXPath } from '@/utils/xpath';
|
import { matchXMLWithXPath } from '@/utils/xpath';
|
||||||
|
|
||||||
import { ExpressionConfig } from '@/models/apiTest/debug';
|
import { JSONPathExtract, RegexExtract, XPathExtract } from '@/models/apiTest/debug';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
config: ExpressionConfig;
|
config: (RegexExtract | JSONPathExtract | XPathExtract) & Record<string, any>;
|
||||||
response?: string; // 响应内容
|
response?: string; // 响应内容
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:visible', value: boolean): void;
|
(e: 'update:visible', value: boolean): void;
|
||||||
(e: 'apply', config: ExpressionConfig): void;
|
(e: 'apply', config: (RegexExtract | JSONPathExtract | XPathExtract) & Record<string, any>): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
|
@ -96,7 +96,9 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { ExpressionConfig } from '@/models/apiTest/debug';
|
import { JSONPathExtract, RegexExtract, XPathExtract } from '@/models/apiTest/debug';
|
||||||
|
|
||||||
|
export type ExpressionConfig = (RegexExtract | JSONPathExtract | XPathExtract) & Record<string, any>;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
config: ExpressionConfig;
|
config: ExpressionConfig;
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
record.required ? '!text-[rgb(var(--danger-5))]' : '!text-[var(--color-text-brand)]',
|
record.required ? '!text-[rgb(var(--danger-5))]' : '!text-[var(--color-text-brand)]',
|
||||||
'!mr-[4px] !p-[4px]',
|
'!mr-[4px] !p-[4px]',
|
||||||
]"
|
]"
|
||||||
@click="record.required = !record.required"
|
@click="toggleRequired(record)"
|
||||||
>
|
>
|
||||||
<div>*</div>
|
<div>*</div>
|
||||||
</MsButton>
|
</MsButton>
|
||||||
|
@ -186,11 +186,7 @@
|
||||||
:list="getMoreActionList(columnConfig.moreAction, record)"
|
:list="getMoreActionList(columnConfig.moreAction, record)"
|
||||||
@select="(e) => handleMoreActionSelect(e, record)"
|
@select="(e) => handleMoreActionSelect(e, record)"
|
||||||
/>
|
/>
|
||||||
<a-trigger
|
<a-trigger v-if="columnConfig.format === RequestBodyFormat.FORM_DATA" trigger="click" position="br">
|
||||||
v-if="columnConfig.format && columnConfig.format !== RequestBodyFormat.X_WWW_FORM_URLENCODED"
|
|
||||||
trigger="click"
|
|
||||||
position="br"
|
|
||||||
>
|
|
||||||
<MsButton type="icon" class="mr-[8px]"><icon-more /></MsButton>
|
<MsButton type="icon" class="mr-[8px]"><icon-more /></MsButton>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="content-type-trigger-content">
|
<div class="content-type-trigger-content">
|
||||||
|
@ -296,6 +292,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script async setup lang="ts">
|
<script async setup lang="ts">
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
|
@ -313,7 +310,7 @@
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useTableStore from '@/hooks/useTableStore';
|
import useTableStore from '@/hooks/useTableStore';
|
||||||
|
|
||||||
import { RequestBodyFormat, RequestContentTypeEnum } from '@/enums/apiEnum';
|
import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
|
||||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
interface Param {
|
interface Param {
|
||||||
|
@ -340,11 +337,12 @@
|
||||||
typeTitleTooltip?: string; // 用于 type 表头列展示的 tooltip
|
typeTitleTooltip?: string; // 用于 type 表头列展示的 tooltip
|
||||||
hasEnable?: boolean; // 用于 operation 列区分是否有 enable 开关
|
hasEnable?: boolean; // 用于 operation 列区分是否有 enable 开关
|
||||||
moreAction?: ActionsItem[]; // 用于 operation 列更多操作按钮配置
|
moreAction?: ActionsItem[]; // 用于 operation 列更多操作按钮配置
|
||||||
format?: RequestBodyFormat | 'query' | 'rest'; // 用于 operation 列区分是否有请求体格式选择器
|
format?: RequestBodyFormat; // 用于 operation 列区分是否有请求体格式选择器
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
selectedKeys?: string[];
|
||||||
params: any[];
|
params: any[];
|
||||||
defaultParamItem?: Partial<Param>; // 默认参数项,用于添加新行时的默认值
|
defaultParamItem?: Partial<Param>; // 默认参数项,用于添加新行时的默认值
|
||||||
columns: ParamTableColumn[];
|
columns: ParamTableColumn[];
|
||||||
|
@ -372,7 +370,7 @@
|
||||||
defaultParamItem: () => ({
|
defaultParamItem: () => ({
|
||||||
required: false,
|
required: false,
|
||||||
name: '',
|
name: '',
|
||||||
type: 'string',
|
type: RequestParamsType.STRING,
|
||||||
value: '',
|
value: '',
|
||||||
min: undefined,
|
min: undefined,
|
||||||
max: undefined,
|
max: undefined,
|
||||||
|
@ -386,6 +384,7 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:selectedKeys', value: string[]): void;
|
||||||
(e: 'change', data: any[], isInit?: boolean): void; // 都触发这个事件以通知父组件参数数组被更改
|
(e: 'change', data: any[], isInit?: boolean): void; // 都触发这个事件以通知父组件参数数组被更改
|
||||||
(e: 'moreActionSelect', event: ActionsItem, record: Record<string, any>): void;
|
(e: 'moreActionSelect', event: ActionsItem, record: Record<string, any>): void;
|
||||||
(e: 'projectChange', projectId: string): void;
|
(e: 'projectChange', projectId: string): void;
|
||||||
|
@ -393,6 +392,8 @@
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const innerSelectedKeys = useVModel(props, 'selectedKeys', emit);
|
||||||
|
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
async function initColumns() {
|
async function initColumns() {
|
||||||
if (props.showSetting && props.tableKey) {
|
if (props.showSetting && props.tableKey) {
|
||||||
|
@ -402,6 +403,7 @@
|
||||||
initColumns();
|
initColumns();
|
||||||
|
|
||||||
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
|
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
|
||||||
|
firstColumnWidth: 24,
|
||||||
tableKey: props.showSetting ? props.tableKey : undefined,
|
tableKey: props.showSetting ? props.tableKey : undefined,
|
||||||
scroll: props.scroll,
|
scroll: props.scroll,
|
||||||
heightUsed: props.heightUsed,
|
heightUsed: props.heightUsed,
|
||||||
|
@ -412,8 +414,16 @@
|
||||||
disabled: props.disabled,
|
disabled: props.disabled,
|
||||||
showSelectorAll: props.showSelectorAll,
|
showSelectorAll: props.showSelectorAll,
|
||||||
isSimpleSetting: props.isSimpleSetting,
|
isSimpleSetting: props.isSimpleSetting,
|
||||||
|
showPagination: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => propsRes.value.selectedKeys,
|
||||||
|
(val) => {
|
||||||
|
innerSelectedKeys.value = Array.from(val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.heightUsed,
|
() => props.heightUsed,
|
||||||
(val) => {
|
(val) => {
|
||||||
|
@ -447,10 +457,12 @@
|
||||||
? isEqual(lastData, props.defaultParamItem)
|
? isEqual(lastData, props.defaultParamItem)
|
||||||
: isEqual(lastData[key], props.defaultParamItem[key]);
|
: isEqual(lastData[key], props.defaultParamItem[key]);
|
||||||
if (isForce || (val !== '' && !isNotChange)) {
|
if (isForce || (val !== '' && !isNotChange)) {
|
||||||
|
const id = new Date().getTime().toString();
|
||||||
propsRes.value.data.push({
|
propsRes.value.data.push({
|
||||||
id: new Date().getTime(),
|
id,
|
||||||
...props.defaultParamItem,
|
...props.defaultParamItem,
|
||||||
} as any);
|
} as any);
|
||||||
|
propsRes.value.selectedKeys.add(id);
|
||||||
emit('change', propsRes.value.data);
|
emit('change', propsRes.value.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -467,12 +479,14 @@
|
||||||
addTableLine();
|
addTableLine();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const id = new Date().getTime().toString();
|
||||||
propsRes.value.data = [
|
propsRes.value.data = [
|
||||||
{
|
{
|
||||||
id: new Date().getTime(), // 默认给时间戳 id,若 props.defaultParamItem 有 id,则覆盖
|
id, // 默认给时间戳 id,若 props.defaultParamItem 有 id,则覆盖
|
||||||
...props.defaultParamItem,
|
...props.defaultParamItem,
|
||||||
},
|
},
|
||||||
] as any[];
|
] as any[];
|
||||||
|
propsRes.value.selectedKeys.add(id);
|
||||||
emit('change', propsRes.value.data, true);
|
emit('change', propsRes.value.data, true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -481,6 +495,11 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function toggleRequired(record: Record<string, any>) {
|
||||||
|
record.required = !record.required;
|
||||||
|
emit('change', propsRes.value.data);
|
||||||
|
}
|
||||||
|
|
||||||
const showQuickInputParam = ref(false);
|
const showQuickInputParam = ref(false);
|
||||||
const activeQuickInputRecord = ref<any>({});
|
const activeQuickInputRecord = ref<any>({});
|
||||||
const quickInputParamValue = ref('');
|
const quickInputParamValue = ref('');
|
||||||
|
@ -500,7 +519,6 @@
|
||||||
activeQuickInputRecord.value.value = quickInputParamValue.value;
|
activeQuickInputRecord.value.value = quickInputParamValue.value;
|
||||||
showQuickInputParam.value = false;
|
showQuickInputParam.value = false;
|
||||||
clearQuickInputParam();
|
clearQuickInputParam();
|
||||||
addTableLine(quickInputParamValue.value, 'value', true);
|
|
||||||
emit('change', propsRes.value.data);
|
emit('change', propsRes.value.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,7 +544,6 @@
|
||||||
activeQuickInputRecord.value.desc = quickInputDescValue.value;
|
activeQuickInputRecord.value.desc = quickInputDescValue.value;
|
||||||
showQuickInputDesc.value = false;
|
showQuickInputDesc.value = false;
|
||||||
clearQuickInputDesc();
|
clearQuickInputDesc();
|
||||||
addTableLine(quickInputDescValue.value, 'desc', true);
|
|
||||||
emit('change', propsRes.value.data);
|
emit('change', propsRes.value.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -605,6 +622,11 @@
|
||||||
padding: 12px 2px;
|
padding: 12px 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
:deep(.arco-table-col-fixed-right) {
|
||||||
|
.arco-table-cell-align-left {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
|
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
|
||||||
&:not(:hover) {
|
&:not(:hover) {
|
||||||
border-color: transparent !important;
|
border-color: transparent !important;
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
import { addReviewModule, updateReviewModule } from '@/api/modules/case-management/caseReview';
|
import { addDebugModule, updateDebugModule } from '@/api/modules/api-test/debug';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
if (props.mode === 'add') {
|
if (props.mode === 'add') {
|
||||||
// 添加根级模块
|
// 添加根级模块
|
||||||
await addReviewModule({
|
await addDebugModule({
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
parentId: props.parentId || '',
|
parentId: props.parentId || '',
|
||||||
name: form.value.field,
|
name: form.value.field,
|
||||||
|
@ -132,7 +132,7 @@
|
||||||
emit('addFinish', form.value.field);
|
emit('addFinish', form.value.field);
|
||||||
} else if (props.mode === 'rename') {
|
} else if (props.mode === 'rename') {
|
||||||
// 模块重命名
|
// 模块重命名
|
||||||
await updateReviewModule({
|
await updateDebugModule({
|
||||||
id: props.nodeId || '',
|
id: props.nodeId || '',
|
||||||
name: form.value.field,
|
name: form.value.field,
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,12 +2,11 @@
|
||||||
<div class="flex w-full gap-[8px] rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]">
|
<div class="flex w-full gap-[8px] rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]">
|
||||||
<div class="text-item-wrapper">
|
<div class="text-item-wrapper">
|
||||||
<div class="light-item">{{ t('apiTestDebug.responseStage') }}</div>
|
<div class="light-item">{{ t('apiTestDebug.responseStage') }}</div>
|
||||||
<div class="light-item">{{ t('apiTestDebug.ready') }}</div>
|
|
||||||
<div class="normal-item">{{ t('apiTestDebug.socketInit') }}</div>
|
|
||||||
<div class="normal-item">{{ t('apiTestDebug.dnsQuery') }}</div>
|
<div class="normal-item">{{ t('apiTestDebug.dnsQuery') }}</div>
|
||||||
<div class="normal-item">{{ t('apiTestDebug.tcpHandshake') }}</div>
|
<div class="normal-item">{{ t('apiTestDebug.tcpHandshake') }}</div>
|
||||||
<div class="normal-item">{{ t('apiTestDebug.sslHandshake') }}</div>
|
<div class="normal-item">{{ t('apiTestDebug.sslHandshake') }}</div>
|
||||||
<div class="normal-item">{{ t('apiTestDebug.waitingTTFB') }}</div>
|
<div class="normal-item">{{ t('apiTestDebug.socketInit') }}</div>
|
||||||
|
<!-- <div class="normal-item">{{ t('apiTestDebug.waitingTTFB') }}</div> -->
|
||||||
<div class="normal-item">{{ t('apiTestDebug.downloadContent') }}</div>
|
<div class="normal-item">{{ t('apiTestDebug.downloadContent') }}</div>
|
||||||
<div class="light-item">{{ t('apiTestDebug.deal') }}</div>
|
<div class="light-item">{{ t('apiTestDebug.deal') }}</div>
|
||||||
<div class="total-item">{{ t('apiTestDebug.total') }}</div>
|
<div class="total-item">{{ t('apiTestDebug.total') }}</div>
|
||||||
|
@ -29,15 +28,14 @@
|
||||||
<a-divider direction="vertical" margin="0" />
|
<a-divider direction="vertical" margin="0" />
|
||||||
<div class="text-item-wrapper--right">
|
<div class="text-item-wrapper--right">
|
||||||
<div class="light-item">{{ t('apiTestDebug.time') }}</div>
|
<div class="light-item">{{ t('apiTestDebug.time') }}</div>
|
||||||
<div class="light-item">{{ props.responseTiming.ready }} ms</div>
|
<div class="normal-item">{{ props.responseTiming.dnsLookupTime }} ms</div>
|
||||||
<div class="normal-item">{{ props.responseTiming.socketInit }} ms</div>
|
<div class="normal-item">{{ props.responseTiming.tcpHandshakeTime }} ms</div>
|
||||||
<div class="normal-item">{{ props.responseTiming.dnsQuery }} ms</div>
|
<div class="normal-item">{{ props.responseTiming.sslHandshakeTime }} ms</div>
|
||||||
<div class="normal-item">{{ props.responseTiming.tcpHandshake }} ms</div>
|
<div class="normal-item">{{ props.responseTiming.socketInitTime }} ms</div>
|
||||||
<div class="normal-item">{{ props.responseTiming.sslHandshake }} ms</div>
|
<!-- <div class="normal-item">{{ props.responseTiming.latency }} ms</div> -->
|
||||||
<div class="normal-item">{{ props.responseTiming.waitingTTFB }} ms</div>
|
<div class="normal-item">{{ props.responseTiming.downloadTime }} ms</div>
|
||||||
<div class="normal-item">{{ props.responseTiming.downloadContent }} ms</div>
|
<div class="light-item">{{ props.responseTiming.transferStartTime }} ms</div>
|
||||||
<div class="light-item">{{ props.responseTiming.deal }} ms</div>
|
<div class="total-item">{{ props.responseTiming.responseTime }} ms</div>
|
||||||
<div class="total-item">{{ props.responseTiming.total }} ms</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -58,13 +56,16 @@
|
||||||
const keys = Object.keys(props.responseTiming).filter((key) => key !== 'total');
|
const keys = Object.keys(props.responseTiming).filter((key) => key !== 'total');
|
||||||
let preLinesTotalLeft = 0;
|
let preLinesTotalLeft = 0;
|
||||||
keys.forEach((key, index) => {
|
keys.forEach((key, index) => {
|
||||||
const itemWidth = (props.responseTiming[key] / props.responseTiming.total) * 100;
|
if (key !== 'responseTime' && key !== 'latency') {
|
||||||
arr.push({
|
// 总耗时就是 100%,不需要绘制
|
||||||
key,
|
const itemWidth = (props.responseTiming[key] / props.responseTiming.responseTime) * 100;
|
||||||
width: `${itemWidth}%`,
|
arr.push({
|
||||||
left: index !== 0 ? `${preLinesTotalLeft}%` : '',
|
key,
|
||||||
});
|
width: `${itemWidth}%`,
|
||||||
preLinesTotalLeft += itemWidth;
|
left: index !== 0 ? `${preLinesTotalLeft}%` : '',
|
||||||
|
});
|
||||||
|
preLinesTotalLeft += itemWidth;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return arr;
|
return arr;
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
<div class="rounded-[var(--border-radius-small)] border border-[var(--color-text-n8)] p-[16px]">
|
<div class="rounded-[var(--border-radius-small)] border border-[var(--color-text-n8)] p-[16px]">
|
||||||
<div class="mb-[8px]">{{ t('apiTestDebug.authType') }}</div>
|
<div class="mb-[8px]">{{ t('apiTestDebug.authType') }}</div>
|
||||||
<a-radio-group v-model:model-value="authForm.authType" class="mb-[16px]" @change="authTypeChange">
|
<a-radio-group v-model:model-value="authForm.authType" class="mb-[16px]" @change="authTypeChange">
|
||||||
<a-radio value="none">No Auth</a-radio>
|
<a-radio :value="RequestAuthType.NONE">No Auth</a-radio>
|
||||||
<a-radio value="basic">Basic Auth</a-radio>
|
<a-radio :value="RequestAuthType.BASIC">Basic Auth</a-radio>
|
||||||
<a-radio value="digest">Digest Auth</a-radio>
|
<a-radio :value="RequestAuthType.DIGEST">Digest Auth</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<a-form v-if="authForm.authType !== 'none'" ref="authFormRef" :model="authForm" layout="vertical">
|
<a-form v-if="authForm.authType !== 'NONE'" ref="authFormRef" :model="authForm" layout="vertical">
|
||||||
<a-form-item :label="t('apiTestDebug.account')">
|
<a-form-item :label="t('apiTestDebug.username')">
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="authForm.account"
|
v-model:model-value="authForm.username"
|
||||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||||
class="w-[450px]"
|
class="w-[450px]"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
|
@ -34,17 +34,15 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
interface AuthForm {
|
import { ExecuteAuthConfig } from '@/models/apiTest/debug';
|
||||||
authType: string;
|
import { RequestAuthType } from '@/enums/apiEnum';
|
||||||
account: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
params: AuthForm;
|
params: ExecuteAuthConfig;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:params', val: AuthForm): void;
|
(e: 'update:params', val: ExecuteAuthConfig): void;
|
||||||
(e: 'change', val: AuthForm): void;
|
(e: 'change', val: ExecuteAuthConfig): void;
|
||||||
}>();
|
}>();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -61,7 +59,7 @@
|
||||||
|
|
||||||
function authTypeChange(val: string | number | boolean) {
|
function authTypeChange(val: string | number | boolean) {
|
||||||
if (val === 'none') {
|
if (val === 'none') {
|
||||||
authForm.value.account = '';
|
authForm.value.username = '';
|
||||||
authForm.value.password = '';
|
authForm.value.password = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { RequestContentTypeEnum } from '@/enums/apiEnum';
|
import { RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
params: Record<string, any>[];
|
params: Record<string, any>[];
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
value: value?.trim(),
|
value: value?.trim(),
|
||||||
required: false,
|
required: false,
|
||||||
type: 'string',
|
type: RequestParamsType.STRING,
|
||||||
min: undefined,
|
min: undefined,
|
||||||
max: undefined,
|
max: undefined,
|
||||||
contentType: RequestContentTypeEnum.TEXT,
|
contentType: RequestContentTypeEnum.TEXT,
|
||||||
|
|
|
@ -1,33 +1,42 @@
|
||||||
<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('apiTestDebug.body') }}</div>
|
<batchAddKeyVal v-if="showParamTable" :params="currentTableParams" @apply="handleBatchParamApply" />
|
||||||
<div class="flex items-center gap-[16px]">
|
<a-radio-group v-model:model-value="bodyType" type="button" size="small" @change="formatChange">
|
||||||
<batchAddKeyVal v-if="showParamTable" :params="currentTableParams" @apply="handleBatchParamApply" />
|
<a-radio v-for="item of RequestBodyFormat" :key="item" :value="item">{{ requestBodyTypeMap[item] }}</a-radio>
|
||||||
<a-radio-group v-model:model-value="format" type="button" size="small" @change="formatChange">
|
</a-radio-group>
|
||||||
<a-radio v-for="item of RequestBodyFormat" :key="item" :value="item">{{ item }}</a-radio>
|
|
||||||
</a-radio-group>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="format === RequestBodyFormat.NONE"
|
v-if="bodyType === 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)]"
|
class="flex h-[100px] items-center justify-center rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] text-[var(--color-text-4)]"
|
||||||
>
|
>
|
||||||
{{ t('apiTestDebug.noneBody') }}
|
{{ t('apiTestDebug.noneBody') }}
|
||||||
</div>
|
</div>
|
||||||
<paramTable
|
<paramTable
|
||||||
v-else-if="showParamTable"
|
v-else-if="bodyType === RequestBodyFormat.FORM_DATA"
|
||||||
v-model:params="currentTableParams"
|
v-model:params="currentTableParams"
|
||||||
:scroll="{ minWidth: 1160 }"
|
:scroll="{ minWidth: 1160 }"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:height-used="heightUsed"
|
:height-used="heightUsed"
|
||||||
@change="handleParamTableChange"
|
@change="handleParamTableChange"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="format === RequestBodyFormat.BINARY">
|
<paramTable
|
||||||
|
v-else-if="bodyType === RequestBodyFormat.WWW_FORM"
|
||||||
|
v-model:params="currentTableParams"
|
||||||
|
:scroll="{ minWidth: 1160 }"
|
||||||
|
:columns="columns"
|
||||||
|
:height-used="heightUsed"
|
||||||
|
@change="handleParamTableChange"
|
||||||
|
/>
|
||||||
|
<div v-else-if="bodyType === RequestBodyFormat.BINARY">
|
||||||
<div class="mb-[16px] flex justify-between gap-[8px] bg-[var(--color-text-n9)] p-[12px]">
|
<div class="mb-[16px] flex justify-between gap-[8px] bg-[var(--color-text-n9)] p-[12px]">
|
||||||
<a-input v-model:model-value="innerParams.binaryDesc" :placeholder="t('common.desc')" :max-length="255" />
|
<a-input
|
||||||
|
v-model:model-value="innerParams.binaryBody.description"
|
||||||
|
:placeholder="t('common.desc')"
|
||||||
|
:max-length="255"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<a-switch v-model:model-value="innerParams.binarySend" class="mr-[8px]" size="small" type="line"></a-switch>
|
<!-- <a-switch v-model:model-value="innerParams.binarySend" class="mr-[8px]" size="small" type="line"></a-switch> -->
|
||||||
<span>{{ t('apiTestDebug.sendAsMainText') }}</span>
|
<span>{{ t('apiTestDebug.sendAsMainText') }}</span>
|
||||||
<a-tooltip position="right">
|
<a-tooltip position="right">
|
||||||
<template #content>
|
<template #content>
|
||||||
|
@ -72,23 +81,14 @@
|
||||||
import paramTable, { type ParamTableColumn } from '../../../components/paramTable.vue';
|
import paramTable, { type ParamTableColumn } from '../../../components/paramTable.vue';
|
||||||
import batchAddKeyVal from './batchAddKeyVal.vue';
|
import batchAddKeyVal from './batchAddKeyVal.vue';
|
||||||
|
|
||||||
|
import { requestBodyTypeMap } from '@/config/apiTest';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { RequestBodyFormat } from '@/enums/apiEnum';
|
import { ExecuteBody } from '@/models/apiTest/debug';
|
||||||
|
import { RequestBodyFormat, RequestParamsType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
export interface BodyParams {
|
|
||||||
format: RequestBodyFormat;
|
|
||||||
formData: Record<string, any>[];
|
|
||||||
formUrlEncode: Record<string, any>[];
|
|
||||||
json: string;
|
|
||||||
xml: string;
|
|
||||||
binary: string;
|
|
||||||
binaryDesc: string;
|
|
||||||
binarySend: boolean;
|
|
||||||
raw: string;
|
|
||||||
}
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
params: BodyParams;
|
params: ExecuteBody;
|
||||||
layout: 'horizontal' | 'vertical';
|
layout: 'horizontal' | 'vertical';
|
||||||
secondBoxHeight: number;
|
secondBoxHeight: number;
|
||||||
}>();
|
}>();
|
||||||
|
@ -100,8 +100,9 @@
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const innerParams = useVModel(props, 'params', emit);
|
const innerParams = useVModel(props, 'params', emit);
|
||||||
|
const bodyType = ref(RequestBodyFormat.NONE);
|
||||||
|
|
||||||
const columns: ParamTableColumn[] = [
|
const columns = computed<ParamTableColumn[]>(() => [
|
||||||
{
|
{
|
||||||
title: 'apiTestDebug.paramName',
|
title: 'apiTestDebug.paramName',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
|
@ -112,32 +113,10 @@
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
slotName: 'type',
|
slotName: 'type',
|
||||||
hasRequired: true,
|
hasRequired: true,
|
||||||
typeOptions: [
|
typeOptions: Object.keys(RequestParamsType).map((key) => ({
|
||||||
{
|
label: RequestParamsType[key],
|
||||||
label: 'string',
|
value: key,
|
||||||
value: 'string',
|
})),
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'integer',
|
|
||||||
value: 'integer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'number',
|
|
||||||
value: 'number',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'array',
|
|
||||||
value: 'array',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'json',
|
|
||||||
value: 'json',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'file',
|
|
||||||
value: 'file',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
width: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -168,9 +147,10 @@
|
||||||
title: '',
|
title: '',
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 50,
|
format: bodyType.value,
|
||||||
|
width: bodyType.value === RequestBodyFormat.FORM_DATA ? 90 : 50,
|
||||||
},
|
},
|
||||||
];
|
]);
|
||||||
|
|
||||||
const heightUsed = ref<number | undefined>(undefined);
|
const heightUsed = ref<number | undefined>(undefined);
|
||||||
|
|
||||||
|
@ -196,61 +176,60 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const format = ref(RequestBodyFormat.NONE);
|
|
||||||
const showParamTable = computed(() => {
|
const showParamTable = computed(() => {
|
||||||
// 仅当格式为FORM_DATA或X_WWW_FORM_URLENCODED时,显示参数表格
|
// 仅当格式为FORM_DATA或X_WWW_FORM_URLENCODED时,显示参数表格
|
||||||
return [RequestBodyFormat.FORM_DATA, RequestBodyFormat.X_WWW_FORM_URLENCODED].includes(format.value);
|
return [RequestBodyFormat.FORM_DATA, RequestBodyFormat.WWW_FORM].includes(bodyType.value);
|
||||||
});
|
});
|
||||||
// 当前显示的参数表格数据
|
// 当前显示的参数表格数据
|
||||||
const currentTableParams = computed({
|
const currentTableParams = computed({
|
||||||
get() {
|
get() {
|
||||||
if (format.value === RequestBodyFormat.FORM_DATA) {
|
if (bodyType.value === RequestBodyFormat.FORM_DATA) {
|
||||||
return innerParams.value.formData;
|
return innerParams.value.formDataBody.formValues;
|
||||||
}
|
}
|
||||||
return innerParams.value.formUrlEncode;
|
return innerParams.value.wwwFormBody.formValues;
|
||||||
},
|
},
|
||||||
set(val) {
|
set(val) {
|
||||||
if (format.value === RequestBodyFormat.FORM_DATA) {
|
if (bodyType.value === RequestBodyFormat.FORM_DATA) {
|
||||||
innerParams.value.formData = val;
|
innerParams.value.formDataBody.formValues = val;
|
||||||
} else {
|
} else {
|
||||||
innerParams.value.formUrlEncode = val;
|
innerParams.value.wwwFormBody.formValues = val;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// 当前显示的代码
|
// 当前显示的代码
|
||||||
const currentBodyCode = computed({
|
const currentBodyCode = computed({
|
||||||
get() {
|
get() {
|
||||||
if (format.value === RequestBodyFormat.JSON) {
|
if (bodyType.value === RequestBodyFormat.JSON) {
|
||||||
return innerParams.value.json;
|
return innerParams.value.jsonBody.jsonValue;
|
||||||
}
|
}
|
||||||
if (format.value === RequestBodyFormat.XML) {
|
if (bodyType.value === RequestBodyFormat.XML) {
|
||||||
return innerParams.value.xml;
|
return innerParams.value.xmlBody.value;
|
||||||
}
|
}
|
||||||
return innerParams.value.raw;
|
return innerParams.value.rawBody.value;
|
||||||
},
|
},
|
||||||
set(val) {
|
set(val) {
|
||||||
if (format.value === RequestBodyFormat.JSON) {
|
if (bodyType.value === RequestBodyFormat.JSON) {
|
||||||
innerParams.value.json = val;
|
innerParams.value.jsonBody.jsonValue = val;
|
||||||
} else if (format.value === RequestBodyFormat.XML) {
|
} else if (bodyType.value === RequestBodyFormat.XML) {
|
||||||
innerParams.value.xml = val;
|
innerParams.value.xmlBody.value = val;
|
||||||
} else {
|
} else {
|
||||||
innerParams.value.raw = val;
|
innerParams.value.rawBody.value = val;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// 当前代码编辑器的语言
|
// 当前代码编辑器的语言
|
||||||
const currentCodeLanguage = computed(() => {
|
const currentCodeLanguage = computed(() => {
|
||||||
if (format.value === RequestBodyFormat.JSON) {
|
if (bodyType.value === RequestBodyFormat.JSON) {
|
||||||
return LanguageEnum.JSON;
|
return LanguageEnum.JSON;
|
||||||
}
|
}
|
||||||
if (format.value === RequestBodyFormat.XML) {
|
if (bodyType.value === RequestBodyFormat.XML) {
|
||||||
return LanguageEnum.XML;
|
return LanguageEnum.XML;
|
||||||
}
|
}
|
||||||
return LanguageEnum.PLAINTEXT;
|
return LanguageEnum.PLAINTEXT;
|
||||||
});
|
});
|
||||||
|
|
||||||
function formatChange() {
|
function formatChange() {
|
||||||
console.log('formatChange', format.value);
|
console.log('formatChange', bodyType.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
</div>
|
</div>
|
||||||
<paramTable
|
<paramTable
|
||||||
v-model:params="innerParams"
|
v-model:params="innerParams"
|
||||||
|
v-model:selected-keys="selectedKeys"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:height-used="heightUsed"
|
:height-used="heightUsed"
|
||||||
:scroll="scroll"
|
:scroll="scroll"
|
||||||
|
@ -21,19 +22,24 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
import { EnableKeyValueParam } from '@/models/apiTest/debug';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
params: any[];
|
selectedKeys?: string[];
|
||||||
|
params: EnableKeyValueParam[];
|
||||||
layout: 'horizontal' | 'vertical';
|
layout: 'horizontal' | 'vertical';
|
||||||
secondBoxHeight: number;
|
secondBoxHeight: number;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:params', value: any[]): void;
|
(e: 'update:selectedKeys', value: string[]): void;
|
||||||
|
(e: 'update:params', value: EnableKeyValueParam[]): void;
|
||||||
(e: 'change'): void; // 数据发生变化
|
(e: 'change'): void; // 数据发生变化
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const innerParams = useVModel(props, 'params', emit);
|
const innerParams = useVModel(props, 'params', emit);
|
||||||
|
const selectedKeys = useVModel(props, 'selectedKeys', emit);
|
||||||
|
|
||||||
const columns: ParamTableColumn[] = [
|
const columns: ParamTableColumn[] = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
@more-action-select="handleMoreActionSelect"
|
@more-action-select="handleMoreActionSelect"
|
||||||
>
|
>
|
||||||
<template #label="{ tab }">
|
<template #label="{ tab }">
|
||||||
<apiMethodName :method="tab.method" class="mr-[4px]" />
|
<apiMethodName v-if="isHttpProtocol" :method="tab.method" class="mr-[4px]" />
|
||||||
{{ tab.label }}
|
{{ tab.label }}
|
||||||
</template>
|
</template>
|
||||||
</MsEditableTab>
|
</MsEditableTab>
|
||||||
|
@ -20,12 +20,13 @@
|
||||||
<div class="mb-[4px] flex items-center justify-between">
|
<div class="mb-[4px] flex items-center justify-between">
|
||||||
<div class="flex flex-1">
|
<div class="flex flex-1">
|
||||||
<a-select
|
<a-select
|
||||||
v-model:model-value="activeDebug.moduleProtocol"
|
v-model:model-value="activeDebug.protocol"
|
||||||
:options="moduleProtocolOptions"
|
:options="protocolOptions"
|
||||||
|
:loading="protocolLoading"
|
||||||
class="mr-[4px] w-[90px]"
|
class="mr-[4px] w-[90px]"
|
||||||
@change="handleActiveDebugChange"
|
@change="(val) => handleActiveDebugProtocolChange(val as string)"
|
||||||
/>
|
/>
|
||||||
<a-input-group class="flex-1">
|
<a-input-group v-if="isHttpProtocol" class="flex-1">
|
||||||
<apiMethodSelect
|
<apiMethodSelect
|
||||||
v-model:model-value="activeDebug.method"
|
v-model:model-value="activeDebug.method"
|
||||||
class="w-[140px]"
|
class="w-[140px]"
|
||||||
|
@ -40,7 +41,12 @@
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-[16px]">
|
<div class="ml-[16px]">
|
||||||
<a-dropdown-button class="exec-btn">
|
<a-dropdown-button
|
||||||
|
:button-props="{ loading: executeLoading }"
|
||||||
|
:disabled="executeLoading"
|
||||||
|
class="exec-btn"
|
||||||
|
@click="execute"
|
||||||
|
>
|
||||||
{{ t('apiTestDebug.serverExec') }}
|
{{ t('apiTestDebug.serverExec') }}
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-down />
|
<icon-down />
|
||||||
|
@ -49,7 +55,7 @@
|
||||||
<a-doption>{{ t('apiTestDebug.localExec') }}</a-doption>
|
<a-doption>{{ t('apiTestDebug.localExec') }}</a-doption>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown-button>
|
</a-dropdown-button>
|
||||||
<a-button type="secondary">
|
<a-button type="secondary" @click="handleSaveShortcut">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
{{ t('common.save') }}
|
{{ t('common.save') }}
|
||||||
<div class="text-[var(--color-text-4)]">(<icon-command size="14" />+S)</div>
|
<div class="text-[var(--color-text-4)]">(<icon-command size="14" />+S)</div>
|
||||||
|
@ -69,62 +75,75 @@
|
||||||
@expand-change="handleExpandChange"
|
@expand-change="handleExpandChange"
|
||||||
>
|
>
|
||||||
<template #first>
|
<template #first>
|
||||||
<div :class="`h-full min-w-[800px] px-[24px] pb-[16px] ${activeLayout === 'horizontal' ? ' pr-[16px]' : ''}`">
|
<div
|
||||||
<a-tabs v-model:active-key="activeDebug.activeTab" class="no-content">
|
:class="`flex h-full min-w-[800px] flex-col px-[24px] pb-[16px] ${
|
||||||
<a-tab-pane v-for="item of contentTabList" :key="item.value" :title="item.label" />
|
activeLayout === 'horizontal' ? ' pr-[16px]' : ''
|
||||||
</a-tabs>
|
}`"
|
||||||
<a-divider margin="0" class="!mb-[16px]"></a-divider>
|
>
|
||||||
<debugHeader
|
<div>
|
||||||
v-if="activeDebug.activeTab === RequestComposition.HEADER"
|
<a-tabs v-model:active-key="activeDebug.activeTab" class="no-content">
|
||||||
v-model:params="activeDebug.headerParams"
|
<a-tab-pane v-for="item of contentTabList" :key="item.value" :title="item.label" />
|
||||||
:layout="activeLayout"
|
</a-tabs>
|
||||||
:second-box-height="secondBoxHeight"
|
<a-divider margin="0" class="!mb-[16px]"></a-divider>
|
||||||
@change="handleActiveDebugChange"
|
</div>
|
||||||
/>
|
<div class="tab-pane-container">
|
||||||
<debugBody
|
<template v-if="isInitPluginForm || activeDebug.activeTab === RequestComposition.PLUGIN">
|
||||||
v-else-if="activeDebug.activeTab === RequestComposition.BODY"
|
<a-spin v-show="activeDebug.activeTab === RequestComposition.PLUGIN" :loading="pluginLoading">
|
||||||
v-model:params="activeDebug.bodyParams"
|
<MsFormCreate v-model:api="fApi" :rule="currentPluginScript" :option="options" />
|
||||||
:layout="activeLayout"
|
</a-spin>
|
||||||
:second-box-height="secondBoxHeight"
|
</template>
|
||||||
@change="handleActiveDebugChange"
|
<debugHeader
|
||||||
/>
|
v-if="activeDebug.activeTab === RequestComposition.HEADER"
|
||||||
<debugQuery
|
v-model:params="activeDebug.headers"
|
||||||
v-else-if="activeDebug.activeTab === RequestComposition.QUERY"
|
:layout="activeLayout"
|
||||||
v-model:params="activeDebug.queryParams"
|
:second-box-height="secondBoxHeight"
|
||||||
:layout="activeLayout"
|
@change="handleActiveDebugChange"
|
||||||
:second-box-height="secondBoxHeight"
|
/>
|
||||||
@change="handleActiveDebugChange"
|
<debugBody
|
||||||
/>
|
v-else-if="activeDebug.activeTab === RequestComposition.BODY"
|
||||||
<debugRest
|
v-model:params="activeDebug.body"
|
||||||
v-else-if="activeDebug.activeTab === RequestComposition.REST"
|
:layout="activeLayout"
|
||||||
v-model:params="activeDebug.restParams"
|
:second-box-height="secondBoxHeight"
|
||||||
:layout="activeLayout"
|
@change="handleActiveDebugChange"
|
||||||
:second-box-height="secondBoxHeight"
|
/>
|
||||||
@change="handleActiveDebugChange"
|
<debugQuery
|
||||||
/>
|
v-else-if="activeDebug.activeTab === RequestComposition.QUERY"
|
||||||
<precondition
|
v-model:params="activeDebug.query"
|
||||||
v-else-if="activeDebug.activeTab === RequestComposition.PRECONDITION"
|
:layout="activeLayout"
|
||||||
v-model:params="activeDebug.preconditions"
|
:second-box-height="secondBoxHeight"
|
||||||
@change="handleActiveDebugChange"
|
@change="handleActiveDebugChange"
|
||||||
/>
|
/>
|
||||||
<postcondition
|
<debugRest
|
||||||
v-else-if="activeDebug.activeTab === RequestComposition.POST_CONDITION"
|
v-else-if="activeDebug.activeTab === RequestComposition.REST"
|
||||||
v-model:params="activeDebug.postConditions"
|
v-model:params="activeDebug.rest"
|
||||||
:response="activeDebug.response.body"
|
:layout="activeLayout"
|
||||||
:layout="activeLayout"
|
:second-box-height="secondBoxHeight"
|
||||||
:second-box-height="secondBoxHeight"
|
@change="handleActiveDebugChange"
|
||||||
@change="handleActiveDebugChange"
|
/>
|
||||||
/>
|
<precondition
|
||||||
<debugAuth
|
v-else-if="activeDebug.activeTab === RequestComposition.PRECONDITION"
|
||||||
v-else-if="activeDebug.activeTab === RequestComposition.AUTH"
|
v-model:params="activeDebug.children[0].preProcessorConfig.processors"
|
||||||
v-model:params="activeDebug.authParams"
|
@change="handleActiveDebugChange"
|
||||||
@change="handleActiveDebugChange"
|
/>
|
||||||
/>
|
<postcondition
|
||||||
<debugSetting
|
v-else-if="activeDebug.activeTab === RequestComposition.POST_CONDITION"
|
||||||
v-else-if="activeDebug.activeTab === RequestComposition.SETTING"
|
v-model:params="activeDebug.children[0].postProcessorConfig.processors"
|
||||||
v-model:params="activeDebug.setting"
|
:response="activeDebug.response.requestResults[0]?.responseResult.body"
|
||||||
@change="handleActiveDebugChange"
|
:layout="activeLayout"
|
||||||
/>
|
:second-box-height="secondBoxHeight"
|
||||||
|
@change="handleActiveDebugChange"
|
||||||
|
/>
|
||||||
|
<debugAuth
|
||||||
|
v-else-if="activeDebug.activeTab === RequestComposition.AUTH"
|
||||||
|
v-model:params="activeDebug.authConfig"
|
||||||
|
@change="handleActiveDebugChange"
|
||||||
|
/>
|
||||||
|
<debugSetting
|
||||||
|
v-else-if="activeDebug.activeTab === RequestComposition.SETTING"
|
||||||
|
v-model:params="activeDebug.otherConfig"
|
||||||
|
@change="handleActiveDebugChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #second>
|
<template #second>
|
||||||
|
@ -147,6 +166,7 @@
|
||||||
title-align="start"
|
title-align="start"
|
||||||
body-class="!p-0"
|
body-class="!p-0"
|
||||||
@before-ok="handleSave"
|
@before-ok="handleSave"
|
||||||
|
@cancel="handleCancel"
|
||||||
>
|
>
|
||||||
<a-form ref="saveModalFormRef" :model="saveModalForm" layout="vertical">
|
<a-form ref="saveModalFormRef" :model="saveModalForm" layout="vertical">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
|
@ -158,16 +178,17 @@
|
||||||
<a-input v-model:model-value="saveModalForm.name" :placeholder="t('apiTestDebug.requestNamePlaceholder')" />
|
<a-input v-model:model-value="saveModalForm.name" :placeholder="t('apiTestDebug.requestNamePlaceholder')" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="url"
|
v-if="isHttpProtocol"
|
||||||
|
field="path"
|
||||||
:label="t('apiTestDebug.requestUrl')"
|
:label="t('apiTestDebug.requestUrl')"
|
||||||
:rules="[{ required: true, message: t('apiTestDebug.requestUrlRequired') }]"
|
:rules="[{ required: true, message: t('apiTestDebug.requestUrlRequired') }]"
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
>
|
>
|
||||||
<a-input v-model:model-value="saveModalForm.url" :placeholder="t('apiTestDebug.commonPlaceholder')" />
|
<a-input v-model:model-value="saveModalForm.path" :placeholder="t('apiTestDebug.commonPlaceholder')" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('apiTestDebug.requestModule')" class="mb-0">
|
<a-form-item :label="t('apiTestDebug.requestModule')" class="mb-0">
|
||||||
<a-tree-select
|
<a-tree-select
|
||||||
v-model:modelValue="saveModalForm.module"
|
v-model:modelValue="saveModalForm.moduleId"
|
||||||
:data="props.moduleTree"
|
:data="props.moduleTree"
|
||||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||||
allow-search
|
allow-search
|
||||||
|
@ -178,150 +199,163 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
|
||||||
import { cloneDeep, debounce } from 'lodash-es';
|
import { cloneDeep, debounce } from 'lodash-es';
|
||||||
|
|
||||||
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 MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
|
||||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
import debugAuth from './auth.vue';
|
import debugAuth from './auth.vue';
|
||||||
import debugBody, { BodyParams } from './body.vue';
|
|
||||||
import debugHeader from './header.vue';
|
|
||||||
import postcondition from './postcondition.vue';
|
import postcondition from './postcondition.vue';
|
||||||
import precondition from './precondition.vue';
|
import precondition from './precondition.vue';
|
||||||
import debugQuery from './query.vue';
|
|
||||||
import response from './response.vue';
|
import response from './response.vue';
|
||||||
import debugRest from './rest.vue';
|
|
||||||
import debugSetting from './setting.vue';
|
import debugSetting from './setting.vue';
|
||||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||||
import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue';
|
import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue';
|
||||||
|
|
||||||
|
import { addDebug, executeDebug } from '@/api/modules/api-test/debug';
|
||||||
|
import { getPluginScript, getProtocolList } from '@/api/modules/api-test/management';
|
||||||
|
import { getSocket } from '@/api/modules/project-management/commonScript';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { useAppStore } from '@/store';
|
||||||
|
import { getGenerateId } from '@/utils';
|
||||||
|
import { scrollIntoView } from '@/utils/dom';
|
||||||
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
|
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
|
||||||
|
|
||||||
|
import { ExecuteBody, ExecuteHTTPRequestFullParams } from '@/models/apiTest/debug';
|
||||||
import { ModuleTreeNode } from '@/models/common';
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
import { RequestBodyFormat, RequestComposition, RequestMethods, ResponseComposition } from '@/enums/apiEnum';
|
import { RequestBodyFormat, RequestComposition, RequestMethods, ResponseComposition } from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
// 懒加载Http协议组件
|
||||||
|
const debugHeader = defineAsyncComponent(() => import('./header.vue'));
|
||||||
|
const debugBody = defineAsyncComponent(() => import('./body.vue'));
|
||||||
|
const debugQuery = defineAsyncComponent(() => import('./query.vue'));
|
||||||
|
const debugRest = defineAsyncComponent(() => import('./rest.vue'));
|
||||||
|
|
||||||
|
export type DebugTabParam = ExecuteHTTPRequestFullParams & TabItem & Record<string, any>;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
module: string; // 当前激活的接口模块
|
module: string; // 当前激活的接口模块
|
||||||
moduleTree: ModuleTreeNode[]; // 接口模块树
|
moduleTree: ModuleTreeNode[]; // 接口模块树
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const initDefaultId = `debug-${Date.now()}`;
|
const initDefaultId = `debug-${Date.now()}`;
|
||||||
const activeRequestTab = ref<string | number>(initDefaultId);
|
const activeRequestTab = ref<string | number>(initDefaultId);
|
||||||
const defaultBodyParams: BodyParams = {
|
const defaultBodyParams: ExecuteBody = {
|
||||||
format: RequestBodyFormat.NONE,
|
bodyType: RequestBodyFormat.NONE,
|
||||||
formData: [],
|
formDataBody: {
|
||||||
formUrlEncode: [],
|
formValues: [],
|
||||||
json: '',
|
},
|
||||||
xml: '',
|
wwwFormBody: {
|
||||||
binary: '',
|
formValues: [],
|
||||||
binaryDesc: '',
|
},
|
||||||
binarySend: false,
|
jsonBody: {
|
||||||
raw: '',
|
jsonValue: '',
|
||||||
|
},
|
||||||
|
xmlBody: { value: '' },
|
||||||
|
binaryBody: {
|
||||||
|
description: '',
|
||||||
|
file: undefined,
|
||||||
|
},
|
||||||
|
rawBody: { value: '' },
|
||||||
};
|
};
|
||||||
const defaultDebugParams = {
|
const defaultDebugParams: DebugTabParam = {
|
||||||
id: initDefaultId,
|
id: initDefaultId,
|
||||||
module: 'root',
|
moduleId: 'root',
|
||||||
moduleProtocol: 'http',
|
protocol: 'HTTP',
|
||||||
url: '',
|
url: '',
|
||||||
activeTab: RequestComposition.HEADER,
|
activeTab: RequestComposition.HEADER,
|
||||||
label: t('apiTestDebug.newApi'),
|
label: t('apiTestDebug.newApi'),
|
||||||
closable: true,
|
closable: true,
|
||||||
method: RequestMethods.GET,
|
method: RequestMethods.GET,
|
||||||
unSaved: false,
|
unSaved: false,
|
||||||
headerParams: [],
|
headers: [],
|
||||||
bodyParams: cloneDeep(defaultBodyParams),
|
body: cloneDeep(defaultBodyParams),
|
||||||
queryParams: [],
|
query: [],
|
||||||
restParams: [],
|
rest: [],
|
||||||
authParams: {
|
polymorphicName: '',
|
||||||
authType: 'none',
|
name: '',
|
||||||
account: '',
|
path: '',
|
||||||
|
projectId: '',
|
||||||
|
uploadFileIds: [],
|
||||||
|
linkFileIds: [],
|
||||||
|
authConfig: {
|
||||||
|
authType: 'NONE',
|
||||||
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
},
|
},
|
||||||
preconditions: [],
|
children: [
|
||||||
postConditions: [],
|
{
|
||||||
setting: {
|
polymorphicName: 'MsCommonElement', // 协议多态名称,写死MsCommonElement
|
||||||
|
assertionConfig: {
|
||||||
|
enableGlobal: false,
|
||||||
|
assertions: [],
|
||||||
|
},
|
||||||
|
postProcessorConfig: {
|
||||||
|
enableGlobal: false,
|
||||||
|
processors: [],
|
||||||
|
},
|
||||||
|
preProcessorConfig: {
|
||||||
|
enableGlobal: false,
|
||||||
|
processors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
otherConfig: {
|
||||||
connectTimeout: 60000,
|
connectTimeout: 60000,
|
||||||
responseTimeout: 60000,
|
responseTimeout: 60000,
|
||||||
certificateAlias: '',
|
certificateAlias: '',
|
||||||
redirect: 'follow',
|
followRedirects: false,
|
||||||
|
autoRedirects: false,
|
||||||
},
|
},
|
||||||
responseActiveTab: ResponseComposition.BODY,
|
responseActiveTab: ResponseComposition.BODY,
|
||||||
response: {
|
response: {
|
||||||
status: 200,
|
requestResults: [
|
||||||
headers: [],
|
{
|
||||||
timing: 12938,
|
body: '',
|
||||||
size: 8734,
|
responseResult: {
|
||||||
env: 'Mock',
|
body: '',
|
||||||
resource: '66',
|
contentType: '',
|
||||||
timingInfo: {
|
headers: '',
|
||||||
ready: 10,
|
dnsLookupTime: 0,
|
||||||
socketInit: 50,
|
downloadTime: 0,
|
||||||
dnsQuery: 20,
|
latency: 0,
|
||||||
tcpHandshake: 80,
|
responseCode: 0,
|
||||||
sslHandshake: 40,
|
responseTime: 0,
|
||||||
waitingTTFB: 30,
|
responseSize: 0,
|
||||||
downloadContent: 10,
|
socketInitTime: 0,
|
||||||
deal: 10,
|
tcpHandshakeTime: 0,
|
||||||
total: 250,
|
transferStartTime: 0,
|
||||||
},
|
},
|
||||||
extract: {
|
},
|
||||||
a: 'asdasd',
|
],
|
||||||
b: 'asdasdasd43f43',
|
console: '',
|
||||||
},
|
|
||||||
console: `GET https://qa-release.fit2cloud.com/test`,
|
|
||||||
content: `请求地址:
|
|
||||||
https://qa-release.fit2cloud.com/test
|
|
||||||
请求头:
|
|
||||||
Connection: keep-alive
|
|
||||||
Content-Length: 0
|
|
||||||
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
|
|
||||||
Host: qa-release.fit2cloud.com
|
|
||||||
User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.9)
|
|
||||||
|
|
||||||
Body:
|
|
||||||
POST https://qa-release.fit2cloud.com/test
|
|
||||||
|
|
||||||
POST data:
|
|
||||||
|
|
||||||
|
|
||||||
[no cookies]
|
|
||||||
`,
|
|
||||||
header: `HTTP/ 1.1 200 OK
|
|
||||||
Content-Length: 2381
|
|
||||||
Content-Type: text/html
|
|
||||||
Server: bfe
|
|
||||||
Date: Wed, 13 Dec 2023 08:53:25 GMTHTTP/ 1.1 200 OK
|
|
||||||
Content-Length: 2381
|
|
||||||
Content-Type: text/html
|
|
||||||
Server: bfe
|
|
||||||
Date: Wed, 13 Dec 2023 08:53:25 GMT`,
|
|
||||||
body: `<?xml version="1.0"?>
|
|
||||||
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
|
|
||||||
<connectionStrings>
|
|
||||||
<add name="MyDB"
|
|
||||||
connectionString="value for the deployed Web.config file"
|
|
||||||
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
|
|
||||||
</connectionStrings>
|
|
||||||
<a>哈哈哈哈哈哈哈</a>
|
|
||||||
<system.web>
|
|
||||||
<customErrors defaultRedirect="GenericError.htm"
|
|
||||||
mode="RemoteOnly" xdt:Transform="Replace">
|
|
||||||
<error statusCode="500" redirect="InternalError.htm"/>
|
|
||||||
</customErrors>
|
|
||||||
</system.web>
|
|
||||||
</configuration>`,
|
|
||||||
}, // 调试返回的响应内容
|
}, // 调试返回的响应内容
|
||||||
};
|
};
|
||||||
const debugTabs = ref<TabItem[]>([cloneDeep(defaultDebugParams)]);
|
const debugTabs = ref<DebugTabParam[]>([cloneDeep(defaultDebugParams)]);
|
||||||
const activeDebug = ref<TabItem>(debugTabs.value[0]);
|
const activeDebug = ref<DebugTabParam>(debugTabs.value[0]);
|
||||||
|
const isHttpProtocol = computed(() => activeDebug.value.protocol === 'HTTP');
|
||||||
|
const isInitPluginForm = ref(false); // 是否初始化过插件表单
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => activeDebug.value.protocol,
|
||||||
|
(val) => {
|
||||||
|
if (val !== 'HTTP') {
|
||||||
|
isInitPluginForm.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function setActiveDebug(item: TabItem) {
|
function setActiveDebug(item: TabItem) {
|
||||||
activeDebug.value = item;
|
activeDebug.value = item as DebugTabParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleActiveDebugChange() {
|
function handleActiveDebugChange() {
|
||||||
|
@ -332,7 +366,7 @@ Date: Wed, 13 Dec 2023 08:53:25 GMT`,
|
||||||
const id = `debug-${Date.now()}`;
|
const id = `debug-${Date.now()}`;
|
||||||
debugTabs.value.push({
|
debugTabs.value.push({
|
||||||
...cloneDeep(defaultDebugParams),
|
...cloneDeep(defaultDebugParams),
|
||||||
module: props.module,
|
moduleId: props.module,
|
||||||
id,
|
id,
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
});
|
});
|
||||||
|
@ -365,7 +399,21 @@ Date: Wed, 13 Dec 2023 08:53:25 GMT`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentTabList = [
|
// 请求内容公共tabKey
|
||||||
|
const commonContentTabKey = [
|
||||||
|
RequestComposition.PRECONDITION,
|
||||||
|
RequestComposition.POST_CONDITION,
|
||||||
|
RequestComposition.ASSERTION,
|
||||||
|
];
|
||||||
|
// 请求内容插件tab
|
||||||
|
const pluginContentTab = [
|
||||||
|
{
|
||||||
|
value: RequestComposition.PLUGIN,
|
||||||
|
label: t('apiTestDebug.pluginData'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// Http 请求的tab
|
||||||
|
const httpContentTabList = [
|
||||||
{
|
{
|
||||||
value: RequestComposition.HEADER,
|
value: RequestComposition.HEADER,
|
||||||
label: t('apiTestDebug.header'),
|
label: t('apiTestDebug.header'),
|
||||||
|
@ -403,13 +451,82 @@ Date: Wed, 13 Dec 2023 08:53:25 GMT`,
|
||||||
label: t('apiTestDebug.setting'),
|
label: t('apiTestDebug.setting'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
// 根据协议类型获取请求内容tab
|
||||||
|
const contentTabList = computed(() =>
|
||||||
|
isHttpProtocol.value
|
||||||
|
? httpContentTabList
|
||||||
|
: [...pluginContentTab, ...httpContentTabList.filter((e) => commonContentTabKey.includes(e.value))]
|
||||||
|
);
|
||||||
|
const protocolLoading = ref(false);
|
||||||
|
const protocolOptions = ref<SelectOptionData[]>([]);
|
||||||
|
|
||||||
const moduleProtocolOptions = ref([
|
async function initProtocolList() {
|
||||||
{
|
try {
|
||||||
label: 'HTTP',
|
protocolLoading.value = true;
|
||||||
value: 'http',
|
const res = await getProtocolList(appStore.currentOrgId);
|
||||||
|
protocolOptions.value = res.map((e) => ({
|
||||||
|
label: e.protocol,
|
||||||
|
value: e.protocol,
|
||||||
|
polymorphicName: e.polymorphicName,
|
||||||
|
pluginId: e.pluginId,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
protocolLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginScriptMap = ref<Record<string, any>>({}); // 存储初始化过后的插件配置
|
||||||
|
const pluginLoading = ref(false);
|
||||||
|
const currentPluginScript = computed<Record<string, any>[]>(
|
||||||
|
() => pluginScriptMap.value[activeDebug.value.protocol] || []
|
||||||
|
);
|
||||||
|
async function initPluginScript() {
|
||||||
|
if (pluginScriptMap.value[activeDebug.value.protocol] !== undefined) {
|
||||||
|
// 已经初始化过
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
pluginLoading.value = true;
|
||||||
|
const res = await getPluginScript(
|
||||||
|
protocolOptions.value.find((e) => e.value === activeDebug.value.protocol)?.pluginId || ''
|
||||||
|
);
|
||||||
|
pluginScriptMap.value[activeDebug.value.protocol] = res.script;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
pluginLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleActiveDebugProtocolChange(val: string) {
|
||||||
|
if (val !== 'HTTP') {
|
||||||
|
activeDebug.value.activeTab = RequestComposition.PLUGIN;
|
||||||
|
initPluginScript();
|
||||||
|
} else {
|
||||||
|
activeDebug.value.activeTab = RequestComposition.HEADER;
|
||||||
|
}
|
||||||
|
handleActiveDebugChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fApi = ref();
|
||||||
|
const options = {
|
||||||
|
form: {
|
||||||
|
layout: 'vertical',
|
||||||
|
labelPosition: 'right',
|
||||||
|
size: 'small',
|
||||||
|
labelWidth: '00px',
|
||||||
|
hideRequiredAsterisk: false,
|
||||||
|
showMessage: true,
|
||||||
|
inlineMessage: false,
|
||||||
|
scrollToFirstError: true,
|
||||||
},
|
},
|
||||||
]);
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
};
|
||||||
|
|
||||||
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');
|
||||||
|
@ -455,11 +572,120 @@ Date: Wed, 13 Dec 2023 08:53:25 GMT`,
|
||||||
splitBoxRef.value?.expand(0.6);
|
splitBoxRef.value?.expand(0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const executeLoading = ref(false);
|
||||||
|
const reportId = ref('');
|
||||||
|
const websocket = ref<WebSocket>();
|
||||||
|
function debugSocket() {
|
||||||
|
websocket.value = getSocket(reportId.value);
|
||||||
|
websocket.value.addEventListener('message', (event) => {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.msgType === 'EXEC_RESULT') {
|
||||||
|
activeDebug.value.response = data.taskResult;
|
||||||
|
executeLoading.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
websocket.value.addEventListener('close', (event) => {
|
||||||
|
console.log('关闭:', event);
|
||||||
|
});
|
||||||
|
|
||||||
|
websocket.value.addEventListener('error', (event) => {
|
||||||
|
console.error('错误:', event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRequestParams() {
|
||||||
|
const polymorphicName = protocolOptions.value.find((e) => e.value === activeDebug.value.protocol)?.polymorphicName; // 协议多态名称
|
||||||
|
|
||||||
|
let requestParams;
|
||||||
|
if (isHttpProtocol.value) {
|
||||||
|
requestParams = {
|
||||||
|
authConfig: activeDebug.value.authConfig,
|
||||||
|
body: { ...activeDebug.value.body, binaryBody: undefined },
|
||||||
|
headers: activeDebug.value.headers,
|
||||||
|
method: activeDebug.value.method,
|
||||||
|
otherConfig: activeDebug.value.otherConfig,
|
||||||
|
path: activeDebug.value.url,
|
||||||
|
query: activeDebug.value.query,
|
||||||
|
rest: activeDebug.value.rest,
|
||||||
|
url: activeDebug.value.url,
|
||||||
|
polymorphicName,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
requestParams = {
|
||||||
|
...fApi.value.form,
|
||||||
|
polymorphicName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
reportId.value = getGenerateId();
|
||||||
|
debugSocket(); // 开启websocket
|
||||||
|
return {
|
||||||
|
id: activeDebug.value.id.toString(),
|
||||||
|
reportId: reportId.value,
|
||||||
|
environmentId: '',
|
||||||
|
tempFileIds: [],
|
||||||
|
request: {
|
||||||
|
...requestParams,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
polymorphicName: 'MsCommonElement', // 协议多态名称,写死MsCommonElement
|
||||||
|
assertionConfig: {
|
||||||
|
// TODO:暂时不做断言
|
||||||
|
enableGlobal: false,
|
||||||
|
assertions: [],
|
||||||
|
},
|
||||||
|
postProcessorConfig: {
|
||||||
|
enableGlobal: false,
|
||||||
|
processors: [],
|
||||||
|
},
|
||||||
|
preProcessorConfig: {
|
||||||
|
enableGlobal: false,
|
||||||
|
processors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function execute() {
|
||||||
|
if (isHttpProtocol.value) {
|
||||||
|
try {
|
||||||
|
executeLoading.value = true;
|
||||||
|
await executeDebug(makeRequestParams());
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
executeLoading.value = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 插件需要校验动态表单
|
||||||
|
fApi.value?.validate(async (valid) => {
|
||||||
|
if (valid === true) {
|
||||||
|
try {
|
||||||
|
executeLoading.value = true;
|
||||||
|
await executeDebug(makeRequestParams());
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
executeLoading.value = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
activeDebug.value.activeTab = RequestComposition.PLUGIN;
|
||||||
|
nextTick(() => {
|
||||||
|
scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const saveModalVisible = ref(false);
|
const saveModalVisible = ref(false);
|
||||||
const saveModalForm = ref({
|
const saveModalForm = ref({
|
||||||
name: '',
|
name: '',
|
||||||
url: activeDebug.value.url,
|
path: activeDebug.value.url || '',
|
||||||
module: activeDebug.value.module,
|
moduleId: activeDebug.value.module,
|
||||||
});
|
});
|
||||||
const saveModalFormRef = ref<FormInstance>();
|
const saveModalFormRef = ref<FormInstance>();
|
||||||
const saveLoading = ref(false);
|
const saveLoading = ref(false);
|
||||||
|
@ -473,22 +699,46 @@ Date: Wed, 13 Dec 2023 08:53:25 GMT`,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleSaveShortcut() {
|
async function handleSaveShortcut() {
|
||||||
saveModalForm.value = {
|
try {
|
||||||
name: '',
|
if (!isHttpProtocol.value) {
|
||||||
url: activeDebug.value.url,
|
// 插件需要校验动态表单
|
||||||
module: activeDebug.value.module,
|
await fApi.value?.validate();
|
||||||
};
|
}
|
||||||
saveModalVisible.value = true;
|
saveModalForm.value = {
|
||||||
|
name: '',
|
||||||
|
path: activeDebug.value.url || '',
|
||||||
|
moduleId: activeDebug.value.module,
|
||||||
|
};
|
||||||
|
saveModalVisible.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
// 校验不通过则不进行保存
|
||||||
|
activeDebug.value.activeTab = RequestComposition.PLUGIN;
|
||||||
|
nextTick(() => {
|
||||||
|
scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSave(done: (closed: boolean) => void) {
|
function handleCancel() {
|
||||||
|
saveModalFormRef.value?.resetFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSave(done: (closed: boolean) => void) {
|
||||||
saveModalFormRef.value?.validate(async (errors) => {
|
saveModalFormRef.value?.validate(async (errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
try {
|
try {
|
||||||
saveLoading.value = true;
|
saveLoading.value = true;
|
||||||
// eslint-disable-next-line no-promise-executor-return
|
await addDebug({
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
...makeRequestParams(),
|
||||||
|
...saveModalForm.value,
|
||||||
|
protocol: activeDebug.value.protocol,
|
||||||
|
method: isHttpProtocol.value ? activeDebug.value.method : activeDebug.value.protocol,
|
||||||
|
uploadFileIds: [],
|
||||||
|
linkFileIds: [],
|
||||||
|
});
|
||||||
saveLoading.value = false;
|
saveLoading.value = false;
|
||||||
saveModalVisible.value = false;
|
saveModalVisible.value = false;
|
||||||
done(true);
|
done(true);
|
||||||
|
@ -497,12 +747,15 @@ Date: Wed, 13 Dec 2023 08:53:25 GMT`,
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
saveLoading.value = false;
|
saveLoading.value = false;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
done(false);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
done(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initProtocolList();
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
registerCatchSaveShortcut(handleSaveShortcut);
|
registerCatchSaveShortcut(handleSaveShortcut);
|
||||||
});
|
});
|
||||||
|
@ -527,6 +780,10 @@ Date: Wed, 13 Dec 2023 08:53:25 GMT`,
|
||||||
.btn-base-primary-disabled();
|
.btn-base-primary-disabled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.tab-pane-container {
|
||||||
|
@apply flex-1 overflow-y-auto;
|
||||||
|
.ms-scroll-bar();
|
||||||
|
}
|
||||||
:deep(.no-content) {
|
:deep(.no-content) {
|
||||||
.arco-tabs-content {
|
.arco-tabs-content {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<condition
|
<condition
|
||||||
v-model:list="postConditions"
|
v-model:list="postConditions"
|
||||||
:condition-types="['script', 'sql', 'extract']"
|
:condition-types="['SCRIPT']"
|
||||||
add-text="apiTestDebug.postCondition"
|
add-text="apiTestDebug.postCondition"
|
||||||
:response="props.response"
|
:response="props.response"
|
||||||
:height-used="heightUsed"
|
:height-used="heightUsed"
|
||||||
@change="emit('change')"
|
@change="emit('change')"
|
||||||
>
|
>
|
||||||
<template #titleRight>
|
<!-- <template #titleRight>
|
||||||
<a-switch v-model:model-value="openGlobalPostCondition" size="small" type="line"></a-switch>
|
<a-switch v-model:model-value="openGlobalPostCondition" size="small" type="line"></a-switch>
|
||||||
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.openGlobalPostCondition') }}</div>
|
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.openGlobalPostCondition') }}</div>
|
||||||
<a-tooltip :content="t('apiTestDebug.openGlobalPostConditionTip')" position="left">
|
<a-tooltip :content="t('apiTestDebug.openGlobalPostConditionTip')" position="left">
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
size="16"
|
size="16"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template> -->
|
||||||
</condition>
|
</condition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -25,22 +25,24 @@
|
||||||
|
|
||||||
import condition from '@/views/api-test/components/condition/index.vue';
|
import condition from '@/views/api-test/components/condition/index.vue';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { ExecuteConditionProcessor } from '@/models/apiTest/debug';
|
||||||
|
|
||||||
|
// import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
params: any[];
|
params: ExecuteConditionProcessor[];
|
||||||
secondBoxHeight?: number;
|
secondBoxHeight?: number;
|
||||||
layout: 'horizontal' | 'vertical';
|
layout: 'horizontal' | 'vertical';
|
||||||
response?: string; // 响应内容
|
response?: string; // 响应内容
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:params', params: any[]): void;
|
(e: 'update:params', params: ExecuteConditionProcessor[]): void;
|
||||||
(e: 'change'): void;
|
(e: 'change'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
// const { t } = useI18n();
|
||||||
// 是否开启全局后置条件
|
// 是否开启全局后置条件
|
||||||
const openGlobalPostCondition = ref(false);
|
// const openGlobalPostCondition = ref(false);
|
||||||
const postConditions = useVModel(props, 'params', emit);
|
const postConditions = useVModel(props, 'params', emit);
|
||||||
const heightUsed = computed(() => {
|
const heightUsed = computed(() => {
|
||||||
if (props.layout === 'horizontal') {
|
if (props.layout === 'horizontal') {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<condition
|
<condition
|
||||||
v-model:list="preconditions"
|
v-model:list="preconditions"
|
||||||
:condition-types="['script', 'sql', 'waitTime']"
|
:condition-types="['SCRIPT', 'TIME_WAITING']"
|
||||||
add-text="apiTestDebug.precondition"
|
add-text="apiTestDebug.precondition"
|
||||||
@change="emit('change')"
|
@change="emit('change')"
|
||||||
>
|
>
|
||||||
<template #titleRight>
|
<!-- <template #titleRight>
|
||||||
<a-switch v-model:model-value="openGlobalPrecondition" size="small" type="line"></a-switch>
|
<a-switch v-model:model-value="openGlobalPrecondition" size="small" type="line"></a-switch>
|
||||||
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.openGlobalPrecondition') }}</div>
|
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.openGlobalPrecondition') }}</div>
|
||||||
<a-tooltip :content="t('apiTestDebug.openGlobalPreconditionTip')" position="left">
|
<a-tooltip :content="t('apiTestDebug.openGlobalPreconditionTip')" position="left">
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
size="16"
|
size="16"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template> -->
|
||||||
</condition>
|
</condition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -23,19 +23,21 @@
|
||||||
|
|
||||||
import condition from '@/views/api-test/components/condition/index.vue';
|
import condition from '@/views/api-test/components/condition/index.vue';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { ExecuteConditionProcessor } from '@/models/apiTest/debug';
|
||||||
|
|
||||||
|
// import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
params: any[];
|
params: ExecuteConditionProcessor[];
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:params', params: any[]): void;
|
(e: 'update:params', params: ExecuteConditionProcessor[]): void;
|
||||||
(e: 'change'): void;
|
(e: 'change'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
// const { t } = useI18n();
|
||||||
// 是否开启全局前置条件
|
// 是否开启全局前置条件
|
||||||
const openGlobalPrecondition = ref(false);
|
// const openGlobalPrecondition = ref(false);
|
||||||
const preconditions = useVModel(props, 'params', emit);
|
const preconditions = useVModel(props, 'params', emit);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,11 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
import { ExecuteRequestCommonParam } from '@/models/apiTest/debug';
|
||||||
|
import { RequestParamsType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
params: any[];
|
params: ExecuteRequestCommonParam[];
|
||||||
layout: 'horizontal' | 'vertical';
|
layout: 'horizontal' | 'vertical';
|
||||||
secondBoxHeight: number;
|
secondBoxHeight: number;
|
||||||
}>();
|
}>();
|
||||||
|
@ -53,24 +56,12 @@
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
slotName: 'type',
|
slotName: 'type',
|
||||||
hasRequired: true,
|
hasRequired: true,
|
||||||
typeOptions: [
|
typeOptions: Object.keys(RequestParamsType)
|
||||||
{
|
.filter((key) => ![RequestParamsType.JSON, RequestParamsType.FILE].includes(key as RequestParamsType))
|
||||||
label: 'string',
|
.map((key) => ({
|
||||||
value: 'string',
|
label: RequestParamsType[key],
|
||||||
},
|
value: key,
|
||||||
{
|
})),
|
||||||
label: 'integer',
|
|
||||||
value: 'integer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'number',
|
|
||||||
value: 'number',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'array',
|
|
||||||
value: 'array',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
width: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -100,8 +91,7 @@
|
||||||
title: '',
|
title: '',
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
format: 'query',
|
width: 50,
|
||||||
width: 80,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -32,36 +32,51 @@
|
||||||
<a-radio value="horizontal">{{ t('apiTestDebug.horizontal') }}</a-radio>
|
<a-radio value="horizontal">{{ t('apiTestDebug.horizontal') }}</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="props.response.status" class="flex items-center justify-between gap-[24px]">
|
<div
|
||||||
|
v-if="props.response.requestResults[0]?.responseResult?.responseCode"
|
||||||
|
class="flex items-center justify-between gap-[24px]"
|
||||||
|
>
|
||||||
<a-popover position="left" content-class="response-popover-content">
|
<a-popover position="left" content-class="response-popover-content">
|
||||||
<div class="text-[rgb(var(--danger-7))]">{{ props.response.status }}</div>
|
<div :style="{ color: statusCodeColor }">
|
||||||
|
{{ props.response.requestResults[0].responseResult.responseCode }}
|
||||||
|
</div>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="flex items-center gap-[8px] text-[14px]">
|
<div class="flex items-center gap-[8px] text-[14px]">
|
||||||
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.statusCode') }}</div>
|
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.statusCode') }}</div>
|
||||||
<div class="text-[rgb(var(--danger-7))]">{{ props.response.status }}</div>
|
<div :style="{ color: statusCodeColor }">
|
||||||
|
{{ props.response.requestResults[0].responseResult.responseCode }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover position="left" content-class="w-[400px]">
|
<a-popover position="left" content-class="w-[400px]">
|
||||||
<div class="one-line-text text-[rgb(var(--success-7))]">{{ props.response.timing }} ms</div>
|
<div class="one-line-text text-[rgb(var(--success-7))]">
|
||||||
|
{{ props.response.requestResults[0].responseResult.responseTime }} ms
|
||||||
|
</div>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="mb-[8px] flex items-center gap-[8px] text-[14px]">
|
<div class="mb-[8px] flex items-center gap-[8px] text-[14px]">
|
||||||
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.responseTime') }}</div>
|
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.responseTime') }}</div>
|
||||||
<div class="text-[rgb(var(--success-7))]">{{ props.response.timing }} ms</div>
|
<div class="text-[rgb(var(--success-7))]">
|
||||||
|
{{ props.response.requestResults[0].responseResult.responseTime }} ms
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<responseTimeLine :response-timing="$props.response.timingInfo" />
|
<responseTimeLine :response-timing="timingInfo" />
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover position="left" content-class="response-popover-content">
|
<a-popover position="left" content-class="response-popover-content">
|
||||||
<div class="one-line-text text-[rgb(var(--success-7))]">{{ props.response.size }} bytes</div>
|
<div class="one-line-text text-[rgb(var(--success-7))]">
|
||||||
|
{{ props.response.requestResults[0].responseResult.responseSize }} bytes
|
||||||
|
</div>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="flex items-center gap-[8px] text-[14px]">
|
<div class="flex items-center gap-[8px] text-[14px]">
|
||||||
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.responseSize') }}</div>
|
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.responseSize') }}</div>
|
||||||
<div class="one-line-text text-[rgb(var(--success-7))]">{{ props.response.size }} bytes</div>
|
<div class="one-line-text text-[rgb(var(--success-7))]">
|
||||||
|
{{ props.response.requestResults[0].responseResult.responseSize }} bytes
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover position="left" content-class="response-popover-content">
|
<!-- <a-popover position="left" content-class="response-popover-content">
|
||||||
<div class="text-[var(--color-text-1)]">{{ props.response.env }}</div>
|
<div class="text-[var(--color-text-1)]">{{ props.response.env }}</div>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="flex items-center gap-[8px] text-[14px]">
|
<div class="flex items-center gap-[8px] text-[14px]">
|
||||||
|
@ -78,7 +93,7 @@
|
||||||
<div class="text-[var(--color-text-1)]">{{ props.response.resource }}</div>
|
<div class="text-[var(--color-text-1)]">{{ props.response.resource }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-[calc(100%-42px)] px-[16px] pb-[16px]">
|
<div class="h-[calc(100%-42px)] px-[16px] pb-[16px]">
|
||||||
|
@ -88,8 +103,9 @@
|
||||||
<div class="response-container">
|
<div class="response-container">
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
v-if="activeTab === ResponseComposition.BODY"
|
v-if="activeTab === ResponseComposition.BODY"
|
||||||
:model-value="props.response.body"
|
ref="responseEditorRef"
|
||||||
language="json"
|
:model-value="props.response.requestResults[0]?.responseResult?.body || ''"
|
||||||
|
:language="responseLanguage"
|
||||||
theme="vs"
|
theme="vs"
|
||||||
height="100%"
|
height="100%"
|
||||||
:languages="['json', 'html', 'xml', 'plaintext']"
|
:languages="['json', 'html', 'xml', 'plaintext']"
|
||||||
|
@ -143,21 +159,28 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { ResponseTiming } from '@/models/apiTest/debug';
|
|
||||||
import { ResponseComposition } from '@/enums/apiEnum';
|
import { ResponseComposition } from '@/enums/apiEnum';
|
||||||
|
|
||||||
export interface Response {
|
export interface Response {
|
||||||
status: number;
|
requestResults: {
|
||||||
timing: number;
|
body: string;
|
||||||
size: number;
|
responseResult: {
|
||||||
env: string;
|
body: string;
|
||||||
resource: string;
|
contentType: string;
|
||||||
body: string;
|
headers: string;
|
||||||
header: string;
|
dnsLookupTime: number;
|
||||||
content: string;
|
downloadTime: number;
|
||||||
|
latency: number;
|
||||||
|
responseCode: number;
|
||||||
|
responseTime: number;
|
||||||
|
responseSize: number;
|
||||||
|
socketInitTime: number;
|
||||||
|
sslHandshakeTime: number;
|
||||||
|
tcpHandshakeTime: number;
|
||||||
|
transferStartTime: number;
|
||||||
|
};
|
||||||
|
}[]; // 请求结果
|
||||||
console: string;
|
console: string;
|
||||||
extract: Record<string, any>;
|
|
||||||
timingInfo: ResponseTiming;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -177,6 +200,66 @@
|
||||||
|
|
||||||
const innerLayout = useVModel(props, 'activeLayout', emit);
|
const innerLayout = useVModel(props, 'activeLayout', emit);
|
||||||
const activeTab = useVModel(props, 'activeTab', emit);
|
const activeTab = useVModel(props, 'activeTab', emit);
|
||||||
|
// 响应时间信息
|
||||||
|
const timingInfo = computed(() => {
|
||||||
|
const {
|
||||||
|
dnsLookupTime,
|
||||||
|
downloadTime,
|
||||||
|
latency,
|
||||||
|
responseTime,
|
||||||
|
socketInitTime,
|
||||||
|
sslHandshakeTime,
|
||||||
|
tcpHandshakeTime,
|
||||||
|
transferStartTime,
|
||||||
|
} = props.response.requestResults[0].responseResult;
|
||||||
|
return {
|
||||||
|
dnsLookupTime,
|
||||||
|
tcpHandshakeTime,
|
||||||
|
sslHandshakeTime,
|
||||||
|
socketInitTime,
|
||||||
|
latency,
|
||||||
|
downloadTime,
|
||||||
|
transferStartTime,
|
||||||
|
responseTime,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// 响应状态码对应颜色
|
||||||
|
const statusCodeColor = computed(() => {
|
||||||
|
const code = props.response.requestResults[0].responseResult.responseCode;
|
||||||
|
if (code >= 200 && code < 300) {
|
||||||
|
return 'rgb(var(--success-7)';
|
||||||
|
}
|
||||||
|
if (code >= 300 && code < 400) {
|
||||||
|
return 'rgb(var(--warning-7)';
|
||||||
|
}
|
||||||
|
return 'rgb(var(--danger-7)';
|
||||||
|
});
|
||||||
|
// 响应体语言类型
|
||||||
|
const responseLanguage = computed(() => {
|
||||||
|
const { contentType } = props.response.requestResults[0].responseResult;
|
||||||
|
if (contentType.includes('json')) {
|
||||||
|
return 'json';
|
||||||
|
}
|
||||||
|
if (contentType.includes('html')) {
|
||||||
|
return 'html';
|
||||||
|
}
|
||||||
|
if (contentType.includes('xml')) {
|
||||||
|
return 'xml';
|
||||||
|
}
|
||||||
|
return 'plaintext';
|
||||||
|
});
|
||||||
|
const responseEditorRef = ref<InstanceType<typeof MsCodeEditor>>();
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.response,
|
||||||
|
(obj) => {
|
||||||
|
if (obj.requestResults[0].responseResult.body.trim() !== '') {
|
||||||
|
nextTick(() => {
|
||||||
|
responseEditorRef.value?.format();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const responseTabList = [
|
const responseTabList = [
|
||||||
{
|
{
|
||||||
|
@ -195,21 +278,21 @@
|
||||||
label: t('apiTestDebug.console'),
|
label: t('apiTestDebug.console'),
|
||||||
value: ResponseComposition.CONSOLE,
|
value: ResponseComposition.CONSOLE,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
label: t('apiTestDebug.extract'),
|
// label: t('apiTestDebug.extract'),
|
||||||
value: ResponseComposition.EXTRACT,
|
// value: ResponseComposition.EXTRACT,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
label: t('apiTestDebug.assertion'),
|
// label: t('apiTestDebug.assertion'),
|
||||||
value: ResponseComposition.ASSERTION,
|
// value: ResponseComposition.ASSERTION,
|
||||||
},
|
// },
|
||||||
];
|
];
|
||||||
|
|
||||||
const { copy, isSupported } = useClipboard();
|
const { copy, isSupported } = useClipboard();
|
||||||
|
|
||||||
function copyScript() {
|
function copyScript() {
|
||||||
if (isSupported) {
|
if (isSupported) {
|
||||||
copy(props.response.body);
|
copy(props.response.requestResults[0].responseResult.body);
|
||||||
Message.success(t('common.copySuccess'));
|
Message.success(t('common.copySuccess'));
|
||||||
} else {
|
} else {
|
||||||
Message.warning(t('apiTestDebug.copyNotSupport'));
|
Message.warning(t('apiTestDebug.copyNotSupport'));
|
||||||
|
@ -219,15 +302,15 @@
|
||||||
function getResponsePreContent(type: keyof typeof ResponseComposition) {
|
function getResponsePreContent(type: keyof typeof ResponseComposition) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ResponseComposition.HEADER:
|
case ResponseComposition.HEADER:
|
||||||
return props.response.header.trim();
|
return props.response.requestResults[0].responseResult.headers.trim();
|
||||||
case ResponseComposition.REAL_REQUEST:
|
case ResponseComposition.REAL_REQUEST:
|
||||||
return props.response.content.trim();
|
return props.response.requestResults[0].body.trim();
|
||||||
case ResponseComposition.CONSOLE:
|
case ResponseComposition.CONSOLE:
|
||||||
return props.response.console.trim();
|
return props.response.console.trim();
|
||||||
case ResponseComposition.EXTRACT:
|
// case ResponseComposition.EXTRACT:
|
||||||
return Object.keys(props.response.extract)
|
// return Object.keys(props.response.extract)
|
||||||
.map((e) => `${e}: ${props.response.extract[e]}`)
|
// .map((e) => `${e}: ${props.response.extract[e]}`)
|
||||||
.join('\n');
|
// .join('\n');
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,11 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
import { ExecuteRequestCommonParam } from '@/models/apiTest/debug';
|
||||||
|
import { RequestParamsType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
params: any[];
|
params: ExecuteRequestCommonParam[];
|
||||||
layout: 'horizontal' | 'vertical';
|
layout: 'horizontal' | 'vertical';
|
||||||
secondBoxHeight: number;
|
secondBoxHeight: number;
|
||||||
}>();
|
}>();
|
||||||
|
@ -53,24 +56,12 @@
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
slotName: 'type',
|
slotName: 'type',
|
||||||
hasRequired: true,
|
hasRequired: true,
|
||||||
typeOptions: [
|
typeOptions: Object.keys(RequestParamsType)
|
||||||
{
|
.filter((key) => ![RequestParamsType.JSON, RequestParamsType.FILE].includes(key as RequestParamsType))
|
||||||
label: 'string',
|
.map((key) => ({
|
||||||
value: 'string',
|
label: RequestParamsType[key],
|
||||||
},
|
value: key,
|
||||||
{
|
})),
|
||||||
label: 'integer',
|
|
||||||
value: 'integer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'number',
|
|
||||||
value: 'number',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'array',
|
|
||||||
value: 'array',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
width: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -100,8 +91,7 @@
|
||||||
title: '',
|
title: '',
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
format: 'query',
|
width: 50,
|
||||||
width: 80,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('apiTestDebug.redirect')">
|
<a-form-item :label="t('apiTestDebug.redirect')">
|
||||||
<a-radio-group v-model:model-value="settingForm.redirect">
|
<a-radio-group v-model:model-value="settingForm.autoRedirects">
|
||||||
<a-radio value="follow">{{ t('apiTestDebug.follow') }}</a-radio>
|
<a-radio value="follow">{{ t('apiTestDebug.follow') }}</a-radio>
|
||||||
<a-radio value="auto">{{ t('apiTestDebug.auto') }}</a-radio>
|
<a-radio value="auto">{{ t('apiTestDebug.auto') }}</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
|
@ -58,18 +58,14 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
interface SettingForm {
|
import { ExecuteOtherConfig } from '@/models/apiTest/debug';
|
||||||
connectTimeout: number;
|
|
||||||
responseTimeout: number;
|
|
||||||
certificateAlias: string;
|
|
||||||
redirect: 'follow' | 'auto';
|
|
||||||
}
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
params: SettingForm;
|
params: ExecuteOtherConfig;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:params', val: SettingForm): void;
|
(e: 'update:params', val: ExecuteOtherConfig): void;
|
||||||
(e: 'change', val: SettingForm): void;
|
(e: 'change', val: ExecuteOtherConfig): void;
|
||||||
}>();
|
}>();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
|
|
@ -108,21 +108,24 @@
|
||||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
import popConfirm from '@/views/api-test/components/popConfirm.vue';
|
import popConfirm from '@/views/api-test/components/popConfirm.vue';
|
||||||
|
|
||||||
import { deleteReviewModule, getReviewModules, moveReviewModule } from '@/api/modules/case-management/caseReview';
|
import {
|
||||||
|
deleteDebugModule,
|
||||||
|
getDebugModuleCount,
|
||||||
|
getDebugModules,
|
||||||
|
moveDebugModule,
|
||||||
|
updateDebugModule,
|
||||||
|
} from '@/api/modules/api-test/debug';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useModal from '@/hooks/useModal';
|
import useModal from '@/hooks/useModal';
|
||||||
import useAppStore from '@/store/modules/app';
|
|
||||||
import { mapTree } from '@/utils';
|
import { mapTree } from '@/utils';
|
||||||
|
|
||||||
import { ModuleTreeNode } from '@/models/common';
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
|
||||||
isExpandAll?: boolean; // 是否展开所有节点
|
isExpandAll?: boolean; // 是否展开所有节点
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['init', 'change', 'newApi', 'import']);
|
const emit = defineEmits(['init', 'change', 'newApi', 'import']);
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
|
||||||
|
@ -147,7 +150,6 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeFolder = ref<string>('all');
|
const activeFolder = ref<string>('all');
|
||||||
const allFileCount = ref(0);
|
|
||||||
const isExpandAll = ref(props.isExpandAll);
|
const isExpandAll = ref(props.isExpandAll);
|
||||||
const rootModulesName = ref<string[]>([]); // 根模块名称列表
|
const rootModulesName = ref<string[]>([]); // 根模块名称列表
|
||||||
|
|
||||||
|
@ -199,7 +201,7 @@
|
||||||
async function initModules() {
|
async function initModules() {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await getReviewModules(appStore.currentProjectId);
|
const res = await getDebugModules();
|
||||||
folderTree.value = mapTree<ModuleTreeNode>(res, (e) => {
|
folderTree.value = mapTree<ModuleTreeNode>(res, (e) => {
|
||||||
return {
|
return {
|
||||||
...e,
|
...e,
|
||||||
|
@ -217,6 +219,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const modulesCount = ref<Record<string, number>>({});
|
||||||
|
const allFileCount = computed(() => modulesCount.value.all || 0);
|
||||||
|
async function initModuleCount() {
|
||||||
|
try {
|
||||||
|
const res = await getDebugModuleCount({
|
||||||
|
keyword: moduleKeyword.value,
|
||||||
|
});
|
||||||
|
modulesCount.value = res;
|
||||||
|
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
count: res[node.id] || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除文件夹
|
* 删除文件夹
|
||||||
* @param node 节点信息
|
* @param node 节点信息
|
||||||
|
@ -233,7 +255,7 @@
|
||||||
maskClosable: false,
|
maskClosable: false,
|
||||||
onBeforeOk: async () => {
|
onBeforeOk: async () => {
|
||||||
try {
|
try {
|
||||||
await deleteReviewModule(node.id);
|
await deleteDebugModule(node.id);
|
||||||
Message.success(t('apiTestDebug.deleteSuccess'));
|
Message.success(t('apiTestDebug.deleteSuccess'));
|
||||||
initModules();
|
initModules();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -288,7 +310,7 @@
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await moveReviewModule({
|
await moveDebugModule({
|
||||||
dragNodeId: dragNode.id as string,
|
dragNodeId: dragNode.id as string,
|
||||||
dropNodeId: dropNode.id || '',
|
dropNodeId: dropNode.id || '',
|
||||||
dropPosition,
|
dropPosition,
|
||||||
|
@ -300,6 +322,7 @@
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
initModules();
|
initModules();
|
||||||
|
initModuleCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,23 +335,9 @@
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
initModules();
|
initModules();
|
||||||
|
initModuleCount();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化模块文件数量
|
|
||||||
*/
|
|
||||||
watch(
|
|
||||||
() => props.modulesCount,
|
|
||||||
(obj) => {
|
|
||||||
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
count: obj?.[node.id] || 0,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
initModules,
|
initModules,
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,7 @@ export default {
|
||||||
'apiTestDebug.serverExec': 'Server execution',
|
'apiTestDebug.serverExec': 'Server execution',
|
||||||
'apiTestDebug.localExec': 'Local execution',
|
'apiTestDebug.localExec': 'Local execution',
|
||||||
'apiTestDebug.noMatchModule': 'No matching module data yet',
|
'apiTestDebug.noMatchModule': 'No matching module data yet',
|
||||||
|
'apiTestDebug.pluginData': 'Request data',
|
||||||
'apiTestDebug.header': 'Header',
|
'apiTestDebug.header': 'Header',
|
||||||
'apiTestDebug.body': 'Body',
|
'apiTestDebug.body': 'Body',
|
||||||
'apiTestDebug.prefix': 'Precondition',
|
'apiTestDebug.prefix': 'Precondition',
|
||||||
|
|
|
@ -5,6 +5,7 @@ export default {
|
||||||
'apiTestDebug.serverExec': '服务端执行',
|
'apiTestDebug.serverExec': '服务端执行',
|
||||||
'apiTestDebug.localExec': '本地执行',
|
'apiTestDebug.localExec': '本地执行',
|
||||||
'apiTestDebug.noMatchModule': '暂无匹配的模块数据',
|
'apiTestDebug.noMatchModule': '暂无匹配的模块数据',
|
||||||
|
'apiTestDebug.pluginData': '请求数据',
|
||||||
'apiTestDebug.header': '请求头',
|
'apiTestDebug.header': '请求头',
|
||||||
'apiTestDebug.body': '请求体',
|
'apiTestDebug.body': '请求体',
|
||||||
'apiTestDebug.prefix': '前置',
|
'apiTestDebug.prefix': '前置',
|
||||||
|
|
|
@ -154,11 +154,11 @@
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
|
||||||
const moduleProtocol = ref('http');
|
const moduleProtocol = ref('HTTP');
|
||||||
const moduleProtocolOptions = ref([
|
const moduleProtocolOptions = ref([
|
||||||
{
|
{
|
||||||
label: 'HTTP',
|
label: 'HTTP',
|
||||||
value: 'http',
|
value: 'HTTP',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
|
|
||||||
const store = useProjectEnvStore();
|
const store = useProjectEnvStore();
|
||||||
|
|
||||||
const params = computed({
|
// TODO: 参数类型
|
||||||
|
const params = computed<any>({
|
||||||
set: (value: any) => {
|
set: (value: any) => {
|
||||||
store.currentEnvDetailInfo.config.postScript = value;
|
store.currentEnvDetailInfo.config.postScript = value;
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
|
|
||||||
const store = useProjectEnvStore();
|
const store = useProjectEnvStore();
|
||||||
|
|
||||||
const params = computed({
|
// TODO:参数类型
|
||||||
|
const params = computed<any>({
|
||||||
set: (value: any) => {
|
set: (value: any) => {
|
||||||
store.currentEnvDetailInfo.config.preScript = value;
|
store.currentEnvDetailInfo.config.preScript = value;
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,17 +28,25 @@
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": ["esnext", "dom"],
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
"skipLibCheck": true, // 跳过node依赖包语法检查
|
"skipLibCheck": true, // 跳过node依赖包语法检查
|
||||||
"types": [
|
"types": [
|
||||||
|
"node",
|
||||||
// "vitest/globals",
|
// "vitest/globals",
|
||||||
// "vite-plugin-svg-icons/client"
|
// "vite-plugin-svg-icons/client"
|
||||||
], // 手动导入TS类型声明文件
|
], // 手动导入TS类型声明文件
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
// 路径映射
|
// 路径映射
|
||||||
"@/*": ["./src/*"],
|
"@/*": [
|
||||||
"#/*": ["types/*"]
|
"./src/*"
|
||||||
|
],
|
||||||
|
"#/*": [
|
||||||
|
"types/*"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue