feat(接口测试): 接口调试-模块树+ header 参数调整

This commit is contained in:
baiqi 2024-02-08 18:01:21 +08:00 committed by Craftsman
parent 82f8a60794
commit a294a486a3
32 changed files with 392 additions and 247 deletions

View File

@ -4,6 +4,7 @@ import {
AddDebugModuleUrl,
DeleteDebugModuleUrl,
ExecuteApiDebugUrl,
GetApiDebugDetailUrl,
GetDebugModuleCountUrl,
GetDebugModulesUrl,
MoveDebugModuleUrl,
@ -13,6 +14,7 @@ import {
import {
AddDebugModuleParams,
DebugDetail,
ExecuteRequestParams,
SaveDebugParams,
UpdateDebugModule,
@ -64,3 +66,8 @@ export function addDebug(data: SaveDebugParams) {
export function updateDebug(data: UpdateDebugParams) {
return MSR.post({ url: UpdateApiDebugUrl, data });
}
// 获取接口调试详情
export function getDebugDetail(id: string) {
return MSR.get<DebugDetail>({ url: GetApiDebugDetailUrl, params: id });
}

View File

@ -1,6 +1,7 @@
export const ExecuteApiDebugUrl = '/api/debug/debug'; // 执行调试
export const AddApiDebugUrl = '/api/debug/add'; // 新增调试
export const UpdateApiDebugUrl = '/api/debug/update'; // 更新调试
export const GetApiDebugDetailUrl = '/api/debug/get'; // 获取接口调试详情
export const UpdateDebugModuleUrl = '/api/debug/module/update'; // 更新模块
export const MoveDebugModuleUrl = '/api/debug/module/move'; // 移动模块
export const GetDebugModuleCountUrl = '/api/debug/module/count'; // 模块统计数量

View File

@ -146,8 +146,8 @@
},
{
title: 'project.commonScript.description',
slotName: 'desc',
dataIndex: 'desc',
slotName: 'description',
dataIndex: 'description',
},
{
title: 'project.commonScript.isRequired',

View File

@ -166,13 +166,6 @@ export const mockStringGroup: MockParamItem[] = [
],
},
];
// 字符串变量特殊处理
export const specialStringVars = [
'@character(pool)',
"@character('lower')",
"@character('upper')",
"@character('symbol')",
];
// mock日期分组
export const mockDateGroup: MockParamItem[] = [
{
@ -901,3 +894,15 @@ export const JMeterAllVars = [
...JMeterInfoGroup,
...JMeterStringGroup,
];
// 同名函数但参数不同,需要特殊处理
export const sameFuncNameVars = [
'@character(pool)',
"@character('lower')",
"@character('upper')",
"@character('symbol')",
'@idCard(birth)',
'@natural(1,100)',
'@integer(1,100)',
];
// 带形参的函数集合,指的是函数入参为形参,如果用户未填写实参则不需要填充到入参框中
export const formalParameterVars = ['@character(pool)', '@idCard(birth)'];

View File

@ -238,12 +238,13 @@
import { useI18n } from '@/hooks/useI18n';
import {
formalParameterVars,
JMeterAllGroup,
JMeterAllVars,
mockAllGroup,
mockAllParams,
mockFunctions,
specialStringVars,
sameFuncNameVars,
} from './config';
import type { MockParamInputGroupItem, MockParamItem } from './types';
import type { AutoComplete, CascaderOption, FormInstance } from '@arco-design/web-vue';
@ -375,7 +376,8 @@
// @
const regex = /@([a-zA-Z]+)(\([^)]*\))?/;
const currentParamType = mockAllParams.find((e) => {
if (specialStringVars.includes(val)) {
if (sameFuncNameVars.includes(val)) {
//
return e.value === val;
}
if (e.value.match(regex)?.[1] === val.match(regex)?.[1]) {
@ -428,6 +430,7 @@
*/
function openParamSetting() {
if (/^\$/.test(innerValue.value)) {
// JMeter
paramSettingType.value = 'jmeter';
if (JMeterAllVars.findIndex((e) => e.value === innerValue.value) !== -1) {
paramForm.value.JMeterType = innerValue.value;
@ -435,6 +438,7 @@
paramForm.value.JMeterType = '';
}
} else if (/^@/.test(innerValue.value)) {
// Mock
const valueArr = innerValue.value.split('|'); // mock
if (valueArr[0]) {
// @
@ -444,16 +448,23 @@
if (variableMatch) {
const variableName = variableMatch[1];
const variableParams = variableMatch[2]?.split(',').map((param) => param.trim());
const formalParameterVar = formalParameterVars.find((e) => e.includes(variableName)); //
if (variableName === 'character') {
handleParamTypeChange(`@${variableName}(${variableParams})`); // character
if (formalParameterVar && variableParams.length > 0) {
// 使 ()
handleParamTypeChange(formalParameterVar);
} else if (sameFuncNameVars.includes(valueArr[0])) {
//
handleParamTypeChange(valueArr[0]);
} else {
handleParamTypeChange(`@${variableName}`); //
}
if (variableName !== 'character' || (variableName === 'character' && variableParams?.[0] !== 'pool')) {
// @character(pool) pool
if (!formalParameterVars.includes(valueArr[0])) {
//
//
//
(variableParams || []).forEach((e, i) => {
//
//
paramForm.value[`param${i + 1}`] = Number.isNaN(Number(e)) ? e : Number(e);
});
}

View File

@ -470,9 +470,12 @@
}
}
}
}
.arco-tree-node-disabled-selectable {
@apply !cursor-default;
.arco-tree-node-disabled-selectable {
@apply cursor-default;
.arco-tree-node-title {
@apply cursor-default;
}
}
}
}
</style>

View File

@ -16,7 +16,7 @@
<div
:class="`ms-split-box ${props.direction === 'horizontal' ? 'ms-split-box--left' : 'ms-split-box--top'} ${
props.disabled && props.direction === 'horizontal' ? 'border-r border-[var(--color-text-n8)]' : ''
} ${props.firstContainerClass}`"
} ${props.firstContainerClass || ''}`"
>
<div
v-if="props.direction === 'horizontal' && props.expandDirection === 'right' && !props.disabled"
@ -171,6 +171,11 @@
.ms-split-box {
@apply relative h-full overflow-auto;
.ms-scroll-bar();
:deep(.arco-split-vertical) {
.arco-split-pane-first {
padding-bottom: 6px; // 6px
}
}
}
.ms-split-box--left {
width: calc(v-bind(innerSize) - 2px);

View File

@ -69,9 +69,9 @@
:title="item.slotName"
>
<template #title>
<div :class="{ 'flex w-full flex-row flex-nowrap items-center': !item.align }">
<div :class="{ 'flex w-full flex-row flex-nowrap items-center gap-[16px]': !item.align }">
<slot :name="item.titleSlotName" :column-config="item">
<div class="text-[var(--color-text-3)]">{{ t(item.title as string) }}</div>
<div v-if="item.title" class="text-[var(--color-text-3)]">{{ t(item.title as string) }}</div>
</slot>
<columnSelectorIcon
v-if="

View File

@ -109,7 +109,6 @@
<style lang="scss" scoped>
.setting-icon {
margin-left: 16px;
color: var(--color-text-4);
background-color: var(--color-text-10);
cursor: pointer;

View File

@ -8,6 +8,8 @@ export enum TableModuleEnum {
export enum TableKeyEnum {
API_TEST = 'apiTest',
API_TEST_DEBUG_FORM_DATA = 'apiTestDebugFormData',
API_TEST_DEBUG_FORM_URL_ENCODE = 'apiTestDebugFormUrlEncoded',
SYSTEM_USER = 'systemUser',
SYSTEM_RESOURCEPOOL = 'systemResourcePool',
SYSTEM_AUTH = 'systemAuth',

View File

@ -20,11 +20,11 @@ import {
} from '@/enums/apiEnum';
// 条件操作类型
export type ConditionType = keyof typeof RequestConditionProcessor;
export type ConditionType = RequestConditionProcessor;
// 断言-匹配条件规则
export type RequestAssertionConditionType = keyof typeof RequestAssertionCondition;
export type RequestAssertionConditionType = RequestAssertionCondition;
// 前后置条件-脚本语言类型
export type RequestConditionScriptLanguageType = keyof typeof RequestConditionScriptLanguage;
export type RequestConditionScriptLanguageType = RequestConditionScriptLanguage;
// 响应时间信息
export interface ResponseTiming {
dnsLookupTime: number;
@ -41,12 +41,17 @@ export interface KeyValueParam {
key: string;
value: string;
}
// 接口请求-带开启关闭的参数集合信息
export interface EnableKeyValueParam extends KeyValueParam {
description: string;
enable: boolean; // 参数是否启用
}
// 接口请求公共参数集合信息
export interface ExecuteRequestCommonParam {
export interface ExecuteRequestCommonParam extends EnableKeyValueParam {
encode: boolean; // 是否编码
maxLength: number;
minLength: number;
paramType: keyof typeof RequestParamsType; // 参数类型
paramType: RequestParamsType; // 参数类型
required: boolean;
description: string;
enable: boolean; // 参数是否启用
@ -57,7 +62,7 @@ export type ExecuteRequestFormBodyFormValue = ExecuteRequestCommonParam & {
fileId: string;
fileName: string;
}[];
contentType?: keyof typeof RequestContentTypeEnum & string;
contentType?: RequestContentTypeEnum & string;
};
export interface ExecuteRequestFormBody {
formValues: ExecuteRequestFormBodyFormValue[];
@ -77,11 +82,6 @@ export interface ExecuteJsonBody {
jsonSchema?: string;
jsonValue: string;
}
// 接口请求-带开启关闭的参数集合信息
export interface EnableKeyValueParam extends KeyValueParam {
description: string;
enable: boolean; // 参数是否启用
}
// 执行请求配置
export interface ExecuteOtherConfig {
autoRedirects: boolean; // 是否自动重定向 默认 false
@ -94,12 +94,12 @@ export interface ExecuteOtherConfig {
export interface ResponseAssertionCommon {
name: string; // 断言名称
enable: boolean; // 是否启用断言
assertionType: keyof typeof ResponseAssertionType; // 断言类型
assertionType: ResponseAssertionType; // 断言类型
}
// 断言-断言列表泛型
export interface ResponseAssertionGenerics<T> {
assertions: T[];
responseFormat?: keyof typeof ResponseBodyXPathAssertionFormat;
responseFormat?: ResponseBodyXPathAssertionFormat;
}
// 断言-响应头断言子项
export interface ResponseHeaderAssertionItem {
@ -119,13 +119,13 @@ export interface ResponseDocumentAssertionElement {
expectedResult: Record<string, any>; // 匹配值 即预期结果
include: boolean; // 是否必含
paramName: string; // 参数名
type: keyof typeof ResponseBodyDocumentAssertionType; // 断言类型
type: ResponseBodyDocumentAssertionType; // 断言类型
typeVerification: boolean; // 是否类型验证
}
// 断言-文档断言
export interface ResponseDocumentAssertion {
enable: boolean; // 是否启用
documentType: keyof typeof ResponseBodyAssertionDocumentType; // 文档类型
documentType: ResponseBodyAssertionDocumentType; // 文档类型
followApiId: string; // 跟随定义的apiId 传空为不跟随接口定义
jsonAssertion: ResponseDocumentAssertionElement;
xmlAssertion: ResponseDocumentAssertionElement;
@ -153,7 +153,7 @@ export interface ScriptCommonConfig {
}
// 断言-响应体断言
export interface ResponseBodyAssertion {
assertionBodyType: keyof typeof ResponseBodyAssertionType; // 断言类型
assertionBodyType: ResponseBodyAssertionType; // 断言类型
documentAssertion: ResponseDocumentAssertion; // 文档断言
jsonPathAssertion: ResponseAssertionGenerics<ResponseJSONPathAssertionItem>; // JSONPath断言
regexAssertion: ResponseAssertionGenerics<ResponseRegexAssertionItem>; // 正则断言
@ -171,7 +171,7 @@ export interface ResponseVariableAssertion {
export interface ExecuteConditionProcessorCommon {
enable: boolean; // 是否启用
name: string; // 请求名称
processorType: keyof typeof RequestConditionProcessor;
processorType: RequestConditionProcessor;
}
// 执行请求-前后置条件-脚本处理器
export type ScriptProcessor = ScriptCommonConfig;
@ -191,27 +191,27 @@ export interface TimeWaitingProcessor {
delay: number; // 等待时间 单位:毫秒
}
// 表达式类型
export type ExpressionType = keyof typeof RequestExtractExpressionEnum;
export type ExpressionType = RequestExtractExpressionEnum;
// 表达式配置
export interface ExpressionCommonConfig {
enable: boolean; // 是否启用
expression: string;
extractType: ExpressionType; // 表达式类型
variableName: string;
variableType: keyof typeof RequestExtractEnvType;
resultMatchingRule: keyof typeof RequestExtractResultMatchingRule; // 结果匹配规则
variableType: RequestExtractEnvType;
resultMatchingRule: RequestExtractResultMatchingRule; // 结果匹配规则
resultMatchingRuleNum: number; // 匹配第几条结果
}
// 正则提取配置
export interface RegexExtract extends ExpressionCommonConfig {
expressionMatchingRule: keyof typeof RequestExtractExpressionRuleType; // 正则表达式匹配规则
extractScope: keyof typeof RequestExtractScope; // 正则提取范围
expressionMatchingRule: RequestExtractExpressionRuleType; // 正则表达式匹配规则
extractScope: RequestExtractScope; // 正则提取范围
}
// JSONPath提取配置
export type JSONPathExtract = ExpressionCommonConfig;
// XPath提取配置
export interface XPathExtract extends ExpressionCommonConfig {
responseFormat: keyof typeof ResponseBodyXPathAssertionFormat; // 响应格式
responseFormat: ResponseBodyXPathAssertionFormat; // 响应格式
}
// 执行请求-前后置条件-参数提取处理器
export interface ExtractProcessor {
@ -246,7 +246,7 @@ export interface ExecuteCommonChild {
}
// 执行请求-认证配置
export interface ExecuteAuthConfig {
authType: keyof typeof RequestAuthType;
authType: RequestAuthType;
password: string;
username: string;
}
@ -256,7 +256,7 @@ export interface ExecuteValueBody {
}
// 执行请求- body 配置
export interface ExecuteBody {
bodyType: keyof typeof RequestBodyFormat;
bodyType: RequestBodyFormat;
binaryBody: ExecuteBinaryBody;
formDataBody: ExecuteRequestFormBody;
jsonBody: ExecuteJsonBody;
@ -269,7 +269,7 @@ export interface ExecuteHTTPRequestFullParams {
authConfig: ExecuteAuthConfig;
body: ExecuteBody;
headers: EnableKeyValueParam[];
method: keyof typeof RequestMethods;
method: RequestMethods;
otherConfig: ExecuteOtherConfig;
path: string;
query: ExecuteRequestCommonParam[];
@ -297,7 +297,7 @@ export interface ExecuteRequestParams {
export interface SaveDebugParams {
name: string;
protocol: string;
method: keyof typeof RequestMethods;
method: RequestMethods;
path: string;
projectId: string;
moduleId: string;
@ -322,3 +322,31 @@ export interface AddDebugModuleParams {
name: string;
parentId: string;
}
// 接口调试详情-请求参数
export interface DebugDetailRequest {
stepId: string;
resourceId: string;
projectId: string;
name: string;
enable: boolean;
children: string[];
parent: string;
polymorphicName: string;
}
// 接口调试详情
export interface DebugDetail {
id: string;
name: string;
protocol: string;
method: string;
path: string;
projectId: string;
moduleId: string;
createTime: number;
createUser: string;
updateTime: number;
updateUser: string;
pos: number;
request: DebugDetailRequest & (ExecuteHTTPRequestFullParams | ExecutePluginRequestParams);
response: string;
}

View File

@ -1,3 +1,5 @@
import { RequestMethods } from '@/enums/apiEnum';
// 请求返回结构
export default interface CommonResponse<T> {
code: number;
@ -53,9 +55,12 @@ export interface MoveModules {
export interface ModuleTreeNode {
id: string;
name: string;
type: string;
type: 'MODULE' | 'API';
children: ModuleTreeNode[];
attachInfo: Record<string, any>; // 附加信息
attachInfo: {
method?: keyof typeof RequestMethods;
protocol: string;
}; // 附加信息
count: 0;
parentId: string;
path: string;

View File

@ -1,7 +1,6 @@
import { findKey } from 'lodash-es';
import JSEncrypt from 'jsencrypt';
import { BatchActionQueryParams, MsTableColumn, MsTableColumnData } from '@/components/pure/ms-table/type';
import { BatchActionQueryParams, MsTableColumnData } from '@/components/pure/ms-table/type';
import { CustomFieldItem } from '@/models/bug-management';
@ -216,6 +215,38 @@ export function mapTree<T>(
.filter(Boolean);
}
/**
*
* @param tree
* @param customNodeFn
* @param customChildrenKey key
* @returns
*/
export function filterTree<T>(
tree: TreeNode<T> | TreeNode<T>[] | T | T[],
filterFn: (node: TreeNode<T>) => boolean,
customChildrenKey = 'children'
): TreeNode<T>[] {
if (!Array.isArray(tree)) {
tree = [tree];
}
const filteredTree: TreeNode<T>[] = [];
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
// 如果节点满足过滤条件,则保留该节点,并递归过滤子节点
if (filterFn(node)) {
const newNode: TreeNode<T> = { ...node };
if (node[customChildrenKey] && node[customChildrenKey].length > 0) {
// 递归过滤子节点,并将过滤后的子节点添加到当前节点中
newNode[customChildrenKey] = filterTree(node[customChildrenKey], filterFn, customChildrenKey);
} else {
newNode[customChildrenKey] = [];
}
filteredTree.push(newNode);
}
}
return filteredTree;
}
/**
* key
* @param trees

View File

@ -47,7 +47,7 @@
const methodColor = computed(() => {
const colorMap = colorMaps.find((item) => item.includes.includes(props.method));
return colorMap?.color;
return colorMap?.color || 'rgb(var(--link-7))'; // key
});
</script>

View File

@ -42,15 +42,16 @@
</template>
<script setup lang="ts">
import { isEmpty } from 'lodash-es';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import { useI18n } from '@/hooks/useI18n';
import { RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
const props = defineProps<{
params: Record<string, any>[];
defaultParamItem?: Record<string, any>; //
}>();
const emit = defineEmits<{
(e: 'apply', resultArr: (Record<string, any> | null)[]): void;
@ -66,8 +67,8 @@
(val) => {
if (val) {
batchParamsCode.value = props.params
.filter((e) => e && (e.name !== '' || e.value !== ''))
.map((item) => `${item.name}:${item.value}`)
.filter((e) => e && (!isEmpty(e.key) || !isEmpty(e.value)))
.map((item) => `${item.key}:${item.value}`)
.join('\n');
}
},
@ -83,19 +84,13 @@
const arr = batchParamsCode.value.replaceAll('\r', '\n').split('\n'); //
const tempObj: Record<string, any> = {}; //
for (let i = 0; i < arr.length; i++) {
const [name, value] = arr[i].split(':');
if (name) {
tempObj[name.trim()] = {
const [key, value] = arr[i].split(':');
if (key) {
tempObj[key.trim()] = {
id: new Date().getTime() + i,
name: name.trim(),
...props.defaultParamItem,
key: key.trim(),
value: value?.trim(),
required: false,
type: RequestParamsType.STRING,
min: undefined,
max: undefined,
contentType: RequestContentTypeEnum.TEXT,
desc: '',
encode: false,
};
}
}

View File

@ -222,7 +222,7 @@
:selectable="false"
:scroll="{ x: '700px' }"
:response="props.response"
:height-used="(props.heightUsed || 0) + 62"
:height-used="(props.heightUsed || 0) + 68"
@change="handleExtractParamTableChange"
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
>

View File

@ -1,5 +1,5 @@
<template>
<MsBaseTable v-bind="propsRes" :hoverable="false" no-disable v-on="propsEvent">
<MsBaseTable v-bind="propsRes" :hoverable="false" no-disable is-simple-setting v-on="propsEvent">
<!-- 表格头 slot -->
<template #encodeTitle>
<div class="flex items-center text-[var(--color-text-3)]">
@ -28,8 +28,8 @@
</div>
</template>
<!-- 表格列 slot -->
<template #name="{ record, columnConfig }">
<a-popover position="tl" :disabled="!record.name || record.name.trim() === ''" class="ms-params-input-popover">
<template #key="{ record, columnConfig }">
<a-popover position="tl" :disabled="!record.key || record.key.trim() === ''" class="ms-params-input-popover">
<template #content>
<div class="param-popover-title">
{{ t('apiTestDebug.paramName') }}
@ -43,7 +43,7 @@
:placeholder="t('apiTestDebug.paramNamePlaceholder')"
class="param-input"
:max-length="255"
@input="(val) => addTableLine(val, 'name')"
@input="(val) => addTableLine(val, 'key')"
/>
</a-popover>
</template>
@ -125,15 +125,17 @@
<a-input-number
v-model:model-value="record.min"
:placeholder="t('apiTestDebug.paramMin')"
:min="0"
class="param-input param-input-number"
@input="(val) => addTableLine(val || '', 'min')"
@change="(val) => addTableLine(val || '', 'min')"
/>
<div class="mx-[4px]"></div>
<a-input-number
v-model:model-value="record.max"
:placeholder="t('apiTestDebug.paramMax')"
:min="0"
class="param-input"
@input="(val) => addTableLine(val || '', 'max')"
@change="(val) => addTableLine(val || '', 'max')"
/>
</div>
</template>
@ -159,10 +161,10 @@
/>
</a-popover>
</template>
<template #desc="{ record, columnConfig }">
<template #description="{ record, columnConfig }">
<paramDescInput
v-model:desc="record[columnConfig.dataIndex as string]"
@input="(val) => addTableLine(val, 'desc')"
@input="(val) => addTableLine(val, 'description')"
@dblclick="quickInputDesc(record)"
@change="handleDescChange"
/>
@ -201,11 +203,11 @@
</template>
</a-trigger>
<a-switch
v-if="columnConfig.hasEnable"
v-model:model-value="record.enable"
v-if="columnConfig.hasDisable"
v-model:model-value="record.disable"
size="small"
type="line"
@change="(val) => addTableLine(val, 'enable')"
@change="(val) => addTableLine(val, 'disable')"
/>
<icon-minus-circle
v-if="paramsLength > 1 && rowIndex !== paramsLength - 1"
@ -292,7 +294,6 @@
</template>
<script async setup lang="ts">
import { useVModel } from '@vueuse/core';
import { isEqual } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue';
@ -311,18 +312,18 @@
import useTableStore from '@/hooks/useTableStore';
import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum';
interface Param {
id: number;
required: boolean;
name: string;
key: string;
type: string;
value: string;
min: number | undefined;
max: number | undefined;
contentType: RequestContentTypeEnum;
desc: string;
description: string;
encode: boolean;
tag: string[];
enable: boolean;
@ -335,14 +336,13 @@
hasRequired?: boolean; // type required
typeOptions?: { label: string; value: string }[]; // type
typeTitleTooltip?: string; // type tooltip
hasEnable?: boolean; // operation enable
hasDisable?: boolean; // operation enable
moreAction?: ActionsItem[]; // operation
format?: RequestBodyFormat; // operation
};
const props = withDefaults(
defineProps<{
selectedKeys?: string[];
params: any[];
defaultParamItem?: Partial<Param>; //
columns: ParamTableColumn[];
@ -369,22 +369,21 @@
isSimpleSetting: true,
defaultParamItem: () => ({
required: false,
name: '',
key: '',
type: RequestParamsType.STRING,
value: '',
min: undefined,
max: undefined,
contentType: RequestContentTypeEnum.TEXT,
tag: [],
desc: '',
description: '',
encode: false,
enable: false,
disable: false,
mustContain: false,
}),
}
);
const emit = defineEmits<{
(e: 'update:selectedKeys', value: string[]): void;
(e: 'change', data: any[], isInit?: boolean): void; //
(e: 'moreActionSelect', event: ActionsItem, record: Record<string, any>): void;
(e: 'projectChange', projectId: string): void;
@ -392,8 +391,6 @@
const { t } = useI18n();
const innerSelectedKeys = useVModel(props, 'selectedKeys', emit);
const tableStore = useTableStore();
async function initColumns() {
if (props.showSetting && props.tableKey) {
@ -417,10 +414,28 @@
showPagination: false,
});
const selectedKeys = computed(() => propsRes.value.data.filter((e) => e.enable).map((e) => e.id));
propsEvent.value.rowSelectChange = (key: string) => {
propsRes.value.data = propsRes.value.data.map((e) => {
if (e.id === key) {
e.enable = !e.enable;
}
return e;
});
emit('change', propsRes.value.data);
};
propsEvent.value.selectAllChange = (v: SelectAllEnum) => {
propsRes.value.data = propsRes.value.data.map((e) => {
e.enable = v !== SelectAllEnum.NONE;
return e;
});
emit('change', propsRes.value.data);
};
watch(
() => propsRes.value.selectedKeys,
(val) => {
innerSelectedKeys.value = Array.from(val);
() => selectedKeys.value,
(arr) => {
propsRes.value.selectedKeys = new Set(arr);
}
);
@ -450,7 +465,8 @@
isForce?: boolean
) {
const lastData = { ...propsRes.value.data[propsRes.value.data.length - 1] };
delete lastData.id;
delete lastData.id; // id
lastData.enable = props.defaultParamItem.enable; // enable
// key key
const isNotChange =
val === undefined || key === undefined
@ -461,8 +477,8 @@
propsRes.value.data.push({
id,
...props.defaultParamItem,
enable: true, //
} as any);
propsRes.value.selectedKeys.add(id);
emit('change', propsRes.value.data);
}
}
@ -473,6 +489,7 @@
if (val.length > 0) {
const lastData = { ...val[val.length - 1] };
delete lastData.id; // id
delete lastData.enable; // enable
const isNotChange = isEqual(lastData, props.defaultParamItem);
propsRes.value.data = val;
if (!isNotChange) {
@ -484,9 +501,9 @@
{
id, // id props.defaultParamItem id
...props.defaultParamItem,
enable: true, //
},
] as any[];
propsRes.value.selectedKeys.add(id);
emit('change', propsRes.value.data, true);
}
},
@ -532,7 +549,7 @@
function quickInputDesc(record: any) {
activeQuickInputRecord.value = record;
showQuickInputDesc.value = true;
quickInputDescValue.value = record.desc;
quickInputDescValue.value = record.description;
}
function clearQuickInputDesc() {
@ -541,7 +558,7 @@
}
function applyQuickInputDesc() {
activeQuickInputRecord.value.desc = quickInputDescValue.value;
activeQuickInputRecord.value.description = quickInputDescValue.value;
showQuickInputDesc.value = false;
clearQuickInputDesc();
emit('change', propsRes.value.data);

View File

@ -1,6 +1,5 @@
<template>
<div class="mb-[8px] font-medium">{{ t('apiTestDebug.auth') }}</div>
<div class="rounded-[var(--border-radius-small)] border border-[var(--color-text-n8)] p-[16px]">
<div class="h-full rounded-[var(--border-radius-small)] border border-[var(--color-text-n8)] p-[16px]">
<div class="mb-[8px]">{{ t('apiTestDebug.authType') }}</div>
<a-radio-group v-model:model-value="authForm.authType" class="mb-[16px]" @change="authTypeChange">
<a-radio :value="RequestAuthType.NONE">No Auth</a-radio>

View File

@ -1,33 +1,42 @@
<template>
<div class="mb-[8px] flex items-center justify-between">
<batchAddKeyVal v-if="showParamTable" :params="currentTableParams" @apply="handleBatchParamApply" />
<a-radio-group v-model:model-value="bodyType" type="button" size="small" @change="formatChange">
<a-radio-group
v-model:model-value="innerParams.bodyType"
type="button"
size="small"
@change="(val) => changeBodyFormat(val as string)"
>
<a-radio v-for="item of RequestBodyFormat" :key="item" :value="item">{{ requestBodyTypeMap[item] }}</a-radio>
</a-radio-group>
<batchAddKeyVal v-if="showParamTable" :params="currentTableParams" @apply="handleBatchParamApply" />
</div>
<div
v-if="bodyType === RequestBodyFormat.NONE"
v-if="innerParams.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)]"
>
{{ t('apiTestDebug.noneBody') }}
</div>
<paramTable
v-else-if="bodyType === RequestBodyFormat.FORM_DATA"
v-else-if="innerParams.bodyType === RequestBodyFormat.FORM_DATA"
v-model:params="currentTableParams"
:scroll="{ minWidth: 1160 }"
:columns="columns"
:height-used="heightUsed"
:show-setting="true"
:table-key="TableKeyEnum.API_TEST_DEBUG_FORM_DATA"
@change="handleParamTableChange"
/>
<paramTable
v-else-if="bodyType === RequestBodyFormat.WWW_FORM"
v-else-if="innerParams.bodyType === RequestBodyFormat.WWW_FORM"
v-model:params="currentTableParams"
:scroll="{ minWidth: 1160 }"
:columns="columns"
:height-used="heightUsed"
:show-setting="true"
:table-key="TableKeyEnum.API_TEST_DEBUG_FORM_URL_ENCODE"
@change="handleParamTableChange"
/>
<div v-else-if="bodyType === RequestBodyFormat.BINARY">
<div v-else-if="innerParams.bodyType === RequestBodyFormat.BINARY">
<div class="mb-[16px] flex justify-between gap-[8px] bg-[var(--color-text-n9)] p-[12px]">
<a-input
v-model:model-value="innerParams.binaryBody.description"
@ -78,14 +87,15 @@
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
import paramTable, { type ParamTableColumn } from '../../../components/paramTable.vue';
import batchAddKeyVal from './batchAddKeyVal.vue';
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
import paramTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import { requestBodyTypeMap } from '@/config/apiTest';
import { useI18n } from '@/hooks/useI18n';
import { ExecuteBody } from '@/models/apiTest/debug';
import { RequestBodyFormat, RequestParamsType } from '@/enums/apiEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
const props = defineProps<{
params: ExecuteBody;
@ -100,7 +110,6 @@
const { t } = useI18n();
const innerParams = useVModel(props, 'params', emit);
const bodyType = ref(RequestBodyFormat.NONE);
const columns = computed<ParamTableColumn[]>(() => [
{
@ -134,8 +143,8 @@
},
{
title: 'apiTestDebug.desc',
dataIndex: 'desc',
slotName: 'desc',
dataIndex: 'description',
slotName: 'description',
},
{
title: 'apiTestDebug.encode',
@ -147,8 +156,8 @@
title: '',
slotName: 'operation',
fixed: 'right',
format: bodyType.value,
width: bodyType.value === RequestBodyFormat.FORM_DATA ? 90 : 50,
format: innerParams.value.bodyType,
width: innerParams.value.bodyType === RequestBodyFormat.FORM_DATA ? 90 : 50,
},
]);
@ -157,7 +166,7 @@
watch(
() => props.layout,
(val) => {
heightUsed.value = val === 'horizontal' ? 422 : 422 + props.secondBoxHeight;
heightUsed.value = val === 'horizontal' ? 428 : 428 + props.secondBoxHeight;
},
{
immediate: true,
@ -168,7 +177,7 @@
() => props.secondBoxHeight,
(val) => {
if (props.layout === 'vertical') {
heightUsed.value = 422 + val;
heightUsed.value = 428 + val;
}
},
{
@ -178,18 +187,18 @@
const showParamTable = computed(() => {
// FORM_DATAX_WWW_FORM_URLENCODED
return [RequestBodyFormat.FORM_DATA, RequestBodyFormat.WWW_FORM].includes(bodyType.value);
return [RequestBodyFormat.FORM_DATA, RequestBodyFormat.WWW_FORM].includes(innerParams.value.bodyType);
});
//
const currentTableParams = computed({
get() {
if (bodyType.value === RequestBodyFormat.FORM_DATA) {
if (innerParams.value.bodyType === RequestBodyFormat.FORM_DATA) {
return innerParams.value.formDataBody.formValues;
}
return innerParams.value.wwwFormBody.formValues;
},
set(val) {
if (bodyType.value === RequestBodyFormat.FORM_DATA) {
if (innerParams.value.bodyType === RequestBodyFormat.FORM_DATA) {
innerParams.value.formDataBody.formValues = val;
} else {
innerParams.value.wwwFormBody.formValues = val;
@ -199,18 +208,18 @@
//
const currentBodyCode = computed({
get() {
if (bodyType.value === RequestBodyFormat.JSON) {
if (innerParams.value.bodyType === RequestBodyFormat.JSON) {
return innerParams.value.jsonBody.jsonValue;
}
if (bodyType.value === RequestBodyFormat.XML) {
if (innerParams.value.bodyType === RequestBodyFormat.XML) {
return innerParams.value.xmlBody.value;
}
return innerParams.value.rawBody.value;
},
set(val) {
if (bodyType.value === RequestBodyFormat.JSON) {
if (innerParams.value.bodyType === RequestBodyFormat.JSON) {
innerParams.value.jsonBody.jsonValue = val;
} else if (bodyType.value === RequestBodyFormat.XML) {
} else if (innerParams.value.bodyType === RequestBodyFormat.XML) {
innerParams.value.xmlBody.value = val;
} else {
innerParams.value.rawBody.value = val;
@ -219,19 +228,15 @@
});
//
const currentCodeLanguage = computed(() => {
if (bodyType.value === RequestBodyFormat.JSON) {
if (innerParams.value.bodyType === RequestBodyFormat.JSON) {
return LanguageEnum.JSON;
}
if (bodyType.value === RequestBodyFormat.XML) {
if (innerParams.value.bodyType === RequestBodyFormat.XML) {
return LanguageEnum.XML;
}
return LanguageEnum.PLAINTEXT;
});
function formatChange() {
console.log('formatChange', bodyType.value);
}
/**
* 批量参数代码转换为参数表格数据
*/
@ -248,6 +253,11 @@
currentTableParams.value = [...resultArr];
emit('change');
}
function changeBodyFormat(val: string) {
innerParams.value.bodyType = val as RequestBodyFormat;
emit('change');
}
</script>
<style lang="less" scoped></style>

View File

@ -1,14 +1,14 @@
<template>
<div class="mb-[8px] flex items-center justify-between">
<div class="font-medium">{{ t('apiTestDebug.header') }}</div>
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" />
<batchAddKeyVal :params="innerParams" :default-param-item="defaultParamItem" @apply="handleBatchParamApply" />
</div>
<paramTable
v-model:params="innerParams"
v-model:selected-keys="selectedKeys"
:columns="columns"
:height-used="heightUsed"
:scroll="scroll"
:default-param-item="defaultParamItem"
draggable
@change="handleParamTableChange"
/>
@ -17,15 +17,14 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import paramTable, { ParamTableColumn } from '../../../components/paramTable.vue';
import batchAddKeyVal from './batchAddKeyVal.vue';
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import { useI18n } from '@/hooks/useI18n';
import { EnableKeyValueParam } from '@/models/apiTest/debug';
const props = defineProps<{
selectedKeys?: string[];
params: EnableKeyValueParam[];
layout: 'horizontal' | 'vertical';
secondBoxHeight: number;
@ -39,13 +38,18 @@
const { t } = useI18n();
const innerParams = useVModel(props, 'params', emit);
const selectedKeys = useVModel(props, 'selectedKeys', emit);
const defaultParamItem = ref<EnableKeyValueParam>({
key: '',
value: '',
description: '',
enable: true,
});
const columns: ParamTableColumn[] = [
{
title: 'apiTestDebug.paramName',
dataIndex: 'name',
slotName: 'name',
dataIndex: 'key',
slotName: 'key',
},
{
title: 'apiTestDebug.paramValue',
@ -54,8 +58,8 @@
},
{
title: 'apiTestDebug.desc',
dataIndex: 'desc',
slotName: 'desc',
dataIndex: 'description',
slotName: 'description',
},
{
title: '',
@ -66,9 +70,9 @@
const heightUsed = computed(() => {
if (props.layout === 'horizontal') {
return 422;
return 428;
}
return 422 + props.secondBoxHeight;
return 428 + props.secondBoxHeight;
});
const scroll = computed(() => (props.layout === 'horizontal' ? { x: '700px' } : { x: '100%' }));

View File

@ -189,7 +189,7 @@
<a-form-item :label="t('apiTestDebug.requestModule')" class="mb-0">
<a-tree-select
v-model:modelValue="saveModalForm.moduleId"
:data="props.moduleTree"
:data="selectTree"
:field-names="{ title: 'name', key: 'id', children: 'children' }"
allow-search
/>
@ -215,18 +215,24 @@
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue';
import { addDebug, executeDebug } from '@/api/modules/api-test/debug';
import { addDebug, executeDebug, getDebugDetail } 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 { useAppStore } from '@/store';
import { getGenerateId } from '@/utils';
import { filterTree, getGenerateId } from '@/utils';
import { scrollIntoView } from '@/utils/dom';
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
import { ExecuteBody, ExecuteHTTPRequestFullParams } from '@/models/apiTest/debug';
import { ModuleTreeNode } from '@/models/common';
import { RequestBodyFormat, RequestComposition, RequestMethods, ResponseComposition } from '@/enums/apiEnum';
import {
RequestAuthType,
RequestBodyFormat,
RequestComposition,
RequestMethods,
ResponseComposition,
} from '@/enums/apiEnum';
// Http
const debugHeader = defineAsyncComponent(() => import('./header.vue'));
@ -237,7 +243,6 @@
export type DebugTabParam = ExecuteHTTPRequestFullParams & TabItem & Record<string, any>;
const props = defineProps<{
module: string; //
moduleTree: ModuleTreeNode[]; //
}>();
@ -264,6 +269,28 @@
},
rawBody: { value: '' },
};
const defaultResponse = {
requestResults: [
{
body: '',
responseResult: {
body: '',
contentType: '',
headers: '',
dnsLookupTime: 0,
downloadTime: 0,
latency: 0,
responseCode: 0,
responseTime: 0,
responseSize: 0,
socketInitTime: 0,
tcpHandshakeTime: 0,
transferStartTime: 0,
},
},
],
console: '',
}; //
const defaultDebugParams: DebugTabParam = {
id: initDefaultId,
moduleId: 'root',
@ -285,7 +312,7 @@
uploadFileIds: [],
linkFileIds: [],
authConfig: {
authType: 'NONE',
authType: RequestAuthType.NONE,
username: '',
password: '',
},
@ -310,32 +337,11 @@
connectTimeout: 60000,
responseTimeout: 60000,
certificateAlias: '',
followRedirects: false,
followRedirects: true,
autoRedirects: false,
},
responseActiveTab: ResponseComposition.BODY,
response: {
requestResults: [
{
body: '',
responseResult: {
body: '',
contentType: '',
headers: '',
dnsLookupTime: 0,
downloadTime: 0,
latency: 0,
responseCode: 0,
responseTime: 0,
responseSize: 0,
socketInitTime: 0,
tcpHandshakeTime: 0,
transferStartTime: 0,
},
},
],
console: '',
}, //
response: cloneDeep(defaultResponse),
};
const debugTabs = ref<DebugTabParam[]>([cloneDeep(defaultDebugParams)]);
const activeDebug = ref<DebugTabParam>(debugTabs.value[0]);
@ -366,13 +372,12 @@
const id = `debug-${Date.now()}`;
debugTabs.value.push({
...cloneDeep(defaultDebugParams),
moduleId: props.module,
id,
...defaultProps,
});
activeRequestTab.value = id;
activeRequestTab.value = defaultProps?.id || id;
nextTick(() => {
if (defaultProps) {
if (defaultProps && !defaultProps.id) {
handleActiveDebugChange();
}
});
@ -584,14 +589,6 @@
executeLoading.value = false;
}
});
websocket.value.addEventListener('close', (event) => {
console.log('关闭:', event);
});
websocket.value.addEventListener('error', (event) => {
console.error('错误:', event);
});
}
function makeRequestParams() {
@ -685,10 +682,16 @@
const saveModalForm = ref({
name: '',
path: activeDebug.value.url || '',
moduleId: activeDebug.value.module,
moduleId: 'root',
});
const saveModalFormRef = ref<FormInstance>();
const saveLoading = ref(false);
const selectTree = computed(() =>
filterTree(props.moduleTree, (e) => {
e.draggable = false;
return e.type === 'MODULE';
})
);
watch(
() => saveModalVisible.value,
@ -708,7 +711,7 @@
saveModalForm.value = {
name: '',
path: activeDebug.value.url || '',
moduleId: activeDebug.value.module,
moduleId: 'root',
};
saveModalVisible.value = true;
} catch (error) {
@ -752,6 +755,26 @@
done(false);
}
const apiDetailLoading = ref(false);
async function openApiTab(apiInfo: ModuleTreeNode) {
try {
apiDetailLoading.value = true;
const res = await getDebugDetail(apiInfo.id);
addDebugTab({
label: apiInfo.name,
...res,
response: cloneDeep(defaultResponse),
...res.request,
url: res.path,
});
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
apiDetailLoading.value = false;
}
}
onBeforeMount(() => {
initProtocolList();
});
@ -766,6 +789,7 @@
defineExpose({
addDebugTab,
openApiTab,
});
</script>

View File

@ -1,7 +1,7 @@
<template>
<condition
v-model:list="postConditions"
:condition-types="['SCRIPT']"
:condition-types="[RequestConditionProcessor.SCRIPT]"
add-text="apiTestDebug.postCondition"
:response="props.response"
:height-used="heightUsed"
@ -26,6 +26,7 @@
import condition from '@/views/api-test/components/condition/index.vue';
import { ExecuteConditionProcessor } from '@/models/apiTest/debug';
import { RequestConditionProcessor } from '@/enums/apiEnum';
// import { useI18n } from '@/hooks/useI18n';
@ -46,9 +47,9 @@
const postConditions = useVModel(props, 'params', emit);
const heightUsed = computed(() => {
if (props.layout === 'horizontal') {
return 422;
return 428;
}
return 422 + (props.secondBoxHeight || 0);
return 428 + (props.secondBoxHeight || 0);
});
</script>

View File

@ -23,7 +23,7 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import batchAddKeyVal from './batchAddKeyVal.vue';
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
import paramTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import { useI18n } from '@/hooks/useI18n';
@ -84,8 +84,8 @@
},
{
title: 'apiTestDebug.desc',
dataIndex: 'desc',
slotName: 'desc',
dataIndex: 'description',
slotName: 'description',
},
{
title: '',
@ -100,7 +100,7 @@
watch(
() => props.layout,
(val) => {
heightUsed.value = val === 'horizontal' ? 422 : 422 + props.secondBoxHeight;
heightUsed.value = val === 'horizontal' ? 428 : 428 + props.secondBoxHeight;
},
{
immediate: true,
@ -111,7 +111,7 @@
() => props.secondBoxHeight,
(val) => {
if (props.layout === 'vertical') {
heightUsed.value = 422 + val;
heightUsed.value = 428 + val;
}
},
{

View File

@ -23,7 +23,7 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import batchAddKeyVal from './batchAddKeyVal.vue';
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
import paramTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import { useI18n } from '@/hooks/useI18n';
@ -84,8 +84,8 @@
},
{
title: 'apiTestDebug.desc',
dataIndex: 'desc',
slotName: 'desc',
dataIndex: 'description',
slotName: 'description',
},
{
title: '',
@ -100,7 +100,7 @@
watch(
() => props.layout,
(val) => {
heightUsed.value = val === 'horizontal' ? 422 : 422 + props.secondBoxHeight;
heightUsed.value = val === 'horizontal' ? 428 : 428 + props.secondBoxHeight;
},
{
immediate: true,
@ -111,7 +111,7 @@
() => props.secondBoxHeight,
(val) => {
if (props.layout === 'vertical') {
heightUsed.value = 422 + val;
heightUsed.value = 428 + val;
}
},
{

View File

@ -1,10 +1,8 @@
<template>
<div class="pb-[24px]">
<div class="mb-[8px] font-medium">{{ t('apiTestDebug.auth') }}</div>
<div class="rounded-[var(--border-radius-small)] border border-[var(--color-text-n8)] p-[16px]">
<div class="mb-[8px]">{{ t('apiTestDebug.setting') }}</div>
<a-form :model="settingForm" layout="vertical">
<a-form-item>
<div class="h-full rounded-[var(--border-radius-small)] border border-[var(--color-text-n8)] p-[16px]">
<a-form :model="settingForm" layout="vertical">
<div class="flex items-center gap-[32px]">
<a-form-item class="flex-1">
<template #label>
<div class="flex items-center">
{{ t('apiTestDebug.connectTimeout') }}
@ -34,22 +32,22 @@
class="w-[160px]"
/>
</a-form-item>
<a-form-item :label="t('apiTestDebug.certificateAlias')">
<a-input
v-model:model-value="settingForm.certificateAlias"
:max-length="255"
:placeholder="t('apiTestDebug.commonPlaceholder')"
class="w-[450px]"
/>
</a-form-item>
<a-form-item :label="t('apiTestDebug.redirect')">
<a-radio-group v-model:model-value="settingForm.autoRedirects">
<a-radio value="follow">{{ t('apiTestDebug.follow') }}</a-radio>
<a-radio value="auto">{{ t('apiTestDebug.auto') }}</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</div>
</div>
<a-form-item :label="t('apiTestDebug.certificateAlias')">
<a-input
v-model:model-value="settingForm.certificateAlias"
:max-length="255"
:placeholder="t('apiTestDebug.commonPlaceholder')"
class="w-[450px]"
/>
</a-form-item>
<a-form-item :label="t('apiTestDebug.redirect')">
<a-radio v-model:model-value="settingForm.followRedirects">{{ t('apiTestDebug.follow') }}</a-radio>
<a-radio v-model:model-value="settingForm.autoRedirects" class="ml-[24px]">
{{ t('apiTestDebug.auto') }}
</a-radio>
</a-form-item>
</a-form>
</div>
</template>

View File

@ -37,7 +37,6 @@
<a-spin class="min-h-[400px] w-full" :loading="loading">
<MsTree
v-model:focus-node-key="focusNodeKey"
v-model:selected-keys="selectedKeys"
:data="folderTree"
:keyword="moduleKeyword"
:node-more-actions="folderMoreActions"
@ -60,7 +59,15 @@
@drop="handleDrop"
>
<template #title="nodeData">
<div class="inline-flex w-full">
<div
v-if="nodeData.type === 'API'"
class="inline-flex w-full cursor-pointer gap-[4px]"
@click="emit('clickApiNode', nodeData)"
>
<apiMethodName :method="nodeData.attachInfo?.method || nodeData.attachInfo?.protocol" />
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
</div>
<div v-else class="inline-flex w-full">
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
<div class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</div>
</div>
@ -106,6 +113,7 @@
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsTree from '@/components/business/ms-tree/index.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import popConfirm from '@/views/api-test/components/popConfirm.vue';
import {
@ -113,7 +121,6 @@
getDebugModuleCount,
getDebugModules,
moveDebugModule,
updateDebugModule,
} from '@/api/modules/api-test/debug';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
@ -124,7 +131,7 @@
const props = defineProps<{
isExpandAll?: boolean; //
}>();
const emit = defineEmits(['init', 'change', 'newApi', 'import']);
const emit = defineEmits(['init', 'clickApiNode', 'newApi', 'import']);
const { t } = useI18n();
const { openModal } = useModal();
@ -145,11 +152,10 @@
const virtualListProps = computed(() => {
return {
height: 'calc(100vh - 325px)',
height: 'calc(100% - 180px)',
};
});
const activeFolder = ref<string>('all');
const isExpandAll = ref(props.isExpandAll);
const rootModulesName = ref<string[]>([]); //
@ -167,16 +173,8 @@
const moduleKeyword = ref('');
const folderTree = ref<ModuleTreeNode[]>([]);
const focusNodeKey = ref<string | number>('');
const selectedKeys = ref<string[]>([]);
const loading = ref(false);
watch(
() => selectedKeys.value,
(arr) => {
emit('change', arr[0]);
}
);
function setFocusNodeKey(node: MsTreeNodeData) {
focusNodeKey.value = node.id || '';
}
@ -207,7 +205,6 @@
...e,
hideMoreAction: e.id === 'root',
draggable: e.id !== 'root',
disabled: e.id === activeFolder.value,
};
});
emit('init', folderTree.value);

View File

@ -6,14 +6,14 @@
<moduleTree
@init="(val) => (folderTree = val)"
@new-api="newApi"
@change="(val) => (activeModule = val)"
@click-api-node="handleApiNodeClick"
@import="importDrawerVisible = true"
/>
</div>
</template>
<template #second>
<div class="flex h-full flex-col">
<debug ref="debugRef" :module="activeModule" :module-tree="folderTree" />
<debug ref="debugRef" :module-tree="folderTree" />
</div>
</template>
</MsSplitBox>
@ -67,7 +67,6 @@
const { t } = useI18n();
const debugRef = ref<InstanceType<typeof debug>>();
const activeModule = ref<string>('root');
const folderTree = ref<ModuleTreeNode[]>([]);
const importDrawerVisible = ref(false);
const curlCode = ref('');
@ -111,6 +110,10 @@
curlCode.value = '';
importDrawerVisible.value = false;
}
function handleApiNodeClick(node: ModuleTreeNode) {
debugRef.value?.openApiTab(node);
}
</script>
<style lang="less" scoped></style>

View File

@ -157,7 +157,7 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { defineModel, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import { debounce } from 'lodash-es';

View File

@ -98,8 +98,8 @@
},
{
title: 'project.environmental.desc',
dataIndex: 'desc',
slotName: 'desc',
dataIndex: 'description',
slotName: 'description',
},
{
title: '',

View File

@ -30,8 +30,8 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import batchAddKeyVal from '@/views/api-test/debug/components/debug/batchAddKeyVal.vue';
import { useI18n } from '@/hooks/useI18n';
@ -121,7 +121,7 @@
{
title: 'project.environmental.desc',
dataIndex: 'description',
slotName: 'desc',
slotName: 'description',
showInTable: true,
showDrag: true,
},

View File

@ -34,7 +34,7 @@
</template>
<script lang="ts" setup>
import { onBeforeMount, ref } from 'vue';
import { defineModel, onBeforeMount, ref } from 'vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';

View File

@ -17,8 +17,8 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import batchAddKeyVal from '@/views/api-test/debug/components/debug/batchAddKeyVal.vue';
import { useI18n } from '@/hooks/useI18n';
@ -59,7 +59,7 @@
{
title: 'apiTestDebug.desc',
dataIndex: 'description',
slotName: 'desc',
slotName: 'description',
},
{
title: '',