fix(all): 修复中级 bug

This commit is contained in:
baiqi 2024-04-09 20:47:17 +08:00 committed by Craftsman
parent 9e04fb2a47
commit cbfb5e2221
47 changed files with 645 additions and 538 deletions

View File

@ -62,9 +62,10 @@ import {
ScenarioDetail,
ScenarioHistoryItem,
ScenarioHistoryPageParams,
ScenarioStepResourceInfo,
} from '@/models/apiTest/scenario';
import { AddModuleParams, CommonList, ModuleTreeNode, MoveModules, TransferFileParams } from '@/models/common';
import { ApiScenarioStatus } from '@/enums/apiEnum';
import { ApiScenarioStatus, ScenarioStepType } from '@/enums/apiEnum';
import type { RequestParam as CaseRequestParam } from '@/views/api-test/components/requestComposition/index.vue';
import type { RequestParam } from '@/views/api-test/scenario/components/common/customApiDrawer.vue';
@ -282,6 +283,6 @@ export function updateScenarioPro(id: string | number, priority: CaseLevel | und
}
// 获取跨项目信息
export function getStepProjectInfo(id: string | number) {
return MSR.get({ url: GetStepProjectInfoUrl, params: id });
export function getStepProjectInfo(id: string, type: ScenarioStepType) {
return MSR.get<ScenarioStepResourceInfo>({ url: GetStepProjectInfoUrl, params: id, data: { resourceType: type } });
}

View File

@ -19,7 +19,7 @@ export const GetSystemRequestUrl = '/api/scenario/get/system-request'; // 获取
export const FollowScenarioUrl = '/api/scenario/follow'; // 关注/取消关注接口场景
export const ScenarioScheduleConfigUrl = '/api/scenario/schedule-config'; // 场景定时任务
export const ScenarioScheduleConfigDeleteUrl = '/api/scenario/schedule-config-delete'; // 场景定时任务
export const GetStepProjectInfoUrl = '/api/scenario/step/project-ifo'; // 获取跨项目信息
export const GetStepProjectInfoUrl = '/api/scenario/step/resource-info'; // 获取跨项目信息
export const BatchRecycleScenarioUrl = '/api/scenario/batch-operation/delete-gc'; // 批量删除接口场景
export const BatchMoveScenarioUrl = '/api/scenario/batch-operation/move'; // 批量移动接口场景
export const BatchCopyScenarioUrl = '/api/scenario/batch-operation/copy'; // 批量复制接口场景

View File

@ -48,7 +48,7 @@
</a-form-item>
<template v-else>
<div v-if="props.multiple" class="flex w-full items-center">
<dropdownMenu @link-file="associatedFile" @change="handleChange" />
<dropdownMenu :disabled="props.disabled" @link-file="associatedFile" @change="handleChange" />
<saveAsFilePopover
v-if="props.fileSaveAsSourceId"
v-model:visible="saveFilePopoverVisible"
@ -85,7 +85,7 @@
:size="props.tagSize"
class="m-0 border-none p-0"
:self-style="{ backgroundColor: 'transparent !important' }"
:closable="data.value !== '__arco__more'"
:closable="data.value !== '__arco__more' || props.disabled"
@close="handleClose(data)"
>
{{ data.value === '__arco__more' ? data.label.replace('...', '') : data.label }}

View File

@ -170,7 +170,7 @@
class="mb-[16px] flex items-baseline gap-[16px] overflow-hidden bg-[var(--color-text-n9)] p-[5px_8px]"
>
<div class="break-all text-[var(--color-text-3)]">{{ t('ms.paramsInput.preview') }}</div>
<a-spin :loading="previewLoading" class="flex flex-1 flex-wrap gap-[8px]">
<a-spin :loading="previewLoading" class="flex flex-1 flex-wrap items-baseline gap-[8px]">
<div class="param-preview">{{ paramPreview }}</div>
<MsButton type="text" @click="getMockValue">{{ t('ms.paramsInput.previewClick') }}</MsButton>
</a-spin>
@ -510,7 +510,7 @@
}
if (valueArr[1]) {
//
const functionRegex = /([a-zA-Z]+)(?:\(([^)]*)\))?/;
const functionRegex = /([a-zA-Z0-9]+)(?:\(([^)]*)\))?/;
const functionMatch = valueArr[1].match(functionRegex);
if (functionMatch) {
@ -654,6 +654,7 @@
padding: 4px 8px;
}
}
max-width: 400px;
}
.ms-params-input-setting-trigger {

View File

@ -7,7 +7,7 @@
v-model:expanded-keys="expandedKeys"
v-model:selected-keys="selectedKeys"
v-model:checked-keys="checkedKeys"
:data="data"
:data="filterTreeData"
class="ms-tree"
:allow-drop="handleAllowDrop"
@drag-start="onDragStart"
@ -197,13 +197,13 @@
init(true);
});
const originTreeData = ref<MsTreeNodeData[]>([]); //
const filterTreeData = ref<MsTreeNodeData[]>([]); //
watch(
() => data.value,
(val) => {
if (!props.keyword) {
originTreeData.value = cloneDeep(val);
filterTreeData.value = cloneDeep(val);
}
},
{
@ -220,7 +220,7 @@
const search = (_data: MsTreeNodeData[]) => {
const result: MsTreeNodeData[] = [];
_data.forEach((item) => {
if (item[props.fieldNames.title].toLowerCase().indexOf(keyword.toLowerCase()) > -1) {
if (item[props.fieldNames.title].toLowerCase().includes(keyword.toLowerCase())) {
result.push({ ...item, expanded: true });
} else if (item[props.fieldNames.children]) {
const filterData = search(item[props.fieldNames.children]);
@ -237,13 +237,17 @@
return result;
};
return search(originTreeData.value);
return search(data.value);
}
//
const updateDebouncedSearch = debounce(() => {
if (props.keyword) {
data.value = searchData(props.keyword);
filterTreeData.value = searchData(props.keyword);
nextTick(() => {
// (expandedKeys)
treeRef.value?.expandNode(expandedKeys.value);
});
}
}, props.searchDebounce);
@ -251,7 +255,7 @@
() => props.keyword,
(val) => {
if (!val) {
data.value = cloneDeep(originTreeData.value);
filterTreeData.value = cloneDeep(data.value);
} else {
updateDebouncedSearch();
}

View File

@ -270,6 +270,7 @@
contextmenu: !props.readOnly, //
...props,
language: props.language.toLowerCase(),
theme: currentTheme.value,
});
//

View File

@ -13,7 +13,7 @@
import { useI18n } from '@/hooks/useI18n';
import { XpathNode } from './types';
import * as XmlBeautify from 'xml-beautify';
import XmlBeautify from 'xml-beautify';
const props = defineProps<{
xmlString: string;
@ -98,7 +98,7 @@
isValidXml.value = true;
parsedXml.value = xmlDoc;
// XML icon
flattenedXml.value = new XmlBeautify({ parser: DOMParser })
flattenedXml.value = new XmlBeautify()
.beautify(props.xmlString)
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')

View File

@ -7,7 +7,7 @@
<a-table
v-bind="{ ...$attrs, ...scrollObj }"
:row-class="getRowClass"
:column-resizable="false"
:column-resizable="true"
:span-method="spanMethod"
:columns="currentColumns"
:expanded-keys="props.expandedKeys"
@ -211,7 +211,7 @@
class="mt-[16px] flex h-[32px] flex-row flex-nowrap items-center"
:class="{ 'justify-between': showBatchAction }"
>
<span v-if="props.actionConfig && selectedCount > 0" class="title text-[var(--color-text-2)]">
<span v-if="props.actionConfig && selectedCount > 0 && !showBatchAction" class="title text-[var(--color-text-2)]">
{{ t('msTable.batch.selected', { count: selectedCount }) }}
<a-button class="clear-btn ml-[12px] px-2" type="text" @click="emit('clearSelector')">
{{ t('msTable.batch.clear') }}

View File

@ -271,7 +271,7 @@ export interface ForEachController {
variable: string; // 变量名
}
export interface CountController {
loops: number; // 循环次数
loops: string; // 循环次数
loopTime: number; // 循环间隔时间
}
export interface WhileScript {
@ -478,3 +478,11 @@ export interface ApiScenarioBatchOptionResult {
success: number;
error: number;
}
// 场景跨项目步骤资源信息
export interface ScenarioStepResourceInfo {
id: string;
num: number;
name: string;
projectId: string;
projectName: string;
}

View File

@ -545,17 +545,24 @@ export function deleteNode<T>(treeArr: TreeNode<T>[], targetKey: string | number
* @param treeArr
* @param targetKeys
*/
export function deleteNodes<T>(treeArr: TreeNode<T>[], targetKeys: (string | number)[], customKey = 'key'): void {
export function deleteNodes<T>(
treeArr: TreeNode<T>[],
targetKeys: (string | number)[],
deleteCondition?: (node: TreeNode<T>, parent?: TreeNode<T>) => boolean,
customKey = 'key'
): void {
const targetKeysSet = new Set(targetKeys);
function deleteNodesInTree(tree: TreeNode<T>[]): void {
for (let i = tree.length - 1; i >= 0; i--) {
const node = tree[i];
if (targetKeysSet.has(node[customKey])) {
tree.splice(i, 1); // 直接删除当前节点
targetKeysSet.delete(node[customKey]); // 删除后从集合中移除
// 重新调整剩余子节点的 sort 序号
for (let j = i; j < tree.length; j++) {
tree[j].sort = j + 1;
if (deleteCondition && deleteCondition(node, node.parent)) {
tree.splice(i, 1); // 直接删除当前节点
targetKeysSet.delete(node[customKey]); // 删除后从集合中移除
// 重新调整剩余子节点的 sort 序号
for (let j = i; j < tree.length; j++) {
tree[j].sort = j + 1;
}
}
} else if (Array.isArray(node.children)) {
deleteNodesInTree(node.children); // 递归删除子节点
@ -634,12 +641,13 @@ export const getHashParameters = (): Record<string, string> => {
* @returns
*/
export const getGenerateId = () => {
const timestamp = new Date().getTime().toString();
const randomDigits = Math.floor(Math.random() * 10000)
.toString()
.padStart(4, '0');
const generateId = timestamp + randomDigits;
return generateId.substring(0, 16);
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
// eslint-disable-next-line no-bitwise
const r = (Math.random() * 16) | 0;
// eslint-disable-next-line no-bitwise
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
};
/**

View File

@ -46,7 +46,12 @@
</div>
<!-- 前后置请求结束 -->
<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]"
:disabled="props.disabled"
@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') }}
@ -58,6 +63,7 @@
class="mr-2"
size="small"
type="line"
:disabled="props.disabled"
@change="emit('change')"
/>
{{ t('apiTestDebug.preconditionAssociatedSceneResult') }}
@ -79,6 +85,7 @@
v-model:model-value="condition.name"
:placeholder="t('apiTestDebug.preconditionScriptNamePlaceholder')"
:max-length="255"
:disabled="props.disabled"
size="small"
@press-enter="isShowEditScriptNameInput = false"
@blur="isShowEditScriptNameInput = false"
@ -201,36 +208,39 @@
v-permission="['PROJECT_CUSTOM_FUNCTION:READ']"
type="text"
class="font-medium"
:disabled="props.disabled"
@click="showQuoteDrawer = true"
>
{{ t('apiTestDebug.quote') }}
</MsButton>
</div>
<div v-show="showParameters() || showScript()" class="h-[calc(100%-47px)] min-h-[300px]">
<a-radio-group v-model:model-value="commonScriptShowType" size="small" type="button" class="mb-[8px] w-fit">
<a-radio v-if="showParameters()" value="parameters">{{ t('apiTestDebug.parameters') }}</a-radio>
<a-radio value="scriptContent">{{ t('apiTestDebug.scriptContent') }}</a-radio>
</a-radio-group>
<paramTable
v-if="commonScriptShowType === 'parameters'"
v-model:params="scriptParams"
:scroll="{ x: '100%' }"
:columns="scriptColumns"
:height-used="heightUsed"
:selectable="false"
/>
<div v-show="commonScriptShowType === 'scriptContent'" class="h-[calc(100%-76px)]">
<div v-if="showParameters() || showScript()" class="min-h-[300px]">
<div>
<a-radio-group v-model:model-value="commonScriptShowType" size="small" type="button" class="mb-[8px] w-fit">
<a-radio v-if="showParameters()" value="parameters">{{ t('apiTestDebug.parameters') }}</a-radio>
<a-radio value="scriptContent">{{ t('apiTestDebug.scriptContent') }}</a-radio>
</a-radio-group>
</div>
<div class="h-[calc(100%-76px)]">
<paramTable
v-if="commonScriptShowType === 'parameters'"
v-model:params="scriptParams"
:disabled-param-value="props.disabled"
:scroll="{ x: '100%' }"
:columns="scriptColumns"
:height-used="heightUsed"
:selectable="false"
/>
<MsCodeEditor
v-if="condition.commonScriptInfo"
v-else-if="commonScriptShowType === 'scriptContent' && condition.commonScriptInfo"
v-model:model-value="condition.commonScriptInfo.script"
theme="vs"
height="100%"
:height="props.sqlCodeEditorHeight || '100%'"
:language="condition.commonScriptInfo.scriptLanguage || LanguageEnum.BEANSHELL_JSR233"
:show-full-screen="false"
:show-theme-change="false"
read-only
>
</MsCodeEditor>
/>
</div>
</div>
</div>
@ -289,7 +299,9 @@
v-model:model-value="condition.variableNames"
:max-length="255"
:disabled="props.disabled"
:placeholder="t('apiTestDebug.storageByColPlaceholder', { a: 'id', b: 'email', c: '{id_1}', d: '{email_1}' })"
:placeholder="
t('apiTestDebug.storageByColPlaceholder', { a: 'id', b: 'email', c: '${id_1}', d: '${email_1}' })
"
@input="() => emit('change')"
/>
</div>
@ -350,6 +362,7 @@
ref="extractParamsTableRef"
:params="condition.extractors"
:disabled-except-param="props.disabled"
:disabled-param-value="props.disabled"
:default-param-item="defaultExtractParamItem"
:columns="extractParamsColumns"
:selectable="false"
@ -411,7 +424,7 @@
>
<template #content>
<moreSetting v-model:config="activeRecord" is-popover class="mt-[12px]" />
<div class="flex items-center justify-end gap-[8px]">
<div v-show="!props.disabled" class="flex items-center justify-end gap-[8px]">
<a-button type="secondary" size="mini" @click="record.moreSettingPopoverVisible = false">
{{ t('common.cancel') }}
</a-button>
@ -426,7 +439,11 @@
</paramTable>
</div>
</div>
<quoteSqlSourceDrawer v-model:visible="quoteSqlSourceDrawerVisible" @apply="handleQuoteSqlSourceApply" />
<quoteSqlSourceDrawer
v-model:visible="quoteSqlSourceDrawerVisible"
:selected-key="condition.dataSourceId"
@apply="handleQuoteSqlSourceApply"
/>
<fastExtraction
v-model:visible="fastExtractionVisible"
:response="props.response"
@ -512,6 +529,7 @@
requestRadioTextProps?: Record<string, any>; //
showPrePostRequest?: boolean; //
totalList?: ExecuteConditionProcessor[]; //
sqlCodeEditorHeight?: string; // sql
}>(),
{
showAssociatedScene: false,
@ -530,27 +548,34 @@
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
const condition = useVModel(props, 'data', emit);
watchEffect(() => {
if (condition.value.processorType === RequestConditionProcessor.SQL && condition.value.dataSourceId) {
// SQL
const dataSourceItem = currentEnvConfig?.value.dataSources.find(
(item) => item.dataSource === condition.value.dataSourceName
);
if (dataSourceItem) {
//
condition.value.dataSourceName = dataSourceItem.dataSource;
condition.value.dataSourceId = dataSourceItem.id;
} else if (currentEnvConfig && currentEnvConfig.value.dataSources.length > 0) {
//
condition.value.dataSourceName = currentEnvConfig.value.dataSources[0].dataSource;
condition.value.dataSourceId = currentEnvConfig.value.dataSources[0].id;
} else {
//
condition.value.dataSourceName = '';
condition.value.dataSourceId = '';
watch(
() => currentEnvConfig?.value,
() => {
if (condition.value.processorType === RequestConditionProcessor.SQL && condition.value.dataSourceId) {
// SQL
const dataSourceItem = currentEnvConfig?.value.dataSources.find(
(item) => item.dataSource === condition.value.dataSourceName
);
if (currentEnvConfig?.value.dataSources.length === 0) {
//
condition.value.dataSourceName = '';
condition.value.dataSourceId = '';
} else if (dataSourceItem) {
//
condition.value.dataSourceName = dataSourceItem.dataSource;
condition.value.dataSourceId = dataSourceItem.id;
} else if (currentEnvConfig && currentEnvConfig.value.dataSources.length > 0) {
//
condition.value.dataSourceName = currentEnvConfig.value.dataSources[0].dataSource;
condition.value.dataSourceId = currentEnvConfig.value.dataSources[0].id;
}
}
},
{
immediate: true,
deep: true,
}
});
);
//
const isShowEditScriptNameInput = ref(false);

View File

@ -38,6 +38,7 @@
:show-associated-scene="props.showAssociatedScene"
:show-pre-post-request="props.showPrePostRequest"
:request-radio-text-props="props.requestRadioTextProps"
:sql-code-editor-height="props.sqlCodeEditorHeight"
@copy="copyListItem"
@delete="deleteListItem"
@change="emit('change')"
@ -55,8 +56,8 @@
import { conditionTypeNameMap } from '@/config/apiTest';
import { useI18n } from '@/hooks/useI18n';
import { ConditionType, ExecuteConditionProcessor } from '@/models/apiTest/common';
import { RequestConditionProcessor } from '@/enums/apiEnum';
import { ConditionType, ExecuteConditionProcessor, RegexExtract } from '@/models/apiTest/common';
import { RequestConditionProcessor, RequestExtractExpressionEnum, RequestExtractScope } from '@/enums/apiEnum';
const props = withDefaults(
defineProps<{
@ -69,6 +70,7 @@
response?: string; //
showAssociatedScene?: boolean;
showPrePostRequest?: boolean; //
sqlCodeEditorHeight?: string;
}>(),
{
showAssociatedScene: false,
@ -201,7 +203,6 @@
if (isEXTRACT) {
return;
}
data.value.push({
id,
processorType: RequestConditionProcessor.EXTRACT,
@ -228,6 +229,11 @@
hasNoIdItem = true;
return {
...item,
extractors: item.extractors?.map((e, j) => ({
...e,
extractScope: (e as RegexExtract).extractScope || RequestExtractScope.BODY,
id: new Date().getTime() + j,
})),
id: new Date().getTime() + i,
};
}

View File

@ -89,10 +89,10 @@
(val) => {
if (!val) {
currentEnv.value = (envOptions.value[0]?.value as string) || '';
nextTick(() => {
initEnvironment();
});
}
nextTick(() => {
initEnvironment();
});
}
);

View File

@ -26,7 +26,7 @@
<template #typeTitle="{ columnConfig }">
<div class="flex items-center text-[var(--color-text-3)]">
{{ t('apiTestDebug.paramType') }}
<a-tooltip :content="columnConfig.typeTitleTooltip" position="right">
<a-tooltip :content="columnConfig.typeTitleTooltip" :disabled="!columnConfig.typeTitleTooltip" position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
size="16"
@ -59,7 +59,7 @@
<template #extractValueTitle>
<div class="flex items-center text-[var(--color-text-3)]">
{{ t('apiTestDebug.extractValueByColumn') }}
<a-tooltip :content="t('caseManagement.caseReview.passRateTip')" position="right">
<a-tooltip :content="t('apiTestDebug.extractValueTitleTip')" position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
size="16"
@ -211,7 +211,7 @@
<MsAddAttachment
v-else-if="record.paramType === RequestParamsType.FILE"
v-model:file-list="record.files"
:disabled="props.disabledExceptParam"
:disabled="props.disabledParamValue"
mode="input"
:multiple="true"
:fields="{
@ -819,7 +819,6 @@
() => props.params,
(arr) => {
if (arr.length > 0) {
let hasNoIdItem = false; // id
paramsData.value = arr.map((item, i) => {
if (!item) {
// undefined
@ -830,7 +829,6 @@
}
if (!item.id) {
// id
hasNoIdItem = true;
return {
...item,
id: new Date().getTime() + i,
@ -838,7 +836,11 @@
}
return item;
});
if (hasNoIdItem && !filterKeyValParams(arr, props.defaultParamItem).lastDataIsDefault && !props.isTreeTable) {
if (
!filterKeyValParams(arr, props.defaultParamItem).lastDataIsDefault &&
!props.isTreeTable &&
!filterKeyValParams(arr, arr[arr.length - 2]).lastDataIsDefault //
) {
addTableLine(arr.length - 1, false, true);
}
} else {
@ -1033,7 +1035,7 @@
*/
function handleSearchParams(val: string, item: FormTableColumn) {
item.autoCompleteParams = item.autoCompleteParams?.map((e) => {
e.isShow = (e.label || '').includes(val);
e.isShow = (e.label || '').toLowerCase().includes(val.toLowerCase());
return e;
});
}

View File

@ -6,6 +6,7 @@
:response="props.response"
:disabled="props.disabled"
:height-used="heightUsed"
:sql-code-editor-height="props.sqlCodeEditorHeight"
@change="emit('change')"
>
<template v-if="props.isDefinition" #titleRight>
@ -44,6 +45,7 @@
isDefinition?: boolean; //
isScenario?: boolean; //
disabled?: boolean;
sqlCodeEditorHeight?: string;
}>();
const emit = defineEmits<{
(e: 'update:params', params: ExecuteConditionProcessor[]): void;

View File

@ -3,6 +3,7 @@
v-model:list="innerConfig.processors"
:disabled="props.disabled"
:condition-types="conditionTypes"
:sql-code-editor-height="props.sqlCodeEditorHeight"
add-text="apiTestDebug.precondition"
@change="emit('change')"
>
@ -39,6 +40,7 @@
isDefinition?: boolean; //
isScenario?: boolean; //
disabled?: boolean;
sqlCodeEditorHeight?: string;
}>();
const emit = defineEmits<{
(e: 'update:config', params: ExecuteConditionConfig): void;

View File

@ -202,4 +202,6 @@ export default {
'apiTestDebug.testSuccess': 'Test success',
'apiTestDebug.searchByDataBaseName': 'Search by data source name',
'apiTestDebug.regexMatchRules': 'Expression matching rules',
'apiTestDebug.extractValueTitleTip':
'Enter the column name and corresponding value in column storage. If you want to extract the first value of the name column, enter name_1',
};

View File

@ -189,4 +189,5 @@ export default {
'apiTestDebug.testSuccess': '测试成功',
'apiTestDebug.searchByDataBaseName': '按数据源名称搜索',
'apiTestDebug.regexMatchRules': '表达式匹配规则',
'apiTestDebug.extractValueTitleTip': '输入按列存储中的列名和对应的数值如提取name列的第一个值则输入name_1',
};

View File

@ -188,7 +188,7 @@
v-model:model-value="importForm.name"
:placeholder="t('apiTestManagement.taskNamePlaceholder')"
:max-length="255"
class="flex-1"
class="w-[550px]"
></a-input>
<MsButton type="text" @click="taskDrawerVisible = true">
{{ t('apiTestManagement.timeTaskList') }}
@ -204,7 +204,7 @@
<a-input
v-model:model-value="importForm.swaggerUrl"
:placeholder="t('apiTestManagement.urlImportPlaceholder')"
class="w-[700px]"
class="w-[550px]"
allow-clear
></a-input>
</a-form-item>
@ -246,7 +246,7 @@
<a-tree-select
v-model:modelValue="importForm.moduleId"
:data="moduleTree"
class="w-[436px]"
class="w-[500px]"
:field-names="{ title: 'name', key: 'id', children: 'children' }"
allow-search
>
@ -370,6 +370,7 @@
visible: boolean;
moduleTree: ModuleTreeNode[];
popupContainer?: string;
activeModule: string;
}>();
const emit = defineEmits(['update:visible', 'done']);
@ -391,8 +392,8 @@
const defaultForm: ImportApiDefinitionRequest = {
platform: RequestImportFormat.SWAGGER,
name: '',
moduleId: '',
coverData: true,
moduleId: 'root',
coverData: false,
syncCase: true,
coverModule: false,
swaggerUrl: '',
@ -406,6 +407,19 @@
};
const importForm = ref({ ...defaultForm });
const importFormRef = ref<FormInstance>();
watch(
() => visible.value,
(val) => {
if (val) {
importForm.value.moduleId = props.activeModule !== 'all' ? props.activeModule : 'root';
}
},
{
immediate: true,
}
);
const moreSettingActive = ref<number[]>([]);
const disabledConfirm = computed(() => {
if (importForm.value.type === RequestImportType.API) {

View File

@ -258,14 +258,18 @@
style="padding-top: 10px"
>
<a-switch v-model="batchForm.append" class="mr-1" size="small" type="line" />
<a-tooltip :content="t('caseManagement.featureCase.enableTags')">
<span class="flex items-center">
<span class="mr-1">{{ t('caseManagement.featureCase.appendTag') }}</span>
<span class="mt-[2px]">
<span class="flex items-center">
<span class="mr-1">{{ t('caseManagement.featureCase.appendTag') }}</span>
<span class="mt-[2px]">
<a-tooltip>
<IconQuestionCircle class="h-[16px] w-[16px] text-[rgb(var(--primary-5))]" />
</span>
<template #content>
<div>{{ t('caseManagement.featureCase.enableTags') }}</div>
<div>{{ t('caseManagement.featureCase.closeTags') }}</div>
</template>
</a-tooltip>
</span>
</a-tooltip>
</span>
</div>
<div class="flex justify-end">
<a-button type="secondary" :disabled="batchUpdateLoading" @click="cancelBatch">

View File

@ -321,14 +321,18 @@
style="padding-top: 10px"
>
<a-switch v-model="batchForm.append" class="mr-1" size="small" type="line" />
<a-tooltip :content="t('caseManagement.featureCase.enableTags')">
<span class="flex items-center">
<span class="mr-1">{{ t('caseManagement.featureCase.appendTag') }}</span>
<span class="mt-[2px]">
<span class="flex items-center">
<span class="mr-1">{{ t('caseManagement.featureCase.appendTag') }}</span>
<span class="mt-[2px]">
<a-tooltip>
<IconQuestionCircle class="h-[16px] w-[16px] text-[rgb(var(--primary-5))]" />
</span>
<template #content>
<div>{{ t('caseManagement.featureCase.enableTags') }}</div>
<div>{{ t('caseManagement.featureCase.closeTags') }}</div>
</template>
</a-tooltip>
</span>
</a-tooltip>
</span>
</div>
<div class="flex justify-end">
<a-button type="secondary" :disabled="batchEditLoading" @click="cancelBatchEdit">

View File

@ -28,7 +28,7 @@
</a-tooltip>
</template>
</MsEditableTab>
<environmentSelect ref="environmentSelectRef" />
<environmentSelect ref="environmentSelectRef" v-model:current-env="activeApiTab.environmentId" />
</div>
<api
v-show="(activeApiTab.id === 'all' && currentTab === 'api') || activeApiTab.type === 'api'"

View File

@ -43,6 +43,7 @@
<importApi
v-model:visible="importDrawerVisible"
:module-tree="folderTree"
:active-module="activeModule"
popup-container="#managementContainer"
@done="handleImportDone"
/>

View File

@ -43,9 +43,11 @@
v-if="!props.step || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST"
class="customApiDrawer-title-right ml-auto flex items-center gap-[16px]"
>
<div class="text-[14px] font-normal text-[var(--color-text-4)]">
{{ t('apiScenario.env', { name: currentEnvConfig?.name }) }}
</div>
<a-tooltip :content="currentEnvConfig?.name" :disabled="!currentEnvConfig?.name">
<div class="one-line-text max-w-[250px] text-[14px] font-normal text-[var(--color-text-4)]">
{{ t('apiScenario.env', { name: currentEnvConfig?.name }) }}
</div>
</a-tooltip>
<a-select
v-model:model-value="requestVModel.customizeRequestEnvEnable"
class="w-[150px]"
@ -119,27 +121,39 @@
</a-input-group>
</div>
<div v-permission="[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"
<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]"
type="primary"
@click="() => execute('serverExec')"
>
{{ 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-if="!requestVModel.executeLoading" type="primary" @click="() => execute('serverExec')">
{{ t('apiTestDebug.serverExec') }}
</a-button>
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">{{ t('common.stop') }}</a-button>
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">
{{ t('common.stop') }}
</a-button>
</div>
</div>
<a-input
@ -201,7 +215,7 @@
<httpHeader
v-if="requestVModel.activeTab === RequestComposition.HEADER"
v-model:params="requestVModel.headers"
:disabled-param-value="!isEditableApi"
:disabled-param-value="!isEditableApi && !isEditableParamValue"
:disabled-except-param="!isEditableApi"
:layout="activeLayout"
:second-box-height="secondBoxHeight"
@ -211,7 +225,7 @@
v-else-if="requestVModel.activeTab === RequestComposition.BODY"
v-model:params="requestVModel.body"
:layout="activeLayout"
:disabled-param-value="!isEditableApi"
:disabled-param-value="!isEditableApi && !isEditableParamValue"
:disabled-except-param="!isEditableApi"
:second-box-height="secondBoxHeight"
:upload-temp-file-api="uploadTempFile"
@ -224,7 +238,7 @@
v-else-if="requestVModel.activeTab === RequestComposition.QUERY"
v-model:params="requestVModel.query"
:layout="activeLayout"
:disabled-param-value="!isEditableApi"
:disabled-param-value="!isEditableApi && !isEditableParamValue"
:disabled-except-param="!isEditableApi"
:second-box-height="secondBoxHeight"
@change="handleActiveDebugChange"
@ -233,7 +247,7 @@
v-else-if="requestVModel.activeTab === RequestComposition.REST"
v-model:params="requestVModel.rest"
:layout="activeLayout"
:disabled-param-value="!isEditableApi"
:disabled-param-value="!isEditableApi && !isEditableParamValue"
:disabled-except-param="!isEditableApi"
:second-box-height="secondBoxHeight"
@change="handleActiveDebugChange"
@ -554,11 +568,15 @@
// api props.request
const isCopyApiNeedInit = computed(() => _stepType.value.isCopyApi && props.request === undefined);
//
const isEditableApi = computed(
() =>
!props.step?.isQuoteScenarioStep &&
(_stepType.value.isCopyApi || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST || !props.step)
);
// api
const isEditableParamValue = computed(() => !props.step?.isQuoteScenarioStep && _stepType.value.isQuoteApi);
// HTTP
const isHttpProtocol = computed(() => requestVModel.value.protocol === 'HTTP');
const isInitPluginForm = ref(false);
@ -999,7 +1017,7 @@
method: isHttpProtocol.value ? requestVModel.value.method : requestVModel.value.protocol,
name: requestVModel.value.name,
unSaved: requestVModel.value.unSaved,
customizeRequest: props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST || !props.request,
customizeRequest: requestVModel.value.customizeRequest,
customizeRequestEnvEnable: requestVModel.value.customizeRequestEnvEnable,
children: [
{

View File

@ -67,27 +67,39 @@
allow-clear
/>
<div v-permission="[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"
<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]"
type="primary"
@click="() => execute('serverExec')"
>
{{ 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-if="!requestVModel.executeLoading" type="primary" @click="() => execute('serverExec')">
{{ t('apiTestDebug.serverExec') }}
</a-button>
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">{{ t('common.stop') }}</a-button>
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">
{{ t('common.stop') }}
</a-button>
</div>
</div>
<div class="px-[16px]">
@ -443,8 +455,8 @@
}
);
// api props.request
const isEditableApi = computed(() => _stepType.value.isCopyCase);
// case
const isEditableApi = computed(() => !activeStep.value?.isQuoteScenarioStep && _stepType.value.isCopyCase);
const isHttpProtocol = computed(() => requestVModel.value.protocol === 'HTTP');
const isInitPluginForm = ref(false);
const loading = ref(false);

View File

@ -255,6 +255,7 @@
fullScenarioArr.push(...res);
});
if (refType === ScenarioStepRefType.COPY) {
// uniqueIdcopyFromStepId
fullScenarioArr = mapTree<MsTableDataItem<ApiScenarioTableItem>>(fullScenarioArr, (node) => {
const id = getGenerateId();
return {
@ -275,6 +276,7 @@
);
handleCancel();
} else {
// iduniqueId id
fullScenarioArr = fullScenarioArr.map((e) => {
const id = getGenerateId();
return {

View File

@ -79,6 +79,9 @@
<template #status="{ record }">
<apiStatus :status="record.status" size="small" />
</template>
<template #priority="{ record }">
<caseLevel :case-level="record.priority" />
</template>
</ms-base-table>
</div>
</template>
@ -90,6 +93,7 @@
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableDataItem } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import { MsTreeNodeData } from '@/components/business/ms-tree/types';
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import apiStatus from '@/views/api-test/components/apiStatus.vue';
@ -475,7 +479,7 @@
case 'scenario':
default:
routeName = ApiTestRouteEnum.API_TEST_SCENARIO;
query.sId = id;
query.id = id;
break;
}
openNewPage(routeName, query);

View File

@ -98,7 +98,8 @@
step.stepType === ScenarioStepType.LOOP_CONTROLLER &&
step.config.loopType === ScenarioStepLoopTypeEnum.LOOP_COUNT &&
step.config.msCountController &&
step.config.msCountController.loops > 0
!Number.isNaN(step.config.msCountController.loops) &&
Number(step.config.msCountController.loops) > 0
) {
// /
const firstHasResultChild = step.children?.find((child) => {

View File

@ -19,11 +19,12 @@
v-model="scriptName"
:placeholder="t('apiScenario.scriptOperationNamePlaceholder')"
:max-length="255"
:disabled="isReadonly"
size="small"
/>
</div>
<div class="mt-[10px] flex flex-1 gap-[8px]">
<conditionContent v-if="visible" v-model:data="activeItem" :is-build-in="true" />
<conditionContent v-if="visible" v-model:data="activeItem" :disabled="isReadonly" :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>
@ -88,6 +89,7 @@
const { t } = useI18n();
const visible = defineModel<boolean>('visible', { required: true });
const isReadonly = computed(() => props.step?.isQuoteScenarioStep);
const currentLoop = ref(1);
const currentResponse = computed(() => {
if (props.step?.uniqueId) {

View File

@ -16,7 +16,7 @@ export const defaultLoopController = {
variable: '', // 变量名
},
msCountController: {
loops: 1, // 循环次数
loops: '1', // 循环次数
loopTime: 0, // 循环间隔时间
},
whileController: {

View File

@ -10,14 +10,7 @@
@press-enter="loadExecuteHistoryList"
/>
</div>
<ms-base-table
v-bind="propsRes"
:first-column-width="44"
:secnario-id="props.scenarioId"
no-disable
filter-icon-align-left
v-on="propsEvent"
>
<ms-base-table v-bind="propsRes" no-disable filter-icon-align-left v-on="propsEvent">
<template #num="{ record }">
<span type="text" class="px-0">{{ record.num }}</span>
</template>
@ -27,12 +20,12 @@
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<a-button type="text" class="arco-btn-text--secondary p-[8px_4px]" @click="triggerModeFilterVisible = true">
<MsButton type="text" class="arco-btn-text--secondary p-[8px_4px]" @click="triggerModeFilterVisible = true">
<div class="font-medium">
{{ t(columnConfig.title as string) }}
</div>
<icon-down :class="triggerModeFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
</MsButton>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
@ -146,10 +139,6 @@
slotName: 'triggerMode',
showTooltip: true,
titleSlotName: 'triggerModeFilter',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
width: 100,
},
{
@ -157,10 +146,6 @@
dataIndex: 'status',
slotName: 'status',
titleSlotName: 'statusFilter',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
width: 100,
},
{

View File

@ -13,7 +13,6 @@
v-model:selected-keys="selectedKeys"
:data="folderTree"
:keyword="moduleKeyword"
:node-more-actions="folderMoreActions"
:default-expand-all="isExpandAll"
:expand-all="isExpandAll"
:empty-text="t('apiScenario.tree.noMatchModule')"
@ -48,7 +47,6 @@
import { getModuleCount, getModuleTree } from '@/api/modules/api-test/scenario';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useAppStore from '@/store/modules/app';
import { mapTree } from '@/utils';
@ -65,27 +63,10 @@
const appStore = useAppStore();
const { t } = useI18n();
const { openModal } = useModal();
function handleSelect(value: string | number | Record<string, any> | undefined) {
switch (value) {
case 'newScenario':
emit('newScenario');
break;
case 'import':
emit('import');
break;
case 'addModule':
document.querySelector('#addModulePopSpan')?.dispatchEvent(new Event('click'));
break;
default:
break;
}
}
const virtualListProps = computed(() => {
return {
height: 'calc(100vh - 343px)',
height: '40vh',
threshold: 200,
fixedSize: true,
buffer: 15, // 10 padding
@ -96,30 +77,10 @@
const folderTree = ref<ModuleTreeNode[]>([]);
const focusNodeKey = ref<string | number>('');
const selectedKeys = ref<Array<string | number>>([]);
const allFolderClass = computed(() =>
selectedKeys.value[0] === 'all' ? 'folder-text folder-text--active' : 'folder-text'
);
const loading = ref(false);
const folderMoreActions: ActionsItem[] = [
{
label: 'common.rename',
eventTag: 'rename',
},
{
isDivider: true,
},
{
label: 'common.delete',
eventTag: 'delete',
danger: true,
},
];
const modulesCount = ref<Record<string, number>>({});
const allScenarioCount = computed(() => modulesCount.value.all || 0);
const isExpandAll = ref(props.isExpandAll);
const rootModulesName = ref<string[]>([]); //
/**
* 初始化模块树
@ -164,10 +125,6 @@
}
);
function changeExpand() {
isExpandAll.value = !isExpandAll.value;
}
/**
* 处理文件夹树节点选中事件
*/

View File

@ -92,6 +92,7 @@
},
],
titleSlotName: 'typeTitle',
typeTitleTooltip: t('apiScenario.params.typeTooltip'),
},
{
title: 'apiScenario.params.paramValue',

View File

@ -1,7 +1,13 @@
<template>
<div class="condition">
<div>
<precondition v-model:config="preProcessorConfig" :is-definition="false" @change="emit('change')" />
<precondition
v-model:config="preProcessorConfig"
:is-definition="false"
sql-code-editor-height="300px"
is-scenario
@change="emit('change')"
/>
</div>
<a-divider class="my-[8px]" type="dashed" />
<div>
@ -9,6 +15,8 @@
v-model:config="postProcessorConfig"
:is-definition="false"
:layout="activeLayout"
sql-code-editor-height="300px"
is-scenario
@change="emit('change')"
/>
</div>
@ -37,10 +45,6 @@
<style lang="less" scoped>
.condition {
.ms-scroll-bar();
overflow: auto;
width: 100%;
height: 700px;
flex-shrink: 0;
@apply h-full overflow-auto;
}
</style>

View File

@ -242,6 +242,9 @@
</template>
</TableFilter>
</template>
<template #stepTotal="{ record }">
{{ record.stepTotal }}
</template>
<template #operation="{ record }">
<MsButton
v-permission="['PROJECT_API_SCENARIO:READ+UPDATE']"
@ -505,14 +508,18 @@
style="padding-top: 10px"
>
<a-switch v-model="batchForm.append" class="mr-1" size="small" type="line" />
<a-tooltip :content="t('caseManagement.featureCase.enableTags')">
<span class="flex items-center">
<span class="mr-1">{{ t('caseManagement.featureCase.appendTag') }}</span>
<span class="mt-[2px]">
<span class="flex items-center">
<span class="mr-1">{{ t('caseManagement.featureCase.appendTag') }}</span>
<span class="mt-[2px]">
<a-tooltip>
<IconQuestionCircle class="h-[16px] w-[16px] text-[rgb(var(--primary-5))]" />
</span>
<template #content>
<div>{{ t('caseManagement.featureCase.enableTags') }}</div>
<div>{{ t('caseManagement.featureCase.closeTags') }}</div>
</template>
</a-tooltip>
</span>
</a-tooltip>
</span>
</div>
<div class="flex justify-end">
<a-button type="secondary" :disabled="batchUpdateLoading" @click="cancelBatch">
@ -553,7 +560,7 @@
{{ t('api_scenario.table.batchModalSubTitle', { count: tableSelected.length }) }}
</div>
</div>
<div v-else-if="isBatchMove">
<div v-else-if="isBatchMove" class="flex items-center">
{{ t('common.batchMove') }}
<div
class="one-line-text ml-[4px] max-w-[100%] text-[var(--color-text-4)]"
@ -795,6 +802,7 @@
{
title: 'apiScenario.table.columns.steps',
dataIndex: 'stepTotal',
slotName: 'stepTotal',
showInTable: false,
showDrag: true,
width: 100,

View File

@ -77,7 +77,7 @@
| ScenarioAddStepActionType.SCRIPT_OPERATION,
step?: ScenarioStepItem
);
(e: 'addDone');
(e: 'addDone', newStep: ScenarioStepItem);
}>();
const appStore = useAppStore();
@ -117,7 +117,7 @@
} else {
steps.value.push(defaultLoopStep);
}
emit('addDone');
emit('addDone', defaultLoopStep);
break;
case ScenarioAddStepActionType.CONDITION_CONTROL:
const defaultConditionStep = buildInsertStepInfos(
@ -132,7 +132,7 @@
} else {
steps.value.push(defaultConditionStep);
}
emit('addDone');
emit('addDone', defaultConditionStep);
break;
case ScenarioAddStepActionType.ONLY_ONCE_CONTROL:
const defaultOnlyOnceStep = buildInsertStepInfos(
@ -147,7 +147,7 @@
} else {
steps.value.push(defaultOnlyOnceStep);
}
emit('addDone');
emit('addDone', defaultOnlyOnceStep);
break;
case ScenarioAddStepActionType.WAIT_TIME:
const defaultWaitTimeStep = buildInsertStepInfos(
@ -162,7 +162,7 @@
} else {
steps.value.push(defaultWaitTimeStep);
}
emit('addDone');
emit('addDone', defaultWaitTimeStep);
break;
case ScenarioAddStepActionType.IMPORT_SYSTEM_API:
case ScenarioAddStepActionType.CUSTOM_API:

View File

@ -26,7 +26,7 @@
:popup-translate="[-7, -10]"
@other-create="(type, step) => emit('otherCreate', type, step, activeCreateAction)"
@close="handleActionsClose"
@add-done="emit('addDone')"
@add-done="emit('addDone', $event)"
>
<span></span>
</createStepActions>
@ -93,7 +93,7 @@
step?: ScenarioStepItem,
activeCreateAction?: CreateStepAction
);
(e: 'addDone');
(e: 'addDone', newStep: ScenarioStepItem);
}>();
const { t } = useI18n();

View File

@ -12,7 +12,7 @@
/>
<div class="flex items-center gap-[4px]">
{{ t('apiScenario.sum') }}
<div class="text-[rgb(var(--primary-5))]">{{ totalStepCount }}</div>
<div class="text-[rgb(var(--primary-5))]">{{ topLevelStepCount }}</div>
{{ t('apiScenario.steps') }}
</div>
</div>
@ -171,6 +171,7 @@
const stepTreeRef = ref<InstanceType<typeof stepTree>>();
const keyword = ref('');
const topLevelStepCount = computed(() => scenario.value.steps.length);
const totalStepCount = computed(() => countNodes(scenario.value.steps));
function handleChangeAll(value: boolean | (string | number | boolean)[]) {
@ -267,7 +268,7 @@
}
function batchDelete() {
deleteNodes(scenario.value.steps, checkedKeys.value, 'uniqueId');
deleteNodes(scenario.value.steps, checkedKeys.value, (node) => !node.isQuoteScenarioStep, 'uniqueId');
Message.success(t('common.deleteSuccess'));
if (scenario.value.steps.length === 0) {
checkedAll.value = false;
@ -357,13 +358,16 @@
if (!node.enable) {
// uniqueId便waitingDebugStepDetails
checkedKeysSet.delete(node.uniqueId);
node.executeStatus = undefined;
node.executeStatus = ScenarioExecuteStatus.UN_EXECUTE; //
} else {
node.executeStatus = ScenarioExecuteStatus.EXECUTING;
node.executeStatus = ScenarioExecuteStatus.EXECUTING; //
}
return !!node.enable;
}
node.executeStatus = undefined; //
if (node.children && node.children.length > 0) {
return true; //
}
return false;
});
const waitingDebugStepDetails = {};

View File

@ -14,14 +14,11 @@
:content="innerData.msCountController.loops?.toString()"
:disabled="!innerData.msCountController.loops"
>
<a-input-number
<a-input
v-model:model-value="innerData.msCountController.loops"
class="w-[80px] px-[8px]"
size="mini"
:step="1"
:min="1"
hide-button
:precision="0"
model-event="input"
:disabled="props.disabled"
@blur="handleInputChange"
@ -29,7 +26,7 @@
<template #prefix>
<div class="text-[12px] text-[var(--color-text-4)]">{{ t('apiScenario.num') }}:</div>
</template>
</a-input-number>
</a-input>
</a-tooltip>
</a-input-group>
<template v-if="innerData.loopType === ScenarioStepLoopTypeEnum.FOREACH">

View File

@ -17,13 +17,13 @@
<div>
<div class="mb-[2px] text-[var(--color-text-4)]">{{ t('apiScenario.belongProject') }}</div>
<div class="text-[14px] text-[var(--color-text-1)]">
{{ originProjectName }}
{{ originProjectInfo?.projectName }}
</div>
</div>
<div>
<div class="mb-[2px] text-[var(--color-text-4)]">{{ t('apiScenario.detailName') }}</div>
<div class="cursor-pointer text-[14px] text-[rgb(var(--primary-5))]" @click="goDetail">
{{ `${props.data.resourceNum}${props.data.resourceName}` }}
{{ `${originProjectInfo?.num}${originProjectInfo?.name}` }}
</div>
</div>
</div>
@ -53,7 +53,7 @@
import useOpenNewPage from '@/hooks/useOpenNewPage';
import useAppStore from '@/store/modules/app';
import { ScenarioStepItem } from '@/models/apiTest/scenario';
import { ScenarioStepItem, ScenarioStepResourceInfo } from '@/models/apiTest/scenario';
import { ScenarioStepType } from '@/enums/apiEnum';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
@ -67,12 +67,11 @@
const { t } = useI18n();
const { openNewPage } = useOpenNewPage();
const originProjectName = ref('');
const originProjectInfo = ref<ScenarioStepResourceInfo>();
async function handleVisibleChange(val: boolean) {
if (val && props.data.originProjectId) {
const res = await getStepProjectInfo(props.data.originProjectId);
originProjectName.value = res.name;
originProjectInfo.value = await getStepProjectInfo(props.data.resourceId || '', props.data.stepType);
}
}
@ -90,7 +89,7 @@
case _stepType.isQuoteScenario:
openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, {
pId: props.data.originProjectId,
sId: props.data.resourceId,
id: props.data.resourceId,
});
break;
case _stepType.isQuoteCase:

View File

@ -14,7 +14,7 @@
:field-names="{ title: 'name', key: 'uniqueId', children: 'children' }"
:virtual-list-props="{
height: '100%',
threshold: 20,
threshold: 200,
fixedSize: true,
buffer: 15, // 10 padding
}"
@ -50,10 +50,7 @@
"
>
<div class="flex cursor-pointer items-center gap-[2px] text-[var(--color-text-1)]">
<MsIcon
:type="step.expanded ? 'icon-icon_split-turn-down-left' : 'icon-icon_split_turn-down_arrow'"
:size="14"
/>
<MsIcon type="icon-icon_split_turn-down_arrow" :size="14" />
{{ step.children?.length || 0 }}
</div>
</a-tooltip>
@ -67,14 +64,14 @@
></a-switch>
<!-- 步骤执行 -->
<MsIcon
v-show="!step.isExecuting"
v-show="!step.isExecuting && step.enable"
type="icon-icon_play-round_filled"
:size="18"
class="cursor-pointer text-[rgb(var(--link-6))]"
@click.stop="executeStep(step)"
/>
<MsIcon
v-show="step.isExecuting"
v-show="step.isExecuting && step.enable"
type="icon-icon_stop"
:size="20"
class="cursor-pointer text-[rgb(var(--link-6))]"
@ -171,13 +168,7 @@
</template>
<template #extra="step">
<stepInsertStepTrigger
v-if="
!step.isQuoteScenarioStep &&
!(
step.stepType === ScenarioStepType.API_SCENARIO &&
[ScenarioStepRefType.REF, ScenarioStepRefType.PARTIAL_REF].includes(step.refType)
)
"
v-if="!step.isQuoteScenarioStep"
v-model:selected-keys="selectedKeys"
v-model:steps="steps"
v-permission="['PROJECT_API_DEBUG:READ+ADD', 'PROJECT_API_DEFINITION:READ+UPDATE']"
@ -331,7 +322,13 @@
<a-radio :value="false">{{ t('apiScenario.sourceScenario') }}</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item :label="t('apiScenario.currentScenarioTip')">
<a-form-item
:label="
scenarioConfigForm.useCurrentScenarioParam
? t('apiScenario.currentScenarioTip')
: t('apiScenario.sourceScenarioTip')
"
>
<a-radio-group v-model:model-value="scenarioConfigForm.useBothScenarioParam">
<a-radio :value="false">{{ t('apiScenario.empty') }}</a-radio>
<a-radio :value="true">
@ -1076,7 +1073,8 @@
}
}
function handleAddStepDone() {
function handleAddStepDone(newStep: ScenarioStepItem) {
selectedKeys.value = [newStep.uniqueId]; //
emit('stepAdd');
scenario.value.unSaved = true;
}
@ -1098,7 +1096,7 @@
// api api api
activeStep.value = step;
if (
(stepDetails.value[step.id] === undefined && step.copyFromStepId) ||
(stepDetails.value[step.id] === undefined && step.copyFromStepId && !step.isNew) ||
(stepDetails.value[step.id] === undefined && !step.isNew)
) {
//
@ -1109,7 +1107,7 @@
activeStep.value = step;
if (
_stepType.isCopyCase &&
((stepDetails.value[step.id] === undefined && step.copyFromStepId) ||
((stepDetails.value[step.id] === undefined && step.copyFromStepId && !step.isNew) ||
(stepDetails.value[step.id] === undefined && !step.isNew))
) {
// case
@ -1444,11 +1442,7 @@
*/
function addCustomApiStep(request: RequestParam) {
request.isNew = false;
stepDetails.value[request.stepId] = {
...request,
customizeRequest: true,
customizeRequestEnvEnable: request.customizeRequestEnvEnable,
};
stepDetails.value[request.stepId] = request;
scenario.value.stepFileParam[request.stepId] = {
linkFileIds: request.linkFileIds,
uploadFileIds: request.uploadFileIds,
@ -1490,6 +1484,7 @@
projectId: appStore.currentProjectId,
});
}
selectedKeys.value = [request.stepId]; //
emit('stepAdd');
scenario.value.unSaved = true;
}
@ -1572,6 +1567,7 @@
projectId: appStore.currentProjectId,
});
}
selectedKeys.value = [id]; //
emit('stepAdd');
scenario.value.unSaved = true;
}

View File

@ -12,70 +12,77 @@ export default function updateStepStatus(
) {
for (let i = 0; i < steps.length; i++) {
const node = steps[i];
if (
[
ScenarioStepType.LOOP_CONTROLLER,
ScenarioStepType.IF_CONTROLLER,
ScenarioStepType.ONCE_ONLY_CONTROLLER,
ScenarioStepType.API_SCENARIO,
].includes(node.stepType)
) {
if (!node.executeStatus) {
// 没有执行状态,说明未参与执行,直接跳过
break;
}
// 逻辑控制器和场景内部可以放入任意步骤,所以它的最终执行结果是根据内部步骤的执行结果来判断的
let hasNotExecuted = false;
let hasFailure = false;
let hasFakeError = false;
if (!node.children || node.children.length === 0) {
// 逻辑控制器内无步骤,则直接是未执行
node.executeStatus = ScenarioExecuteStatus.UN_EXECUTE;
} else {
for (let j = 0; j < node.children.length; j++) {
const childNode = node.children[j];
updateStepStatus([childNode], stepResponses);
if (
childNode.executeStatus &&
[ScenarioExecuteStatus.EXECUTING, ScenarioExecuteStatus.UN_EXECUTE].includes(childNode.executeStatus)
) {
// 子节点未执行或正在执行,则逻辑控制器也是未执行
hasNotExecuted = true;
} else if (childNode.executeStatus === ScenarioExecuteStatus.FAILED) {
// 子节点有一个失败,逻辑控制器就是失败
hasFailure = true;
} else if (childNode.executeStatus === ScenarioExecuteStatus.FAKE_ERROR) {
// 子节点有一个误报,逻辑控制器就是误报
hasFakeError = true;
if (node.enable) {
// 启用的步骤才计算
if (
[
ScenarioStepType.LOOP_CONTROLLER,
ScenarioStepType.IF_CONTROLLER,
ScenarioStepType.ONCE_ONLY_CONTROLLER,
ScenarioStepType.API_SCENARIO,
].includes(node.stepType)
) {
if (!node.executeStatus) {
// 没有执行状态,说明未参与执行,直接跳过
// eslint-disable-next-line no-continue
continue;
}
// 逻辑控制器和场景内部可以放入任意步骤,所以它的最终执行结果是根据内部步骤的执行结果来判断的
let hasNotExecuted = false;
let hasFailure = false;
let hasFakeError = false;
if (!node.children || node.children.length === 0) {
// 逻辑控制器内无步骤,则直接是未执行
node.executeStatus = ScenarioExecuteStatus.UN_EXECUTE;
} else {
for (let j = 0; j < node.children.length; j++) {
const childNode = node.children[j];
updateStepStatus([childNode], stepResponses);
if (childNode.executeStatus === ScenarioExecuteStatus.FAILED) {
// 子节点有一个失败,逻辑控制器就是失败
hasFailure = true;
} else if (childNode.executeStatus === ScenarioExecuteStatus.FAKE_ERROR) {
// 子节点有一个误报,逻辑控制器就是误报
hasFakeError = true;
} else if (
childNode.executeStatus &&
[ScenarioExecuteStatus.EXECUTING, ScenarioExecuteStatus.UN_EXECUTE].includes(childNode.executeStatus)
) {
// 子节点未执行或正在执行,则逻辑控制器也是未执行
hasNotExecuted = true;
}
}
// 递归完子节点后,判断当前逻辑控制器的状态
if (hasFailure) {
node.executeStatus = ScenarioExecuteStatus.FAILED;
} else if (hasFakeError) {
node.executeStatus = ScenarioExecuteStatus.FAKE_ERROR;
} else if (hasNotExecuted) {
node.executeStatus = ScenarioExecuteStatus.UN_EXECUTE;
} else {
node.executeStatus = ScenarioExecuteStatus.SUCCESS;
}
}
// 递归完子节点后,判断当前逻辑控制器的状态
if (hasNotExecuted) {
} else if (node.stepType === ScenarioStepType.CONSTANT_TIMER) {
// 等待时间直接设置为成功
node.executeStatus = ScenarioExecuteStatus.SUCCESS;
} else if (node.executeStatus === ScenarioExecuteStatus.EXECUTING) {
// 非逻辑控制器直接更改本身状态
if (stepResponses[node.uniqueId] && stepResponses[node.uniqueId].length > 0) {
// 存在多个请求结果说明是循环控制器下的步骤,需要判断其子步骤的执行结果
if (stepResponses[node.uniqueId].some((report) => !report.isSuccessful)) {
node.executeStatus = ScenarioExecuteStatus.FAILED;
} else if (
stepResponses[node.uniqueId].some((report) => report.status === ScenarioExecuteStatus.FAKE_ERROR)
) {
node.executeStatus = ScenarioExecuteStatus.FAKE_ERROR;
} else {
node.executeStatus = ScenarioExecuteStatus.SUCCESS;
}
} else {
node.executeStatus = ScenarioExecuteStatus.UN_EXECUTE;
} else if (hasFailure) {
node.executeStatus = ScenarioExecuteStatus.FAILED;
} else if (hasFakeError) {
node.executeStatus = ScenarioExecuteStatus.FAKE_ERROR;
} else {
node.executeStatus = ScenarioExecuteStatus.SUCCESS;
}
}
} else if (node.stepType === ScenarioStepType.CONSTANT_TIMER) {
// 等待时间直接设置为成功
node.executeStatus = ScenarioExecuteStatus.SUCCESS;
} else if (node.executeStatus === ScenarioExecuteStatus.EXECUTING) {
// 非逻辑控制器直接更改本身状态
if (stepResponses[node.uniqueId] && stepResponses[node.uniqueId].length > 0) {
if (stepResponses[node.uniqueId].some((report) => !report.isSuccessful)) {
node.executeStatus = ScenarioExecuteStatus.FAILED;
} else if (stepResponses[node.uniqueId].some((report) => report.status === ScenarioExecuteStatus.FAKE_ERROR)) {
node.executeStatus = ScenarioExecuteStatus.FAKE_ERROR;
} else {
node.executeStatus = ScenarioExecuteStatus.SUCCESS;
}
} else {
node.executeStatus = ScenarioExecuteStatus.UN_EXECUTE;
}
}
}
}

View File

@ -200,8 +200,8 @@
function share() {
if (isSupported) {
const url = window.location.href;
const dIdParam = `&sId=${scenario.value.id}`;
const copyUrl = url.includes('sId') ? url.split('&sId')[0] : url;
const dIdParam = `&id=${scenario.value.id}`;
const copyUrl = url.includes('id') ? url.split('&id')[0] : url;
copy(`${copyUrl}${dIdParam}`);
Message.success(t('common.copySuccess'));
} else {

View File

@ -192,19 +192,22 @@
if (scenario.reportId === data.reportId) {
// tabtab
data.taskResult.requestResults.forEach((result) => {
if (scenario.stepResponses[result.stepId] === undefined) {
scenario.stepResponses[result.stepId] = [];
}
scenario.stepResponses[result.stepId].push({
...result,
console: data.taskResult.console,
});
if (result.status === ScenarioExecuteStatus.FAKE_ERROR) {
scenario.executeFakeErrorCount += 1;
} else if (result.isSuccessful) {
scenario.executeSuccessCount += 1;
} else {
scenario.executeFailCount += 1;
if (result.stepId) {
// id
if (scenario.stepResponses[result.stepId] === undefined) {
scenario.stepResponses[result.stepId] = [];
}
scenario.stepResponses[result.stepId].push({
...result,
console: data.taskResult.console,
});
if (result.status === ScenarioExecuteStatus.FAKE_ERROR) {
scenario.executeFakeErrorCount += 1;
} else if (result.isSuccessful) {
scenario.executeSuccessCount += 1;
} else {
scenario.executeFailCount += 1;
}
}
});
}
@ -306,6 +309,8 @@
if (node.enable) {
node.executeStatus = ScenarioExecuteStatus.EXECUTING;
waitingDebugStepDetails[node.id] = activeScenarioTab.value.stepDetails[node.id];
} else {
node.executeStatus = ScenarioExecuteStatus.UN_EXECUTE;
}
return !!node.enable;
});
@ -573,8 +578,8 @@
onBeforeMount(() => {
selectRecycleCount();
if (route.query.sId) {
openScenarioTab(route.query.sId as string);
if (route.query.id) {
openScenarioTab(route.query.id as string);
}
});

View File

@ -1,244 +1,262 @@
export default {
'apiScenario.env': 'Environment: {name}',
'apiScenario.allScenario': 'All scenarios',
'apiScenario.createScenario': 'Create scenario',
'apiScenario.importScenario': 'Import scenario',
'apiScenario.tree.selectorPlaceholder': 'Please enter the module name',
'apiScenario.tree.folder.allScenario': 'All scenarios',
'apiScenario.tree.recycleBin': 'Recycle bin',
'apiScenario.tree.noMatchModule': 'No matching module/scene yet',
'apiScenario.createSubModule': 'Create sub-module',
'apiScenario.module.deleteTipTitle': 'Delete {name} module?',
'apiScenario.env': 'Current Environment: {name}',
'apiScenario.allScenario': 'All Scenarios',
'apiScenario.createScenario': 'Create Scenario',
'apiScenario.importScenario': 'Import Scenario',
'apiScenario.tree.selectorPlaceholder': 'Please enter module name',
'apiScenario.tree.folder.allScenario': 'All Scenarios',
'apiScenario.tree.recycleBin': 'Recycle Bin',
'apiScenario.tree.noMatchModule': 'No matching module/scenario found',
'apiScenario.createSubModule': 'Create Submodule',
'apiScenario.module.deleteTipTitle': 'Delete Module {name}?',
'apiScenario.module.deleteTipContent':
'After deletion, all scenarios under the module will be deleted synchronously. Please operate with caution.',
'apiScenario.deleteConfirm': 'Confirm',
'apiScenario.deleteSuccess': 'Success',
'apiScenario.moveSuccess': 'Success',
'apiScenario.baseInfo': 'Base info',
'Deleting will also delete all scenarios under the module. Proceed with caution.',
'apiScenario.deleteConfirm': 'Confirm Delete',
'apiScenario.deleteSuccess': 'Delete Successful',
'apiScenario.moveSuccess': 'Move Successful',
'apiScenario.baseInfo': 'Basic Information',
'apiScenario.step': 'Step',
'apiScenario.params': 'Params',
'apiScenario.params': 'Parameters',
'apiScenario.prePost': 'Pre/Post',
'apiScenario.assertion': 'Assertion',
'apiScenario.executeHistory': 'Execute history',
'apiScenario.changeHistory': 'Change history',
'apiScenario.dependency': 'Dependencies',
'apiScenario.quote': 'Reference',
'apiScenario.params.convention': 'Convention parameter',
'apiScenario.executeHistory': 'Execution History',
'apiScenario.changeHistory': 'Change History',
'apiScenario.dependency': 'Dependency',
'apiScenario.quote': 'Quote',
'apiScenario.params.convention': 'Conventional Parameters',
'apiScenario.params.searchPlaceholder': 'Search by name or tag',
'apiScenario.params.priority':
'Variable priority: Temporary parameters>Scene parameters>Environment parameters>Global parameters; Note: Avoid using variables with the same name. When using variables with the same name, scene level CSV has the highest priority',
'apiScenario.params.name': 'Variable name',
'Variable Priority: Temporary Parameters > Scenario Parameters > Environment Parameters > Global Parameters; Note: Avoid using variables with the same name. In case of same name variables, scenario-level CSV has the highest priority.',
'apiScenario.params.name': 'Variable Name',
'apiScenario.params.type': 'Type',
'apiScenario.params.paramValue': 'Parameter value',
'apiScenario.params.paramValue': 'Parameter Value',
'apiScenario.params.tag': 'Tag',
'apiScenario.params.desc': 'Description',
'apiScenario.table.columns.name': 'Name',
'apiScenario.table.columns.level': 'Level',
'apiScenario.table.columns.status': 'status',
'apiScenario.table.columns.runResult': 'Run result',
'apiScenario.params.typeTooltip': 'Json: only supports UI testing',
'apiScenario.table.columns.name': 'Scenario Name',
'apiScenario.table.columns.level': 'Scenario Level',
'apiScenario.table.columns.status': 'Status',
'apiScenario.table.columns.runResult': 'Execution Result',
'apiScenario.table.columns.tags': 'Tags',
'apiScenario.table.columns.scenarioEnv': 'Scenario environment',
'apiScenario.table.columns.steps': 'Steps',
'apiScenario.table.columns.passRate': 'Pass rate',
'apiScenario.table.columns.module': 'Module',
'apiScenario.table.columns.createUser': 'Create user',
'apiScenario.table.columns.createTime': 'Create time',
'apiScenario.table.columns.updateUser': 'Update user',
'apiScenario.table.columns.updateTime': 'Update time',
'apiScenario.table.columns.deleteUser': 'Delete User',
'apiScenario.table.columns.operation': 'Operation',
'apiScenario.table.columns.deleteTime': 'Delete time',
'api_scenario.table.tableNoDataAndPlease': 'No data yet, please',
'apiScenario.table.columns.scenarioEnv': 'Scenario Environment',
'apiScenario.table.columns.steps': 'Number of Steps',
'apiScenario.table.columns.passRate': 'Pass Rate',
'apiScenario.table.columns.module': 'Belongs to Module',
'apiScenario.table.columns.createUser': 'Creator',
'apiScenario.table.columns.createTime': 'Creation Time',
'apiScenario.table.columns.updateUser': 'Updater',
'apiScenario.table.columns.updateTime': 'Update Time',
'apiScenario.table.columns.operation': 'Operator',
'apiScenario.table.columns.deleteUser': 'Deleter',
'apiScenario.table.columns.deleteTime': 'Deletion Time',
'api_scenario.table.searchPlaceholder': 'Search by ID/Name/Tag',
'api_scenario.table.batchModalSubTitle': '(Selected {count} scenarios)',
'api_scenario.table.chooseAttr': 'Choose Attribute',
'api_scenario.table.attrRequired': 'Attribute cannot be empty',
'api_scenario.table.batchUpdate': 'Batch Update to',
'api_scenario.table.valueRequired': 'Attribute value cannot be empty',
'api_scenario.table.deleteScenarioTipTitle': 'Confirm Delete {name}?',
'api_scenario.table.batchDeleteScenarioTip': 'Confirm delete {count} selected scenarios?',
'api_scenario.table.deleteScenarioTip':
'Deleting may cause failure in resources referencing this scenario. Proceed with caution!',
'api_scenario.table.apiStatus': 'Status',
'api_scenario.table.status.underway': 'Underway',
'api_scenario.table.status.completed': 'Completed',
'api_scenario.table.status.deprecate': 'Deprecated',
'api_scenario.table.tableNoDataAndPlease': 'No data, please',
'api_scenario.table.or': 'or',
'apiScenario.execute': 'Execute',
'apiScenario.prev': 'Last time',
'apiScenario.next': 'Next time',
'apiScenario.sumLoop': 'A total of {count} loops',
'apiScenario.times': 'bout',
'apiScenario.executionResult': 'Execution result',
// 批量操作文案
'api_scenario.batch_operation.success': 'Success {success} error {error} ',
'api_scenario.table.batchMoveConfirm': 'Ready to {opt} {count} scenarios',
'apiScenario.name': 'Scene name',
'apiScenario.nameRequired': 'Scene name cannot be empty',
'apiScenario.namePlaceholder': 'Please enter scene name',
'apiScenario.belongModule': 'Belonging module',
'apiScenario.level': 'Scene level',
'apiScenario.status': 'Scene status',
'apiScenario.prev': 'Previous',
'apiScenario.next': 'Next',
'apiScenario.sumLoop': 'Total {count} loops',
'apiScenario.times': 'times',
'apiScenario.executionResult': 'Execution Result',
'apiScenario.refreshRefScenario': 'Refresh Referenced Scenario Data',
'apiScenario.updateRefScenarioSuccess': 'Referenced scenario data has been updated',
'apiScenario.replaceSuccess': 'Step replacement successful',
// Batch operation text
'api_scenario.batch_operation.success': 'Successfully {opt} to {name}',
'api_scenario.table.batchMoveConfirm': '{opt} {count} scenarios to selected module',
'apiScenario.name': 'Scenario Name',
'apiScenario.nameRequired': 'Scenario name cannot be empty',
'apiScenario.namePlaceholder': 'Please enter scenario name',
'apiScenario.belongModule': 'Belongs to Module',
'apiScenario.level': 'Scenario Level',
'apiScenario.status': 'Scenario Status',
'apiScenario.descPlaceholder': 'Please describe the scenario',
'apiScenario.addStep': 'Add steps',
'apiScenario.addStep': 'Add Step',
'apiScenario.requestScenario': 'Request/Scenario',
'apiScenario.importSystemApi': 'Import system requests',
'apiScenario.customApi': 'Custom request',
'apiScenario.logicControl': 'Logic control',
'apiScenario.loopControl': 'Loop controller',
'apiScenario.importSystemApi': 'Import System Request',
'apiScenario.customApi': 'Custom Request',
'apiScenario.logicControl': 'Logic Control',
'apiScenario.loopControl': 'Loop Controller',
'apiScenario.tutorial': 'Tutorial',
'apiScenario.conditionControl': 'Conditional controller',
'apiScenario.onlyOnceControl': 'Only once controller',
'apiScenario.conditionControl': 'Condition Controller',
'apiScenario.onlyOnceControl': 'Only Once Controller',
'apiScenario.other': 'Other',
'apiScenario.scriptOperation': 'Script operation',
'apiScenario.waitTime': 'Waiting time',
'apiScenario.quoteApi': 'Reference API',
'apiScenario.scriptOperation': 'Script Operation',
'apiScenario.waitTime': 'Wait Time',
'apiScenario.quoteApi': 'Quote API',
'apiScenario.copyApi': 'Copy API',
'apiScenario.quoteCase': 'Reference CASE',
'apiScenario.quoteCase': 'Quote CASE',
'apiScenario.copyCase': 'Copy CASE',
'apiScenario.quoteScenario': 'Reference scene',
'apiScenario.copyScenario': 'Copy scene',
'apiScenario.sum': 'Common',
'apiScenario.quoteScenario': 'Quote Scenario',
'apiScenario.copyScenario': 'Copy Scenario',
'apiScenario.sum': 'Total',
'apiScenario.steps': 'steps',
'apiScenario.expandAllStep': 'Expand all substeps',
'apiScenario.collapseAllStep': 'Collapse all substeps',
'apiScenario.executeTime': 'Execution time',
'apiScenario.executeResult': 'Execution result',
'apiScenario.checkReport': 'View report',
'apiScenario.expandAllStep': 'Expand All Substeps',
'apiScenario.collapseAllStep': 'Collapse All Substeps',
'apiScenario.executeTime': 'Execution Time:',
'apiScenario.executeResult': 'Execution Result',
'apiScenario.checkReport': 'View Report',
'apiScenario.searchByName': 'Search by step name/description',
'apiScenario.api': 'API',
'apiScenario.case': 'CASE',
'apiScenario.case': 'Case',
'apiScenario.scenario': 'Scenario',
'apiScenario.sumSelected': 'Total selection',
'apiScenario.scenarioConfig': 'Scene configuration',
'apiScenario.noMatchStep': 'No matching step data yet',
'apiScenario.pleaseInputStepName': 'Please enter a step name',
'apiScenario.belongProject': 'Project',
'apiScenario.sumSelected': 'Selected',
'apiScenario.scenarioConfig': 'Scenario Configuration',
'apiScenario.noMatchStep': 'No matching step data',
'apiScenario.pleaseInputStepName': 'Please enter step name',
'apiScenario.belongProject': 'Belongs to Project',
'apiScenario.detailName': 'Name',
'apiScenario.crossProject': 'Cross-project',
'apiScenario.crossProject': 'Cross Project',
'apiScenario.expandStepTip': 'Expand {count} substeps',
'apiScenario.collapseStepTip': 'Collapse {count} substeps',
'apiScenario.inside': 'Add substep',
'apiScenario.before': 'Insert above',
'apiScenario.after': 'Insert below',
'apiScenario.num': 'frequency',
'apiScenario.space': 'Interval(ms)',
'apiScenario.timeout': 'Timeout(ms)',
'apiScenario.waitTimeMs': 'Wait(ms)',
'apiScenario.pleaseInputStepDesc': 'Please enter a step description',
'apiScenario.variable': 'Variable name {suffix}',
'apiScenario.valuePrefix': 'variable prefix',
'apiScenario.value': 'variable',
'apiScenario.conditionValue': 'variable',
'apiScenario.msWhileVariableValue': 'variable',
'apiScenario.msWhileVariableScriptValue': 'expression',
'apiScenario.condition': 'condition',
'apiScenario.expression': 'expression',
'apiScenario.equal': 'equal',
'apiScenario.notEqualTo': 'not equal to',
'apiScenario.greater': 'more than the',
'apiScenario.less': 'less than',
'apiScenario.greaterOrEqual': 'greater or equal to',
'apiScenario.lessOrEqual': 'less than or equal to',
'apiScenario.inside': 'Add Substep',
'apiScenario.before': 'Insert Above',
'apiScenario.after': 'Insert Below',
'apiScenario.num': 'Number',
'apiScenario.space': 'Interval (ms)',
'apiScenario.timeout': 'Timeout (ms)',
'apiScenario.waitTimeMs': 'Wait (ms)',
'apiScenario.pleaseInputStepDesc': 'Please enter step description',
'apiScenario.variable': 'Variable Name {suffix}',
'apiScenario.valuePrefix': 'Variable Prefix',
'apiScenario.value': 'Variable Value',
'apiScenario.conditionValue': 'Variable Value',
'apiScenario.msWhileVariableValue': 'Variable Value',
'apiScenario.msWhileVariableScriptValue': 'Expression',
'apiScenario.condition': 'Condition',
'apiScenario.expression': 'Expression',
'apiScenario.equal': 'Equal',
'apiScenario.notEqualTo': 'Not Equal',
'apiScenario.greater': 'Greater',
'apiScenario.less': 'Less',
'apiScenario.greaterOrEqual': 'Greater or Equal',
'apiScenario.lessOrEqual': 'Less or Equal',
'apiScenario.include': 'Include',
'apiScenario.notInclude': 'Not included',
'apiScenario.notInclude': 'Not Include',
'apiScenario.null': 'Null',
'apiScenario.notNull': 'Not Null',
'apiScenario.range': 'Scope',
'apiScenario.topStep': 'First level steps',
'apiScenario.allStep': 'All substeps',
'apiScenario.saveAsApi': 'Save as new interface',
'apiScenario.scenarioLevel': 'Scene level',
'apiScenario.running': 'Executing',
'apiScenario.unExecute': 'Not performed',
'apiScenario.response': 'Response content',
'apiScenario.quoteMode': 'Reference mode',
'apiScenario.fullQuote': 'Full quote',
'apiScenario.stepQuote': 'Step reference',
'apiScenario.runRule': 'Parameter value rules',
'apiScenario.currentScenario': 'Prioritize current scene parameters',
'apiScenario.sourceScenario': 'Priority source scene parameters',
'apiScenario.currentScenarioTip': 'When the current scene parameter does not exist, take',
'apiScenario.sourceScenarioTip': 'When the source scene parameter does not exist, take',
'apiScenario.empty': 'Null value',
'apiScenario.currentScenarioParams': 'Current scene parameters',
'apiScenario.sourceScenarioParams': 'Source scene parameters',
'apiScenario.sourceScenarioEnv': 'Source scene environment',
'apiScenario.valuePriority': 'Value priority',
'apiScenario.range': 'Range',
'apiScenario.topStep': 'Top-level Step',
'apiScenario.allStep': 'All Substeps',
'apiScenario.saveAsApi': 'Save as New API',
'apiScenario.scenarioLevel': 'Scenario Level',
'apiScenario.running': 'Running',
'apiScenario.unExecute': 'Not Executed',
'apiScenario.response': 'Response Content',
'apiScenario.quoteMode': 'Quote Mode',
'apiScenario.fullQuote': 'Full Quote',
'apiScenario.stepQuote': 'Step Quote',
'apiScenario.runRule': 'Parameter Value Rule',
'apiScenario.currentScenario': 'Priority: Current Scenario Parameters',
'apiScenario.sourceScenario': 'Priority: Source Scenario Parameters',
'apiScenario.currentScenarioTip': 'When current scenario parameters do not exist, take',
'apiScenario.sourceScenarioTip': 'When source scenario parameters do not exist, take',
'apiScenario.empty': 'Empty Value',
'apiScenario.currentScenarioParams': 'Current Scenario Parameters',
'apiScenario.sourceScenarioParams': 'Source Scenario Parameters',
'apiScenario.sourceScenarioEnv': 'Source Scenario Environment',
'apiScenario.valuePriority': 'Value Priority:',
'apiScenario.currentScenarioAndNull':
'Current step parameters > Current scene parameters > Current environment parameters > Null value',
'Current Step Parameters > Current Scenario Parameters > Current Environment Parameters > Empty Value',
'apiScenario.currentScenarioAndNullAndSourceEnv':
'Current step parameters > Current scene parameters > Source environment parameters > Null value',
'Current Step Parameters > Current Scenario Parameters > Source Environment Parameters > Empty Value',
'apiScenario.currentScenarioAndSourceScenario':
'Current step parameters > Current scene parameters > Current environment parameters > Source scene parameters',
'Current Step Parameters > Current Scenario Parameters > Current Environment Parameters > Source Scenario Parameters',
'apiScenario.currentScenarioAndSourceScenarioAndSourceEnv':
'Current step parameters > Current scene parameters > Source scene parameters > Source environment parameters',
'apiScenario.sourceScenarioAndNull': 'Source Scene Parameters > Null',
'apiScenario.sourceScenarioAndNullAndSourceEnv': 'Source Scene Parameters > Source Environment Parameters > Null',
'Current Step Parameters > Current Scenario Parameters > Source Scenario Parameters > Source Environment Parameters',
'apiScenario.sourceScenarioAndNull': 'Source Scenario Parameters > Empty Value',
'apiScenario.sourceScenarioAndNullAndSourceEnv':
'Source Scenario Parameters > Source Environment Parameters > Empty Value',
'apiScenario.sourceScenarioAndCurrentScenario':
'Source scene parameters > Current step parameters > Current scene parameters > Current environment parameters',
'Source Scenario Parameters > Current Step Parameters > Current Scenario Parameters > Current Environment Parameters',
'apiScenario.sourceScenarioAndCurrentScenarioAndSourceEnv':
'Source scene parameters > Source environment parameters > Current step parameters > Current scene parameters',
'Source Scenario Parameters > Source Environment Parameters > Current Step Parameters > Current Scenario Parameters',
'apiScenario.fullQuoteTip':
'Full quotation: Follow the source step content and step status changes, the step status cannot be adjusted',
'Full Quote: Follows the original step content and step status changes. Step status cannot be adjusted.',
'apiScenario.stepQuoteTip':
'Step reference: only follow the content changes of the source step, and the step status can be adjusted',
'apiScenario.sourceScenarioEnvTip': 'Operating environment, including environmental parameters',
'apiScenario.setSuccess': 'Setup successful',
'apiScenario.pleaseInputUrl': 'Please enter url',
// 执行历史
'Step Quote: Only follows the original step content changes. Step status can be adjusted.',
'apiScenario.sourceScenarioEnvTip': 'Runtime environment, including environment parameters',
'apiScenario.setSuccess': 'Set Successful',
'apiScenario.pleaseInputUrl': 'Please enter URL',
// Execution History
'apiScenario.executeHistory.searchPlaceholder': 'Search by ID or name',
'apiScenario.executeHistory.num': 'Number',
'apiScenario.executeHistory.execution.triggerMode': 'Trigger mode',
'apiScenario.executeHistory.execution.status': 'Execution result',
'apiScenario.executeHistory.num': 'No.',
'apiScenario.executeHistory.execution.triggerMode': 'Execution Mode',
'apiScenario.executeHistory.execution.status': 'Execution Result',
'apiScenario.executeHistory.execution.operator': 'Operator',
'apiScenario.executeHistory.execution.operatorTime': 'Operation time',
'apiScenario.executeHistory.execution.operation': 'Execution result',
'apiScenario.executeHistory.status.pending': 'Pending',
'apiScenario.executeHistory.execution.operatorTime': 'Operation Time',
'apiScenario.executeHistory.execution.operation': 'Execution Result',
'apiScenario.executeHistory.status.pending': 'Queued',
'apiScenario.executeHistory.status.running': 'Running',
'apiScenario.executeHistory.status.rerunning': 'Rerunning',
'apiScenario.executeHistory.status.error': 'Error',
'apiScenario.executeHistory.status.success': 'Success',
'apiScenario.executeHistory.status.fake.error': 'Fake error',
'apiScenario.executeHistory.status.error': 'Failed',
'apiScenario.executeHistory.status.success': 'Successful',
'apiScenario.executeHistory.status.fake.error': 'False Positive',
'apiScenario.executeHistory.status.fake.stopped': 'Stopped',
// 操作历史
// Operation History
'apiScenario.historyListTip':
'View and compare historical changes. According to the rules set by the administrator, the change history data will be automatically deleted.',
'apiScenario.changeOrder': 'Change serial number',
'View and compare historical modifications. According to administrator settings, change history data will be automatically deleted.',
'apiScenario.changeOrder': 'Change Order',
'apiScenario.type': 'Type',
'apiScenario.operationUser': 'Operator',
'apiScenario.updateTime': 'Update time',
'apiScenario.updateTime': 'Update Time',
// 回收站
// Recycle Bin
'api_scenario.recycle.recover': 'Recover',
'api_scenario.recycle.recoveredSuccessfully': 'Recover success',
'api_scenario.recycle.list': 'Recycle list',
'api_scenario.recycle.batchCleanOut': 'Delete',
'api_scenario.recycle.completedDeleteCaseTitle': 'Are you sure you want to completely delete {name}?',
'api_scenario.recycle.recoveredSuccessfully': 'Recover Successful',
'api_scenario.recycle.list': 'Recycle Bin List',
'api_scenario.recycle.batchCleanOut': 'Permanently Delete',
'api_scenario.recycle.completedDeleteCaseTitle': 'Confirm Permanently Delete {name}?',
'api_scenario.recycle.cleanOutDeleteOnRecycleTip':
'After deletion, the scene cannot be restored, please operate with caution!',
'api_scenario.table.searchPlaceholder': 'Search by ID/Name/Tag',
'apiScenario.quoteTreeNoData': 'There is currently no reference data. You can switch projects to obtain data.',
'Deleting will permanently remove the scenario. Proceed with caution!',
'apiScenario.quoteTreeNoData': 'No quotable data available, switch projects to retrieve data',
'apiScenario.quoteTreeSearchTip': 'Enter module name to search',
'apiScenario.quoteTableSearchTip': 'Search by path or name',
'apiScenario.collapseAll': 'Collapse all submodules',
'apiScenario.expandAll': 'Expand all submodules',
'apiScenario.collapseAll': 'Collapse All Submodules',
'apiScenario.expandAll': 'Expand All Submodules',
'apiScenario.scriptOperationName': 'Script operation name',
'apiScenario.scriptOperationNamePlaceholder': 'Please enter the script operation name',
'apiScenario.scriptOperationName': 'Script Operation Name',
'apiScenario.scriptOperationNamePlaceholder': 'Please enter script operation name',
'apiScenario.setting.cookie.config': 'Cookie configuration',
'apiScenario.setting.cookie.config': 'Cookie Configuration',
'apiScenario.setting.environment.cookie': 'Environment Cookie',
'apiScenario.setting.share.cookie': 'Shared Cookie',
'apiScenario.setting.run.config': 'Run configuration',
'apiScenario.setting.step.waitTime': 'Step wait time',
'apiScenario.setting.waitTime': 'Wait time',
'apiScenario.setting.step.rule': 'Step execution failure rule',
'apiScenario.setting.step.rule.ignore': 'Ignore error and continue execution',
'apiScenario.setting.step.rule.stop': 'Stop/end execution',
'apiScenario.setting.run.config': 'Run Configuration',
'apiScenario.setting.step.waitTime': 'Step Wait Time',
'apiScenario.setting.waitTime': 'Wait Time',
'apiScenario.setting.step.rule': 'Step Execution Failure Rule',
'apiScenario.setting.step.rule.ignore': 'Ignore Error and Continue Execution',
'apiScenario.setting.step.rule.stop': 'Stop/End Execution',
'apiScenario.setting.cookie.config.tip':
'When there are both global and scene variable cookies, shared cookies will overwrite both global and scene variable cookies',
'When both global variable cookie and scenario variable cookie exist, shared cookie will override global cookie and scenario variable cookie',
'apiScenario.setting.share.cookie.tip':
'As long as the system extracts the returned cookie information from the result of a certain step, the subsequent steps will use this cookie. If a cookie variable is added to the request, it will also be overwritten',
'If the system extracts cookie information from the result of a certain step, subsequent steps will use this cookie. If the request adds a Cookie variable, it will be overridden',
'apiScenario.setting.waitTime.tip':
'When running a scenario, each step of the scenario will wait for a certain time after execution before triggering the next step to start execution',
// 定时任务
'apiScenario.schedule.create': 'Create schedule',
'apiScenario.schedule.update': 'Update schedule',
'apiScenario.schedule.delete': 'Delete schedule',
'apiScenario.schedule.config.resource_pool': 'Resource pool',
'apiScenario.schedule.task.status': 'Task status',
'apiScenario.schedule.task.schedule': 'Trigger time',
'apiScenario.schedule.abbreviation': 'SCHEDULE',
'apiScenario.schedule.task.status.tooltip.one': 'Open: execute schedule task',
'apiScenario.schedule.task.status.tooltip.two': 'Close: stop executing schedule task',
'apiScenario.schedule.table.tooltip.enable.one': 'Schedule is open',
'apiScenario.schedule.table.tooltip.enable.two': 'Next trigger time{time}',
'apiScenario.schedule.table.tooltip.disable': 'Schedule is close',
'When running a scenario, each step of the scenario will wait for the specified time before triggering the next step',
// Scheduled Task
'apiScenario.schedule.create': 'Create Scheduled Task',
'apiScenario.schedule.update': 'Update Scheduled Task',
'apiScenario.schedule.delete': 'Delete Scheduled Task',
'apiScenario.schedule.config.resource_pool': 'Run Resource Pool',
'apiScenario.schedule.task.status': 'Task Status',
'apiScenario.schedule.task.schedule': 'Task Trigger Time',
'apiScenario.schedule.abbreviation': 'Scheduled',
'apiScenario.schedule.task.status.tooltip.one': 'Enabled: Execute the scheduled task',
'apiScenario.schedule.task.status.tooltip.two': 'Disabled: Stop the scheduled task',
'apiScenario.schedule.table.tooltip.enable.one': 'Scheduled task is enabled',
'apiScenario.schedule.table.tooltip.enable.two': 'Next run time: {time}',
'apiScenario.schedule.table.tooltip.disable': 'Scheduled task is disabled',
};

View File

@ -31,6 +31,7 @@ export default {
'apiScenario.params.paramValue': '参数值',
'apiScenario.params.tag': '标签',
'apiScenario.params.desc': '描述',
'apiScenario.params.typeTooltip': 'Json仅支持 UI 测试',
'apiScenario.table.columns.name': '场景名称',
'apiScenario.table.columns.level': '场景等级',
'apiScenario.table.columns.status': '状态',

View File

@ -72,7 +72,7 @@ export default {
'caseManagement.featureCase.fileName': '文件名',
'caseManagement.featureCase.description': '描述',
'caseManagement.featureCase.tags': '标签',
'caseManagement.featureCase.enableTags': `开启:新增标签,关闭:覆盖原有标签`,
'caseManagement.featureCase.enableTags': '开启:新增标签',
'caseManagement.featureCase.closeTags': '关闭:覆盖原有标签',
'caseManagement.featureCase.appendTag': '追加标签',
'caseManagement.featureCase.batchEdit': '批量编辑 (已选 { number } 条用例)',