feat(接口测试): 用例列表同步&对比table抽屉Drawer
This commit is contained in:
parent
122c54691a
commit
1f77a8db41
|
@ -12,6 +12,7 @@
|
|||
props.disabled ? 'ms-form-table--disabled' : '',
|
||||
]"
|
||||
bordered
|
||||
:row-class="rowClass"
|
||||
v-on="propsEvent"
|
||||
@drag-change="tableChange"
|
||||
@init-end="validateAndUpdateErrorMessageList"
|
||||
|
@ -294,6 +295,7 @@
|
|||
disabled?: boolean; // 是否禁用
|
||||
showSelectorAll?: boolean; // 是否显示全选
|
||||
rowSelection?: TableRowSelection;
|
||||
diffMode?: 'add' | 'delete';
|
||||
spanMethod?: (data: {
|
||||
record: TableData;
|
||||
column: TableColumnData | TableOperationColumn;
|
||||
|
@ -537,6 +539,18 @@
|
|||
emit('selectAll', checked);
|
||||
}
|
||||
|
||||
function rowClass(record: TableData, rowIndex: number) {
|
||||
if (record.diff) {
|
||||
if (props.diffMode === 'add') {
|
||||
return 'add-row-class';
|
||||
}
|
||||
if (props.diffMode === 'delete') {
|
||||
return 'delete-row-class';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validateAndUpdateErrorMessageList,
|
||||
});
|
||||
|
@ -719,4 +733,20 @@
|
|||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
:deep(.add-row-class) {
|
||||
.arco-table-td {
|
||||
background: rgb(var(--success-1));
|
||||
.arco-table-td-content {
|
||||
background: rgb(var(--success-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.delete-row-class) {
|
||||
.arco-table-td {
|
||||
background: rgb(var(--danger-1));
|
||||
.arco-table-td-content {
|
||||
background: rgb(var(--danger-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -47,9 +47,26 @@
|
|||
</div>
|
||||
</template>
|
||||
<template #num="{ record }">
|
||||
<div class="flex items-center">
|
||||
<MsButton type="text" @click="isApi ? openCaseDetailDrawer(record.id) : openCaseTab(record)">
|
||||
{{ record.num }}
|
||||
</MsButton>
|
||||
<!-- TODO 后台缺少字段 等待联调 -->
|
||||
<a-tooltip v-if="record.apiChange" class="ms-tooltip-white">
|
||||
<!-- 接口参数发生变更提示 -->
|
||||
<MsIcon type="icon-icon_warning_colorful" size="16" />
|
||||
<template #content>
|
||||
<div class="flex flex-row">
|
||||
<span class="text-[var(--color-text-1)]">
|
||||
{{ t('case.apiParamsHasChange') }}
|
||||
</span>
|
||||
<MsButton class="ml-[8px]" @click="showDifferences(record)">
|
||||
{{ t('case.changeDifferences') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<template #protocol="{ record }">
|
||||
<apiMethodName :method="record.protocol" />
|
||||
|
@ -281,6 +298,17 @@
|
|||
/>
|
||||
<!-- 执行结果抽屉 -->
|
||||
<caseAndScenarioReportDrawer v-model:visible="showExecuteResult" :report-id="activeReportId" />
|
||||
<!-- 同步抽屉 -->
|
||||
<SyncModal v-model:visible="showSyncModal" :batch-params="batchParams" />
|
||||
<!-- diff对比抽屉 -->
|
||||
<DifferentDrawer
|
||||
v-model:visible="showDifferentDrawer"
|
||||
:detail="caseDetail as RequestParam"
|
||||
:api-detail="apiDetail as RequestParam"
|
||||
:active-api-case-id="activeApiCaseId"
|
||||
:active-defined-id="activeDefinedId"
|
||||
@close="closeDifferent"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -299,6 +327,8 @@
|
|||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||
import caseDetailDrawer from './caseDetailDrawer.vue';
|
||||
import createAndEditCaseDrawer from './createAndEditCaseDrawer.vue';
|
||||
import DifferentDrawer from './differentDrawer.vue';
|
||||
import SyncModal from './syncModal.vue';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||
import BatchRunModal from '@/views/api-test/components/batchRunModal.vue';
|
||||
|
@ -390,8 +420,7 @@
|
|||
sorter: true,
|
||||
},
|
||||
fixed: 'left',
|
||||
width: 130,
|
||||
showTooltip: true,
|
||||
width: 150,
|
||||
columnSelectorDisabled: true,
|
||||
},
|
||||
{
|
||||
|
@ -568,6 +597,11 @@
|
|||
eventTag: 'execute',
|
||||
permission: ['PROJECT_API_DEFINITION_CASE:READ+EXECUTE'],
|
||||
},
|
||||
{
|
||||
label: 'case.apiSyncChange',
|
||||
eventTag: 'sync',
|
||||
permission: ['PROJECT_API_DEFINITION_CASE:READ+UPDATE'],
|
||||
},
|
||||
{
|
||||
label: 'common.delete',
|
||||
eventTag: 'delete',
|
||||
|
@ -820,9 +854,14 @@
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
const batchConditionParams = ref<any>();
|
||||
|
||||
const showSyncModal = ref<boolean>(false);
|
||||
// 同步 TODO 等待联调
|
||||
function syncParams() {
|
||||
showSyncModal.value = true;
|
||||
}
|
||||
|
||||
// 处理表格选中后批量操作
|
||||
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
|
||||
tableSelected.value = params?.selectedIds || [];
|
||||
|
@ -840,6 +879,9 @@
|
|||
showBatchExecute.value = true;
|
||||
});
|
||||
break;
|
||||
case 'sync':
|
||||
syncParams();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -920,6 +962,23 @@
|
|||
showExecuteResult.value = true;
|
||||
}
|
||||
|
||||
const activeApiCaseId = ref<string>('');
|
||||
const activeDefinedId = ref<string>('');
|
||||
const showDifferentDrawer = ref<boolean>(false);
|
||||
|
||||
// 查看对比 TODO 等待联调
|
||||
async function showDifferences(record: ApiCaseDetail) {
|
||||
activeApiCaseId.value = record.id;
|
||||
activeDefinedId.value = record.apiDefinitionId;
|
||||
showDifferentDrawer.value = true;
|
||||
}
|
||||
// 关闭对比 TODO 等待联调
|
||||
function closeDifferent() {
|
||||
showDifferentDrawer.value = false;
|
||||
activeApiCaseId.value = '';
|
||||
activeDefinedId.value = '';
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
loadCaseList,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,470 @@
|
|||
<template>
|
||||
<div v-if="showDiff(RequestComposition.HEADER)" class="title">{{ t('apiTestDebug.header') }}</div>
|
||||
<div
|
||||
v-if="showDiff(RequestComposition.HEADER) && hiddenEmptyTable(RequestComposition.HEADER)"
|
||||
:style="{ 'padding-bottom': `${getBottomDistance(RequestComposition.HEADER)}px` }"
|
||||
>
|
||||
<MsFormTable
|
||||
:columns="headerColumns"
|
||||
:data="previewDetail?.headers?.filter((e) => e.key !== '') || []"
|
||||
:selectable="false"
|
||||
:diff-mode="props.mode"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="showDiff(RequestComposition.HEADER) && !hiddenEmptyTable(RequestComposition.HEADER)"
|
||||
class="not-setting-data"
|
||||
:style="{ height: `${getBottomDistance(RequestComposition.HEADER, true)}px` }"
|
||||
>
|
||||
{{ t('case.notSetData') }}
|
||||
</div>
|
||||
<div v-if="showDiff(RequestComposition.QUERY)" class="title">Query</div>
|
||||
<div
|
||||
v-if="showDiff(RequestComposition.QUERY) && hiddenEmptyTable(RequestComposition.QUERY)"
|
||||
:style="{ 'padding-bottom': `${getBottomDistance(RequestComposition.QUERY)}px` }"
|
||||
>
|
||||
<MsFormTable
|
||||
:columns="queryRestColumns"
|
||||
:data="previewDetail?.query?.filter((e) => e.key !== '') || []"
|
||||
:selectable="false"
|
||||
:diff-mode="props.mode"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="showDiff(RequestComposition.QUERY) && !hiddenEmptyTable(RequestComposition.QUERY)"
|
||||
class="not-setting-data"
|
||||
:style="{ height: `${getBottomDistance(RequestComposition.QUERY, true)}px` }"
|
||||
>
|
||||
{{ t('case.notSetData') }}
|
||||
</div>
|
||||
<div v-if="showDiff(RequestComposition.REST)" class="title">REST</div>
|
||||
<div
|
||||
v-if="showDiff(RequestComposition.REST)"
|
||||
:style="{ 'padding-bottom': `${getBottomDistance(RequestComposition.REST)}px` }"
|
||||
>
|
||||
<MsFormTable
|
||||
:columns="queryRestColumns?.filter((e) => e.key !== '')"
|
||||
:data="previewDetail?.rest || []"
|
||||
:selectable="false"
|
||||
:diff-mode="props.mode"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="showDiff(RequestComposition.REST) && !hiddenEmptyTable(RequestComposition.REST)"
|
||||
class="not-setting-data"
|
||||
:style="{ height: `${getBottomDistance(RequestComposition.REST, true)}px` }"
|
||||
>
|
||||
{{ t('case.notSetData') }}
|
||||
</div>
|
||||
|
||||
<div class="title flex items-center justify-between">
|
||||
<div class="detail-item-title-text">
|
||||
{{ `${t('apiTestManagement.requestBody')}-${previewDetail?.body?.bodyType}` }}
|
||||
</div>
|
||||
<a-radio-group
|
||||
v-if="previewDetail?.body?.bodyType === RequestBodyFormat.JSON && props.isApi"
|
||||
v-model:model-value="bodyShowType"
|
||||
type="button"
|
||||
size="mini"
|
||||
>
|
||||
<a-radio value="schema">Schema</a-radio>
|
||||
<a-radio value="json">JSON</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
(previewDetail?.body?.bodyType === RequestBodyFormat.FORM_DATA ||
|
||||
previewDetail?.body?.bodyType === RequestBodyFormat.WWW_FORM) &&
|
||||
showDiff(previewDetail?.body?.bodyType) &&
|
||||
hiddenEmptyTable(previewDetail?.body?.bodyType)
|
||||
"
|
||||
:style="{ 'padding-bottom': `${getBottomDistance(previewDetail.value?.body?.bodyType)}px` }"
|
||||
>
|
||||
<MsFormTable
|
||||
:columns="bodyColumns"
|
||||
:data="bodyTableData"
|
||||
:selectable="false"
|
||||
:show-setting="true"
|
||||
:table-key="TableKeyEnum.API_TEST_DEBUG_FORM_DATA"
|
||||
:diff-mode="props.mode"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="showDiff(previewDetail?.body?.bodyType) && !hiddenEmptyTable(previewDetail?.body?.bodyType)"
|
||||
class="not-setting-data"
|
||||
:style="{ height: `${getBottomDistance(previewDetail?.body?.bodyType, true)}px` }"
|
||||
>
|
||||
{{ t('case.notSetData') }}
|
||||
</div>
|
||||
<template
|
||||
v-else-if="
|
||||
[RequestBodyFormat.JSON, RequestBodyFormat.RAW, RequestBodyFormat.XML].includes(previewDetail?.body?.bodyType)
|
||||
"
|
||||
>
|
||||
<MsJsonSchema
|
||||
v-if="previewDetail?.body?.bodyType === RequestBodyFormat.JSON && bodyShowType === 'schema' && props.isApi"
|
||||
:data="previewDetail.body.jsonBody.jsonSchemaTableData"
|
||||
disabled
|
||||
/>
|
||||
<MsCodeEditor
|
||||
v-else
|
||||
:model-value="bodyCode"
|
||||
theme="vs"
|
||||
height="200px"
|
||||
:language="bodyCodeLanguage"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
read-only
|
||||
>
|
||||
<template #rightTitle>
|
||||
<a-button
|
||||
type="outline"
|
||||
class="arco-btn-outline--secondary p-[0_8px]"
|
||||
size="mini"
|
||||
@click="copyScript(bodyCode)"
|
||||
>
|
||||
<template #icon>
|
||||
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</MsCodeEditor>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||
import MsFormTable, { FormTableColumn } from '@/components/pure/ms-form-table/index.vue';
|
||||
import MsJsonSchema from '@/components/pure/ms-json-schema/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { RequestBodyFormat, RequestComposition, RequestParamsType } from '@/enums/apiEnum';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
|
||||
const { copy, isSupported } = useClipboard({ legacy: true });
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
diffDistanceMap: Record<string, any>; // 距离填充
|
||||
detail: RequestParam;
|
||||
mode: 'add' | 'delete';
|
||||
isApi?: boolean;
|
||||
}>();
|
||||
|
||||
const previewDetail = ref<RequestParam>(props.detail);
|
||||
|
||||
/**
|
||||
* 请求头
|
||||
*/
|
||||
const headerColumns: FormTableColumn[] = [
|
||||
{
|
||||
title: 'apiTestManagement.paramName',
|
||||
dataIndex: 'key',
|
||||
inputType: 'text',
|
||||
width: 220,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.paramVal',
|
||||
dataIndex: 'value',
|
||||
inputType: 'text',
|
||||
width: 220,
|
||||
},
|
||||
{
|
||||
title: 'common.desc',
|
||||
dataIndex: 'description',
|
||||
inputType: 'text',
|
||||
showTooltip: true,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Query & Rest
|
||||
*/
|
||||
const queryRestColumns: FormTableColumn[] = [
|
||||
{
|
||||
title: 'apiTestManagement.paramName',
|
||||
dataIndex: 'key',
|
||||
inputType: 'text',
|
||||
width: 220,
|
||||
columnSelectorDisabled: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.required',
|
||||
dataIndex: 'required',
|
||||
slotName: 'required',
|
||||
inputType: 'text',
|
||||
columnSelectorDisabled: true,
|
||||
valueFormat: (record) => {
|
||||
return record.required ? t('common.yes') : t('common.no');
|
||||
},
|
||||
width: 68,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramType',
|
||||
dataIndex: 'paramType',
|
||||
inputType: 'text',
|
||||
width: 96,
|
||||
columnSelectorDisabled: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.paramVal',
|
||||
dataIndex: 'value',
|
||||
inputType: 'text',
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramLengthRange',
|
||||
dataIndex: 'lengthRange',
|
||||
slotName: 'lengthRange',
|
||||
inputType: 'text',
|
||||
showInTable: false,
|
||||
valueFormat: (record) => {
|
||||
return [null, undefined].includes(record.minLength) && [null, undefined].includes(record.maxLength)
|
||||
? '-'
|
||||
: `${record.minLength} ${t('common.to')} ${record.maxLength}`;
|
||||
},
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.encode',
|
||||
dataIndex: 'encode',
|
||||
slotName: 'encode',
|
||||
inputType: 'text',
|
||||
showInTable: false,
|
||||
valueFormat: (record) => {
|
||||
return record.encode ? t('common.yes') : t('common.no');
|
||||
},
|
||||
width: 68,
|
||||
},
|
||||
{
|
||||
title: 'common.desc',
|
||||
dataIndex: 'description',
|
||||
inputType: 'text',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'operation',
|
||||
slotName: 'operation',
|
||||
fixed: 'right',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
|
||||
const bodyShowType = ref('schema');
|
||||
|
||||
/**
|
||||
* 请求体
|
||||
*/
|
||||
const bodyColumns = computed<FormTableColumn[]>(() => {
|
||||
if ([RequestBodyFormat.FORM_DATA, RequestBodyFormat.WWW_FORM].includes(previewDetail.value?.body?.bodyType)) {
|
||||
return [
|
||||
{
|
||||
title: 'apiTestManagement.paramName',
|
||||
dataIndex: 'key',
|
||||
inputType: 'text',
|
||||
width: 220,
|
||||
columnSelectorDisabled: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.required',
|
||||
dataIndex: 'required',
|
||||
slotName: 'required',
|
||||
inputType: 'text',
|
||||
columnSelectorDisabled: true,
|
||||
valueFormat: (record) => {
|
||||
return record.required ? t('common.yes') : t('common.no');
|
||||
},
|
||||
width: 68,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.paramsType',
|
||||
dataIndex: 'paramType',
|
||||
inputType: 'text',
|
||||
width: 96,
|
||||
columnSelectorDisabled: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.paramVal',
|
||||
dataIndex: 'value',
|
||||
inputType: 'text',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramLengthRange',
|
||||
dataIndex: 'lengthRange',
|
||||
slotName: 'lengthRange',
|
||||
inputType: 'text',
|
||||
showInTable: false,
|
||||
valueFormat: (record) => {
|
||||
return [null, undefined].includes(record.minLength) && [null, undefined].includes(record.maxLength)
|
||||
? '-'
|
||||
: `${record.minLength} ${t('common.to')} ${record.maxLength}`;
|
||||
},
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.encode',
|
||||
dataIndex: 'encode',
|
||||
slotName: 'encode',
|
||||
inputType: 'text',
|
||||
showInTable: false,
|
||||
valueFormat: (record) => {
|
||||
return record.encode ? t('common.yes') : t('common.no');
|
||||
},
|
||||
width: 68,
|
||||
},
|
||||
{
|
||||
title: 'common.desc',
|
||||
dataIndex: 'description',
|
||||
inputType: 'text',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'operation',
|
||||
slotName: 'operation',
|
||||
fixed: 'right',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
title: 'common.desc',
|
||||
dataIndex: 'description',
|
||||
inputType: 'text',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.paramVal',
|
||||
dataIndex: 'value',
|
||||
inputType: 'text',
|
||||
showTooltip: true,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const bodyTableData = computed(() => {
|
||||
switch (previewDetail.value?.body?.bodyType) {
|
||||
case RequestBodyFormat.FORM_DATA:
|
||||
return (previewDetail.value.body.formDataBody?.formValues || [])
|
||||
.map((e) => ({
|
||||
...e,
|
||||
value: e.paramType === RequestParamsType.FILE ? e.files?.map((file) => file.fileName).join('、') : e.value,
|
||||
}))
|
||||
?.filter((e) => e.key !== '');
|
||||
case RequestBodyFormat.WWW_FORM:
|
||||
return previewDetail.value.body.wwwFormBody?.formValues?.filter((e) => e.key !== '') || [];
|
||||
case RequestBodyFormat.BINARY:
|
||||
return [
|
||||
{
|
||||
description: previewDetail.value.body.binaryBody.description,
|
||||
value: previewDetail.value.body.binaryBody.file?.fileName,
|
||||
},
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
const bodyCode = computed(() => {
|
||||
switch (previewDetail.value?.body?.bodyType) {
|
||||
case RequestBodyFormat.FORM_DATA:
|
||||
return previewDetail.value.body.formDataBody?.formValues?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||
case RequestBodyFormat.WWW_FORM:
|
||||
return previewDetail.value.body.wwwFormBody?.formValues?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||
case RequestBodyFormat.RAW:
|
||||
return previewDetail.value.body.rawBody?.value;
|
||||
case RequestBodyFormat.JSON:
|
||||
return previewDetail.value.body.jsonBody?.jsonValue;
|
||||
case RequestBodyFormat.XML:
|
||||
return previewDetail.value.body.xmlBody?.value;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
const bodyCodeLanguage = computed(() => {
|
||||
if (previewDetail.value?.body?.bodyType === RequestBodyFormat.JSON) {
|
||||
return LanguageEnum.JSON;
|
||||
}
|
||||
if (previewDetail.value?.body?.bodyType === RequestBodyFormat.XML) {
|
||||
return LanguageEnum.XML;
|
||||
}
|
||||
return LanguageEnum.PLAINTEXT;
|
||||
});
|
||||
|
||||
function copyScript(val: string) {
|
||||
if (isSupported) {
|
||||
copy(val);
|
||||
Message.success(t('common.copySuccess'));
|
||||
} else {
|
||||
Message.warning(t('apiTestDebug.copyNotSupport'));
|
||||
}
|
||||
}
|
||||
|
||||
const typeKey = computed(() => (props.isApi ? 'api' : 'case'));
|
||||
|
||||
// 设置非空间距确保行内容长度不同能够水平看齐对比
|
||||
function getBottomDistance(type: string, isEmpty = false) {
|
||||
const isEmptyDefaultDis = isEmpty ? 34 : 0;
|
||||
if (props.diffDistanceMap[type] && props.diffDistanceMap[type][typeKey.value]) {
|
||||
return props.diffDistanceMap[type][typeKey.value] * 32 + isEmptyDefaultDis;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 是否显示对比
|
||||
function showDiff(type: string) {
|
||||
return props.diffDistanceMap[type]?.display;
|
||||
}
|
||||
|
||||
// 去掉空表
|
||||
function hiddenEmptyTable(type: string) {
|
||||
if (props.isApi) {
|
||||
return props.diffDistanceMap[type]?.showEmptyApiTable;
|
||||
}
|
||||
return props.diffDistanceMap[type]?.showEmptyCaseTable;
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.detail) {
|
||||
previewDetail.value = cloneDeep(props.detail);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.title-type {
|
||||
color: var(--color-text-1);
|
||||
@apply font-medium;
|
||||
}
|
||||
.title {
|
||||
color: var(--color-text-1);
|
||||
@apply my-4;
|
||||
}
|
||||
.detail-item-title {
|
||||
margin-bottom: 8px;
|
||||
gap: 16px;
|
||||
@apply flex items-center justify-between;
|
||||
.detail-item-title-text {
|
||||
@apply font-medium;
|
||||
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
.not-setting-data {
|
||||
border: 1px solid var(--color-border-2);
|
||||
border-radius: 4px;
|
||||
@apply flex items-center justify-center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,407 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="showDiffVisible"
|
||||
:title="t('case.apiAndCaseDiff')"
|
||||
width="100%"
|
||||
class="diff-modal"
|
||||
:footer="false"
|
||||
no-content-padding
|
||||
unmount-on-close
|
||||
:closable="false"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div>{{ t('case.apiAndCaseDiff') }}</div>
|
||||
<div class="flex items-center text-[14px]">
|
||||
<div class="-mt-[2px] mr-[8px]"> {{ t('case.syncItem') }}</div>
|
||||
<a-checkbox-group v-model="form.checkType">
|
||||
<a-checkbox v-for="item of checkList" :key="item.value" :value="item.value">
|
||||
<div class="flex items-center"
|
||||
>{{ item.label }}
|
||||
|
||||
<a-tooltip v-if="item.tooltip" :content="item.tooltip" position="top">
|
||||
<div class="flex items-center">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
<a-divider direction="vertical" :margin="0" class="!mr-[8px]"></a-divider>
|
||||
<a-switch v-model:model-value="form.ignoreUpdate" size="small" />
|
||||
<a-select
|
||||
v-model="form.ignoreUpdateType"
|
||||
class="ml-[8px] w-[160px]"
|
||||
:placeholder="t('caseManagement.featureCase.PleaseSelect')"
|
||||
:disabled="!form.ignoreUpdate"
|
||||
@change="changeIgnoreType"
|
||||
>
|
||||
<a-option v-for="item of ignoreList" :key="item.value" :value="item.value">
|
||||
{{ t(item.label) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
<a-switch v-model:model-value="form.deleteParams" size="small" />
|
||||
<div class="ml-[8px] font-normal text-[var(--color-text-1)]">{{ t('case.deleteNotCorrespondValue') }}</div>
|
||||
<a-divider direction="vertical" :margin="0" class="!ml-[8px]"></a-divider>
|
||||
<a-button class="mx-[12px]" type="secondary" @click="cancel">{{ t('common.cancel') }}</a-button>
|
||||
<a-button type="primary" :loading="syncLoading" :disabled="!form.checkType.length" @click="confirmBatchSync">
|
||||
{{ t('case.apiSyncChange') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 图例 -->
|
||||
<div class="legend-container">
|
||||
<div class="item mr-[24px]">
|
||||
<div class="legend add"></div>
|
||||
{{ t('case.diffAdd') }}
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="legend delete"></div>
|
||||
{{ t('common.delete') }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 对比 -->
|
||||
<div class="diff-container">
|
||||
<div class="diff-item ml-[16px] mr-[8px]">
|
||||
<div class="title-type"> [{{ apiDetailInfo?.num }}] {{ apiDetailInfo?.name }} </div>
|
||||
<DiffItem :diff-distance-map="diffDistanceMap" mode="add" is-api :detail="apiDefinedRequest as RequestParam" />
|
||||
</div>
|
||||
<div class="diff-item ml-[8px] mr-[16px]">
|
||||
<div class="title-type"> [{{ caseDetail?.num }}] {{ caseDetail?.name }} </div>
|
||||
<DiffItem :diff-distance-map="diffDistanceMap" mode="delete" :detail="caseDetail as RequestParam" />
|
||||
</div>
|
||||
</div>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||
import DiffItem from './diffItem.vue';
|
||||
|
||||
import { getCaseDetail, getDefinitionDetail } from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { EnableKeyValueParam, ExecuteRequestCommonParam } from '@/models/apiTest/common';
|
||||
import { ApiDefinitionDetail } from '@/models/apiTest/management';
|
||||
import { RequestBodyFormat, RequestComposition } from '@/enums/apiEnum';
|
||||
|
||||
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
import { parseRequestBodyFiles } from '@/views/api-test/components/utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
activeApiCaseId: string;
|
||||
activeDefinedId: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const showDiffVisible = defineModel<boolean>('visible', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const checkList = ref([
|
||||
{
|
||||
value: RequestComposition.HEADER,
|
||||
label: t('apiTestDebug.header'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.BODY,
|
||||
label: t('apiTestDebug.body'),
|
||||
tooltip: t('case.onlySyncNewParamsOrValue'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.QUERY,
|
||||
label: RequestComposition.QUERY,
|
||||
},
|
||||
{
|
||||
value: RequestComposition.REST,
|
||||
label: RequestComposition.REST,
|
||||
},
|
||||
]);
|
||||
|
||||
const initForm = {
|
||||
deleteParams: false,
|
||||
checkType: [],
|
||||
noticeApiCaseCreator: true,
|
||||
noticeApiScenarioCreator: true,
|
||||
ignoreUpdate: false,
|
||||
ignoreUpdateType: ['THIS_TIME'],
|
||||
};
|
||||
|
||||
const form = ref({ ...initForm });
|
||||
|
||||
const ignoreList = ref([
|
||||
{
|
||||
value: 'THIS_TIME',
|
||||
label: t('case.ignoreThisChange'),
|
||||
},
|
||||
{
|
||||
value: 'ALL',
|
||||
label: t('case.ignoreAllChange'),
|
||||
},
|
||||
]);
|
||||
|
||||
// 忽略更新
|
||||
function changeIgnoreType() {}
|
||||
|
||||
function cancel() {
|
||||
showDiffVisible.value = false;
|
||||
emit('close');
|
||||
}
|
||||
const syncLoading = ref<boolean>(false);
|
||||
// 同步
|
||||
function confirmBatchSync() {}
|
||||
|
||||
const defaultCaseParams = inject<RequestParam>('defaultCaseParams');
|
||||
const caseDetail = ref<Record<string, any>>({});
|
||||
|
||||
const apiDetailInfo = ref<Record<string, any>>({});
|
||||
const apiDefinedRequest = ref<Record<string, any>>({});
|
||||
|
||||
const diffDistanceMap = ref<Record<string, any>>({});
|
||||
|
||||
/**
|
||||
* 设置对比
|
||||
* @params apiValue 接口数据
|
||||
* @params caseValue 用例数据
|
||||
* @params typeKey 用于增加间距Map的对应KEY\HEADER\QUERY等
|
||||
* @params nameKey 对应参数名称key
|
||||
*/
|
||||
function setDiff(
|
||||
apiValue: Record<string, any>[],
|
||||
caseValue: Record<string, any>[],
|
||||
typeKey: string,
|
||||
nameKey = 'key'
|
||||
) {
|
||||
const apiDefinedValue = (apiValue || []).filter((e) => e.key !== '') || [];
|
||||
|
||||
const apiCaseValue = (caseValue || []).filter((e) => e.key !== '') || [];
|
||||
|
||||
const caseValueMap = new Map();
|
||||
const apiValueMap = new Map();
|
||||
apiDefinedValue.forEach((item: any) => apiValueMap.set(item[nameKey], item));
|
||||
apiCaseValue.forEach((item: any) => caseValueMap.set(item[nameKey], item));
|
||||
|
||||
const definedData: Record<string, any>[] = apiDefinedValue.map((item) => {
|
||||
if (!caseValueMap.has(item[nameKey])) {
|
||||
return {
|
||||
...cloneDeep(item),
|
||||
diff: 'change',
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
const caseData: Record<string, any>[] = apiCaseValue.map((item) => {
|
||||
if (!apiValueMap.has(item[nameKey])) {
|
||||
return {
|
||||
...cloneDeep(item),
|
||||
diff: 'change',
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
// 设置对比水平对齐间距绝对值
|
||||
const disAbs = Math.abs(caseData.length - definedData.length);
|
||||
|
||||
diffDistanceMap.value[typeKey] = {
|
||||
case: caseData.length < definedData.length ? disAbs : 0,
|
||||
api: caseData.length > definedData.length ? disAbs : 0,
|
||||
display: caseData.length !== 0 || definedData.length !== 0, // 如果都为空则不展示
|
||||
showEmptyCaseTable: caseData.length !== 0, // 隐藏用例空表
|
||||
showEmptyApiTable: definedData.length !== 0, // 隐藏api空表
|
||||
};
|
||||
|
||||
return {
|
||||
caseData,
|
||||
definedData,
|
||||
};
|
||||
}
|
||||
|
||||
// 处理对比数据
|
||||
function processData() {
|
||||
// 处理请求头
|
||||
const headersObj = setDiff(
|
||||
apiDefinedRequest.value?.headers as any,
|
||||
caseDetail.value.headers,
|
||||
RequestComposition.HEADER
|
||||
);
|
||||
if (apiDefinedRequest.value?.headers) {
|
||||
apiDefinedRequest.value.headers = headersObj.definedData as EnableKeyValueParam[];
|
||||
caseDetail.value.headers = headersObj.caseData;
|
||||
}
|
||||
// 处理query
|
||||
const queryDiffObj = setDiff(
|
||||
apiDefinedRequest.value?.query as any,
|
||||
caseDetail.value.query,
|
||||
RequestComposition.QUERY
|
||||
);
|
||||
if (apiDefinedRequest.value?.query) {
|
||||
apiDefinedRequest.value.query = queryDiffObj.definedData as ExecuteRequestCommonParam[];
|
||||
caseDetail.value.query = queryDiffObj.caseData;
|
||||
}
|
||||
// 处理rest
|
||||
const restDiffObj = setDiff(apiDefinedRequest.value?.rest as any, caseDetail.value.rest, RequestComposition.REST);
|
||||
if (apiDefinedRequest.value?.rest) {
|
||||
apiDefinedRequest.value.rest = restDiffObj.definedData as ExecuteRequestCommonParam[];
|
||||
caseDetail.value.rest = restDiffObj.caseData;
|
||||
}
|
||||
// 处理请求体
|
||||
if (apiDefinedRequest.value?.body?.bodyType) {
|
||||
switch (apiDefinedRequest.value?.body?.bodyType) {
|
||||
// FORM_DATA格式
|
||||
case RequestBodyFormat.FORM_DATA:
|
||||
const bodyFormDataDiffObj = setDiff(
|
||||
apiDefinedRequest.value.body.formDataBody?.formValues as any,
|
||||
caseDetail.value.body.formDataBody?.formValues,
|
||||
RequestBodyFormat.FORM_DATA
|
||||
);
|
||||
apiDefinedRequest.value.body.formDataBody.formValues =
|
||||
bodyFormDataDiffObj.definedData as ExecuteRequestCommonParam[];
|
||||
caseDetail.value.body.formDataBody.formValues = bodyFormDataDiffObj.caseData;
|
||||
break;
|
||||
// WWW_FORM格式
|
||||
case RequestBodyFormat.WWW_FORM:
|
||||
const bodyWwwFormDiffObj = setDiff(
|
||||
apiDefinedRequest.value.body.wwwFormBody?.formValues as any,
|
||||
caseDetail.value.body.wwwFormBody?.formValues,
|
||||
RequestBodyFormat.WWW_FORM
|
||||
);
|
||||
apiDefinedRequest.value.body.wwwFormBody.formValues =
|
||||
bodyWwwFormDiffObj.definedData as ExecuteRequestCommonParam[];
|
||||
caseDetail.value.body.wwwFormBody.formValues = bodyWwwFormDiffObj.caseData;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 获取用例详情
|
||||
async function getCaseDetailInfo(id: string) {
|
||||
try {
|
||||
const res = await getCaseDetail(id);
|
||||
let parseRequestBodyResult;
|
||||
if (res.protocol === 'HTTP') {
|
||||
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||
}
|
||||
caseDetail.value = {
|
||||
...cloneDeep(defaultCaseParams as RequestParam),
|
||||
...({
|
||||
...res.request,
|
||||
...res,
|
||||
url: res.path,
|
||||
...parseRequestBodyResult,
|
||||
} as Partial<TabItem>),
|
||||
};
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getApiDetail(apiDefinitionId: string) {
|
||||
try {
|
||||
const detail = await getDefinitionDetail(apiDefinitionId);
|
||||
apiDetailInfo.value = detail as ApiDefinitionDetail;
|
||||
apiDefinedRequest.value = detail.request as unknown as RequestParam;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getRequestDetail(definedId: string, apiCaseId: string) {
|
||||
try {
|
||||
await Promise.all([getApiDetail(definedId), getCaseDetailInfo(apiCaseId)]);
|
||||
processData();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.activeDefinedId && props.activeApiCaseId) {
|
||||
getRequestDetail(props.activeDefinedId, props.activeApiCaseId);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.legend-container {
|
||||
padding: 8px 0;
|
||||
@apply flex items-center justify-center;
|
||||
.item {
|
||||
@apply flex items-center;
|
||||
.legend {
|
||||
margin-right: 8px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 2px;
|
||||
&.add {
|
||||
border: 0.5px solid rgb(var(--success-6));
|
||||
}
|
||||
&.delete {
|
||||
border: 0.5px solid rgb(var(--danger-6));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.diff-container {
|
||||
@apply flex;
|
||||
.diff-item {
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
min-height: calc(100vh - 110px);
|
||||
border-radius: 12px;
|
||||
background: white;
|
||||
box-shadow: 0 0 10px rgba(120 56 135/ 5%);
|
||||
@apply flex-1;
|
||||
.title-type {
|
||||
color: var(--color-text-1);
|
||||
@apply font-medium;
|
||||
}
|
||||
.title {
|
||||
color: var(--color-text-1);
|
||||
@apply my-4;
|
||||
}
|
||||
.detail-item-title {
|
||||
margin-bottom: 8px;
|
||||
gap: 16px;
|
||||
@apply flex items-center justify-between;
|
||||
.detail-item-title-text {
|
||||
@apply font-medium;
|
||||
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.arco-table-td-content) {
|
||||
padding: 5px 8px;
|
||||
}
|
||||
:deep(.ms-json-schema) .arco-table-td-content {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.diff-modal {
|
||||
.ms-drawer-body {
|
||||
background: var(--color-text-n9);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,130 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="showBatchSyncModal"
|
||||
title-align="start"
|
||||
class="ms-modal-upload ms-modal-medium"
|
||||
:width="600"
|
||||
@close="cancel"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('case.apiSyncChange') }}
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
{{
|
||||
t('common.selectedCount', {
|
||||
count: props.batchParams.currentSelectCount || 0,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-alert class="mb-[16px]" type="warning">{{ t('case.apiSyncModalAlert') }}</a-alert>
|
||||
<div class="mb-[8px]">
|
||||
{{ t('case.syncItem') }}
|
||||
</div>
|
||||
<a-checkbox-group v-model="form.checkType">
|
||||
<a-checkbox v-for="item of checkList" :key="item.value" :value="item.value">
|
||||
<div class="flex items-center">
|
||||
{{ item.label }}
|
||||
<a-tooltip v-if="item.tooltip" :content="item.tooltip" position="top">
|
||||
<div class="flex items-center">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
<div class="my-[16px] flex items-center">
|
||||
<a-switch v-model:model-value="form.deleteParams" size="small" />
|
||||
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('case.deleteNotCorrespondValue') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="my-[16px] flex items-center">
|
||||
{{ t('case.changeNotice') }}
|
||||
<a-tooltip :content="t('case.confirmMessageStatusEnable')" position="bl">
|
||||
<div class="flex items-center">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="my-[16px] flex items-center">
|
||||
<a-switch v-model:model-value="form.noticeApiCaseCreator" size="small" />
|
||||
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('case.NoticeApiCaseCreator') }}</div>
|
||||
</div>
|
||||
<div class="my-[16px] flex items-center">
|
||||
<a-switch v-model:model-value="form.noticeApiScenarioCreator" size="small" />
|
||||
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('case.NoticeApiScenarioCreator') }}</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<a-button type="secondary" @click="cancel">{{ t('common.cancel') }}</a-button>
|
||||
<a-button type="primary" :loading="syncLoading" :disabled="!form.checkType.length" @click="confirmBatchSync">
|
||||
{{ t('case.apiSyncChange') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import type { BatchActionQueryParams } from '@/components/pure/ms-table/type';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { RequestComposition } from '@/enums/apiEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
batchParams: BatchActionQueryParams;
|
||||
}>();
|
||||
|
||||
const showBatchSyncModal = defineModel<boolean>('visible', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const initForm = {
|
||||
deleteParams: false,
|
||||
checkType: [],
|
||||
noticeApiCaseCreator: true,
|
||||
noticeApiScenarioCreator: true,
|
||||
};
|
||||
|
||||
const form = ref({ ...initForm });
|
||||
|
||||
const checkList = ref([
|
||||
{
|
||||
value: RequestComposition.HEADER,
|
||||
label: t('apiTestDebug.header'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.BODY,
|
||||
label: t('apiTestDebug.body'),
|
||||
tooltip: t('case.onlySyncNewParamsOrValue'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.QUERY,
|
||||
label: RequestComposition.QUERY,
|
||||
},
|
||||
{
|
||||
value: RequestComposition.REST,
|
||||
label: RequestComposition.REST,
|
||||
},
|
||||
]);
|
||||
|
||||
const syncLoading = ref<boolean>(false);
|
||||
|
||||
function cancel() {
|
||||
showBatchSyncModal.value = false;
|
||||
}
|
||||
// 同步 TODO 等待联调
|
||||
function confirmBatchSync() {}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -205,6 +205,26 @@ export default {
|
|||
'case.recycle.confirmRecovery': 'Confirm Recovery',
|
||||
'case.createCase': 'Create Case',
|
||||
'case.updateCase': 'Update Case',
|
||||
'case.changeDifferences': 'Interface versus use case differences',
|
||||
'case.apiParamsHasChange': 'Interface parameter is changed',
|
||||
'case.apiSyncChange': 'Sync',
|
||||
'case.apiSyncModalAlert':
|
||||
'Interface definition request parameter synchronization to the interface use case, may result in abnormal interfaces to perform case execution!',
|
||||
'case.syncItem': 'Sync item',
|
||||
'case.onlySyncNewParamsOrValue':
|
||||
'JSON format, only the synchronous interface definition of new parameters and parameter values',
|
||||
'case.deleteNotCorrespondValue':
|
||||
'Remove parameters in the use case that cannot correspond to the interface definition',
|
||||
'case.changeNotice': 'Change notice',
|
||||
'case.confirmMessageStatusEnable':
|
||||
'Verify that the "CASE Update "event in the Message Managementinterface test is configured for receivers and is on',
|
||||
'case.NoticeApiCaseCreator': 'Notifies the creator of the interface use case',
|
||||
'case.NoticeApiScenarioCreator': 'Notify the founder of citing the use case scenario',
|
||||
'case.apiAndCaseDiff': 'Interface vs. use case differences',
|
||||
'case.ignoreThisChange': 'Ignore this change',
|
||||
'case.ignoreAllChange': 'Ignore all changes',
|
||||
'case.diffAdd': 'Add',
|
||||
'case.notSetData': 'No data has been set',
|
||||
'case.saveContinueText': 'Save and Continue Creating',
|
||||
'case.detail.changeHistoryTip':
|
||||
"View and compare historical changes. According to the administrator's settings, historical data will be automatically deleted",
|
||||
|
|
|
@ -196,6 +196,22 @@ export default {
|
|||
'case.recycle.confirmRecovery': '确认恢复',
|
||||
'case.createCase': '创建用例',
|
||||
'case.updateCase': '更新用例',
|
||||
'case.changeDifferences': '接口与用例差异对比',
|
||||
'case.apiParamsHasChange': '接口参数发生变更',
|
||||
'case.apiSyncChange': '同步',
|
||||
'case.apiSyncModalAlert': '接口定义请求参数同步到接口用例中,可能导致待执行的接口用例执行异常!',
|
||||
'case.syncItem': '同步项',
|
||||
'case.onlySyncNewParamsOrValue': 'JSON 格式,仅同步接口定义新增的参数与参数值',
|
||||
'case.deleteNotCorrespondValue': '删除用例中无法与接口定义对应的参数',
|
||||
'case.changeNotice': '变更通知',
|
||||
'case.confirmMessageStatusEnable': '请确认消息管理-接口测试中的"CASE更新"事件已配置接收人且状态为开启',
|
||||
'case.NoticeApiCaseCreator': '通知接口用例的创建人',
|
||||
'case.NoticeApiScenarioCreator': '通知引用该用例的场景创建人',
|
||||
'case.apiAndCaseDiff': '接口与用例差异对比',
|
||||
'case.ignoreThisChange': '忽略本次变更差异',
|
||||
'case.ignoreAllChange': '忽略全部变更差异',
|
||||
'case.diffAdd': '新增',
|
||||
'case.notSetData': '暂未设置数据',
|
||||
'case.saveContinueText': '保存并继续创建',
|
||||
'case.detail.changeHistoryTip': '查看、对比历史修改,根据管理员设置规则,变更历史数据将自动删除',
|
||||
'case.detail.noReminders': '不再提醒',
|
||||
|
|
Loading…
Reference in New Issue