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, AddDebugModuleUrl,
DeleteDebugModuleUrl, DeleteDebugModuleUrl,
ExecuteApiDebugUrl, ExecuteApiDebugUrl,
GetApiDebugDetailUrl,
GetDebugModuleCountUrl, GetDebugModuleCountUrl,
GetDebugModulesUrl, GetDebugModulesUrl,
MoveDebugModuleUrl, MoveDebugModuleUrl,
@ -13,6 +14,7 @@ import {
import { import {
AddDebugModuleParams, AddDebugModuleParams,
DebugDetail,
ExecuteRequestParams, ExecuteRequestParams,
SaveDebugParams, SaveDebugParams,
UpdateDebugModule, UpdateDebugModule,
@ -64,3 +66,8 @@ export function addDebug(data: SaveDebugParams) {
export function updateDebug(data: UpdateDebugParams) { export function updateDebug(data: UpdateDebugParams) {
return MSR.post({ url: UpdateApiDebugUrl, data }); 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 ExecuteApiDebugUrl = '/api/debug/debug'; // 执行调试
export const AddApiDebugUrl = '/api/debug/add'; // 新增调试 export const AddApiDebugUrl = '/api/debug/add'; // 新增调试
export const UpdateApiDebugUrl = '/api/debug/update'; // 更新调试 export const UpdateApiDebugUrl = '/api/debug/update'; // 更新调试
export const GetApiDebugDetailUrl = '/api/debug/get'; // 获取接口调试详情
export const UpdateDebugModuleUrl = '/api/debug/module/update'; // 更新模块 export const UpdateDebugModuleUrl = '/api/debug/module/update'; // 更新模块
export const MoveDebugModuleUrl = '/api/debug/module/move'; // 移动模块 export const MoveDebugModuleUrl = '/api/debug/module/move'; // 移动模块
export const GetDebugModuleCountUrl = '/api/debug/module/count'; // 模块统计数量 export const GetDebugModuleCountUrl = '/api/debug/module/count'; // 模块统计数量

View File

@ -146,8 +146,8 @@
}, },
{ {
title: 'project.commonScript.description', title: 'project.commonScript.description',
slotName: 'desc', slotName: 'description',
dataIndex: 'desc', dataIndex: 'description',
}, },
{ {
title: 'project.commonScript.isRequired', 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日期分组 // mock日期分组
export const mockDateGroup: MockParamItem[] = [ export const mockDateGroup: MockParamItem[] = [
{ {
@ -901,3 +894,15 @@ export const JMeterAllVars = [
...JMeterInfoGroup, ...JMeterInfoGroup,
...JMeterStringGroup, ...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 { useI18n } from '@/hooks/useI18n';
import { import {
formalParameterVars,
JMeterAllGroup, JMeterAllGroup,
JMeterAllVars, JMeterAllVars,
mockAllGroup, mockAllGroup,
mockAllParams, mockAllParams,
mockFunctions, mockFunctions,
specialStringVars, sameFuncNameVars,
} from './config'; } from './config';
import type { MockParamInputGroupItem, MockParamItem } from './types'; import type { MockParamInputGroupItem, MockParamItem } from './types';
import type { AutoComplete, CascaderOption, FormInstance } from '@arco-design/web-vue'; import type { AutoComplete, CascaderOption, FormInstance } from '@arco-design/web-vue';
@ -375,7 +376,8 @@
// @ // @
const regex = /@([a-zA-Z]+)(\([^)]*\))?/; const regex = /@([a-zA-Z]+)(\([^)]*\))?/;
const currentParamType = mockAllParams.find((e) => { const currentParamType = mockAllParams.find((e) => {
if (specialStringVars.includes(val)) { if (sameFuncNameVars.includes(val)) {
//
return e.value === val; return e.value === val;
} }
if (e.value.match(regex)?.[1] === val.match(regex)?.[1]) { if (e.value.match(regex)?.[1] === val.match(regex)?.[1]) {
@ -428,6 +430,7 @@
*/ */
function openParamSetting() { function openParamSetting() {
if (/^\$/.test(innerValue.value)) { if (/^\$/.test(innerValue.value)) {
// JMeter
paramSettingType.value = 'jmeter'; paramSettingType.value = 'jmeter';
if (JMeterAllVars.findIndex((e) => e.value === innerValue.value) !== -1) { if (JMeterAllVars.findIndex((e) => e.value === innerValue.value) !== -1) {
paramForm.value.JMeterType = innerValue.value; paramForm.value.JMeterType = innerValue.value;
@ -435,6 +438,7 @@
paramForm.value.JMeterType = ''; paramForm.value.JMeterType = '';
} }
} else if (/^@/.test(innerValue.value)) { } else if (/^@/.test(innerValue.value)) {
// Mock
const valueArr = innerValue.value.split('|'); // mock const valueArr = innerValue.value.split('|'); // mock
if (valueArr[0]) { if (valueArr[0]) {
// @ // @
@ -444,16 +448,23 @@
if (variableMatch) { if (variableMatch) {
const variableName = variableMatch[1]; const variableName = variableMatch[1];
const variableParams = variableMatch[2]?.split(',').map((param) => param.trim()); const variableParams = variableMatch[2]?.split(',').map((param) => param.trim());
const formalParameterVar = formalParameterVars.find((e) => e.includes(variableName)); //
if (variableName === 'character') { if (formalParameterVar && variableParams.length > 0) {
handleParamTypeChange(`@${variableName}(${variableParams})`); // character // 使 ()
handleParamTypeChange(formalParameterVar);
} else if (sameFuncNameVars.includes(valueArr[0])) {
//
handleParamTypeChange(valueArr[0]);
} else { } else {
handleParamTypeChange(`@${variableName}`); // handleParamTypeChange(`@${variableName}`); //
} }
if (variableName !== 'character' || (variableName === 'character' && variableParams?.[0] !== 'pool')) { if (!formalParameterVars.includes(valueArr[0])) {
// @character(pool) pool //
//
//
(variableParams || []).forEach((e, i) => { (variableParams || []).forEach((e, i) => {
// //
paramForm.value[`param${i + 1}`] = Number.isNaN(Number(e)) ? e : Number(e); paramForm.value[`param${i + 1}`] = Number.isNaN(Number(e)) ? e : Number(e);
}); });
} }

View File

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

View File

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

View File

@ -69,9 +69,9 @@
:title="item.slotName" :title="item.slotName"
> >
<template #title> <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"> <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> </slot>
<columnSelectorIcon <columnSelectorIcon
v-if=" v-if="

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
import { findKey } from 'lodash-es';
import JSEncrypt from 'jsencrypt'; 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'; import { CustomFieldItem } from '@/models/bug-management';
@ -216,6 +215,38 @@ export function mapTree<T>(
.filter(Boolean); .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 * key
* @param trees * @param trees

View File

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

View File

@ -42,15 +42,16 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { isEmpty } from 'lodash-es';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue'; import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
const props = defineProps<{ const props = defineProps<{
params: Record<string, any>[]; params: Record<string, any>[];
defaultParamItem?: Record<string, any>; //
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'apply', resultArr: (Record<string, any> | null)[]): void; (e: 'apply', resultArr: (Record<string, any> | null)[]): void;
@ -66,8 +67,8 @@
(val) => { (val) => {
if (val) { if (val) {
batchParamsCode.value = props.params batchParamsCode.value = props.params
.filter((e) => e && (e.name !== '' || e.value !== '')) .filter((e) => e && (!isEmpty(e.key) || !isEmpty(e.value)))
.map((item) => `${item.name}:${item.value}`) .map((item) => `${item.key}:${item.value}`)
.join('\n'); .join('\n');
} }
}, },
@ -83,19 +84,13 @@
const arr = batchParamsCode.value.replaceAll('\r', '\n').split('\n'); // const arr = batchParamsCode.value.replaceAll('\r', '\n').split('\n'); //
const tempObj: Record<string, any> = {}; // const tempObj: Record<string, any> = {}; //
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
const [name, value] = arr[i].split(':'); const [key, value] = arr[i].split(':');
if (name) { if (key) {
tempObj[name.trim()] = { tempObj[key.trim()] = {
id: new Date().getTime() + i, id: new Date().getTime() + i,
name: name.trim(), ...props.defaultParamItem,
key: key.trim(),
value: value?.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" :selectable="false"
:scroll="{ x: '700px' }" :scroll="{ x: '700px' }"
:response="props.response" :response="props.response"
:height-used="(props.heightUsed || 0) + 62" :height-used="(props.heightUsed || 0) + 68"
@change="handleExtractParamTableChange" @change="handleExtractParamTableChange"
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)" @more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
> >

View File

@ -1,5 +1,5 @@
<template> <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 --> <!-- 表格头 slot -->
<template #encodeTitle> <template #encodeTitle>
<div class="flex items-center text-[var(--color-text-3)]"> <div class="flex items-center text-[var(--color-text-3)]">
@ -28,8 +28,8 @@
</div> </div>
</template> </template>
<!-- 表格列 slot --> <!-- 表格列 slot -->
<template #name="{ record, columnConfig }"> <template #key="{ record, columnConfig }">
<a-popover position="tl" :disabled="!record.name || record.name.trim() === ''" class="ms-params-input-popover"> <a-popover position="tl" :disabled="!record.key || record.key.trim() === ''" class="ms-params-input-popover">
<template #content> <template #content>
<div class="param-popover-title"> <div class="param-popover-title">
{{ t('apiTestDebug.paramName') }} {{ t('apiTestDebug.paramName') }}
@ -43,7 +43,7 @@
:placeholder="t('apiTestDebug.paramNamePlaceholder')" :placeholder="t('apiTestDebug.paramNamePlaceholder')"
class="param-input" class="param-input"
:max-length="255" :max-length="255"
@input="(val) => addTableLine(val, 'name')" @input="(val) => addTableLine(val, 'key')"
/> />
</a-popover> </a-popover>
</template> </template>
@ -125,15 +125,17 @@
<a-input-number <a-input-number
v-model:model-value="record.min" v-model:model-value="record.min"
:placeholder="t('apiTestDebug.paramMin')" :placeholder="t('apiTestDebug.paramMin')"
:min="0"
class="param-input param-input-number" class="param-input param-input-number"
@input="(val) => addTableLine(val || '', 'min')" @change="(val) => addTableLine(val || '', 'min')"
/> />
<div class="mx-[4px]"></div> <div class="mx-[4px]"></div>
<a-input-number <a-input-number
v-model:model-value="record.max" v-model:model-value="record.max"
:placeholder="t('apiTestDebug.paramMax')" :placeholder="t('apiTestDebug.paramMax')"
:min="0"
class="param-input" class="param-input"
@input="(val) => addTableLine(val || '', 'max')" @change="(val) => addTableLine(val || '', 'max')"
/> />
</div> </div>
</template> </template>
@ -159,10 +161,10 @@
/> />
</a-popover> </a-popover>
</template> </template>
<template #desc="{ record, columnConfig }"> <template #description="{ record, columnConfig }">
<paramDescInput <paramDescInput
v-model:desc="record[columnConfig.dataIndex as string]" v-model:desc="record[columnConfig.dataIndex as string]"
@input="(val) => addTableLine(val, 'desc')" @input="(val) => addTableLine(val, 'description')"
@dblclick="quickInputDesc(record)" @dblclick="quickInputDesc(record)"
@change="handleDescChange" @change="handleDescChange"
/> />
@ -201,11 +203,11 @@
</template> </template>
</a-trigger> </a-trigger>
<a-switch <a-switch
v-if="columnConfig.hasEnable" v-if="columnConfig.hasDisable"
v-model:model-value="record.enable" v-model:model-value="record.disable"
size="small" size="small"
type="line" type="line"
@change="(val) => addTableLine(val, 'enable')" @change="(val) => addTableLine(val, 'disable')"
/> />
<icon-minus-circle <icon-minus-circle
v-if="paramsLength > 1 && rowIndex !== paramsLength - 1" v-if="paramsLength > 1 && rowIndex !== paramsLength - 1"
@ -292,7 +294,6 @@
</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';
@ -311,18 +312,18 @@
import useTableStore from '@/hooks/useTableStore'; import useTableStore from '@/hooks/useTableStore';
import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum'; import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum';
interface Param { interface Param {
id: number; id: number;
required: boolean; required: boolean;
name: string; key: string;
type: string; type: string;
value: string; value: string;
min: number | undefined; min: number | undefined;
max: number | undefined; max: number | undefined;
contentType: RequestContentTypeEnum; contentType: RequestContentTypeEnum;
desc: string; description: string;
encode: boolean; encode: boolean;
tag: string[]; tag: string[];
enable: boolean; enable: boolean;
@ -335,14 +336,13 @@
hasRequired?: boolean; // type required hasRequired?: boolean; // type required
typeOptions?: { label: string; value: string }[]; // type typeOptions?: { label: string; value: string }[]; // type
typeTitleTooltip?: string; // type tooltip typeTitleTooltip?: string; // type tooltip
hasEnable?: boolean; // operation enable hasDisable?: boolean; // operation enable
moreAction?: ActionsItem[]; // operation moreAction?: ActionsItem[]; // operation
format?: RequestBodyFormat; // 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[];
@ -369,22 +369,21 @@
isSimpleSetting: true, isSimpleSetting: true,
defaultParamItem: () => ({ defaultParamItem: () => ({
required: false, required: false,
name: '', key: '',
type: RequestParamsType.STRING, type: RequestParamsType.STRING,
value: '', value: '',
min: undefined, min: undefined,
max: undefined, max: undefined,
contentType: RequestContentTypeEnum.TEXT, contentType: RequestContentTypeEnum.TEXT,
tag: [], tag: [],
desc: '', description: '',
encode: false, encode: false,
enable: false, disable: false,
mustContain: false, mustContain: false,
}), }),
} }
); );
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;
@ -392,8 +391,6 @@
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) {
@ -417,10 +414,28 @@
showPagination: false, 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( watch(
() => propsRes.value.selectedKeys, () => selectedKeys.value,
(val) => { (arr) => {
innerSelectedKeys.value = Array.from(val); propsRes.value.selectedKeys = new Set(arr);
} }
); );
@ -450,7 +465,8 @@
isForce?: boolean isForce?: boolean
) { ) {
const lastData = { ...propsRes.value.data[propsRes.value.data.length - 1] }; const lastData = { ...propsRes.value.data[propsRes.value.data.length - 1] };
delete lastData.id; delete lastData.id; // id
lastData.enable = props.defaultParamItem.enable; // enable
// key key // key key
const isNotChange = const isNotChange =
val === undefined || key === undefined val === undefined || key === undefined
@ -461,8 +477,8 @@
propsRes.value.data.push({ propsRes.value.data.push({
id, id,
...props.defaultParamItem, ...props.defaultParamItem,
enable: true, //
} as any); } as any);
propsRes.value.selectedKeys.add(id);
emit('change', propsRes.value.data); emit('change', propsRes.value.data);
} }
} }
@ -473,6 +489,7 @@
if (val.length > 0) { if (val.length > 0) {
const lastData = { ...val[val.length - 1] }; const lastData = { ...val[val.length - 1] };
delete lastData.id; // id delete lastData.id; // id
delete lastData.enable; // enable
const isNotChange = isEqual(lastData, props.defaultParamItem); const isNotChange = isEqual(lastData, props.defaultParamItem);
propsRes.value.data = val; propsRes.value.data = val;
if (!isNotChange) { if (!isNotChange) {
@ -484,9 +501,9 @@
{ {
id, // id props.defaultParamItem id id, // id props.defaultParamItem id
...props.defaultParamItem, ...props.defaultParamItem,
enable: true, //
}, },
] as any[]; ] as any[];
propsRes.value.selectedKeys.add(id);
emit('change', propsRes.value.data, true); emit('change', propsRes.value.data, true);
} }
}, },
@ -532,7 +549,7 @@
function quickInputDesc(record: any) { function quickInputDesc(record: any) {
activeQuickInputRecord.value = record; activeQuickInputRecord.value = record;
showQuickInputDesc.value = true; showQuickInputDesc.value = true;
quickInputDescValue.value = record.desc; quickInputDescValue.value = record.description;
} }
function clearQuickInputDesc() { function clearQuickInputDesc() {
@ -541,7 +558,7 @@
} }
function applyQuickInputDesc() { function applyQuickInputDesc() {
activeQuickInputRecord.value.desc = quickInputDescValue.value; activeQuickInputRecord.value.description = quickInputDescValue.value;
showQuickInputDesc.value = false; showQuickInputDesc.value = false;
clearQuickInputDesc(); clearQuickInputDesc();
emit('change', propsRes.value.data); emit('change', propsRes.value.data);

View File

@ -1,6 +1,5 @@
<template> <template>
<div class="mb-[8px] font-medium">{{ t('apiTestDebug.auth') }}</div> <div class="h-full 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="RequestAuthType.NONE">No Auth</a-radio> <a-radio :value="RequestAuthType.NONE">No Auth</a-radio>

View File

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

View File

@ -1,14 +1,14 @@
<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.header') }}</div> <div class="font-medium">{{ t('apiTestDebug.header') }}</div>
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" /> <batchAddKeyVal :params="innerParams" :default-param-item="defaultParamItem" @apply="handleBatchParamApply" />
</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"
:default-param-item="defaultParamItem"
draggable draggable
@change="handleParamTableChange" @change="handleParamTableChange"
/> />
@ -17,15 +17,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import paramTable, { ParamTableColumn } from '../../../components/paramTable.vue'; import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
import batchAddKeyVal from './batchAddKeyVal.vue'; import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { EnableKeyValueParam } from '@/models/apiTest/debug'; import { EnableKeyValueParam } from '@/models/apiTest/debug';
const props = defineProps<{ const props = defineProps<{
selectedKeys?: string[];
params: EnableKeyValueParam[]; params: EnableKeyValueParam[];
layout: 'horizontal' | 'vertical'; layout: 'horizontal' | 'vertical';
secondBoxHeight: number; secondBoxHeight: number;
@ -39,13 +38,18 @@
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 defaultParamItem = ref<EnableKeyValueParam>({
key: '',
value: '',
description: '',
enable: true,
});
const columns: ParamTableColumn[] = [ const columns: ParamTableColumn[] = [
{ {
title: 'apiTestDebug.paramName', title: 'apiTestDebug.paramName',
dataIndex: 'name', dataIndex: 'key',
slotName: 'name', slotName: 'key',
}, },
{ {
title: 'apiTestDebug.paramValue', title: 'apiTestDebug.paramValue',
@ -54,8 +58,8 @@
}, },
{ {
title: 'apiTestDebug.desc', title: 'apiTestDebug.desc',
dataIndex: 'desc', dataIndex: 'description',
slotName: 'desc', slotName: 'description',
}, },
{ {
title: '', title: '',
@ -66,9 +70,9 @@
const heightUsed = computed(() => { const heightUsed = computed(() => {
if (props.layout === 'horizontal') { 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%' })); 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-form-item :label="t('apiTestDebug.requestModule')" class="mb-0">
<a-tree-select <a-tree-select
v-model:modelValue="saveModalForm.moduleId" v-model:modelValue="saveModalForm.moduleId"
:data="props.moduleTree" :data="selectTree"
:field-names="{ title: 'name', key: 'id', children: 'children' }" :field-names="{ title: 'name', key: 'id', children: 'children' }"
allow-search allow-search
/> />
@ -215,18 +215,24 @@
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 { addDebug, executeDebug, getDebugDetail } from '@/api/modules/api-test/debug';
import { getPluginScript, getProtocolList } from '@/api/modules/api-test/management'; import { getPluginScript, getProtocolList } from '@/api/modules/api-test/management';
import { getSocket } from '@/api/modules/project-management/commonScript'; import { getSocket } from '@/api/modules/project-management/commonScript';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { getGenerateId } from '@/utils'; import { filterTree, getGenerateId } from '@/utils';
import { scrollIntoView } from '@/utils/dom'; 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 { 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 {
RequestAuthType,
RequestBodyFormat,
RequestComposition,
RequestMethods,
ResponseComposition,
} from '@/enums/apiEnum';
// Http // Http
const debugHeader = defineAsyncComponent(() => import('./header.vue')); const debugHeader = defineAsyncComponent(() => import('./header.vue'));
@ -237,7 +243,6 @@
export type DebugTabParam = ExecuteHTTPRequestFullParams & TabItem & Record<string, any>; export type DebugTabParam = ExecuteHTTPRequestFullParams & TabItem & Record<string, any>;
const props = defineProps<{ const props = defineProps<{
module: string; //
moduleTree: ModuleTreeNode[]; // moduleTree: ModuleTreeNode[]; //
}>(); }>();
@ -264,6 +269,28 @@
}, },
rawBody: { value: '' }, 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 = { const defaultDebugParams: DebugTabParam = {
id: initDefaultId, id: initDefaultId,
moduleId: 'root', moduleId: 'root',
@ -285,7 +312,7 @@
uploadFileIds: [], uploadFileIds: [],
linkFileIds: [], linkFileIds: [],
authConfig: { authConfig: {
authType: 'NONE', authType: RequestAuthType.NONE,
username: '', username: '',
password: '', password: '',
}, },
@ -310,32 +337,11 @@
connectTimeout: 60000, connectTimeout: 60000,
responseTimeout: 60000, responseTimeout: 60000,
certificateAlias: '', certificateAlias: '',
followRedirects: false, followRedirects: true,
autoRedirects: false, autoRedirects: false,
}, },
responseActiveTab: ResponseComposition.BODY, responseActiveTab: ResponseComposition.BODY,
response: { response: cloneDeep(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 debugTabs = ref<DebugTabParam[]>([cloneDeep(defaultDebugParams)]); const debugTabs = ref<DebugTabParam[]>([cloneDeep(defaultDebugParams)]);
const activeDebug = ref<DebugTabParam>(debugTabs.value[0]); const activeDebug = ref<DebugTabParam>(debugTabs.value[0]);
@ -366,13 +372,12 @@
const id = `debug-${Date.now()}`; const id = `debug-${Date.now()}`;
debugTabs.value.push({ debugTabs.value.push({
...cloneDeep(defaultDebugParams), ...cloneDeep(defaultDebugParams),
moduleId: props.module,
id, id,
...defaultProps, ...defaultProps,
}); });
activeRequestTab.value = id; activeRequestTab.value = defaultProps?.id || id;
nextTick(() => { nextTick(() => {
if (defaultProps) { if (defaultProps && !defaultProps.id) {
handleActiveDebugChange(); handleActiveDebugChange();
} }
}); });
@ -584,14 +589,6 @@
executeLoading.value = false; executeLoading.value = false;
} }
}); });
websocket.value.addEventListener('close', (event) => {
console.log('关闭:', event);
});
websocket.value.addEventListener('error', (event) => {
console.error('错误:', event);
});
} }
function makeRequestParams() { function makeRequestParams() {
@ -685,10 +682,16 @@
const saveModalForm = ref({ const saveModalForm = ref({
name: '', name: '',
path: activeDebug.value.url || '', path: activeDebug.value.url || '',
moduleId: activeDebug.value.module, moduleId: 'root',
}); });
const saveModalFormRef = ref<FormInstance>(); const saveModalFormRef = ref<FormInstance>();
const saveLoading = ref(false); const saveLoading = ref(false);
const selectTree = computed(() =>
filterTree(props.moduleTree, (e) => {
e.draggable = false;
return e.type === 'MODULE';
})
);
watch( watch(
() => saveModalVisible.value, () => saveModalVisible.value,
@ -708,7 +711,7 @@
saveModalForm.value = { saveModalForm.value = {
name: '', name: '',
path: activeDebug.value.url || '', path: activeDebug.value.url || '',
moduleId: activeDebug.value.module, moduleId: 'root',
}; };
saveModalVisible.value = true; saveModalVisible.value = true;
} catch (error) { } catch (error) {
@ -752,6 +755,26 @@
done(false); 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(() => { onBeforeMount(() => {
initProtocolList(); initProtocolList();
}); });
@ -766,6 +789,7 @@
defineExpose({ defineExpose({
addDebugTab, addDebugTab,
openApiTab,
}); });
</script> </script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,8 +30,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core'; 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 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'; import { useI18n } from '@/hooks/useI18n';
@ -121,7 +121,7 @@
{ {
title: 'project.environmental.desc', title: 'project.environmental.desc',
dataIndex: 'description', dataIndex: 'description',
slotName: 'desc', slotName: 'description',
showInTable: true, showInTable: true,
showDrag: true, showDrag: true,
}, },

View File

@ -34,7 +34,7 @@
</template> </template>
<script lang="ts" setup> <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 MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';

View File

@ -17,8 +17,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core'; 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 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'; import { useI18n } from '@/hooks/useI18n';
@ -59,7 +59,7 @@
{ {
title: 'apiTestDebug.desc', title: 'apiTestDebug.desc',
dataIndex: 'description', dataIndex: 'description',
slotName: 'desc', slotName: 'description',
}, },
{ {
title: '', title: '',