fix(all): 修复严重 bug
This commit is contained in:
parent
00ee3a53a3
commit
5fb59a66f7
|
@ -102,6 +102,7 @@ module.exports = {
|
|||
'^localforage$',
|
||||
'vue-draggable-plus',
|
||||
'jsonpath-plus',
|
||||
'lossless-json',
|
||||
], // node依赖
|
||||
['.*/assets/.*', '^@/assets$'], // 项目静态资源
|
||||
['^@/components/pure/.*', '^@/components/business/.*', '.*\\.vue$'], // 组件
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
"jsonpath-plus": "^8.0.0",
|
||||
"localforage": "^1.10.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lossless-json": "^4.0.1",
|
||||
"mitt": "^3.0.1",
|
||||
"monaco-editor": "^0.39.0",
|
||||
"nprogress": "^0.2.0",
|
||||
|
|
|
@ -743,7 +743,7 @@
|
|||
.arco-switch-type-circle.arco-switch-checked:not(:disabled) {
|
||||
background-color: rgb(var(--primary-5)) !important;
|
||||
}
|
||||
.arco-switch-disabled {
|
||||
.arco-switch-type-circle.arco-switch-disabled {
|
||||
background-color: rgb(var(--primary-3)) !important;
|
||||
}
|
||||
.arco-switch-type-line.arco-switch-small {
|
||||
|
|
|
@ -438,7 +438,7 @@
|
|||
variableType: RequestExtractEnvType.TEMPORARY,
|
||||
extractScope: RequestExtractScope.BODY,
|
||||
expression: '',
|
||||
extractType: RequestExtractExpressionEnum.REGEX,
|
||||
extractType: RequestExtractExpressionEnum.JSON_PATH,
|
||||
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
|
||||
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
|
||||
resultMatchingRuleNum: 1,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="flex items-center">
|
||||
<div v-if="caseLevel" class="flex items-center">
|
||||
<div
|
||||
class="mr-[4px] h-[8px] w-[8px] rounded-full"
|
||||
:style="{
|
||||
|
@ -15,7 +15,7 @@
|
|||
import { CaseLevel } from './types';
|
||||
|
||||
const props = defineProps<{
|
||||
caseLevel: CaseLevel;
|
||||
caseLevel?: CaseLevel;
|
||||
}>();
|
||||
|
||||
const caseLevelMap = {
|
||||
|
@ -41,7 +41,7 @@
|
|||
},
|
||||
};
|
||||
|
||||
const caseLevel = computed(() => caseLevelMap[props.caseLevel] || {});
|
||||
const caseLevel = computed(() => (props.caseLevel ? caseLevelMap[props.caseLevel] : undefined));
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -26,14 +26,7 @@
|
|||
></a-input>
|
||||
<div class="config-card-footer">
|
||||
<div>
|
||||
<a-button
|
||||
type="outline"
|
||||
class="px-[8px]"
|
||||
size="mini"
|
||||
:disabled="apiConfig.userUrl.trim() === ''"
|
||||
:loading="testApiLoading"
|
||||
@click="testApi"
|
||||
>
|
||||
<a-button type="outline" class="px-[8px]" size="mini" :loading="testApiLoading" @click="testApi">
|
||||
{{ t('ms.personal.test') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
import MsCodeEditorTheme from './themes';
|
||||
import { CustomTheme, editorProps, Language, LanguageEnum, Theme } from './types';
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import * as XmlBeautify from 'xml-beautify';
|
||||
import XmlBeautify from 'xml-beautify';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MonacoEditor',
|
||||
|
@ -235,7 +235,7 @@
|
|||
} else if (currentLanguage.value === LanguageEnum.XML) {
|
||||
// XML需要手动格式化
|
||||
const value = editor.getValue();
|
||||
const formattedCode = new XmlBeautify({ parser: DOMParser }).beautify(value);
|
||||
const formattedCode = new XmlBeautify().beautify(value);
|
||||
editor.setValue(formattedCode);
|
||||
emit('update:modelValue', formattedCode);
|
||||
emit('change', formattedCode);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||
import { JSONPath } from 'jsonpath-plus';
|
||||
import { isSafeNumber, parse } from 'lossless-json';
|
||||
|
||||
import JPPicker from '@/assets/js/jsonpath-picker-vanilla/jsonpath-picker-vanilla';
|
||||
|
||||
|
@ -28,14 +29,20 @@
|
|||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'pick', path: string, result: any[]): void;
|
||||
(e: 'pick', path: string, parseJson: string | Record<string, any>, result: any[]): void;
|
||||
}>();
|
||||
|
||||
function initJsonPathPicker() {
|
||||
try {
|
||||
json.value = props.data;
|
||||
if (typeof props.data === 'string') {
|
||||
json.value = JSON.parse(props.data);
|
||||
json.value = parse(props.data, undefined, (value) => {
|
||||
if (!isSafeNumber(value) || Number(value).toString().length < value.length) {
|
||||
// 大数、超长小数、科学计数法、小数位全为 0 等情况下,JS 精度丢失,所以需要用字符串存储
|
||||
return `Number(${value.toString()})`;
|
||||
}
|
||||
return Number(value);
|
||||
}) as Record<string, any>;
|
||||
}
|
||||
JPPicker.jsonPathPicker(jr.value, json.value, [ip.value], props.opt);
|
||||
} catch (error) {
|
||||
|
@ -61,8 +68,15 @@
|
|||
if (ev.target && ev.target.classList.contains('pick-path')) {
|
||||
setTimeout(() => {
|
||||
if (ip.value) {
|
||||
jsonPath.value = ip.value.value;
|
||||
emit('pick', jsonPath.value, JSONPath({ json: json.value, path: jsonPath.value }));
|
||||
jsonPath.value = `$${ip.value.value}`;
|
||||
emit(
|
||||
'pick',
|
||||
jsonPath.value,
|
||||
json.value,
|
||||
JSONPath({ json: json.value, path: jsonPath.value }).map((e) =>
|
||||
e.toString().replace(/Number\(([^)]+)\)/g, '$1')
|
||||
)
|
||||
);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
|
|
@ -48,21 +48,27 @@
|
|||
const sameNodesIndex = document.evaluate(
|
||||
`count(ancestor-or-self::*/preceding-sibling::${node.nodeName}) + 1`,
|
||||
node,
|
||||
null,
|
||||
(prefix) => {
|
||||
// 获取 XML 文档的根元素
|
||||
const root = node.ownerDocument.documentElement;
|
||||
// 从根元素获取命名空间的 URI
|
||||
const nsUri = root.getAttributeNS('http://www.w3.org/2000/xmlns/', prefix || '');
|
||||
return nsUri || null;
|
||||
},
|
||||
XPathResult.NUMBER_TYPE,
|
||||
null
|
||||
).numberValue; // 这里是执行 XPATH 表达式,获取当前节点在同级节点中的下标
|
||||
|
||||
const xpath = `${currentPath}/${node.tagName}[${sameNodesIndex}]`; // 拼接规则:当前路径/当前节点名[当前节点在同级同名节点中的下标]
|
||||
tempXmls.value.push({ content: node.tagName, xpath });
|
||||
const xpath = `${currentPath}/${node.nodeName}[${sameNodesIndex}]`; // 拼接规则:当前路径/当前节点名[当前节点在同级同名节点中的下标]
|
||||
tempXmls.value.push({ content: node.nodeName, xpath });
|
||||
const children = Array.from(node.children);
|
||||
children.forEach((child) => {
|
||||
flattenXml(child, xpath); // 递归处理子节点
|
||||
});
|
||||
} else {
|
||||
// 同级的同名节点数量等于 1 时,不需要给当前节点名的 xpath 添加下标,因为这个标签是唯一的
|
||||
const xpath = `${currentPath}/${node.tagName}`;
|
||||
tempXmls.value.push({ content: node.tagName, xpath });
|
||||
const xpath = `${currentPath}/${node.nodeName}`;
|
||||
tempXmls.value.push({ content: node.nodeName, xpath });
|
||||
const children = Array.from(node.children);
|
||||
children.forEach((child) => {
|
||||
flattenXml(child, xpath);
|
||||
|
|
|
@ -12,8 +12,17 @@ export function matchXMLWithXPath(xmlText: string, xpathQuery: string): xpath.Se
|
|||
// 解析 XML 文本
|
||||
const xmlDoc = new DOMParser().parseFromString(xmlText, 'text/xml');
|
||||
|
||||
// 创建一个命名空间解析器
|
||||
const resolver = (prefix: string) => {
|
||||
// 获取 XML 文档的根元素
|
||||
const root = xmlDoc.documentElement;
|
||||
// 从根元素获取命名空间的 URI
|
||||
const nsUri = root.getAttributeNS('http://www.w3.org/2000/xmlns/', prefix);
|
||||
return nsUri || null;
|
||||
};
|
||||
|
||||
// 使用 XPath 查询匹配的节点
|
||||
const nodes = xpath.select(xpathQuery, xmlDoc);
|
||||
const nodes = xpath.selectWithResolver(xpathQuery, xmlDoc, { lookupNamespaceURI: resolver });
|
||||
|
||||
// 返回匹配结果
|
||||
return nodes;
|
||||
|
|
|
@ -48,9 +48,9 @@
|
|||
<div class="flex items-center justify-between">
|
||||
<a-radio-group v-model="condition.enableCommonScript" class="mb-[8px]" @change="emit('change')">
|
||||
<a-radio :value="false">{{ t('apiTestDebug.manual') }}</a-radio>
|
||||
<a-radio v-if="hasAnyPermission(['PROJECT_CUSTOM_FUNCTION:READ'])" :value="true">{{
|
||||
t('apiTestDebug.quote')
|
||||
}}</a-radio>
|
||||
<a-radio v-if="hasAnyPermission(['PROJECT_CUSTOM_FUNCTION:READ'])" :value="true">
|
||||
{{ t('apiTestDebug.quote') }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
<div v-if="props.showAssociatedScene" class="flex items-center">
|
||||
<a-switch
|
||||
|
@ -134,7 +134,6 @@
|
|||
</div>
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<a-button
|
||||
v-if="props.isFormat"
|
||||
:disabled="props.disabled"
|
||||
type="outline"
|
||||
class="arco-btn-outline--secondary p-[0_8px]"
|
||||
|
@ -146,18 +145,6 @@
|
|||
</template>
|
||||
{{ t('project.commonScript.formatting') }}
|
||||
</a-button>
|
||||
<!-- <a-button-->
|
||||
<!-- :disabled="props.disabled"-->
|
||||
<!-- type="outline"-->
|
||||
<!-- class="arco-btn-outline--secondary p-[0_8px]"-->
|
||||
<!-- size="mini"-->
|
||||
<!-- @click="undoScript"-->
|
||||
<!-- >-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <MsIcon type="icon-icon_undo_outlined" class="text-var(--color-text-4)" size="12" />-->
|
||||
<!-- </template>-->
|
||||
<!-- {{ t('common.revoke') }}-->
|
||||
<!-- </a-button>-->
|
||||
<a-button
|
||||
:disabled="props.disabled"
|
||||
type="outline"
|
||||
|
@ -312,14 +299,8 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="sql-table-container">
|
||||
<div class="mb-[8px] flex items-center text-[var(--color-text-1)]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.extractParameter') }}
|
||||
<a-tooltip position="right" :content="t('apiTestDebug.storageResultTip')">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<paramTable
|
||||
:params="condition.extractParams"
|
||||
|
@ -331,7 +312,15 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mt-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByResult') }}</div>
|
||||
<div class="mb-[8px] flex items-center text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.storageByResult') }}
|
||||
<a-tooltip position="right" :content="t('apiTestDebug.storageResultTip')">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-input
|
||||
v-model:model-value="condition.resultVariable"
|
||||
:disabled="props.disabled"
|
||||
|
@ -398,7 +387,7 @@
|
|||
@change="() => handleExpressionChange(rowIndex)"
|
||||
>
|
||||
<template #suffix>
|
||||
<a-tooltip :disabled="!disabledExpressionSuffix">
|
||||
<a-tooltip v-if="!props.disabled" :disabled="!!props.response">
|
||||
<template #content>
|
||||
<div>{{ t('apiTestDebug.expressionTip1') }}</div>
|
||||
<div>{{ t('apiTestDebug.expressionTip2') }}</div>
|
||||
|
@ -407,11 +396,7 @@
|
|||
<MsIcon
|
||||
type="icon-icon_flashlamp"
|
||||
:size="15"
|
||||
:class="
|
||||
disabledExpressionSuffix || props.disabled
|
||||
? 'ms-params-input-suffix-icon--disabled'
|
||||
: 'ms-params-input-suffix-icon'
|
||||
"
|
||||
:class="!props.response ? 'ms-params-input-suffix-icon--disabled' : 'ms-params-input-suffix-icon'"
|
||||
@click.stop="() => showFastExtraction(record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
|
@ -522,7 +507,6 @@
|
|||
requestRadioTextProps?: Record<string, any>; // 前后置请求前后置按钮文本
|
||||
showPrePostRequest?: boolean; // 是否展示前后置请求忽略
|
||||
totalList?: ExecuteConditionProcessor[]; // 总列表
|
||||
isFormat?: boolean;
|
||||
}>(),
|
||||
{
|
||||
showAssociatedScene: false,
|
||||
|
@ -744,10 +728,6 @@ if (!result){
|
|||
dataIndex: 'extractType',
|
||||
slotName: 'extractType',
|
||||
typeOptions: [
|
||||
{
|
||||
label: t('apiTestDebug.regular'),
|
||||
value: RequestExtractExpressionEnum.REGEX,
|
||||
},
|
||||
{
|
||||
label: 'JSONPath',
|
||||
value: RequestExtractExpressionEnum.JSON_PATH,
|
||||
|
@ -756,6 +736,10 @@ if (!result){
|
|||
label: 'XPath',
|
||||
value: RequestExtractExpressionEnum.X_PATH,
|
||||
},
|
||||
{
|
||||
label: t('apiTestDebug.regular'),
|
||||
value: RequestExtractExpressionEnum.REGEX,
|
||||
},
|
||||
],
|
||||
width: 120,
|
||||
},
|
||||
|
@ -822,7 +806,6 @@ if (!result){
|
|||
width: 80,
|
||||
},
|
||||
];
|
||||
const disabledExpressionSuffix = ref(false);
|
||||
|
||||
function handleExtractParamTableChange(resultArr: any[], isInit?: boolean) {
|
||||
condition.value.extractors = [...resultArr];
|
||||
|
@ -838,7 +821,7 @@ if (!result){
|
|||
variableType: RequestExtractEnvType.TEMPORARY,
|
||||
extractScope: RequestExtractScope.BODY,
|
||||
expression: '',
|
||||
extractType: RequestExtractExpressionEnum.REGEX,
|
||||
extractType: RequestExtractExpressionEnum.JSON_PATH,
|
||||
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
|
||||
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
|
||||
resultMatchingRuleNum: 1,
|
||||
|
@ -849,7 +832,7 @@ if (!result){
|
|||
const activeRecord = ref({ ...defaultExtractParamItem }); // 用于暂存当前操作的提取参数表格项
|
||||
|
||||
function showFastExtraction(record: ExpressionConfig) {
|
||||
if (props.disabled) return;
|
||||
if (props.disabled || !props.response) return;
|
||||
activeRecord.value = { ...record };
|
||||
fastExtractionVisible.value = true;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsList from '@/components/pure/ms-list/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
|
@ -155,7 +156,7 @@
|
|||
*/
|
||||
function copyListItem(item: ExecuteConditionProcessor) {
|
||||
let copyItem = {
|
||||
...item,
|
||||
...cloneDeep(item),
|
||||
id: new Date().getTime(),
|
||||
};
|
||||
const isExistPre = data.value.filter(
|
||||
|
@ -171,7 +172,7 @@
|
|||
}
|
||||
|
||||
copyItem = {
|
||||
...item,
|
||||
...cloneDeep(item),
|
||||
beforeStepScript: !isExistPre,
|
||||
id: new Date().getTime(),
|
||||
};
|
||||
|
|
|
@ -115,7 +115,7 @@
|
|||
</div>
|
||||
<div class="match-result">
|
||||
<div v-if="isMatched && matchResult.length === 0">{{ t('apiTestDebug.noMatchResult') }}</div>
|
||||
<pre v-for="(e, i) of matchResult" :key="i">{{ e }}</pre>
|
||||
<pre v-for="(e, i) of matchResult" :key="i">{{ `${e}` }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<a-collapse
|
||||
|
@ -184,6 +184,7 @@
|
|||
const innerVisible = useVModel(props, 'visible', emit);
|
||||
const expressionForm = ref({ ...props.config });
|
||||
const expressionFormRef = ref<typeof FormInstance>();
|
||||
const parseJson = ref<string | Record<string, any>>({});
|
||||
const matchResult = ref<any[]>([]); // 当前匹配结果
|
||||
const isMatched = ref(false); // 是否执行过匹配
|
||||
|
||||
|
@ -198,8 +199,9 @@
|
|||
}
|
||||
);
|
||||
|
||||
function handlePathPick(xpath: string) {
|
||||
expressionForm.value.expression = xpath;
|
||||
function handlePathPick(path: string, _parseJson: string | Record<string, any>) {
|
||||
expressionForm.value.expression = path;
|
||||
parseJson.value = _parseJson;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -231,9 +233,9 @@
|
|||
try {
|
||||
matchResult.value =
|
||||
JSONPath({
|
||||
json: props.response ? JSON.parse(props.response) : '',
|
||||
json: parseJson.value,
|
||||
path: expressionForm.value.expression,
|
||||
}) || [];
|
||||
})?.map((e) => e.toString().replace(/Number\(([^)]+)\)/g, '$1')) || [];
|
||||
} catch (error) {
|
||||
matchResult.value = JSONPath({ json: props.response || '', path: expressionForm.value.expression }) || [];
|
||||
}
|
||||
|
|
|
@ -113,7 +113,6 @@
|
|||
:disabled="props.disabledExceptParam"
|
||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||
class="ms-form-table-input"
|
||||
:max-length="255"
|
||||
size="mini"
|
||||
@input="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
|
||||
/>
|
||||
|
@ -205,7 +204,6 @@
|
|||
:disabled="props.disabledParamValue"
|
||||
class="ms-form-table-input"
|
||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||
:max-length="255"
|
||||
size="mini"
|
||||
@input="() => addTableLine(rowIndex)"
|
||||
/>
|
||||
|
|
|
@ -140,7 +140,11 @@
|
|||
function searchDataSource() {
|
||||
propsRes.value.data = cloneDeep(currentEnvConfig?.value.dataSources) as any[];
|
||||
if (keyword.value.trim() !== '') {
|
||||
propsRes.value.data = propsRes.value.data.filter((e) => e.dataSource.includes(keyword.value));
|
||||
propsRes.value.data = currentEnvConfig?.value.dataSources.filter((e) =>
|
||||
e.dataSource.includes(keyword.value)
|
||||
) as any[];
|
||||
} else {
|
||||
propsRes.value.data = cloneDeep(currentEnvConfig?.value.dataSources) as any[];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,23 +72,28 @@
|
|||
hasAnyPermission([props.permissionMap.execute])
|
||||
"
|
||||
>
|
||||
<a-dropdown-button
|
||||
v-if="hasLocalExec"
|
||||
:disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
|
||||
class="exec-btn"
|
||||
@click="() => execute(isPriorityLocalExec ? 'localExec' : 'serverExec')"
|
||||
@select="execute"
|
||||
>
|
||||
{{ isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }}
|
||||
<template #icon>
|
||||
<icon-down />
|
||||
</template>
|
||||
<template #content>
|
||||
<a-doption :value="isPriorityLocalExec ? 'serverExec' : 'localExec'">
|
||||
{{ isPriorityLocalExec ? t('apiTestDebug.serverExec') : t('apiTestDebug.localExec') }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown-button>
|
||||
<template v-if="hasLocalExec">
|
||||
<a-dropdown-button
|
||||
v-if="!requestVModel.executeLoading"
|
||||
:disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
|
||||
class="exec-btn"
|
||||
@click="() => execute(isPriorityLocalExec ? 'localExec' : 'serverExec')"
|
||||
@select="execute"
|
||||
>
|
||||
{{ isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }}
|
||||
<template #icon>
|
||||
<icon-down />
|
||||
</template>
|
||||
<template #content>
|
||||
<a-doption :value="isPriorityLocalExec ? 'serverExec' : 'localExec'">
|
||||
{{ isPriorityLocalExec ? t('apiTestDebug.serverExec') : t('apiTestDebug.localExec') }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown-button>
|
||||
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">
|
||||
{{ t('common.stop') }}
|
||||
</a-button>
|
||||
</template>
|
||||
<a-button
|
||||
v-else-if="!requestVModel.executeLoading"
|
||||
class="mr-[12px]"
|
||||
|
@ -162,7 +167,7 @@
|
|||
class="no-content relative mt-[8px] border-b"
|
||||
/>
|
||||
</div>
|
||||
<div ref="splitContainerRef" class="request-and-response h-[calc(100%-87px)]">
|
||||
<div ref="splitContainerRef" class="request-and-response h-[calc(100%-100px)]">
|
||||
<MsSplitBox
|
||||
ref="horizontalSplitBoxRef"
|
||||
:size="!props.isCase && props.isDefinition ? 0.7 : 1"
|
||||
|
@ -551,9 +556,9 @@
|
|||
import { getPluginScript, getProtocolList } from '@/api/modules/api-test/common';
|
||||
import { addCase } from '@/api/modules/api-test/management';
|
||||
import { getSocket } from '@/api/modules/project-management/commonScript';
|
||||
import { getLocalConfig } from '@/api/modules/user/index';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { filterTree, getGenerateId, parseQueryParams } from '@/utils';
|
||||
import { scrollIntoView } from '@/utils/dom';
|
||||
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
|
||||
|
@ -646,6 +651,7 @@
|
|||
const emit = defineEmits(['addDone', 'execute']);
|
||||
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const loading = defineModel<boolean>('detailLoading', { default: false });
|
||||
|
@ -814,26 +820,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
const hasLocalExec = ref(false); // 是否配置了api本地执行
|
||||
const isPriorityLocalExec = ref(false); // 是否优先本地执行
|
||||
const localExecuteUrl = ref('');
|
||||
async function initLocalConfig() {
|
||||
if (hasLocalExec.value) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await getLocalConfig();
|
||||
const apiLocalExec = res.find((e) => e.type === 'API');
|
||||
if (apiLocalExec) {
|
||||
hasLocalExec.value = true;
|
||||
isPriorityLocalExec.value = apiLocalExec.enable || false;
|
||||
localExecuteUrl.value = apiLocalExec.userUrl || '';
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
const hasLocalExec = computed(() => userStore.hasLocalExec); // 是否配置了api本地执行
|
||||
const isPriorityLocalExec = computed(() => userStore.isPriorityLocalExec); // 是否优先本地执行
|
||||
const localExecuteUrl = computed(() => userStore.localExecuteUrl); // 本地执行地址
|
||||
|
||||
const pluginScriptMap = ref<Record<string, PluginConfig>>({}); // 存储初始化过后的插件配置
|
||||
const temporaryPluginFormMap: Record<string, any> = {}; // 缓存插件表单,避免切换tab导致动态表单数据丢失
|
||||
|
@ -1300,7 +1289,7 @@
|
|||
requestVModel.value.executeLoading = true;
|
||||
requestVModel.value.response = cloneDeep(defaultResponse);
|
||||
const res = await props.executeApi(makeRequestParams(executeType));
|
||||
if (executeType === 'localExec' && props.localExecuteApi) {
|
||||
if (executeType === 'localExec' && props.localExecuteApi && localExecuteUrl.value) {
|
||||
await props.localExecuteApi(localExecuteUrl.value, res);
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -1318,7 +1307,7 @@
|
|||
requestVModel.value.executeLoading = true;
|
||||
requestVModel.value.response = cloneDeep(defaultResponse);
|
||||
const res = await props.executeApi(makeRequestParams(executeType));
|
||||
if (executeType === 'localExec' && props.localExecuteApi) {
|
||||
if (executeType === 'localExec' && props.localExecuteApi && localExecuteUrl.value) {
|
||||
await props.localExecuteApi(localExecuteUrl.value, res);
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -1661,12 +1650,6 @@
|
|||
saveModalFormRef.value?.resetFields();
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (!props.isCase) {
|
||||
initLocalConfig();
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (!props.isDefinition) {
|
||||
registerCatchSaveShortcut(handleSaveShortcut);
|
||||
|
@ -1681,7 +1664,6 @@
|
|||
|
||||
defineExpose({
|
||||
execute,
|
||||
isPriorityLocalExec,
|
||||
makeRequestParams,
|
||||
changeVerticalExpand,
|
||||
});
|
||||
|
|
|
@ -206,7 +206,7 @@
|
|||
const responseTabs = defineModel<ResponseItem[]>('responseDefinition', {
|
||||
required: true,
|
||||
});
|
||||
const activeResponse = ref<ResponseItem>(responseTabs.value[0]);
|
||||
const activeResponse = ref<ResponseItem>(responseTabs.value[0] || defaultResponseItem);
|
||||
|
||||
function addResponseTab(defaultProps?: Partial<ResponseItem>) {
|
||||
responseTabs.value.push({
|
||||
|
@ -267,7 +267,7 @@
|
|||
break;
|
||||
case 'copy':
|
||||
addResponseTab({
|
||||
..._tab,
|
||||
...cloneDeep(_tab),
|
||||
id: new Date().getTime(),
|
||||
label: `copy_${t(_tab.label || _tab.name)}`,
|
||||
name: `copy_${t(_tab.label || _tab.name)}`,
|
||||
|
|
|
@ -11,8 +11,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
|
|
|
@ -23,8 +23,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
class="mr-[8px] w-[240px]"
|
||||
@search="loadApiList"
|
||||
@press-enter="loadApiList"
|
||||
@clear="loadApiList"
|
||||
/>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary !p-[8px]" @click="loadApiList">
|
||||
<template #icon>
|
||||
|
|
|
@ -140,6 +140,7 @@
|
|||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import { ProtocolItem } from '@/models/apiTest/common';
|
||||
|
@ -174,14 +175,14 @@
|
|||
(e: 'deleteApi', id: string): void;
|
||||
(e: 'import'): void;
|
||||
}>();
|
||||
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
||||
|
||||
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
|
||||
|
||||
const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
||||
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
||||
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
|
||||
const apiTabs = defineModel<RequestParam[]>('apiTabs', {
|
||||
required: true,
|
||||
});
|
||||
|
@ -387,7 +388,7 @@
|
|||
activeApiTab.value.definitionActiveKey = 'definition';
|
||||
activeApiTab.value.isExecute = true;
|
||||
activeApiTab.value.mode = 'debug';
|
||||
requestCompositionRef.value?.execute(requestCompositionRef.value?.isPriorityLocalExec ? 'localExec' : 'serverExec');
|
||||
requestCompositionRef.value?.execute(userStore.localExecuteUrl ? 'localExec' : 'serverExec');
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
|
|
|
@ -319,19 +319,23 @@
|
|||
</div>
|
||||
</template>
|
||||
<a-spin v-else :loading="previewDetail.executeLoading" class="h-[calc(100%-45px)] w-full pb-[18px]">
|
||||
<result
|
||||
<div
|
||||
v-show="
|
||||
previewDetail.protocol === 'HTTP' || previewDetail.response?.requestResults[0]?.responseResult.responseCode
|
||||
"
|
||||
v-model:active-tab="previewDetail.responseActiveTab"
|
||||
:request-result="previewDetail.response?.requestResults[0]"
|
||||
:console="previewDetail.response?.console"
|
||||
:is-http-protocol="previewDetail.protocol === 'HTTP'"
|
||||
:is-priority-local-exec="props.isPriorityLocalExec"
|
||||
:request-url="previewDetail.url"
|
||||
is-definition
|
||||
@execute="emit('execute', props.isPriorityLocalExec ? 'localExec' : 'serverExec')"
|
||||
/>
|
||||
class="h-full"
|
||||
>
|
||||
<result
|
||||
v-model:active-tab="previewDetail.responseActiveTab"
|
||||
:request-result="previewDetail.response?.requestResults[0]"
|
||||
:console="previewDetail.response?.console"
|
||||
:is-http-protocol="previewDetail.protocol === 'HTTP'"
|
||||
:is-priority-local-exec="props.isPriorityLocalExec"
|
||||
:request-url="previewDetail.url"
|
||||
is-definition
|
||||
@execute="emit('execute', props.isPriorityLocalExec ? 'localExec' : 'serverExec')"
|
||||
/>
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-collapse-item>
|
||||
</a-collapse>
|
||||
|
|
|
@ -456,15 +456,15 @@
|
|||
{
|
||||
polymorphicName: 'MsCommonElement', // 协议多态名称,写死MsCommonElement
|
||||
assertionConfig: {
|
||||
enableGlobal: true,
|
||||
enableGlobal: false,
|
||||
assertions: [],
|
||||
},
|
||||
postProcessorConfig: {
|
||||
enableGlobal: true,
|
||||
enableGlobal: false,
|
||||
processors: [],
|
||||
},
|
||||
preProcessorConfig: {
|
||||
enableGlobal: true,
|
||||
enableGlobal: false,
|
||||
processors: [],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -9,26 +9,28 @@
|
|||
@close="handleClose"
|
||||
@cancel="handleDrawerCancel"
|
||||
>
|
||||
<div class="ml-[16px] mt-[10px]">
|
||||
<!-- <stepTypeVue v-if="props.step" :step="props.step" /> -->
|
||||
{{ t('apiScenario.scriptOperationName') }}
|
||||
</div>
|
||||
<div class="ml-[16px] mt-[3px] max-w-[70%]">
|
||||
<a-input
|
||||
v-model="scriptName"
|
||||
:placeholder="t('apiScenario.scriptOperationNamePlaceholder')"
|
||||
:max-length="255"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-[10px] flex flex-1 gap-[8px]">
|
||||
<conditionContent v-if="visible" v-model:data="activeItem" :is-build-in="true" :is-format="true" />
|
||||
</div>
|
||||
<div v-if="currentResponse?.console" class="p-[8px]">
|
||||
<div class="mb-[8px] font-medium text-[var(--color-text-1)]">{{ t('apiScenario.executionResult') }}</div>
|
||||
<loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" />
|
||||
<div class="h-[300px] bg-[var(--color-text-n9)] p-[12px]">
|
||||
<pre class="response-header-pre">{{ currentResponse?.console }}</pre>
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="ml-[16px] mt-[10px]">
|
||||
<!-- <stepTypeVue v-if="props.step" :step="props.step" /> -->
|
||||
{{ t('apiScenario.scriptOperationName') }}
|
||||
</div>
|
||||
<div class="ml-[16px] mt-[3px] max-w-[70%]">
|
||||
<a-input
|
||||
v-model="scriptName"
|
||||
:placeholder="t('apiScenario.scriptOperationNamePlaceholder')"
|
||||
:max-length="255"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-[10px] flex flex-1 gap-[8px]">
|
||||
<conditionContent v-if="visible" v-model:data="activeItem" :is-build-in="true" />
|
||||
</div>
|
||||
<div v-if="currentResponse?.console" class="p-[8px]">
|
||||
<div class="mb-[8px] font-medium text-[var(--color-text-1)]">{{ t('apiScenario.executionResult') }}</div>
|
||||
<loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" />
|
||||
<div class="h-[300px] bg-[var(--color-text-n9)] p-[12px]">
|
||||
<pre class="response-header-pre">{{ currentResponse?.console }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="!props.detail" #footer>
|
||||
|
|
|
@ -66,12 +66,12 @@
|
|||
</div>
|
||||
</template>
|
||||
<div v-if="!checkedAll && !indeterminate" class="action-group ml-auto">
|
||||
<a-input
|
||||
<!-- <a-input
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('apiScenario.searchByName')"
|
||||
allow-clear
|
||||
class="w-[200px]"
|
||||
/>
|
||||
/> -->
|
||||
<a-tooltip v-if="!props.isNew" position="left" :content="t('apiScenario.refreshRefScenario')">
|
||||
<a-button type="outline" class="arco-btn-outline--secondary !mr-0 !p-[8px]" @click="refreshStepInfo">
|
||||
<template #icon>
|
||||
|
|
|
@ -5,9 +5,8 @@
|
|||
v-model:model-value="innerData.delay"
|
||||
class="max-w-[500px] px-[8px]"
|
||||
size="mini"
|
||||
:step="1"
|
||||
:step="1000"
|
||||
:min="0"
|
||||
hide-button
|
||||
:precision="0"
|
||||
model-event="input"
|
||||
:disabled="props.disabled"
|
||||
|
|
Loading…
Reference in New Issue