fix(all): 修复严重 bug

This commit is contained in:
baiqi 2024-04-03 17:02:00 +08:00 committed by 刘瑞斌
parent 00ee3a53a3
commit 5fb59a66f7
26 changed files with 166 additions and 169 deletions

View File

@ -102,6 +102,7 @@ module.exports = {
'^localforage$', '^localforage$',
'vue-draggable-plus', 'vue-draggable-plus',
'jsonpath-plus', 'jsonpath-plus',
'lossless-json',
], // node依赖 ], // node依赖
['.*/assets/.*', '^@/assets$'], // 项目静态资源 ['.*/assets/.*', '^@/assets$'], // 项目静态资源
['^@/components/pure/.*', '^@/components/business/.*', '.*\\.vue$'], // 组件 ['^@/components/pure/.*', '^@/components/business/.*', '.*\\.vue$'], // 组件

View File

@ -63,6 +63,7 @@
"jsonpath-plus": "^8.0.0", "jsonpath-plus": "^8.0.0",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lossless-json": "^4.0.1",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"monaco-editor": "^0.39.0", "monaco-editor": "^0.39.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",

View File

@ -743,7 +743,7 @@
.arco-switch-type-circle.arco-switch-checked:not(:disabled) { .arco-switch-type-circle.arco-switch-checked:not(:disabled) {
background-color: rgb(var(--primary-5)) !important; background-color: rgb(var(--primary-5)) !important;
} }
.arco-switch-disabled { .arco-switch-type-circle.arco-switch-disabled {
background-color: rgb(var(--primary-3)) !important; background-color: rgb(var(--primary-3)) !important;
} }
.arco-switch-type-line.arco-switch-small { .arco-switch-type-line.arco-switch-small {

View File

@ -438,7 +438,7 @@
variableType: RequestExtractEnvType.TEMPORARY, variableType: RequestExtractEnvType.TEMPORARY,
extractScope: RequestExtractScope.BODY, extractScope: RequestExtractScope.BODY,
expression: '', expression: '',
extractType: RequestExtractExpressionEnum.REGEX, extractType: RequestExtractExpressionEnum.JSON_PATH,
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION, expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM, resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
resultMatchingRuleNum: 1, resultMatchingRuleNum: 1,

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="flex items-center"> <div v-if="caseLevel" class="flex items-center">
<div <div
class="mr-[4px] h-[8px] w-[8px] rounded-full" class="mr-[4px] h-[8px] w-[8px] rounded-full"
:style="{ :style="{
@ -15,7 +15,7 @@
import { CaseLevel } from './types'; import { CaseLevel } from './types';
const props = defineProps<{ const props = defineProps<{
caseLevel: CaseLevel; caseLevel?: CaseLevel;
}>(); }>();
const caseLevelMap = { const caseLevelMap = {
@ -41,7 +41,7 @@
}, },
}; };
const caseLevel = computed(() => caseLevelMap[props.caseLevel] || {}); const caseLevel = computed(() => (props.caseLevel ? caseLevelMap[props.caseLevel] : undefined));
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -26,14 +26,7 @@
></a-input> ></a-input>
<div class="config-card-footer"> <div class="config-card-footer">
<div> <div>
<a-button <a-button type="outline" class="px-[8px]" size="mini" :loading="testApiLoading" @click="testApi">
type="outline"
class="px-[8px]"
size="mini"
:disabled="apiConfig.userUrl.trim() === ''"
:loading="testApiLoading"
@click="testApi"
>
{{ t('ms.personal.test') }} {{ t('ms.personal.test') }}
</a-button> </a-button>
<a-button <a-button

View File

@ -81,7 +81,7 @@
import MsCodeEditorTheme from './themes'; import MsCodeEditorTheme from './themes';
import { CustomTheme, editorProps, Language, LanguageEnum, Theme } from './types'; import { CustomTheme, editorProps, Language, LanguageEnum, Theme } from './types';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import * as XmlBeautify from 'xml-beautify'; import XmlBeautify from 'xml-beautify';
export default defineComponent({ export default defineComponent({
name: 'MonacoEditor', name: 'MonacoEditor',
@ -235,7 +235,7 @@
} else if (currentLanguage.value === LanguageEnum.XML) { } else if (currentLanguage.value === LanguageEnum.XML) {
// XML // XML
const value = editor.getValue(); const value = editor.getValue();
const formattedCode = new XmlBeautify({ parser: DOMParser }).beautify(value); const formattedCode = new XmlBeautify().beautify(value);
editor.setValue(formattedCode); editor.setValue(formattedCode);
emit('update:modelValue', formattedCode); emit('update:modelValue', formattedCode);
emit('change', formattedCode); emit('change', formattedCode);

View File

@ -6,6 +6,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, onUnmounted, Ref, ref, watch } from 'vue'; import { onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { JSONPath } from 'jsonpath-plus'; import { JSONPath } from 'jsonpath-plus';
import { isSafeNumber, parse } from 'lossless-json';
import JPPicker from '@/assets/js/jsonpath-picker-vanilla/jsonpath-picker-vanilla'; import JPPicker from '@/assets/js/jsonpath-picker-vanilla/jsonpath-picker-vanilla';
@ -28,14 +29,20 @@
); );
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'pick', path: string, result: any[]): void; (e: 'pick', path: string, parseJson: string | Record<string, any>, result: any[]): void;
}>(); }>();
function initJsonPathPicker() { function initJsonPathPicker() {
try { try {
json.value = props.data; json.value = props.data;
if (typeof props.data === 'string') { 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); JPPicker.jsonPathPicker(jr.value, json.value, [ip.value], props.opt);
} catch (error) { } catch (error) {
@ -61,8 +68,15 @@
if (ev.target && ev.target.classList.contains('pick-path')) { if (ev.target && ev.target.classList.contains('pick-path')) {
setTimeout(() => { setTimeout(() => {
if (ip.value) { if (ip.value) {
jsonPath.value = ip.value.value; jsonPath.value = `$${ip.value.value}`;
emit('pick', jsonPath.value, JSONPath({ json: json.value, path: jsonPath.value })); emit(
'pick',
jsonPath.value,
json.value,
JSONPath({ json: json.value, path: jsonPath.value }).map((e) =>
e.toString().replace(/Number\(([^)]+)\)/g, '$1')
)
);
} }
}, 0); }, 0);
} }

View File

@ -48,21 +48,27 @@
const sameNodesIndex = document.evaluate( const sameNodesIndex = document.evaluate(
`count(ancestor-or-self::*/preceding-sibling::${node.nodeName}) + 1`, `count(ancestor-or-self::*/preceding-sibling::${node.nodeName}) + 1`,
node, 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, XPathResult.NUMBER_TYPE,
null null
).numberValue; // XPATH ).numberValue; // XPATH
const xpath = `${currentPath}/${node.tagName}[${sameNodesIndex}]`; // /[] const xpath = `${currentPath}/${node.nodeName}[${sameNodesIndex}]`; // /[]
tempXmls.value.push({ content: node.tagName, xpath }); tempXmls.value.push({ content: node.nodeName, xpath });
const children = Array.from(node.children); const children = Array.from(node.children);
children.forEach((child) => { children.forEach((child) => {
flattenXml(child, xpath); // flattenXml(child, xpath); //
}); });
} else { } else {
// 1 xpath // 1 xpath
const xpath = `${currentPath}/${node.tagName}`; const xpath = `${currentPath}/${node.nodeName}`;
tempXmls.value.push({ content: node.tagName, xpath }); tempXmls.value.push({ content: node.nodeName, xpath });
const children = Array.from(node.children); const children = Array.from(node.children);
children.forEach((child) => { children.forEach((child) => {
flattenXml(child, xpath); flattenXml(child, xpath);

View File

@ -12,8 +12,17 @@ export function matchXMLWithXPath(xmlText: string, xpathQuery: string): xpath.Se
// 解析 XML 文本 // 解析 XML 文本
const xmlDoc = new DOMParser().parseFromString(xmlText, 'text/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 查询匹配的节点 // 使用 XPath 查询匹配的节点
const nodes = xpath.select(xpathQuery, xmlDoc); const nodes = xpath.selectWithResolver(xpathQuery, xmlDoc, { lookupNamespaceURI: resolver });
// 返回匹配结果 // 返回匹配结果
return nodes; return nodes;

View File

@ -48,9 +48,9 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<a-radio-group v-model="condition.enableCommonScript" class="mb-[8px]" @change="emit('change')"> <a-radio-group v-model="condition.enableCommonScript" class="mb-[8px]" @change="emit('change')">
<a-radio :value="false">{{ t('apiTestDebug.manual') }}</a-radio> <a-radio :value="false">{{ t('apiTestDebug.manual') }}</a-radio>
<a-radio v-if="hasAnyPermission(['PROJECT_CUSTOM_FUNCTION:READ'])" :value="true">{{ <a-radio v-if="hasAnyPermission(['PROJECT_CUSTOM_FUNCTION:READ'])" :value="true">
t('apiTestDebug.quote') {{ t('apiTestDebug.quote') }}
}}</a-radio> </a-radio>
</a-radio-group> </a-radio-group>
<div v-if="props.showAssociatedScene" class="flex items-center"> <div v-if="props.showAssociatedScene" class="flex items-center">
<a-switch <a-switch
@ -134,7 +134,6 @@
</div> </div>
<div class="flex items-center gap-[8px]"> <div class="flex items-center gap-[8px]">
<a-button <a-button
v-if="props.isFormat"
:disabled="props.disabled" :disabled="props.disabled"
type="outline" type="outline"
class="arco-btn-outline--secondary p-[0_8px]" class="arco-btn-outline--secondary p-[0_8px]"
@ -146,18 +145,6 @@
</template> </template>
{{ t('project.commonScript.formatting') }} {{ t('project.commonScript.formatting') }}
</a-button> </a-button>
<!-- <a-button-->
<!-- :disabled="props.disabled"-->
<!-- type="outline"-->
<!-- class="arco-btn-outline&#45;&#45;secondary p-[0_8px]"-->
<!-- size="mini"-->
<!-- @click="undoScript"-->
<!-- >-->
<!-- <template #icon>-->
<!-- <MsIcon type="icon-icon_undo_outlined" class="text-var(&#45;&#45;color-text-4)" size="12" />-->
<!-- </template>-->
<!-- {{ t('common.revoke') }}-->
<!-- </a-button>-->
<a-button <a-button
:disabled="props.disabled" :disabled="props.disabled"
type="outline" type="outline"
@ -312,14 +299,8 @@
/> />
</div> </div>
<div class="sql-table-container"> <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') }} {{ 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> </div>
<paramTable <paramTable
:params="condition.extractParams" :params="condition.extractParams"
@ -331,7 +312,15 @@
/> />
</div> </div>
<div class="mt-[16px]"> <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 <a-input
v-model:model-value="condition.resultVariable" v-model:model-value="condition.resultVariable"
:disabled="props.disabled" :disabled="props.disabled"
@ -398,7 +387,7 @@
@change="() => handleExpressionChange(rowIndex)" @change="() => handleExpressionChange(rowIndex)"
> >
<template #suffix> <template #suffix>
<a-tooltip :disabled="!disabledExpressionSuffix"> <a-tooltip v-if="!props.disabled" :disabled="!!props.response">
<template #content> <template #content>
<div>{{ t('apiTestDebug.expressionTip1') }}</div> <div>{{ t('apiTestDebug.expressionTip1') }}</div>
<div>{{ t('apiTestDebug.expressionTip2') }}</div> <div>{{ t('apiTestDebug.expressionTip2') }}</div>
@ -407,11 +396,7 @@
<MsIcon <MsIcon
type="icon-icon_flashlamp" type="icon-icon_flashlamp"
:size="15" :size="15"
:class=" :class="!props.response ? 'ms-params-input-suffix-icon--disabled' : 'ms-params-input-suffix-icon'"
disabledExpressionSuffix || props.disabled
? 'ms-params-input-suffix-icon--disabled'
: 'ms-params-input-suffix-icon'
"
@click.stop="() => showFastExtraction(record)" @click.stop="() => showFastExtraction(record)"
/> />
</a-tooltip> </a-tooltip>
@ -522,7 +507,6 @@
requestRadioTextProps?: Record<string, any>; // requestRadioTextProps?: Record<string, any>; //
showPrePostRequest?: boolean; // showPrePostRequest?: boolean; //
totalList?: ExecuteConditionProcessor[]; // totalList?: ExecuteConditionProcessor[]; //
isFormat?: boolean;
}>(), }>(),
{ {
showAssociatedScene: false, showAssociatedScene: false,
@ -744,10 +728,6 @@ if (!result){
dataIndex: 'extractType', dataIndex: 'extractType',
slotName: 'extractType', slotName: 'extractType',
typeOptions: [ typeOptions: [
{
label: t('apiTestDebug.regular'),
value: RequestExtractExpressionEnum.REGEX,
},
{ {
label: 'JSONPath', label: 'JSONPath',
value: RequestExtractExpressionEnum.JSON_PATH, value: RequestExtractExpressionEnum.JSON_PATH,
@ -756,6 +736,10 @@ if (!result){
label: 'XPath', label: 'XPath',
value: RequestExtractExpressionEnum.X_PATH, value: RequestExtractExpressionEnum.X_PATH,
}, },
{
label: t('apiTestDebug.regular'),
value: RequestExtractExpressionEnum.REGEX,
},
], ],
width: 120, width: 120,
}, },
@ -822,7 +806,6 @@ if (!result){
width: 80, width: 80,
}, },
]; ];
const disabledExpressionSuffix = ref(false);
function handleExtractParamTableChange(resultArr: any[], isInit?: boolean) { function handleExtractParamTableChange(resultArr: any[], isInit?: boolean) {
condition.value.extractors = [...resultArr]; condition.value.extractors = [...resultArr];
@ -838,7 +821,7 @@ if (!result){
variableType: RequestExtractEnvType.TEMPORARY, variableType: RequestExtractEnvType.TEMPORARY,
extractScope: RequestExtractScope.BODY, extractScope: RequestExtractScope.BODY,
expression: '', expression: '',
extractType: RequestExtractExpressionEnum.REGEX, extractType: RequestExtractExpressionEnum.JSON_PATH,
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION, expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM, resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
resultMatchingRuleNum: 1, resultMatchingRuleNum: 1,
@ -849,7 +832,7 @@ if (!result){
const activeRecord = ref({ ...defaultExtractParamItem }); // const activeRecord = ref({ ...defaultExtractParamItem }); //
function showFastExtraction(record: ExpressionConfig) { function showFastExtraction(record: ExpressionConfig) {
if (props.disabled) return; if (props.disabled || !props.response) return;
activeRecord.value = { ...record }; activeRecord.value = { ...record };
fastExtractionVisible.value = true; fastExtractionVisible.value = true;
} }

View File

@ -60,6 +60,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { cloneDeep } from 'lodash-es';
import MsList from '@/components/pure/ms-list/index.vue'; import MsList from '@/components/pure/ms-list/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types'; import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
@ -155,7 +156,7 @@
*/ */
function copyListItem(item: ExecuteConditionProcessor) { function copyListItem(item: ExecuteConditionProcessor) {
let copyItem = { let copyItem = {
...item, ...cloneDeep(item),
id: new Date().getTime(), id: new Date().getTime(),
}; };
const isExistPre = data.value.filter( const isExistPre = data.value.filter(
@ -171,7 +172,7 @@
} }
copyItem = { copyItem = {
...item, ...cloneDeep(item),
beforeStepScript: !isExistPre, beforeStepScript: !isExistPre,
id: new Date().getTime(), id: new Date().getTime(),
}; };

View File

@ -115,7 +115,7 @@
</div> </div>
<div class="match-result"> <div class="match-result">
<div v-if="isMatched && matchResult.length === 0">{{ t('apiTestDebug.noMatchResult') }}</div> <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>
</div> </div>
<a-collapse <a-collapse
@ -184,6 +184,7 @@
const innerVisible = useVModel(props, 'visible', emit); const innerVisible = useVModel(props, 'visible', emit);
const expressionForm = ref({ ...props.config }); const expressionForm = ref({ ...props.config });
const expressionFormRef = ref<typeof FormInstance>(); const expressionFormRef = ref<typeof FormInstance>();
const parseJson = ref<string | Record<string, any>>({});
const matchResult = ref<any[]>([]); // const matchResult = ref<any[]>([]); //
const isMatched = ref(false); // const isMatched = ref(false); //
@ -198,8 +199,9 @@
} }
); );
function handlePathPick(xpath: string) { function handlePathPick(path: string, _parseJson: string | Record<string, any>) {
expressionForm.value.expression = xpath; expressionForm.value.expression = path;
parseJson.value = _parseJson;
} }
/* /*
@ -231,9 +233,9 @@
try { try {
matchResult.value = matchResult.value =
JSONPath({ JSONPath({
json: props.response ? JSON.parse(props.response) : '', json: parseJson.value,
path: expressionForm.value.expression, path: expressionForm.value.expression,
}) || []; })?.map((e) => e.toString().replace(/Number\(([^)]+)\)/g, '$1')) || [];
} catch (error) { } catch (error) {
matchResult.value = JSONPath({ json: props.response || '', path: expressionForm.value.expression }) || []; matchResult.value = JSONPath({ json: props.response || '', path: expressionForm.value.expression }) || [];
} }

View File

@ -113,7 +113,6 @@
:disabled="props.disabledExceptParam" :disabled="props.disabledExceptParam"
:placeholder="t('apiTestDebug.commonPlaceholder')" :placeholder="t('apiTestDebug.commonPlaceholder')"
class="ms-form-table-input" class="ms-form-table-input"
:max-length="255"
size="mini" size="mini"
@input="() => addTableLine(rowIndex, columnConfig.addLineDisabled)" @input="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
/> />
@ -205,7 +204,6 @@
:disabled="props.disabledParamValue" :disabled="props.disabledParamValue"
class="ms-form-table-input" class="ms-form-table-input"
:placeholder="t('apiTestDebug.commonPlaceholder')" :placeholder="t('apiTestDebug.commonPlaceholder')"
:max-length="255"
size="mini" size="mini"
@input="() => addTableLine(rowIndex)" @input="() => addTableLine(rowIndex)"
/> />

View File

@ -140,7 +140,11 @@
function searchDataSource() { function searchDataSource() {
propsRes.value.data = cloneDeep(currentEnvConfig?.value.dataSources) as any[]; propsRes.value.data = cloneDeep(currentEnvConfig?.value.dataSources) as any[];
if (keyword.value.trim() !== '') { 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[];
} }
} }

View File

@ -72,23 +72,28 @@
hasAnyPermission([props.permissionMap.execute]) hasAnyPermission([props.permissionMap.execute])
" "
> >
<a-dropdown-button <template v-if="hasLocalExec">
v-if="hasLocalExec" <a-dropdown-button
:disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)" v-if="!requestVModel.executeLoading"
class="exec-btn" :disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
@click="() => execute(isPriorityLocalExec ? 'localExec' : 'serverExec')" class="exec-btn"
@select="execute" @click="() => execute(isPriorityLocalExec ? 'localExec' : 'serverExec')"
> @select="execute"
{{ isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }} >
<template #icon> {{ isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }}
<icon-down /> <template #icon>
</template> <icon-down />
<template #content> </template>
<a-doption :value="isPriorityLocalExec ? 'serverExec' : 'localExec'"> <template #content>
{{ isPriorityLocalExec ? t('apiTestDebug.serverExec') : t('apiTestDebug.localExec') }} <a-doption :value="isPriorityLocalExec ? 'serverExec' : 'localExec'">
</a-doption> {{ isPriorityLocalExec ? t('apiTestDebug.serverExec') : t('apiTestDebug.localExec') }}
</template> </a-doption>
</a-dropdown-button> </template>
</a-dropdown-button>
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">
{{ t('common.stop') }}
</a-button>
</template>
<a-button <a-button
v-else-if="!requestVModel.executeLoading" v-else-if="!requestVModel.executeLoading"
class="mr-[12px]" class="mr-[12px]"
@ -162,7 +167,7 @@
class="no-content relative mt-[8px] border-b" class="no-content relative mt-[8px] border-b"
/> />
</div> </div>
<div ref="splitContainerRef" class="request-and-response h-[calc(100%-87px)]"> <div ref="splitContainerRef" class="request-and-response h-[calc(100%-100px)]">
<MsSplitBox <MsSplitBox
ref="horizontalSplitBoxRef" ref="horizontalSplitBoxRef"
:size="!props.isCase && props.isDefinition ? 0.7 : 1" :size="!props.isCase && props.isDefinition ? 0.7 : 1"
@ -551,9 +556,9 @@
import { getPluginScript, getProtocolList } from '@/api/modules/api-test/common'; import { getPluginScript, getProtocolList } from '@/api/modules/api-test/common';
import { addCase } from '@/api/modules/api-test/management'; import { addCase } from '@/api/modules/api-test/management';
import { getSocket } from '@/api/modules/project-management/commonScript'; import { getSocket } from '@/api/modules/project-management/commonScript';
import { getLocalConfig } from '@/api/modules/user/index';
import { useI18n } from '@/hooks/useI18n'; 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 { filterTree, getGenerateId, parseQueryParams } from '@/utils';
import { scrollIntoView } from '@/utils/dom'; import { scrollIntoView } from '@/utils/dom';
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event'; import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
@ -646,6 +651,7 @@
const emit = defineEmits(['addDone', 'execute']); const emit = defineEmits(['addDone', 'execute']);
const appStore = useAppStore(); const appStore = useAppStore();
const userStore = useUserStore();
const { t } = useI18n(); const { t } = useI18n();
const loading = defineModel<boolean>('detailLoading', { default: false }); const loading = defineModel<boolean>('detailLoading', { default: false });
@ -814,26 +820,9 @@
} }
} }
const hasLocalExec = ref(false); // api const hasLocalExec = computed(() => userStore.hasLocalExec); // api
const isPriorityLocalExec = ref(false); // const isPriorityLocalExec = computed(() => userStore.isPriorityLocalExec); //
const localExecuteUrl = ref(''); const localExecuteUrl = computed(() => userStore.localExecuteUrl); //
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 pluginScriptMap = ref<Record<string, PluginConfig>>({}); // const pluginScriptMap = ref<Record<string, PluginConfig>>({}); //
const temporaryPluginFormMap: Record<string, any> = {}; // tab const temporaryPluginFormMap: Record<string, any> = {}; // tab
@ -1300,7 +1289,7 @@
requestVModel.value.executeLoading = true; requestVModel.value.executeLoading = true;
requestVModel.value.response = cloneDeep(defaultResponse); requestVModel.value.response = cloneDeep(defaultResponse);
const res = await props.executeApi(makeRequestParams(executeType)); 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); await props.localExecuteApi(localExecuteUrl.value, res);
} }
} catch (error) { } catch (error) {
@ -1318,7 +1307,7 @@
requestVModel.value.executeLoading = true; requestVModel.value.executeLoading = true;
requestVModel.value.response = cloneDeep(defaultResponse); requestVModel.value.response = cloneDeep(defaultResponse);
const res = await props.executeApi(makeRequestParams(executeType)); 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); await props.localExecuteApi(localExecuteUrl.value, res);
} }
} catch (error) { } catch (error) {
@ -1661,12 +1650,6 @@
saveModalFormRef.value?.resetFields(); saveModalFormRef.value?.resetFields();
} }
onBeforeMount(() => {
if (!props.isCase) {
initLocalConfig();
}
});
onMounted(() => { onMounted(() => {
if (!props.isDefinition) { if (!props.isDefinition) {
registerCatchSaveShortcut(handleSaveShortcut); registerCatchSaveShortcut(handleSaveShortcut);
@ -1681,7 +1664,6 @@
defineExpose({ defineExpose({
execute, execute,
isPriorityLocalExec,
makeRequestParams, makeRequestParams,
changeVerticalExpand, changeVerticalExpand,
}); });

View File

@ -206,7 +206,7 @@
const responseTabs = defineModel<ResponseItem[]>('responseDefinition', { const responseTabs = defineModel<ResponseItem[]>('responseDefinition', {
required: true, required: true,
}); });
const activeResponse = ref<ResponseItem>(responseTabs.value[0]); const activeResponse = ref<ResponseItem>(responseTabs.value[0] || defaultResponseItem);
function addResponseTab(defaultProps?: Partial<ResponseItem>) { function addResponseTab(defaultProps?: Partial<ResponseItem>) {
responseTabs.value.push({ responseTabs.value.push({
@ -267,7 +267,7 @@
break; break;
case 'copy': case 'copy':
addResponseTab({ addResponseTab({
..._tab, ...cloneDeep(_tab),
id: new Date().getTime(), id: new Date().getTime(),
label: `copy_${t(_tab.label || _tab.name)}`, label: `copy_${t(_tab.label || _tab.name)}`,
name: `copy_${t(_tab.label || _tab.name)}`, name: `copy_${t(_tab.label || _tab.name)}`,

View File

@ -11,8 +11,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type'; import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';

View File

@ -23,8 +23,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue';
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 MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';

View File

@ -9,6 +9,7 @@
class="mr-[8px] w-[240px]" class="mr-[8px] w-[240px]"
@search="loadApiList" @search="loadApiList"
@press-enter="loadApiList" @press-enter="loadApiList"
@clear="loadApiList"
/> />
<a-button type="outline" class="arco-btn-outline--secondary !p-[8px]" @click="loadApiList"> <a-button type="outline" class="arco-btn-outline--secondary !p-[8px]" @click="loadApiList">
<template #icon> <template #icon>

View File

@ -140,6 +140,7 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import useUserStore from '@/store/modules/user';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import { ProtocolItem } from '@/models/apiTest/common'; import { ProtocolItem } from '@/models/apiTest/common';
@ -174,14 +175,14 @@
(e: 'deleteApi', id: string): void; (e: 'deleteApi', id: string): void;
(e: 'import'): void; (e: 'import'): void;
}>(); }>();
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
const userStore = useUserStore();
const appStore = useAppStore(); const appStore = useAppStore();
const { t } = useI18n(); const { t } = useI18n();
const { openModal } = useModal(); const { openModal } = useModal();
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
const apiTabs = defineModel<RequestParam[]>('apiTabs', { const apiTabs = defineModel<RequestParam[]>('apiTabs', {
required: true, required: true,
}); });
@ -387,7 +388,7 @@
activeApiTab.value.definitionActiveKey = 'definition'; activeApiTab.value.definitionActiveKey = 'definition';
activeApiTab.value.isExecute = true; activeApiTab.value.isExecute = true;
activeApiTab.value.mode = 'debug'; activeApiTab.value.mode = 'debug';
requestCompositionRef.value?.execute(requestCompositionRef.value?.isPriorityLocalExec ? 'localExec' : 'serverExec'); requestCompositionRef.value?.execute(userStore.localExecuteUrl ? 'localExec' : 'serverExec');
} }
function handleDelete() { function handleDelete() {

View File

@ -319,19 +319,23 @@
</div> </div>
</template> </template>
<a-spin v-else :loading="previewDetail.executeLoading" class="h-[calc(100%-45px)] w-full pb-[18px]"> <a-spin v-else :loading="previewDetail.executeLoading" class="h-[calc(100%-45px)] w-full pb-[18px]">
<result <div
v-show=" v-show="
previewDetail.protocol === 'HTTP' || previewDetail.response?.requestResults[0]?.responseResult.responseCode previewDetail.protocol === 'HTTP' || previewDetail.response?.requestResults[0]?.responseResult.responseCode
" "
v-model:active-tab="previewDetail.responseActiveTab" class="h-full"
:request-result="previewDetail.response?.requestResults[0]" >
:console="previewDetail.response?.console" <result
:is-http-protocol="previewDetail.protocol === 'HTTP'" v-model:active-tab="previewDetail.responseActiveTab"
:is-priority-local-exec="props.isPriorityLocalExec" :request-result="previewDetail.response?.requestResults[0]"
:request-url="previewDetail.url" :console="previewDetail.response?.console"
is-definition :is-http-protocol="previewDetail.protocol === 'HTTP'"
@execute="emit('execute', props.isPriorityLocalExec ? 'localExec' : 'serverExec')" :is-priority-local-exec="props.isPriorityLocalExec"
/> :request-url="previewDetail.url"
is-definition
@execute="emit('execute', props.isPriorityLocalExec ? 'localExec' : 'serverExec')"
/>
</div>
</a-spin> </a-spin>
</a-collapse-item> </a-collapse-item>
</a-collapse> </a-collapse>

View File

@ -456,15 +456,15 @@
{ {
polymorphicName: 'MsCommonElement', // MsCommonElement polymorphicName: 'MsCommonElement', // MsCommonElement
assertionConfig: { assertionConfig: {
enableGlobal: true, enableGlobal: false,
assertions: [], assertions: [],
}, },
postProcessorConfig: { postProcessorConfig: {
enableGlobal: true, enableGlobal: false,
processors: [], processors: [],
}, },
preProcessorConfig: { preProcessorConfig: {
enableGlobal: true, enableGlobal: false,
processors: [], processors: [],
}, },
}, },

View File

@ -9,26 +9,28 @@
@close="handleClose" @close="handleClose"
@cancel="handleDrawerCancel" @cancel="handleDrawerCancel"
> >
<div class="ml-[16px] mt-[10px]"> <div class="flex h-full flex-col">
<!-- <stepTypeVue v-if="props.step" :step="props.step" /> --> <div class="ml-[16px] mt-[10px]">
{{ t('apiScenario.scriptOperationName') }} <!-- <stepTypeVue v-if="props.step" :step="props.step" /> -->
</div> {{ t('apiScenario.scriptOperationName') }}
<div class="ml-[16px] mt-[3px] max-w-[70%]"> </div>
<a-input <div class="ml-[16px] mt-[3px] max-w-[70%]">
v-model="scriptName" <a-input
:placeholder="t('apiScenario.scriptOperationNamePlaceholder')" v-model="scriptName"
:max-length="255" :placeholder="t('apiScenario.scriptOperationNamePlaceholder')"
size="small" :max-length="255"
/> size="small"
</div> />
<div class="mt-[10px] flex flex-1 gap-[8px]"> </div>
<conditionContent v-if="visible" v-model:data="activeItem" :is-build-in="true" :is-format="true" /> <div class="mt-[10px] flex flex-1 gap-[8px]">
</div> <conditionContent v-if="visible" v-model:data="activeItem" :is-build-in="true" />
<div v-if="currentResponse?.console" class="p-[8px]"> </div>
<div class="mb-[8px] font-medium text-[var(--color-text-1)]">{{ t('apiScenario.executionResult') }}</div> <div v-if="currentResponse?.console" class="p-[8px]">
<loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" /> <div class="mb-[8px] font-medium text-[var(--color-text-1)]">{{ t('apiScenario.executionResult') }}</div>
<div class="h-[300px] bg-[var(--color-text-n9)] p-[12px]"> <loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" />
<pre class="response-header-pre">{{ currentResponse?.console }}</pre> <div class="h-[300px] bg-[var(--color-text-n9)] p-[12px]">
<pre class="response-header-pre">{{ currentResponse?.console }}</pre>
</div>
</div> </div>
</div> </div>
<template v-if="!props.detail" #footer> <template v-if="!props.detail" #footer>

View File

@ -66,12 +66,12 @@
</div> </div>
</template> </template>
<div v-if="!checkedAll && !indeterminate" class="action-group ml-auto"> <div v-if="!checkedAll && !indeterminate" class="action-group ml-auto">
<a-input <!-- <a-input
v-model:model-value="keyword" v-model:model-value="keyword"
:placeholder="t('apiScenario.searchByName')" :placeholder="t('apiScenario.searchByName')"
allow-clear allow-clear
class="w-[200px]" class="w-[200px]"
/> /> -->
<a-tooltip v-if="!props.isNew" position="left" :content="t('apiScenario.refreshRefScenario')"> <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"> <a-button type="outline" class="arco-btn-outline--secondary !mr-0 !p-[8px]" @click="refreshStepInfo">
<template #icon> <template #icon>

View File

@ -5,9 +5,8 @@
v-model:model-value="innerData.delay" v-model:model-value="innerData.delay"
class="max-w-[500px] px-[8px]" class="max-w-[500px] px-[8px]"
size="mini" size="mini"
:step="1" :step="1000"
:min="0" :min="0"
hide-button
:precision="0" :precision="0"
model-event="input" model-event="input"
:disabled="props.disabled" :disabled="props.disabled"