feat(接口测试): csv部分页面
This commit is contained in:
parent
1c1d52f45f
commit
472e7ae5eb
|
@ -403,7 +403,7 @@
|
||||||
function handleOpenSaveAs(item: TagData) {
|
function handleOpenSaveAs(item: TagData) {
|
||||||
inputFilesPopoverVisible.value = false;
|
inputFilesPopoverVisible.value = false;
|
||||||
// 这里先判定 uid 是否存在,存在则是刚上传的文件;否则是已保存过后的详情文件
|
// 这里先判定 uid 是否存在,存在则是刚上传的文件;否则是已保存过后的详情文件
|
||||||
savingFile.value = fileList.value.find((file) => (file.uid || file[props.fields.id]) === item.value);
|
savingFile.value = fileList.value.find((file) => file.uid === item.value || file[props.fields.id] === item.value);
|
||||||
saveFilePopoverVisible.value = true;
|
saveFilePopoverVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,7 @@
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
initModuleOptions();
|
initModuleOptions();
|
||||||
saveFileForm.value.name = props.savingFile?.name?.split('.').shift() || '';
|
saveFileForm.value.name = (props.savingFile?.name || props.savingFile?.fileName)?.split('.').shift() || '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<a-form ref="formRef" :model="propsRes">
|
<a-form ref="formRef" :model="propsRes" layout="vertical">
|
||||||
<MsBaseTable
|
<MsBaseTable
|
||||||
v-bind="propsRes"
|
v-bind="propsRes"
|
||||||
:hoverable="false"
|
:hoverable="false"
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
ResponseDefinition,
|
ResponseDefinition,
|
||||||
} from '@/models/apiTest/common';
|
} from '@/models/apiTest/common';
|
||||||
import type { MockParams } from '@/models/apiTest/mock';
|
import type { MockParams } from '@/models/apiTest/mock';
|
||||||
|
import type { CsvVariable } from '@/models/apiTest/scenario';
|
||||||
import {
|
import {
|
||||||
FullResponseAssertionType,
|
FullResponseAssertionType,
|
||||||
RequestAssertionCondition,
|
RequestAssertionCondition,
|
||||||
|
@ -242,16 +243,13 @@ export const regexDefaultParamItem = {
|
||||||
moreSettingPopoverVisible: false,
|
moreSettingPopoverVisible: false,
|
||||||
};
|
};
|
||||||
// 响应断言类型映射
|
// 响应断言类型映射
|
||||||
export const responseAssertionTypeMap: Record<FullResponseAssertionType, string> = {
|
export const responseAssertionTypeMap: Record<string, string> = {
|
||||||
[FullResponseAssertionType.DOCUMENT]: 'apiTestManagement.document',
|
[FullResponseAssertionType.DOCUMENT]: 'apiTestManagement.document',
|
||||||
[FullResponseAssertionType.RESPONSE_CODE]: 'apiTestManagement.responseCode',
|
[FullResponseAssertionType.RESPONSE_CODE]: 'apiTestManagement.responseCode',
|
||||||
[FullResponseAssertionType.RESPONSE_HEADER]: 'apiTestManagement.responseHeader',
|
[FullResponseAssertionType.RESPONSE_HEADER]: 'apiTestManagement.responseHeader',
|
||||||
[FullResponseAssertionType.RESPONSE_TIME]: 'apiTestManagement.responseTime',
|
[FullResponseAssertionType.RESPONSE_TIME]: 'apiTestManagement.responseTime',
|
||||||
[FullResponseAssertionType.SCRIPT]: 'apiTestManagement.script',
|
[FullResponseAssertionType.SCRIPT]: 'apiTestManagement.script',
|
||||||
[FullResponseAssertionType.VARIABLE]: 'apiTestManagement.variable',
|
[FullResponseAssertionType.VARIABLE]: 'apiTestManagement.variable',
|
||||||
[FullResponseAssertionType.JSON_PATH]: 'jsonPath',
|
|
||||||
[FullResponseAssertionType.XPATH]: 'xPath',
|
|
||||||
[FullResponseAssertionType.REGEX]: 'apiTestManagement.regex',
|
|
||||||
};
|
};
|
||||||
// 提取类型选项
|
// 提取类型选项
|
||||||
export const extractTypeOptions = [
|
export const extractTypeOptions = [
|
||||||
|
@ -415,3 +413,32 @@ export const matchRuleOptions = [
|
||||||
];
|
];
|
||||||
// mock 参数为文件类型的匹配规则选项
|
// mock 参数为文件类型的匹配规则选项
|
||||||
export const mockFileMatchRules = ['EQUALS', 'NOT_EQUALS', 'IS_EMPTY', 'IS_NOT_EMPTY'];
|
export const mockFileMatchRules = ['EQUALS', 'NOT_EQUALS', 'IS_EMPTY', 'IS_NOT_EMPTY'];
|
||||||
|
|
||||||
|
// 场景-常规参数默认值
|
||||||
|
export const defaultNormalParamItem = {
|
||||||
|
key: '',
|
||||||
|
paramType: 'CONSTANT',
|
||||||
|
value: '',
|
||||||
|
description: '',
|
||||||
|
tags: [],
|
||||||
|
enable: true,
|
||||||
|
};
|
||||||
|
// 场景-csv参数默认值
|
||||||
|
export const defaultCsvParamItem: CsvVariable = {
|
||||||
|
id: '',
|
||||||
|
fileId: '',
|
||||||
|
scenarioId: '',
|
||||||
|
name: '',
|
||||||
|
fileName: '',
|
||||||
|
scope: 'SCENARIO',
|
||||||
|
enable: true,
|
||||||
|
association: false,
|
||||||
|
encoding: 'UTF-8',
|
||||||
|
random: false,
|
||||||
|
variableNames: '',
|
||||||
|
ignoreFirstLine: false,
|
||||||
|
delimiter: ',',
|
||||||
|
allowQuotedData: false,
|
||||||
|
recycleOnEof: false,
|
||||||
|
stopThreadOnEof: false,
|
||||||
|
};
|
||||||
|
|
|
@ -26,7 +26,13 @@
|
||||||
<template #typeTitle="{ columnConfig }">
|
<template #typeTitle="{ columnConfig }">
|
||||||
<div class="flex items-center text-[var(--color-text-3)]">
|
<div class="flex items-center text-[var(--color-text-3)]">
|
||||||
{{ t('apiTestDebug.paramType') }}
|
{{ t('apiTestDebug.paramType') }}
|
||||||
<a-tooltip :content="columnConfig.typeTitleTooltip" :disabled="!columnConfig.typeTitleTooltip" position="right">
|
<a-tooltip :disabled="!columnConfig.typeTitleTooltip" position="right">
|
||||||
|
<template #content>
|
||||||
|
<template v-if="Array.isArray(columnConfig.typeTitleTooltip)">
|
||||||
|
<div v-for="tip of columnConfig.typeTitleTooltip" :key="tip">{{ tip }}</div>
|
||||||
|
</template>
|
||||||
|
<div v-else>{{ columnConfig.typeTitleTooltip }}</div>
|
||||||
|
</template>
|
||||||
<icon-question-circle
|
<icon-question-circle
|
||||||
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||||
size="16"
|
size="16"
|
||||||
|
@ -184,6 +190,17 @@
|
||||||
<template #expression="{ record, rowIndex, columnConfig }">
|
<template #expression="{ record, rowIndex, columnConfig }">
|
||||||
<slot name="expression" :record="record" :row-index="rowIndex" :column-config="columnConfig"></slot>
|
<slot name="expression" :record="record" :row-index="rowIndex" :column-config="columnConfig"></slot>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 作用域 -->
|
||||||
|
<template #scope="{ record, columnConfig, rowIndex }">
|
||||||
|
<a-select
|
||||||
|
v-model:model-value="record.scope"
|
||||||
|
:disabled="props.disabledExceptParam"
|
||||||
|
:options="columnConfig.typeOptions || []"
|
||||||
|
class="ms-form-table-input w-[180px]"
|
||||||
|
size="mini"
|
||||||
|
@change="() => addTableLine(rowIndex)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<!-- 参数值 -->
|
<!-- 参数值 -->
|
||||||
<template #value="{ record, columnConfig, rowIndex }">
|
<template #value="{ record, columnConfig, rowIndex }">
|
||||||
<a-popover
|
<a-popover
|
||||||
|
@ -238,6 +255,27 @@
|
||||||
@apply="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
|
@apply="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 文件 -->
|
||||||
|
<template #file="{ record, rowIndex }">
|
||||||
|
<MsAddAttachment
|
||||||
|
v-model:file-list="record.files"
|
||||||
|
:disabled="props.disabledParamValue"
|
||||||
|
:multiple="false"
|
||||||
|
mode="input"
|
||||||
|
:fields="{
|
||||||
|
id: 'fileId',
|
||||||
|
name: 'fileAlias',
|
||||||
|
}"
|
||||||
|
:file-save-as-source-id="props.fileSaveAsSourceId"
|
||||||
|
:file-save-as-api="props.fileSaveAsApi"
|
||||||
|
:file-module-options-api="props.fileModuleOptionsApi"
|
||||||
|
input-class="ms-form-table-input h-[24px]"
|
||||||
|
input-size="small"
|
||||||
|
tag-size="small"
|
||||||
|
@change="(files, file) => handleFileChange(files, record, rowIndex, file)"
|
||||||
|
@delete-file="() => emitChange('deleteFile')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<!-- 长度范围 -->
|
<!-- 长度范围 -->
|
||||||
<template #lengthRange="{ record, rowIndex }">
|
<template #lengthRange="{ record, rowIndex }">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
|
@ -430,9 +468,20 @@
|
||||||
/>
|
/>
|
||||||
<div v-else class="text-[var(--color-text-1)]">{{ '-' }}</div>
|
<div v-else class="text-[var(--color-text-1)]">{{ '-' }}</div>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 单独启用/禁用列 -->
|
||||||
|
<template #enable="{ record, rowIndex }">
|
||||||
|
<a-switch
|
||||||
|
v-model="record.enable"
|
||||||
|
:disabled="props.disabledExceptParam"
|
||||||
|
size="small"
|
||||||
|
type="line"
|
||||||
|
class="ml-[8px]"
|
||||||
|
@change="() => addTableLine(rowIndex)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<template v-if="!props.disabledExceptParam" #operation="{ record, rowIndex, columnConfig }">
|
<template v-if="!props.disabledExceptParam" #operation="{ record, rowIndex, columnConfig }">
|
||||||
<div class="flex flex-row items-center" :class="{ 'justify-end': columnConfig.align === 'right' }">
|
<div class="flex w-full flex-row items-center" :class="{ 'justify-end': columnConfig.align === 'right' }">
|
||||||
<a-switch
|
<a-switch
|
||||||
v-if="columnConfig.hasDisable"
|
v-if="columnConfig.hasDisable"
|
||||||
v-model="record.enable"
|
v-model="record.enable"
|
||||||
|
@ -573,7 +622,7 @@
|
||||||
isNormal?: boolean; // 用于 value 列区分是普通输入框还是 MsParamsInput
|
isNormal?: boolean; // 用于 value 列区分是普通输入框还是 MsParamsInput
|
||||||
hasRequired?: boolean; // 用于 type 列区分是否有 required 星号
|
hasRequired?: boolean; // 用于 type 列区分是否有 required 星号
|
||||||
typeOptions?: { label: string; value: string }[]; // 用于 type 列选择器选项
|
typeOptions?: { label: string; value: string }[]; // 用于 type 列选择器选项
|
||||||
typeTitleTooltip?: string; // 用于 type 表头列展示的 tooltip
|
typeTitleTooltip?: string | string[]; // 用于 type 表头列展示的 tooltip
|
||||||
hasDisable?: boolean; // 用于 operation 列区分是否有 enable 开关
|
hasDisable?: boolean; // 用于 operation 列区分是否有 enable 开关
|
||||||
moreAction?: ActionsItem[]; // 用于 operation 列更多操作按钮配置
|
moreAction?: ActionsItem[]; // 用于 operation 列更多操作按钮配置
|
||||||
format?: RequestBodyFormat; // 用于 operation 列区分是否有请求体格式选择器
|
format?: RequestBodyFormat; // 用于 operation 列区分是否有请求体格式选择器
|
||||||
|
|
|
@ -8,7 +8,12 @@
|
||||||
>
|
>
|
||||||
<template #assertionItem="{ record }">
|
<template #assertionItem="{ record }">
|
||||||
<div class="flex items-center gap-[4px]">
|
<div class="flex items-center gap-[4px]">
|
||||||
【{{ t(responseAssertionTypeMap[(record as ResponseAssertionTableItem).assertionType]) }}】
|
【{{
|
||||||
|
t(
|
||||||
|
responseAssertionTypeMap[(record as ResponseAssertionTableItem).assertionType] ||
|
||||||
|
'apiTestDebug.responseBody'
|
||||||
|
)
|
||||||
|
}}】
|
||||||
{{ record.name }}
|
{{ record.name }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -16,7 +21,7 @@
|
||||||
{{
|
{{
|
||||||
record.assertionType === FullResponseAssertionType.RESPONSE_TIME
|
record.assertionType === FullResponseAssertionType.RESPONSE_TIME
|
||||||
? t('advanceFilter.operator.le')
|
? t('advanceFilter.operator.le')
|
||||||
: t(statusCodeOptions.find((item) => item.value === record.condition)?.label || '')
|
: t(statusCodeOptions.find((item) => item.value === record.condition)?.label || '-')
|
||||||
}}
|
}}
|
||||||
</template>
|
</template>
|
||||||
<template #status="{ record }">
|
<template #status="{ record }">
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="history-container">
|
<div class="history-container">
|
||||||
<a-alert v-if="isShowTip" :show-icon="true" class="mb-[8px]" type="info">
|
<a-alert :show-icon="true" class="mb-[8px]" type="info">
|
||||||
{{ t('apiScenario.params.priority') }}
|
{{ t('apiScenario.params.priority') }}
|
||||||
</a-alert>
|
</a-alert>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="mb-[16px]">
|
<div class="mb-[8px] flex items-center justify-between">
|
||||||
<a-radio-group v-model="activeRadio" type="button" size="medium">
|
<a-radio-group v-model="activeRadio" type="button" size="medium">
|
||||||
<a-radio value="convention">{{ t('apiScenario.params.convention') }}</a-radio>
|
<a-radio value="convention">{{ t('apiScenario.params.convention') }}</a-radio>
|
||||||
|
<a-radio value="csv">{{ t('apiScenario.params.csv') }}</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>-->
|
|
||||||
<div class="mb-[8px] flex items-center justify-between">
|
|
||||||
<a-input-search
|
<a-input-search
|
||||||
|
v-if="activeRadio === 'convention'"
|
||||||
v-model="searchValue"
|
v-model="searchValue"
|
||||||
:placeholder="t('apiScenario.params.searchPlaceholder')"
|
:placeholder="t('apiScenario.params.searchPlaceholder')"
|
||||||
allow-clear
|
allow-clear
|
||||||
|
@ -21,58 +21,146 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<paramTable
|
<paramTable
|
||||||
v-model:params="innerParams"
|
v-if="activeRadio === 'convention'"
|
||||||
|
v-model:params="commonVariables"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:default-param-item="defaultParamItem"
|
:default-param-item="defaultNormalParamItem"
|
||||||
draggable
|
:draggable="false"
|
||||||
|
:selectable="false"
|
||||||
@change="handleParamTableChange"
|
@change="handleParamTableChange"
|
||||||
@batch-add="batchAddKeyValVisible = true"
|
@batch-add="batchAddKeyValVisible = true"
|
||||||
/>
|
/>
|
||||||
|
<paramTable
|
||||||
|
v-else
|
||||||
|
v-model:params="csvVariables"
|
||||||
|
:columns="csvColumns"
|
||||||
|
:default-param-item="defaultCsvParamItem"
|
||||||
|
:draggable="false"
|
||||||
|
:selectable="false"
|
||||||
|
@change="handleParamTableChange"
|
||||||
|
@batch-add="batchAddKeyValVisible = true"
|
||||||
|
>
|
||||||
|
<template #operationPre="{ record }">
|
||||||
|
<a-trigger trigger="click" position="br" class="scenario-csv-trigger">
|
||||||
|
<MsButton type="text" class="!mr-0">{{ t('apiScenario.params.config') }}</MsButton>
|
||||||
|
<template #content>
|
||||||
|
<div class="scenario-csv-trigger-content">
|
||||||
|
<div class="mb-[16px] flex items-center">
|
||||||
|
<div class="font-semibold text-[var(--color-text-1)]">{{ t('apiScenario.params.csvConfig') }}</div>
|
||||||
|
<!-- <div class="text-[var(--color-text-4)]">({{ record.key }})</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="scenario-csv-trigger-content-scroll">
|
||||||
|
<a-form ref="paramFormRef" :model="record" layout="vertical">
|
||||||
|
<a-form-item
|
||||||
|
field="key"
|
||||||
|
:label="t('apiScenario.params.csvName')"
|
||||||
|
:rules="[{ required: true, message: t('apiScenario.params.csvNameNotNull') }]"
|
||||||
|
asterisk-position="end"
|
||||||
|
class="mb-[16px]"
|
||||||
|
>
|
||||||
|
<a-input v-model:model-value="record.key" :max-length="255"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="variableNames" :label="t('apiScenario.params.csvParamName')" class="mb-[16px]">
|
||||||
|
<a-input
|
||||||
|
v-model:model-value="record.variableNames"
|
||||||
|
:placeholder="t('apiScenario.params.csvParamNamePlaceholder')"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="encoding" :label="t('apiScenario.params.csvFileCode')" class="mb-[16px]">
|
||||||
|
<a-select
|
||||||
|
v-model:model-value="record.encoding"
|
||||||
|
:options="encodingOptions"
|
||||||
|
class="w-[120px]"
|
||||||
|
></a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="delimiter" :label="t('apiScenario.params.csvSplitChar')" class="mb-[16px]">
|
||||||
|
<a-input
|
||||||
|
v-model:model-value="record.delimiter"
|
||||||
|
:placeholder="t('common.pleaseInput')"
|
||||||
|
:max-length="64"
|
||||||
|
class="w-[120px]"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
field="ignoreFirstLine"
|
||||||
|
:label="t('apiScenario.params.csvIgnoreFirstLine')"
|
||||||
|
class="mb-[16px]"
|
||||||
|
>
|
||||||
|
<a-radio-group v-model:model-value="record.ignoreFirstLine">
|
||||||
|
<a-radio :value="false">False</a-radio>
|
||||||
|
<a-radio :value="true">True</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="random" :label="t('apiScenario.params.csvIsRandom')" class="mb-[16px]">
|
||||||
|
<a-radio-group v-model:model-value="record.random">
|
||||||
|
<a-radio :value="false">False</a-radio>
|
||||||
|
<a-radio :value="true">True</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="allowQuotedData" :label="t('apiScenario.params.csvQuoteAllow')" class="mb-[16px]">
|
||||||
|
<a-radio-group v-model:model-value="record.allowQuotedData">
|
||||||
|
<a-radio :value="false">False</a-radio>
|
||||||
|
<a-radio :value="true">True</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="recycleOnEof" :label="t('apiScenario.params.csvRecycle')" class="mb-[16px]">
|
||||||
|
<a-radio-group v-model:model-value="record.recycleOnEof">
|
||||||
|
<a-radio :value="false">False</a-radio>
|
||||||
|
<a-radio :value="true">True</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="stopThreadOnEof" :label="t('apiScenario.params.csvStop')" class="mb-[16px]">
|
||||||
|
<a-radio-group v-model:model-value="record.stopThreadOnEof">
|
||||||
|
<a-radio :value="false">False</a-radio>
|
||||||
|
<a-radio :value="true">True</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-trigger>
|
||||||
|
</template>
|
||||||
|
</paramTable>
|
||||||
<batchAddKeyVal
|
<batchAddKeyVal
|
||||||
v-model:visible="batchAddKeyValVisible"
|
v-model:visible="batchAddKeyValVisible"
|
||||||
:params="innerParams"
|
:params="commonVariables"
|
||||||
:default-param-item="defaultParamItem"
|
:default-param-item="defaultNormalParamItem"
|
||||||
no-param-type
|
no-param-type
|
||||||
@apply="handleBatchParamApply"
|
@apply="handleBatchParamApply"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useVModel } from '@vueuse/core';
|
import { FormInstance } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
|
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
|
||||||
import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
|
import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
|
||||||
|
|
||||||
import { CommonVariable } from '@/models/apiTest/scenario';
|
import { CommonVariable, CsvVariable } from '@/models/apiTest/scenario';
|
||||||
|
|
||||||
|
import { defaultCsvParamItem, defaultNormalParamItem } from '@/views/api-test/components/config';
|
||||||
import { filterKeyValParams } from '@/views/api-test/components/utils';
|
import { filterKeyValParams } from '@/views/api-test/components/utils';
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
activeKey?: string;
|
|
||||||
params: CommonVariable[];
|
|
||||||
}>();
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:params', value: CommonVariable[]): void;
|
|
||||||
(e: 'change'): void; // 数据发生变化
|
(e: 'change'): void; // 数据发生变化
|
||||||
}>();
|
}>();
|
||||||
const innerParams = useVModel(props, 'params', emit);
|
|
||||||
const isShowTip = ref<boolean>(true);
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const commonVariables = defineModel<CommonVariable[]>('commonVariables', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
const csvVariables = defineModel<CsvVariable[]>('csvVariables', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
const searchValue = ref('');
|
const searchValue = ref('');
|
||||||
const firstSearch = ref(true);
|
const firstSearch = ref(true);
|
||||||
const backupParams = ref(props.params);
|
const backupParams = ref(commonVariables.value);
|
||||||
const batchAddKeyValVisible = ref(false);
|
const batchAddKeyValVisible = ref(false);
|
||||||
|
const activeRadio = ref('convention');
|
||||||
const defaultParamItem = {
|
|
||||||
key: '',
|
|
||||||
paramType: 'CONSTANT',
|
|
||||||
value: '',
|
|
||||||
description: '',
|
|
||||||
tags: [],
|
|
||||||
enable: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns: ParamTableColumn[] = [
|
const columns: ParamTableColumn[] = [
|
||||||
{
|
{
|
||||||
|
@ -104,6 +192,11 @@
|
||||||
dataIndex: 'value',
|
dataIndex: 'value',
|
||||||
slotName: 'value',
|
slotName: 'value',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'apiScenario.table.columns.status',
|
||||||
|
dataIndex: 'enable',
|
||||||
|
slotName: 'enable',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'apiScenario.params.tag',
|
title: 'apiScenario.params.tag',
|
||||||
dataIndex: 'tags',
|
dataIndex: 'tags',
|
||||||
|
@ -120,12 +213,12 @@
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
titleSlotName: 'batchAddTitle',
|
titleSlotName: 'batchAddTitle',
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
width: 70,
|
width: 90,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
|
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
|
||||||
innerParams.value = [...resultArr];
|
commonVariables.value = [...resultArr];
|
||||||
if (!isInit) {
|
if (!isInit) {
|
||||||
emit('change');
|
emit('change');
|
||||||
firstSearch.value = true;
|
firstSearch.value = true;
|
||||||
|
@ -135,16 +228,16 @@
|
||||||
// 搜索
|
// 搜索
|
||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
if (firstSearch.value) {
|
if (firstSearch.value) {
|
||||||
backupParams.value = [...innerParams.value];
|
backupParams.value = [...commonVariables.value];
|
||||||
firstSearch.value = false;
|
firstSearch.value = false;
|
||||||
}
|
}
|
||||||
if (!searchValue.value) {
|
if (!searchValue.value) {
|
||||||
innerParams.value = [...backupParams.value];
|
commonVariables.value = [...backupParams.value];
|
||||||
} else {
|
} else {
|
||||||
const result = backupParams.value.filter(
|
const result = backupParams.value.filter(
|
||||||
(item) => item.key.includes(searchValue.value) || item.tags.includes(searchValue.value)
|
(item) => item.key.includes(searchValue.value) || item.tags.includes(searchValue.value)
|
||||||
);
|
);
|
||||||
innerParams.value = [...result];
|
commonVariables.value = [...result];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,14 +245,113 @@
|
||||||
* 批量参数代码转换为参数表格数据
|
* 批量参数代码转换为参数表格数据
|
||||||
*/
|
*/
|
||||||
function handleBatchParamApply(resultArr: any[]) {
|
function handleBatchParamApply(resultArr: any[]) {
|
||||||
const filterResult = filterKeyValParams(innerParams.value, defaultParamItem);
|
const filterResult = filterKeyValParams(commonVariables.value, defaultNormalParamItem);
|
||||||
if (filterResult.lastDataIsDefault) {
|
if (filterResult.lastDataIsDefault) {
|
||||||
innerParams.value = [...resultArr, innerParams.value[innerParams.value.length - 1]].filter(Boolean);
|
commonVariables.value = [...resultArr, commonVariables.value[commonVariables.value.length - 1]].filter(Boolean);
|
||||||
} else {
|
} else {
|
||||||
innerParams.value = resultArr.filter(Boolean);
|
commonVariables.value = resultArr.filter(Boolean);
|
||||||
}
|
}
|
||||||
emit('change');
|
emit('change');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const csvColumns: ParamTableColumn[] = [
|
||||||
|
{
|
||||||
|
title: 'apiScenario.params.csvName',
|
||||||
|
dataIndex: 'key',
|
||||||
|
slotName: 'key',
|
||||||
|
needValidRepeat: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiScenario.params.csvScoped',
|
||||||
|
dataIndex: 'scope',
|
||||||
|
slotName: 'scope',
|
||||||
|
typeOptions: [
|
||||||
|
{
|
||||||
|
label: t('apiScenario.scenario'),
|
||||||
|
value: 'SCENARIO',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('apiScenario.step'),
|
||||||
|
value: 'STEP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
width: 80,
|
||||||
|
titleSlotName: 'typeTitle',
|
||||||
|
typeTitleTooltip: [t('apiScenario.params.csvScopedTip1'), t('apiScenario.params.csvScopedTip2')],
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: 'apiScenario.params.file',
|
||||||
|
// dataIndex: 'file',
|
||||||
|
// slotName: 'file',
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: 'apiScenario.table.columns.status',
|
||||||
|
dataIndex: 'enable',
|
||||||
|
slotName: 'enable',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
slotName: 'operation',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
width: 90,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const configFormRef = ref<FormInstance>();
|
||||||
|
const encodingOptions = [
|
||||||
|
{
|
||||||
|
label: 'UTF-8',
|
||||||
|
value: 'UTF-8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'UTF-16',
|
||||||
|
value: 'UTF-16',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ISO-8859-15',
|
||||||
|
value: 'ISO-8859-15',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'US-ASCII',
|
||||||
|
value: 'US-ASCII',
|
||||||
|
},
|
||||||
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less">
|
||||||
|
.scenario-csv-trigger {
|
||||||
|
@apply bg-white;
|
||||||
|
.scenario-csv-trigger-content {
|
||||||
|
padding: 16px;
|
||||||
|
width: 400px;
|
||||||
|
border-radius: var(--border-radius-medium);
|
||||||
|
box-shadow: 0 5px 5px -3px rgb(0 0 0 / 10%), 0 8px 10px 1px rgb(0 0 0 / 6%), 0 3px 14px 2px rgb(0 0 0 / 5%);
|
||||||
|
&::before {
|
||||||
|
@apply absolute left-0 top-0;
|
||||||
|
|
||||||
|
content: '';
|
||||||
|
z-index: -1;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
border: 1px solid var(--color-text-input-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
transform: scale(0.5, 0.5);
|
||||||
|
}
|
||||||
|
.scenario-csv-trigger-content-scroll {
|
||||||
|
.ms-scroll-bar();
|
||||||
|
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-right: -6px;
|
||||||
|
max-height: 400px;
|
||||||
|
.scenario-csv-trigger-content-scroll-preview {
|
||||||
|
@apply w-full overflow-y-auto overflow-x-hidden break-all;
|
||||||
|
.ms-scroll-bar();
|
||||||
|
|
||||||
|
max-height: 100px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
>
|
>
|
||||||
<params
|
<params
|
||||||
v-if="activeKey === ScenarioCreateComposition.PARAMS"
|
v-if="activeKey === ScenarioCreateComposition.PARAMS"
|
||||||
v-model:params="scenario.scenarioConfig.variable.commonVariables"
|
v-model:commonVariables="scenario.scenarioConfig.variable.commonVariables"
|
||||||
|
v-model:csvVariables="scenario.scenarioConfig.variable.csvVariables"
|
||||||
/>
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane
|
<a-tab-pane
|
||||||
|
|
|
@ -62,7 +62,8 @@
|
||||||
>
|
>
|
||||||
<params
|
<params
|
||||||
v-if="activeKey === ScenarioDetailComposition.PARAMS"
|
v-if="activeKey === ScenarioDetailComposition.PARAMS"
|
||||||
v-model:params="scenario.scenarioConfig.variable.commonVariables"
|
v-model:commonVariables="scenario.scenarioConfig.variable.commonVariables"
|
||||||
|
v-model:csvVariables="scenario.scenarioConfig.variable.csvVariables"
|
||||||
@change="scenario.unSaved = true"
|
@change="scenario.unSaved = true"
|
||||||
/>
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
|
@ -515,6 +515,7 @@
|
||||||
environmentId: appStore.getCurrentEnvId || '',
|
environmentId: appStore.getCurrentEnvId || '',
|
||||||
});
|
});
|
||||||
const scenarioDetail = await getScenarioDetail(res.id);
|
const scenarioDetail = await getScenarioDetail(res.id);
|
||||||
|
// 添加后获取后台组装的场景信息
|
||||||
scenarioDetail.stepDetails = {};
|
scenarioDetail.stepDetails = {};
|
||||||
scenarioDetail.isNew = false;
|
scenarioDetail.isNew = false;
|
||||||
scenarioDetail.id = res.id;
|
scenarioDetail.id = res.id;
|
||||||
|
|
|
@ -24,6 +24,14 @@ export default {
|
||||||
'apiScenario.dependency': 'Dependency',
|
'apiScenario.dependency': 'Dependency',
|
||||||
'apiScenario.quote': 'Quote',
|
'apiScenario.quote': 'Quote',
|
||||||
'apiScenario.params.convention': 'Conventional Parameters',
|
'apiScenario.params.convention': 'Conventional Parameters',
|
||||||
|
'apiScenario.params.csv': 'CSV Parameters',
|
||||||
|
'apiScenario.params.csvName': 'CSV name',
|
||||||
|
'apiScenario.params.csvScoped': 'Scope',
|
||||||
|
'apiScenario.params.file': 'File',
|
||||||
|
'apiScenario.params.csvScopedTip1':
|
||||||
|
'Scenario level: Load CSV before executing the scenario. Data can be read from CSV in any step of the current scenario.',
|
||||||
|
'apiScenario.params.csvScopedTip2':
|
||||||
|
'Step level: The CSV needs to be added to the scenario step. The CSV is loaded when executing this step, and the scope is the request within the step.',
|
||||||
'apiScenario.params.searchPlaceholder': 'Search by name or tag',
|
'apiScenario.params.searchPlaceholder': 'Search by name or tag',
|
||||||
'apiScenario.params.priority':
|
'apiScenario.params.priority':
|
||||||
'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.',
|
'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.',
|
||||||
|
|
|
@ -23,6 +23,24 @@ export default {
|
||||||
'apiScenario.dependency': '依赖关系',
|
'apiScenario.dependency': '依赖关系',
|
||||||
'apiScenario.quote': '引用关系',
|
'apiScenario.quote': '引用关系',
|
||||||
'apiScenario.params.convention': '常规参数',
|
'apiScenario.params.convention': '常规参数',
|
||||||
|
'apiScenario.params.csv': 'CSV 参数',
|
||||||
|
'apiScenario.params.csvName': 'CSV 名称',
|
||||||
|
'apiScenario.params.csvNameNotNull': 'CSV 名称不能为空',
|
||||||
|
'apiScenario.params.csvScoped': '作用域',
|
||||||
|
'apiScenario.params.file': '文件',
|
||||||
|
'apiScenario.params.csvScopedTip1': '场景级:执行场景前加载CSV,当前场景任意步骤均可从CSV中读取到数据',
|
||||||
|
'apiScenario.params.csvScopedTip2': '步骤级:需在场景步骤中添加该CSV,执行该步骤时加载CSV,作用域为步骤内的请求',
|
||||||
|
'apiScenario.params.config': '配置',
|
||||||
|
'apiScenario.params.csvConfig': 'CSV 配置',
|
||||||
|
'apiScenario.params.csvParamName': '参数名称',
|
||||||
|
'apiScenario.params.csvParamNamePlaceholder': '多个参数名以 , 隔开',
|
||||||
|
'apiScenario.params.csvFileCode': '文件编码',
|
||||||
|
'apiScenario.params.csvSplitChar': '分隔符',
|
||||||
|
'apiScenario.params.csvIgnoreFirstLine': '忽略首行',
|
||||||
|
'apiScenario.params.csvIsRandom': '是否随机',
|
||||||
|
'apiScenario.params.csvQuoteAllow': '允许带引号',
|
||||||
|
'apiScenario.params.csvRecycle': '遇到文件结束符再次循环',
|
||||||
|
'apiScenario.params.csvStop': '遇到文件结束符停止线程',
|
||||||
'apiScenario.params.searchPlaceholder': '通过名称或标签搜索',
|
'apiScenario.params.searchPlaceholder': '通过名称或标签搜索',
|
||||||
'apiScenario.params.priority':
|
'apiScenario.params.priority':
|
||||||
'变量优先级:临时参数>场景参数 >环境参数>全局参数;注: 避免使用同名变量,同名变量时场景级 CSV 优先级最高',
|
'变量优先级:临时参数>场景参数 >环境参数>全局参数;注: 避免使用同名变量,同名变量时场景级 CSV 优先级最高',
|
||||||
|
|
Loading…
Reference in New Issue