feat(接口测试): csv参数表格&mock 调整

This commit is contained in:
baiqi 2024-05-16 14:14:17 +08:00 committed by Craftsman
parent 5784ac55a0
commit dd97da9e43
22 changed files with 457 additions and 210 deletions

View File

@ -93,7 +93,7 @@
class="m-0 border-none p-0"
:self-style="{ backgroundColor: 'transparent !important' }"
:closable="data.value !== '__arco__more' && !props.disabled"
@close="handleClose(data)"
@close="() => handleClose(data)"
>
{{ data.value === '__arco__more' ? data.label.replace('...', '') : data.label }}
</MsTag>

View File

@ -121,7 +121,7 @@
{
title: 'project.fileManagement.type',
dataIndex: 'fileType',
width: 90,
width: 100,
},
{
title: 'project.fileManagement.size',

View File

@ -148,8 +148,11 @@
personalMenusVisible.value = false;
orgKeyword.value = '';
await userStore.isLogin(true);
if (!appStore.currentProjectId || appStore.currentProjectId === 'no_such_project') {
// (, )
if (
(!appStore.currentProjectId || appStore.currentProjectId === 'no_such_project') &&
!(route.name as string).startsWith(SettingRouteEnum.SETTING)
) {
// (, )访
router.push({
name: NO_PROJECT_ROUTE_NAME,
});

View File

@ -1,17 +1,18 @@
<template>
<MsMinderEditor
v-model:activeExtraKey="activeExtraKey"
:tags="tags"
:import-json="props.importJson"
:replaceable-tags="replaceableTags"
:insert-node="insertNode"
:priority-disable-check="priorityDisableCheck"
:after-tag-edit="afterTagEdit"
:extract-content-tab-list="extractContentTabList"
single-tag
tag-enable
sequence-enable
@click="handleNodeClick"
>
</MsMinderEditor>
/>
</template>
<script setup lang="ts">
@ -34,6 +35,25 @@
const tags = [...topTags, t('ms.minders.precondition'), ...descTags, t('ms.minders.stepExpect'), t('common.remark')];
const visible = ref<boolean>(false);
const nodeData = ref<any>({});
const extractContentTabList = [
{
label: t('common.baseInfo'),
value: 'baseInfo',
},
{
label: t('caseManagement.featureCase.attachment'),
value: 'attachment',
},
{
value: 'comments',
label: t('caseManagement.featureCase.comments'),
},
{
value: 'bug',
label: t('caseManagement.featureCase.bug'),
},
];
const activeExtraKey = ref('baseInfo');
function handleNodeClick(data: any) {
if (data.resource && data.resource.includes(caseTag)) {

View File

@ -1,5 +1,6 @@
<template>
<div class="ms-minder-editor-container">
<div class="flex-1">
<minderHeader
:sequence-enable="props.sequenceEnable"
:tag-enable="props.tagEnable"
@ -43,16 +44,21 @@
:priority-prefix="props.priorityPrefix"
:priority-start-with-zero="props.priorityStartWithZero"
:insert-node="props.insertNode"
@after-mount="emit('afterMount')"
@after-mount="() => emit('afterMount')"
@save="save"
@enter-node="handleEnterNode"
/>
</div>
<div v-if="props.extractContentTabList?.length" class="ms-minder-editor-extra-content">
<MsTab v-model:activeKey="activeExtraKey" :content-tab-list="props.extractContentTabList" mode="button" />
</div>
</div>
</template>
<script lang="ts" name="minderEditor" setup>
import { onMounted } from 'vue';
import MsTab from '@/components/pure/ms-tab/index.vue';
import minderHeader from './main/header.vue';
import mainEditor from './main/mainEditor.vue';
@ -104,6 +110,10 @@
emit('enterNode', data);
}
const activeExtraKey = defineModel<string>('activeExtraKey', {
default: '',
});
onMounted(() => {
nextTick(() => {
if (window.minder.on) {
@ -123,6 +133,14 @@
<style lang="less" scoped>
.ms-minder-editor-container {
@apply relative h-full;
@apply relative flex h-full;
.ms-minder-editor-extra-content {
@apply border-l;
padding: 16px;
width: 35%;
min-width: 360px;
border-color: var(--color-text-n8);
}
}
</style>

View File

@ -44,6 +44,7 @@ export const mainEditorProps = {
default: 500,
},
disabled: Boolean,
extractContentTabList: [] as PropType<{ label: string; value: string }[]>,
};
export const headerProps = {

View File

@ -1,5 +1,9 @@
<template>
<a-tabs v-model:active-key="innerActiveKey" :class="[props.class, props.noContent ? 'no-content' : '']">
<a-tabs
v-if="props.mode === 'origin'"
v-model:active-key="innerActiveKey"
:class="[props.class, props.noContent ? 'no-content' : '']"
>
<a-tab-pane v-for="item of props.contentTabList" :key="item.value" :title="item.label">
<template v-if="props.showBadge" #title>
<a-badge
@ -18,11 +22,23 @@
</template>
</a-tab-pane>
</a-tabs>
<div v-else class="ms-tab--button">
<div
v-for="item of props.contentTabList"
:key="item.value"
class="ms-tab--button-item"
:class="item.value === innerActiveKey ? 'ms-tab--button-item--active' : ''"
@click="innerActiveKey = item.value"
>
{{ item.label }}
</div>
</div>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
mode?: 'origin' | 'button';
activeKey: string;
contentTabList: { label: string; value: string }[];
class?: string;
@ -31,6 +47,7 @@
showBadge?: boolean;
}>(),
{
mode: 'origin',
showBadge: true,
getTextFunc: (value: any) => value,
class: '',
@ -63,4 +80,35 @@
display: none;
}
}
.ms-tab--button {
@apply flex;
border-radius: var(--border-radius-small);
.ms-tab--button-item {
@apply cursor-pointer;
padding: 4px 12px;
border: 1px solid var(--color-text-n8);
color: var(--color-text-2);
&:first-child {
border-top-left-radius: var(--border-radius-small);
border-bottom-left-radius: var(--border-radius-small);
}
&:last-child {
border-top-right-radius: var(--border-radius-small);
border-bottom-right-radius: var(--border-radius-small);
}
&:not(:last-child) {
margin-right: -1px;
}
&:hover {
color: rgb(var(--primary-5));
}
}
.ms-tab--button-item--active {
z-index: 2;
border: 1px solid rgb(var(--primary-5)) !important;
color: rgb(var(--primary-5));
}
}
</style>

View File

@ -247,7 +247,7 @@
wrapper-id="ms-table-footer-wrapper"
:size="props.paginationSize"
@batch-action="handleBatchAction"
@clear="emit('clearSelector')"
@clear="() => emit('clearSelector')"
/>
</div>
<ms-pagination
@ -269,7 +269,7 @@
:show-subdirectory="!!attrs.showSubdirectory"
@init-data="handleInitColumn"
@page-size-change="pageSizeChange"
@module-change="emit('moduleChange')"
@module-change="() => emit('moduleChange')"
></ColumnSelector>
</div>
</template>

View File

@ -234,7 +234,7 @@ export interface AssertionConfig {
assertions: ExecuteAssertionItem[];
}
export interface CsvVariable {
id: string;
id?: string;
fileId: string;
scenarioId: string;
name: string;
@ -250,6 +250,8 @@ export interface CsvVariable {
allowQuotedData: boolean;
recycleOnEof: boolean;
stopThreadOnEof: boolean;
// 以下为前端字段
settingVisible: boolean;
}
export interface CommonVariable {
id: string | number;

View File

@ -297,7 +297,7 @@ export const mockDefaultParams: MockParams = {
matchAll: true,
},
body: {
bodyType: RequestBodyFormat.FORM_DATA,
bodyType: RequestBodyFormat.NONE,
formDataBody: {
matchRules: [],
matchAll: true,
@ -348,7 +348,7 @@ export const mockDefaultParams: MockParams = {
uploadFileIds: [],
linkFileIds: [],
};
export const makeDefaultParams = () => {
export const makeMockDefaultParams = () => {
const defaultParams = cloneDeep(mockDefaultParams);
defaultParams.id = Date.now().toString();
defaultParams.mockMatchRule.body.formDataBody.matchRules.push({
@ -426,7 +426,6 @@ export const defaultNormalParamItem = {
};
// 场景-csv参数默认值
export const defaultCsvParamItem: CsvVariable = {
id: '',
fileId: '',
scenarioId: '',
name: '',
@ -442,4 +441,5 @@ export const defaultCsvParamItem: CsvVariable = {
allowQuotedData: false,
recycleOnEof: false,
stopThreadOnEof: false,
settingVisible: false,
};

View File

@ -125,6 +125,30 @@
/>
</a-popover>
</template>
<template #name="{ record, columnConfig, rowIndex }">
<a-popover
position="tl"
:disabled="!record[columnConfig.dataIndex as string] || record[columnConfig.dataIndex as string].trim() === ''"
class="ms-params-input-popover"
>
<template #content>
<div class="param-popover-title">
{{ t('apiTestDebug.paramName') }}
</div>
<div class="param-popover-value">
{{ record[columnConfig.dataIndex as string] }}
</div>
</template>
<a-input
v-model:model-value="record[columnConfig.dataIndex as string]"
:disabled="props.disabledExceptParam || columnConfig.disabledColumn"
:placeholder="t('apiTestDebug.commonPlaceholder')"
class="ms-form-table-input"
size="mini"
@input="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
/>
</a-popover>
</template>
<!-- 参数类型 -->
<template #paramType="{ record, columnConfig, rowIndex }">
<a-tooltip
@ -242,7 +266,7 @@
input-class="ms-form-table-input h-[24px]"
input-size="small"
tag-size="small"
@change="(files, file) => handleFileChange(files, record, rowIndex, file)"
@change="(files, file) => handleFilesChange(files, record, rowIndex, file)"
@delete-file="() => emitChange('deleteFile')"
/>
<MsParamsInput
@ -251,14 +275,14 @@
:disabled="props.disabledParamValue"
size="mini"
@change="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
@dblclick="quickInputParams(record)"
@dblclick="() => quickInputParams(record)"
@apply="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
/>
</template>
<!-- 文件 -->
<template #file="{ record, rowIndex }">
<MsAddAttachment
v-model:file-list="record.files"
:file-list="[record]"
:disabled="props.disabledParamValue"
:multiple="false"
mode="input"
@ -321,7 +345,7 @@
:disabled="props.disabledExceptParam || columnConfig.disabledColumn"
size="mini"
@input="() => addTableLine(rowIndex)"
@dblclick="quickInputDesc(record)"
@dblclick="() => quickInputDesc(record)"
@change="handleDescChange"
/>
</template>
@ -464,7 +488,7 @@
v-if="Array.isArray(record.domain)"
:tag-list="getDomain(record.domain)"
size="small"
@click="showHostModal(record)"
@click="() => showHostModal(record)"
/>
<div v-else class="text-[var(--color-text-1)]">{{ '-' }}</div>
</template>
@ -924,7 +948,10 @@
emitChange('toggleRequired');
}
async function handleFileChange(
/**
* 处理表格内多个文件上传/关联
*/
async function handleFilesChange(
files: MsFileItem[],
record: Record<string, any>,
rowIndex: number,
@ -957,6 +984,45 @@
}));
}
addTableLine(rowIndex);
emitChange('handleFilesChange');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
appStore.hideLoading();
}
}
/**
* 处理表格内单个文件上传/关联
*/
async function handleFileChange(
files: MsFileItem[],
record: Record<string, any>,
rowIndex: number,
file?: MsFileItem
) {
try {
if (props.uploadTempFileApi && file?.local) {
appStore.showLoading();
const res = await props.uploadTempFileApi(file.file);
record = {
...record,
...file,
fileId: res.data,
fileName: file.name || '',
fileAlias: file.name || '',
};
} else if (file) {
record = {
...record,
...file,
fileId: file.uid || file.fileId || '',
fileName: file.originalName || '',
fileAlias: file.name || '',
};
}
addTableLine(rowIndex);
emitChange('handleFileChange');
} catch (error) {
// eslint-disable-next-line no-console

View File

@ -11,7 +11,7 @@
:default-param-item="defaultHeaderParamsItem"
:draggable="!props.disabledExceptParam"
@change="handleParamTableChange"
@batch-add="batchAddKeyValVisible = true"
@batch-add="() => (batchAddKeyValVisible = true)"
/>
<batchAddKeyVal
v-model:visible="batchAddKeyValVisible"

View File

@ -281,7 +281,7 @@
async function initModuleCount() {
try {
const res = await getDebugModuleCount({
keyword: moduleKeyword.value,
keyword: '',
});
modulesCount.value = res;
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {

View File

@ -10,7 +10,7 @@
@open-api-tab="(record, isExecute) => openApiTab({ apiInfo: record, isCopy: false, isExecute })"
@open-copy-api-tab="openApiTab({ apiInfo: $event, isCopy: true })"
@add-api-tab="addApiTab"
@import="emit('import')"
@import="() => emit('import')"
@open-edit-api-tab="openApiTab"
/>
</div>
@ -156,6 +156,7 @@
import { ModuleTreeNode } from '@/models/common';
import {
RequestAuthType,
RequestBodyFormat,
RequestComposition,
RequestDefinitionStatus,
RequestMethods,
@ -348,6 +349,7 @@
loadedApiTab = {
...loadedApiTab,
...(apiInfo as ApiDefinitionDetail).request,
activeTab: (apiInfo as ApiDefinitionDetail).activeTab,
};
}
// tabtab
@ -385,6 +387,7 @@
request = {
...res.request,
...(apiInfo as ApiDefinitionDetail).request,
activeTab: (apiInfo as ApiDefinitionDetail).activeTab,
};
}
addApiTab({
@ -417,6 +420,16 @@
}
async function openApiTabAndDebugMock(mock: MockDetail) {
let activeTab = RequestComposition.BODY;
if (mock.mockMatchRule.body.bodyType !== RequestBodyFormat.NONE) {
activeTab = RequestComposition.BODY;
} else if (mock.mockMatchRule.header.matchRules.length > 0) {
activeTab = RequestComposition.HEADER;
} else if (mock.mockMatchRule.query.matchRules.length > 0) {
activeTab = RequestComposition.QUERY;
} else if (mock.mockMatchRule.rest.matchRules.length > 0) {
activeTab = RequestComposition.REST;
}
openApiTab({
apiInfo: {
id: mock.apiDefinitionId as string,
@ -465,6 +478,7 @@
value: e.value,
})) || [],
},
activeTab,
} as unknown as ApiDefinitionDetail,
isExecute: true,
isDebugMock: true,

View File

@ -351,14 +351,12 @@
:deep(.arco-tabs-content) {
@apply flex-1 pt-0;
.arco-tabs-content-item {
@apply px-0;
@apply overflow-y-auto px-0;
.ms-scroll-bar();
}
.arco-tabs-content-list {
@apply h-full;
}
.arco-tabs-content-list .arco-tabs-content-item:nth-of-type(1) .arco-tabs-pane {
@apply h-full overflow-hidden;
}
.arco-collapse {
height: calc(100% - 85px);
}

View File

@ -59,7 +59,7 @@
</div>
</template>
</MsDetailCard>
<a-form ref="mockForm" :model="mockDetail" :disabled="isReadOnly">
<a-form ref="mockFormRef" :model="mockDetail" :disabled="isReadOnly">
<a-form-item
class="hidden-item mb-[16px]"
field="name"
@ -196,7 +196,7 @@
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue';
import { FormInstance, Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue';
@ -234,7 +234,7 @@
defaultHeaderParamsItem,
defaultMatchRuleItem,
defaultRequestParamsItem,
makeDefaultParams,
makeMockDefaultParams,
} from '@/views/api-test/components/config';
import { filterKeyValParams, parseRequestBodyFiles } from '@/views/api-test/components/utils';
@ -258,7 +258,7 @@
const loading = ref(false);
const isEdit = ref(props.isEditMode);
const mockDetail = ref<MockParams>(makeDefaultParams());
const mockDetail = ref<MockParams>(makeMockDefaultParams());
const isReadOnly = computed(() => !mockDetail.value.isNew && !isEdit.value);
const title = computed(() => {
if (isReadOnly.value) {
@ -538,13 +538,17 @@
appendDefaultMatchRuleItem();
}
isEdit.value = !!props.isEditMode;
if (props.isEditMode) {
//
appendDefaultMatchRuleItem();
}
if (mockDetail.value.mockMatchRule.body.bodyType !== RequestBodyFormat.NONE) {
activeTab.value = RequestComposition.BODY;
} else if (mockDetail.value.mockMatchRule.header.matchRules.length > 0) {
activeTab.value = RequestComposition.HEADER;
} else if (mockDetail.value.mockMatchRule.query) {
} else if (mockDetail.value.mockMatchRule.query.matchRules.length > 0) {
activeTab.value = RequestComposition.QUERY;
} else if (mockDetail.value.mockMatchRule.rest) {
} else if (mockDetail.value.mockMatchRule.rest.matchRules.length > 0) {
activeTab.value = RequestComposition.REST;
}
} catch (error) {
@ -609,7 +613,7 @@
}
function handleCancel() {
mockDetail.value = makeDefaultParams();
mockDetail.value = makeMockDefaultParams();
isEdit.value = false;
visible.value = false;
}
@ -624,7 +628,10 @@
handleCancel();
}
async function handleSave(isContinue = false) {
const mockFormRef = ref<FormInstance>();
function handleSave(isContinue = false) {
mockFormRef.value?.validate(async (errors) => {
if (!errors) {
try {
loading.value = true;
const { body } = mockDetail.value.mockMatchRule;
@ -709,7 +716,8 @@
}
emit('addDone');
if (isContinue) {
mockDetail.value = makeDefaultParams();
mockDetail.value.name = '';
mockDetail.value.tags = [];
} else {
handleCancel();
}
@ -720,6 +728,8 @@
loading.value = false;
}
}
});
}
</script>
<style lang="less" scoped></style>

View File

@ -163,7 +163,7 @@
<template v-else-if="currentSelectedDefinitionResponse">
<MsTab
v-model:active-key="definitionActiveTab"
:content-tab-list="responseCompositionTabList.filter((e) => e.value !== 'DELAY')"
:content-tab-list="responseCompositionTabList"
class="no-content relative my-[8px] border-b"
:show-badge="false"
/>
@ -286,6 +286,19 @@
disabled
@change="() => emit('change')"
/>
<a-input-number
v-else
v-model:model-value="mockResponse.delay"
:disabled="props.disabled"
mode="button"
:step="100"
:precision="0"
:max="600000"
:min="0"
class="w-[200px]"
>
<template #suffix> ms </template>
</a-input-number>
</div>
</template>
</a-spin>

View File

@ -299,7 +299,7 @@
};
});
const moduleKeyword = ref('');
const moduleKeyword = ref(''); //
const folderTree = ref<ModuleTreeNode[]>([]);
const focusNodeKey = ref<string | number>('');
const selectedKeys = ref<Array<string | number>>([props.activeModule]);
@ -371,7 +371,7 @@
const isExpandApi = ref(false);
const lastModuleCountParam = ref<ApiDefinitionGetModuleParams>({
projectId: appStore.currentProjectId,
keyword: moduleKeyword.value,
keyword: '',
protocol: moduleProtocol.value,
moduleIds: [],
});
@ -427,7 +427,7 @@
if (props.trash) {
res = await getTrashModuleTree({
//
keyword: moduleKeyword.value,
keyword: '',
protocol: moduleProtocol.value,
projectId: appStore.currentProjectId,
moduleIds: [],
@ -435,7 +435,7 @@
} else if (isExpandApi.value && !props.readOnly) {
//
res = await getModuleTree({
keyword: moduleKeyword.value,
keyword: '',
protocol: moduleProtocol.value,
projectId: appStore.currentProjectId,
moduleIds: [],
@ -443,7 +443,7 @@
} else {
res = await getModuleTreeOnlyModules({
//
keyword: moduleKeyword.value,
keyword: '',
protocol: moduleProtocol.value,
projectId: appStore.currentProjectId,
moduleIds: [],

View File

@ -27,8 +27,8 @@
:default-param-item="defaultNormalParamItem"
:draggable="false"
:selectable="false"
@change="handleParamTableChange"
@batch-add="batchAddKeyValVisible = true"
@change="handleCommonVariablesChange"
@batch-add="() => (batchAddKeyValVisible = true)"
/>
<paramTable
v-else
@ -37,12 +37,19 @@
:default-param-item="defaultCsvParamItem"
:draggable="false"
:selectable="false"
@change="handleParamTableChange"
@batch-add="batchAddKeyValVisible = true"
@change="handleCsvVariablesChange"
@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>
<a-trigger
v-model:popup-visible="record.settingVisible"
trigger="click"
position="br"
class="scenario-csv-trigger"
>
<MsButton type="text" class="!mr-0" @click="handleRecordConfig(record)">
{{ t('apiScenario.params.config') }}
</MsButton>
<template #content>
<div class="scenario-csv-trigger-content">
<div class="mb-[16px] flex items-center">
@ -50,32 +57,32 @@
<!-- <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 ref="paramFormRef" :model="paramForm" layout="vertical">
<a-form-item
field="key"
field="name"
: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-input v-model:model-value="paramForm.name" :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"
v-model:model-value="paramForm.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"
v-model:model-value="paramForm.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"
v-model:model-value="paramForm.delimiter"
:placeholder="t('common.pleaseInput')"
:max-length="64"
class="w-[120px]"
@ -86,37 +93,41 @@
:label="t('apiScenario.params.csvIgnoreFirstLine')"
class="mb-[16px]"
>
<a-radio-group v-model:model-value="record.ignoreFirstLine">
<a-radio-group v-model:model-value="paramForm.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-group v-model:model-value="paramForm.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-group v-model:model-value="paramForm.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-group v-model:model-value="paramForm.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-group v-model:model-value="paramForm.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 class="flex items-center justify-end gap-[8px]">
<a-button type="secondary" @click="cancelConfig">{{ t('common.cancel') }}</a-button>
<a-button type="primary" @click="applyConfig">{{ t('ms.paramsInput.apply') }}</a-button>
</div>
</div>
</template>
</a-trigger>
@ -134,6 +145,7 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { FormInstance } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue';
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
@ -215,7 +227,7 @@
},
];
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
function handleCommonVariablesChange(resultArr: any[], isInit?: boolean) {
commonVariables.value = [...resultArr];
if (!isInit) {
emit('change');
@ -223,6 +235,14 @@
}
}
function handleCsvVariablesChange(resultArr: any[], isInit?: boolean) {
csvVariables.value = [...resultArr];
if (!isInit) {
emit('change');
firstSearch.value = true;
}
}
//
function handleSearch() {
if (firstSearch.value) {
@ -255,8 +275,8 @@
const csvColumns: ParamTableColumn[] = [
{
title: 'apiScenario.params.csvName',
dataIndex: 'key',
slotName: 'key',
dataIndex: 'name',
slotName: 'name',
needValidRepeat: true,
},
{
@ -277,11 +297,11 @@
titleSlotName: 'typeTitle',
typeTitleTooltip: [t('apiScenario.params.csvScopedTip1'), t('apiScenario.params.csvScopedTip2')],
},
// {
// title: 'apiScenario.params.file',
// dataIndex: 'file',
// slotName: 'file',
// },
{
title: 'apiScenario.params.file',
dataIndex: 'file',
slotName: 'file',
},
{
title: 'apiScenario.table.columns.status',
dataIndex: 'enable',
@ -295,7 +315,8 @@
},
];
const configFormRef = ref<FormInstance>();
const paramFormRef = ref<FormInstance>();
const paramForm = ref<CsvVariable>(cloneDeep(defaultCsvParamItem));
const encodingOptions = [
{
label: 'UTF-8',
@ -305,6 +326,10 @@
label: 'UTF-16',
value: 'UTF-16',
},
{
label: 'GBK',
value: 'GBK',
},
{
label: 'ISO-8859-15',
value: 'ISO-8859-15',
@ -314,6 +339,31 @@
value: 'US-ASCII',
},
];
function handleRecordConfig(record: CsvVariable) {
paramForm.value = cloneDeep(record);
}
function cancelConfig() {
paramFormRef.value?.resetFields();
}
function applyConfig() {
paramFormRef.value?.validate((errors) => {
if (!errors) {
csvVariables.value = csvVariables.value.map((e) => {
if (e.id === paramForm.value.id) {
return {
...paramForm.value,
settingVisible: false,
};
}
return e;
});
emit('change');
}
});
}
</script>
<style lang="less">

View File

@ -273,7 +273,7 @@
try {
loading.value = true;
const res = await getModuleTree({
keyword: moduleKeyword.value,
keyword: '',
projectId: appStore.currentProjectId,
moduleIds: [],
});

View File

@ -50,7 +50,7 @@
v-on="propsEvent"
@batch-action="handleTableBatch"
@change="changeHandler"
@module-change="initData()"
@module-change="initData"
@cell-click="handleCellClick"
>
<template #num="{ record }">
@ -216,7 +216,7 @@
</MsTag>
</div>
</div>
<div class="h-[calc(100%-32px)]">
<div class="mt-[16px] h-[calc(100%-32px)] border-t border-[var(--color-text-n8)]">
<!-- 脑图开始 -->
<MsMinder minder-type="FeatureCase" :import-json="importJson" @node-click="handleNodeClick" />
<MsDrawer v-model:visible="visible" :width="480" :mask="false">

View File

@ -103,6 +103,7 @@
import { getFirstRouteNameByPermission, routerNameHasPermission } from '@/utils/permission';
import type { LoginData } from '@/models/user';
import { SettingRouteEnum } from '@/enums/routeEnum';
import { ValidatedError } from '@arco-design/web-vue/es/form/interface';
@ -176,8 +177,11 @@
const { username, password } = values;
loginConfig.value.username = rememberPassword ? username : '';
loginConfig.value.password = rememberPassword ? password : '';
if (!appStore.currentProjectId || appStore.currentProjectId === 'no_such_project') {
// &/
if (
(!appStore.currentProjectId || appStore.currentProjectId === 'no_such_project') &&
!(router.currentRoute.value as unknown as string).startsWith(SettingRouteEnum.SETTING)
) {
// &/访
router.push({
name: NO_PROJECT_ROUTE_NAME,
});